kopia lustrzana https://github.com/vilemduha/blendercam
Porównaj commity
148 Commity
82b10d6bcf
...
a174dd7099
Autor | SHA1 | Data |
---|---|---|
Release robot | a174dd7099 | |
Alain Pelletier | 74eb0cbb17 | |
Alain Pelletier | 407e5d8c46 | |
Release robot | 7c5e846004 | |
Alain Pelletier | 737870b96e | |
Joe Marshall | 2e6ad35485 | |
Denis Klimov | 5a5b02709f | |
Release robot | fba545940a | |
Alain Pelletier | 88f7b5fc0c | |
Joe Marshall | e507f5e4c2 | |
Alain Pelletier | 4f9c0d1f2d | |
Joe Marshall | 1bec1bca68 | |
Joe Marshall | f1fd736d6e | |
Joe Marshall | ac33091b5c | |
Joe Marshall | 80743420b3 | |
Joe Marshall | 283a40ea82 | |
Joe Marshall | f80676fdb7 | |
Joe Marshall | 033c6507ee | |
Joe Marshall | cc0e1ce3c3 | |
Joe Marshall | e144901e9b | |
Joe Marshall | 90055a4c48 | |
Joe Marshall | 8fcabf2099 | |
Joe Marshall | dfc9f7ffbb | |
Joe Marshall | 6657769857 | |
Joe Marshall | 838cc1f5f0 | |
Joe Marshall | 638d22c3e3 | |
Joe Marshall | 3563a853bf | |
Alain Pelletier | f8ab8c45dc | |
Joe Marshall | f1a6ee1f85 | |
Joe Marshall | 2fa08c3bfb | |
Release robot | fbd4eccb1f | |
Joe Marshall | b63abfe007 | |
Joe Marshall | 45dee9abcc | |
Joe Marshall | 8a91c0324c | |
Joe Marshall | 3019120837 | |
Joe Marshall | fffef961da | |
Joe Marshall | f046f76447 | |
Joe Marshall | 093ff7288b | |
Joe Marshall | 924d20d4c7 | |
Joe Marshall | 1e05bcf7bb | |
Joe Marshall | 9203d46952 | |
Joe Marshall | a31b64961e | |
Joe Marshall | baa29586d7 | |
Joe Marshall | 5d12e32a9b | |
Joe Marshall | 7fae236a11 | |
Joe Marshall | 94860361bc | |
Joe Marshall | 302ea11809 | |
Joe Marshall | 7dcf78706a | |
Release robot | 527241fe34 | |
Joe Marshall | 366e0aa7c7 | |
Release robot | 8e2ac449a3 | |
Joe Marshall | 3f90793a27 | |
Joe Marshall | c38bf30688 | |
Joe Marshall | e64861bd9f | |
Joe Marshall | 9d1bbc8ce6 | |
Joe Marshall | fdf6a81617 | |
Joe Marshall | f786176bb4 | |
Joe Marshall | 1d9cd3782c | |
Joe Marshall | 3efe967498 | |
Joe Marshall | b1bb2719d2 | |
Release robot | 7482c7af6e | |
Joe Marshall | 98ad6282d6 | |
Joe Marshall | 06b80c365e | |
Release robot | 596d056aaa | |
Joe Marshall | 4d4e5d6b0f | |
Joe Marshall | 3d6a1d04bf | |
Joe Marshall | 5b1e4d3105 | |
Joe Marshall | 47070b4555 | |
Joe Marshall | f7bda388bd | |
Joe Marshall | 7543764590 | |
Joe Marshall | 3ba7be8b5c | |
Joe Marshall | d00bb9e130 | |
Joe Marshall | d27f2cca28 | |
Joe Marshall | 6d4d2cead0 | |
Joe Marshall | 25a2818407 | |
Joe Marshall | 78c50efc46 | |
Joe Marshall | 8203f8b59a | |
Joe Marshall | e5cb5cebc9 | |
Joe Marshall | a85957dcb7 | |
Joe Marshall | 829b62b793 | |
Joe Marshall | 21a0a5879c | |
Joe Marshall | 825e0cac5e | |
Joe Marshall | 89ff885f1c | |
Joe Marshall | 48a60f35a6 | |
Joe Marshall | 42de2b1728 | |
Joe Marshall | 0a7bfc1a72 | |
Joe Marshall | 322dd24db2 | |
Alain Pelletier | fc93b62e8a | |
Joe Marshall | a923e955b8 | |
Joe Marshall | 0d5e9f005b | |
Joe Marshall | 505f2e43f1 | |
Joe Marshall | fd55f11e4f | |
Joe Marshall | f84920b40c | |
Joe Marshall | a943809ec3 | |
Joe Marshall | a23b58606c | |
Joe Marshall | 5a058afba7 | |
Joe Marshall | b107b2eed2 | |
Joe Marshall | a2ed453255 | |
Joe Marshall | 141cb3d17e | |
Joe Marshall | db7bbb1bf1 | |
Joe Marshall | 481a670966 | |
Joe Marshall | 881e0d3437 | |
Joe Marshall | 02fd1f530c | |
Joe Marshall | 0d400711a6 | |
Joe Marshall | 0661fd1de3 | |
Joe Marshall | ff5088114b | |
Joe Marshall | 08dd7490a9 | |
Joe Marshall | d56d9c8e55 | |
Joe Marshall | f2df26479d | |
Joe Marshall | 61b529bbbf | |
Joe Marshall | a4a86f1e56 | |
Joe Marshall | 6a4326e47f | |
Joe Marshall | 9327bf3b2e | |
Joe Marshall | 76ce1dda5d | |
Joe Marshall | 7138c29d70 | |
Joe Marshall | 0bd654a8b6 | |
Joe Marshall | 8078f99e84 | |
Joe Marshall | a465ce9fa2 | |
Joe Marshall | 5cd56009eb | |
Joe Marshall | eb4c0fa22e | |
Joe Marshall | ff4498012e | |
Joe Marshall | d3ea1c1db2 | |
Joe Marshall | 2b56fe70e6 | |
Joe Marshall | 2c48304853 | |
Joe Marshall | 4fc826fbde | |
Joe Marshall | e0c3f30a89 | |
Joe Marshall | 69e5476966 | |
Alain Pelletier | b1a7f136ab | |
Joe Marshall | 3f797afe2d | |
Joe Marshall | f637370810 | |
Alain Pelletier | 7c6f4ea254 | |
Alain Pelletier | f846fb4243 | |
abosafia | fd7ab2b10e | |
Joe Marshall | bf760e8e60 | |
Alain Pelletier | 71d06d2c51 | |
Joe Marshall | 374d8122df | |
Alain Pelletier | ddf7479022 | |
abosafia | baab210120 | |
Alain Pelletier | 2f3fee765a | |
Alain Pelletier | 249508b2a3 | |
Joe Marshall | 2058d61aec | |
Joe Marshall | 01259026af | |
Joe Marshall | 85722763f4 | |
Alain Pelletier | 41d8e33b6c | |
Joe Marshall | 7996e70243 | |
Joe Marshall | fb99710cab | |
Alain Pelletier | a621b29cce | |
Joe Marshall | 11e4a88c52 |
|
@ -0,0 +1,99 @@
|
|||
name: Run tests on push / PR
|
||||
on:
|
||||
push:
|
||||
pull_request:
|
||||
jobs:
|
||||
build_and_test:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: ['ubuntu-latest','windows-latest']
|
||||
blender_version: ['3.6.7','4.0.2']
|
||||
include:
|
||||
- os: 'macos-latest'
|
||||
blender_version: 'ignored'
|
||||
runs-on: ${{matrix.os}}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Install bash 4(macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install bash
|
||||
- name: Install blender (macOS)
|
||||
if: runner.os == 'macOS'
|
||||
run: brew install --cask blender
|
||||
- name: Cache blender
|
||||
id: cache-blender
|
||||
if: runner.os != 'macOS'
|
||||
uses: actions/cache/restore@v3
|
||||
with:
|
||||
path: blender
|
||||
key: ${{ matrix.os }}-${{ matrix.blender_version}}-blender
|
||||
- name: Download blender
|
||||
id: download
|
||||
if: steps.cache-blender.outputs.cache-hit != 'true' && runner.os != 'macOS'
|
||||
shell: bash
|
||||
run: |
|
||||
declare -A os_suffixes
|
||||
os_suffixes["ubuntu-latest"]="linux-x64.tar.xz"
|
||||
os_suffixes["macos-latest"]="macos-x64.dmg"
|
||||
os_suffixes["windows-latest"]="windows-x64.zip"
|
||||
export OS_SUFFIX=${os_suffixes["${{matrix.os}}"]}
|
||||
IFS='.' read -ra BLENDER_SPLIT <<< "${{matrix.blender_version}}"
|
||||
export BLENDER_MAJOR=${BLENDER_SPLIT[0]}.${BLENDER_SPLIT[1]}
|
||||
export BLENDER_MINOR=${BLENDER_SPLIT[2]}
|
||||
export BLENDER_ARCHIVE="blender-${BLENDER_MAJOR}.${BLENDER_MINOR}-${OS_SUFFIX}"
|
||||
echo Major version: $BLENDER_MAJOR
|
||||
echo Minor version: $BLENDER_MINOR
|
||||
echo Archive name: $BLENDER_ARCHIVE
|
||||
curl -O -L https://download.blender.org/release/Blender${BLENDER_MAJOR}/${BLENDER_ARCHIVE}
|
||||
echo "BLENDER_ARCHIVE=${BLENDER_ARCHIVE}" >> "$GITHUB_OUTPUT"
|
||||
- name: Extract blender
|
||||
if: steps.cache-blender.outputs.cache-hit != 'true' && runner.os != 'macOS'
|
||||
run: |
|
||||
import shutil
|
||||
import os
|
||||
os.makedirs("blender",exist_ok=True)
|
||||
shutil.unpack_archive("${{ steps.download.outputs.BLENDER_ARCHIVE }}","blender")
|
||||
shell: python
|
||||
- name: Save blender
|
||||
uses: actions/cache/save@v3
|
||||
if: steps.cache-blender.outputs.cache-hit != 'true' && runner.os != 'macOS'
|
||||
with:
|
||||
path: blender
|
||||
key: ${{ matrix.os }}-${{ matrix.blender_version}}-blender
|
||||
- name: Make addon zip
|
||||
uses: thedoctor0/zip-release@0.7.5
|
||||
if: always()
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: 'blendercam.zip'
|
||||
directory: './scripts/addons'
|
||||
- name: Run tests
|
||||
shell: bash
|
||||
run: |
|
||||
if [ "${{ runner.os }}" != "macOS" ]; then
|
||||
export BLENDER_BIN_PATH=${PWD}/blender/$(ls -AU blender | head -1)
|
||||
export PATH=$PATH:${BLENDER_BIN_PATH}
|
||||
fi
|
||||
export ADDON_PATH=${PWD}/scripts/addons/blendercam.zip
|
||||
cd scripts/addons/cam/tests
|
||||
python install_addon.py ${ADDON_PATH}
|
||||
python test_suite.py
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: always()
|
||||
with:
|
||||
name: blendercam-${{matrix.os}}-${{matrix.blender_version}}
|
||||
path: ./scripts/addons/cam
|
||||
rerun-failed-jobs:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [ build_and_test ]
|
||||
if: failure()
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Rerun failed jobs in the current workflow (because mac blender is unstable)
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: gh run rerun ${{ github.run_id }} --failed
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
name: Make new release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version_bump:
|
||||
description: 'New version:'
|
||||
required: true
|
||||
default: 'patch'
|
||||
type: choice
|
||||
options:
|
||||
- overwrite previous tag
|
||||
- minor
|
||||
- major
|
||||
- patch
|
||||
jobs:
|
||||
release:
|
||||
runs-on: "ubuntu-latest"
|
||||
permissions:
|
||||
contents: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4.1.1
|
||||
- name: Bump version
|
||||
shell: python
|
||||
env:
|
||||
VERSION_BUMP: ${{ inputs.version_bump }}
|
||||
run: |
|
||||
from pathlib import Path
|
||||
import re
|
||||
import os
|
||||
v_file=Path("scripts","addons","cam","version.py")
|
||||
version_txt=v_file.read_text()
|
||||
major,minor,patch = re.match(r".*\(\s*(\d+),(\s*\d+),(\s*\d+)\)",version_txt).groups()
|
||||
major=int(major)
|
||||
minor=int(minor)
|
||||
patch=int(patch)
|
||||
bump = os.getenv("VERSION_BUMP")
|
||||
if bump == "minor":
|
||||
minor+=1
|
||||
if bump=='patch':
|
||||
patch+=1
|
||||
elif bump=='minor':
|
||||
minor+=1
|
||||
patch=0
|
||||
elif bump=='major':
|
||||
major+=1
|
||||
minor=0
|
||||
patch=0
|
||||
v_file.write_text(f"__version__=({major},{minor},{patch})")
|
||||
# update in bl_info structure (which can't be dynamic because blender...)
|
||||
init_file=Path("scripts","addons","cam","__init__.py")
|
||||
init_text=init_file.read_text()
|
||||
version_regex= r"\"version\"\s*:\s*\(([\d\s,]+)\)"
|
||||
init_text = re.sub(version_regex,f'"version":({major},{minor},{patch})',init_text)
|
||||
init_file.write_text(init_text)
|
||||
|
||||
env_file = Path(os.getenv('GITHUB_ENV'))
|
||||
env_file.write_text(f"VERSION_TAG={major}.{minor}.{patch}")
|
||||
print(f"New version: {major}.{minor}.{patch}")
|
||||
- name: Make addon zip
|
||||
uses: thedoctor0/zip-release@0.7.5
|
||||
with:
|
||||
type: 'zip'
|
||||
filename: 'blendercam.zip'
|
||||
directory: './scripts/addons'
|
||||
- name: Write version number
|
||||
if: ${{ inputs.version_bump }} != "overwrite previous tag"
|
||||
run: |
|
||||
git config --global user.name 'Release robot'
|
||||
git config --global user.email 'release-robot@users.noreply.github.com'
|
||||
git commit -am "Version number" || true
|
||||
- name: Push changes
|
||||
uses: ad-m/github-push-action@master
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
branch: ${{ github.ref }}
|
||||
- name: make release
|
||||
uses: ncipollo/release-action@v1
|
||||
with:
|
||||
artifacts: "scripts/addons/blendercam.zip"
|
||||
tag: ${{ env.VERSION_TAG }}
|
||||
allowUpdates: true
|
||||
body: "To install BlenderCAM, download blendercam.zip and *don't* extract it. In blender, go to preferences, add-ons, and select 'install from file' and select the blendercam.zip file you downloaded"
|
1443
scripts/__init__.py
1443
scripts/__init__.py
Plik diff jest za duży
Load Diff
|
@ -1,694 +0,0 @@
|
|||
bl_info = {
|
||||
"name": "G - Pack",
|
||||
"author": "Velem Novak",
|
||||
"version": (0, 2, 0),
|
||||
"blender": (2, 77, 0),
|
||||
"location": "Object > G-Pack",
|
||||
"description": "UV packing game",
|
||||
"warning": "",
|
||||
"wiki_url": "http://www.blendercam.blogspot.com",
|
||||
"category": "Object"
|
||||
}
|
||||
|
||||
|
||||
import bpy
|
||||
import math, random, os
|
||||
import mathutils
|
||||
from mathutils import *
|
||||
import bmesh
|
||||
PRECISION=0.0000000001
|
||||
|
||||
|
||||
|
||||
def activate(ob):
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
ob.select=True
|
||||
bpy.context.scene.objects.active=ob
|
||||
|
||||
|
||||
|
||||
def createMeshFromData(name, verts, faces):
|
||||
# Create mesh and object
|
||||
me = bpy.data.meshes.new(name+'Mesh')
|
||||
ob = bpy.data.objects.new(name, me)
|
||||
#ob.show_name = True
|
||||
|
||||
# Link object to scene and make active
|
||||
scn = bpy.context.scene
|
||||
scn.objects.link(ob)
|
||||
scn.objects.active = ob
|
||||
ob.select = True
|
||||
|
||||
# Create mesh from given verts, faces.
|
||||
me.from_pydata(verts, [], faces)
|
||||
# Update mesh with new data
|
||||
me.update()
|
||||
return ob
|
||||
|
||||
def getIslands(me):
|
||||
|
||||
bm = bmesh.from_edit_mesh(me)
|
||||
for f in bm.faces:
|
||||
f.select=False
|
||||
all=False
|
||||
done={}
|
||||
islands=[]
|
||||
while not len(done)>=len(bm.faces):
|
||||
island=[]
|
||||
for i,p in enumerate(bm.faces):
|
||||
|
||||
if done.get(i) == None:
|
||||
p.select=True
|
||||
done[i]=True
|
||||
island.append(p.index)
|
||||
break
|
||||
nf = [p]
|
||||
while len(nf)>0:
|
||||
selected_faces = nf
|
||||
nf = []
|
||||
|
||||
for f in selected_faces:
|
||||
for edge in f.edges:
|
||||
if edge.seam==False:
|
||||
linkede = edge.link_faces
|
||||
for face in linkede:
|
||||
if not face.select and done.get(face.index)==None:
|
||||
done[face.index]=True
|
||||
nf.append(face)
|
||||
face.select=True
|
||||
island.append(face.index)
|
||||
islands.append(island)
|
||||
return islands
|
||||
#print(islands)
|
||||
|
||||
def GameDropOb(ob,margin,enablerotation):
|
||||
|
||||
activate(ob)
|
||||
#ob.rotation_euler.x=math.pi/2
|
||||
#bpy.ops.object.transform_apply(location=False, rotation=True, scale=False)
|
||||
#ob.location.z = ob.location.y
|
||||
#ob.location.x=0
|
||||
|
||||
ob.select=True
|
||||
ob.game.physics_type='RIGID_BODY'
|
||||
ob.game.use_collision_bounds=True
|
||||
ob.game.collision_bounds_type = 'TRIANGLE_MESH'
|
||||
ob.game.collision_margin = margin
|
||||
ob.game.velocity_max = 1
|
||||
ob.game.damping = 0.5
|
||||
ob.game.rotation_damping = 0.9
|
||||
|
||||
ob.game.lock_location_y = True
|
||||
ob.game.lock_rotation_x = True
|
||||
if not enablerotation:
|
||||
ob.game.lock_rotation_y = True#conditional#
|
||||
ob.game.lock_rotation_z = True
|
||||
|
||||
|
||||
bpy.ops.object.game_property_new(name="island")
|
||||
|
||||
|
||||
def UVobs(obs,set):
|
||||
uvobs=[]
|
||||
zoffset=0
|
||||
for ob in obs:
|
||||
activate(ob)
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
if set.startConditions=='NEW':
|
||||
bpy.ops.uv.pack_islands(margin=0.01)
|
||||
#print('a')
|
||||
islands = getIslands(ob.data)
|
||||
#print('b')
|
||||
bpy.ops.object.editmode_toggle()
|
||||
|
||||
|
||||
print(len(islands))
|
||||
for iidx,island in enumerate(islands):
|
||||
out_verts=[]
|
||||
out_faces=[]
|
||||
|
||||
print(iidx,len(islands))
|
||||
vertidx=0
|
||||
|
||||
vertindices= {}
|
||||
loops=[]
|
||||
for fi in island:
|
||||
face = ob.data.polygons[fi]
|
||||
oface=[]
|
||||
|
||||
for vert, loop in zip(face.vertices, face.loop_indices):
|
||||
uv = ob.data.uv_layers.active.data[loop].uv.copy()
|
||||
|
||||
# if vertindices.get(vert) == None:
|
||||
#
|
||||
# vertindices[vert]=vertidx
|
||||
# nvertindex = vertidx
|
||||
# out_verts.append((uv.x,0,uv.y))
|
||||
# vertidx+=1
|
||||
#
|
||||
#
|
||||
# nvertindex = vertindices[vert]
|
||||
#
|
||||
# #print(vert,nvertindex, vertindices)
|
||||
# #print()
|
||||
# oface.append(nvertindex)
|
||||
|
||||
loops.append(loop)
|
||||
out_verts.append((uv.x,0,uv.y))
|
||||
oface.append(vertidx)
|
||||
vertidx+=1
|
||||
#print(oface)
|
||||
out_faces.append(oface)
|
||||
#print(out_verts,out_faces)
|
||||
uvob = createMeshFromData(ob.name + 'UVObj', out_verts, out_faces)
|
||||
|
||||
activate(uvob)
|
||||
bpy.ops.mesh.uv_texture_add()
|
||||
#print(uvob.name)
|
||||
#print(bpy.context.active_object.name)
|
||||
activate(uvob)
|
||||
vertidx = 0
|
||||
for fi in island:
|
||||
face = ob.data.polygons[fi]
|
||||
oface=[]
|
||||
for vert, loop in zip(face.vertices, face.loop_indices):
|
||||
uvob.data.uv_layers.active.data[vertidx].uv = (loops[vertidx],0)#ob.data.uv_layers.active.data[loop].uv
|
||||
|
||||
#print('loop',loops[vertidx])
|
||||
vertidx+=1
|
||||
|
||||
print(uvob.name)
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.mesh.remove_doubles(threshold = 0.0000001)
|
||||
|
||||
#print('d')
|
||||
|
||||
bpy.ops.object.editmode_toggle()
|
||||
bpy.ops.object.modifier_add(type='SOLIDIFY')
|
||||
bpy.context.object.modifiers["Solidify"].thickness = min(0.3, min(uvob.dimensions.x,uvob.dimensions.y)) #0.1
|
||||
|
||||
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Solidify")
|
||||
#print('e')
|
||||
|
||||
uvob['source']=ob.name
|
||||
uvob['island']=iidx
|
||||
uvob['islandindices']=island
|
||||
if set.startConditions=='NEW':
|
||||
uvob.location.z+=zoffset#we shift the uv to not collide when packing more objects
|
||||
uvobs.append(uvob)
|
||||
bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
|
||||
zoffset+=uvob.dimensions.z+0.005
|
||||
#fal
|
||||
print('c')
|
||||
for ob in uvobs:
|
||||
ob.select=True
|
||||
|
||||
s=bpy.context.scene
|
||||
s.objects.active=uvobs[0]
|
||||
|
||||
bpy.ops.object.material_slot_add()
|
||||
mat=bpy.data.materials.new('GPackMaterial')
|
||||
|
||||
|
||||
uvobs[0].material_slots[0].material=mat
|
||||
mat.use_object_color = True
|
||||
for ob in uvobs:
|
||||
ob.color = (0.5+random.random(),0.5+random.random(),0.5+random.random(),1)
|
||||
mat.diffuse_color = (1,1,1)
|
||||
bpy.ops.object.make_links_data(type='MATERIAL')
|
||||
|
||||
return uvobs
|
||||
|
||||
def doGameUV(context):
|
||||
|
||||
#getIslands(bpy.context.active_object)
|
||||
obs=bpy.context.selected_objects
|
||||
activeOb=bpy.context.object
|
||||
origscene=bpy.context.scene
|
||||
import_scene('GPack')
|
||||
|
||||
|
||||
set=bpy.context.scene.gpacker_settings
|
||||
|
||||
uvobs = UVobs(obs,set)
|
||||
|
||||
for ob in uvobs:
|
||||
GameDropOb(ob,set.initialmargin, set.enablerotation)
|
||||
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for ob in uvobs:
|
||||
ob.select=True
|
||||
|
||||
bpy.ops.group.create()
|
||||
bpy.ops.object.make_links_scene(scene='GPack')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
bpy.context.window.screen.scene = bpy.data.scenes['GPack']
|
||||
|
||||
bpy.ops.view3d.viewnumpad(type='CAMERA')
|
||||
bpy.context.space_data.viewport_shade = 'MATERIAL'
|
||||
#bpy.ops.view3d.zoom_camera_1_to_1()
|
||||
#bpy.context.scene.update()
|
||||
for ob in bpy.data.scenes['GPack'].objects:
|
||||
if ob.game.properties.get('startconditions')!=None:
|
||||
ob.game.properties['startconditions'].value = set.startConditions
|
||||
ob.game.properties['doobjects'].value = False
|
||||
ob.game.properties['xsize'].value = set.xsize
|
||||
ob.game.properties['ysize'].value = set.ysize
|
||||
|
||||
#PLAY THE GAME!
|
||||
bpy.ops.view3d.game_start()
|
||||
|
||||
if set.apply==True:
|
||||
print('after game')
|
||||
#reassign UV's
|
||||
|
||||
bpy.context.scene.update()
|
||||
#get size object
|
||||
for ob in bpy.context.scene.objects:
|
||||
if ob.name[:5]=='ssize':
|
||||
scale=ob.location.z+1
|
||||
for uvob in uvobs:
|
||||
uvobmat=uvob.matrix_world
|
||||
ob=bpy.data.objects[uvob['source']]
|
||||
|
||||
assigns=[]
|
||||
|
||||
for uvfi,fi in enumerate(uvob['islandindices']):
|
||||
face = ob.data.polygons[fi]
|
||||
uvface = uvob.data.polygons[uvfi]
|
||||
|
||||
for vert1, loop1 in zip(uvface.vertices, uvface.loop_indices):
|
||||
co=uvobmat*uvob.data.vertices[vert1].co/scale
|
||||
|
||||
idxuv = int(uvob.data.uv_layers.active.data[loop1].uv.x)
|
||||
print(idxuv)
|
||||
uv=ob.data.uv_layers.active.data[idxuv].uv
|
||||
uv.x = co.x
|
||||
uv.y = co.z
|
||||
|
||||
|
||||
|
||||
#print(fdict)
|
||||
assigns=[]
|
||||
|
||||
|
||||
print(len(assigns))
|
||||
|
||||
|
||||
bpy.context.window.screen.scene = origscene
|
||||
bpy.data.scenes.remove(bpy.data.scenes['GPack'], do_unlink = True)
|
||||
bpy.data.texts.remove(bpy.data.texts['root'])
|
||||
activate(activeOb)
|
||||
for ob in obs:
|
||||
ob.select=True
|
||||
|
||||
#packing of curves
|
||||
|
||||
def getBoundsWorldspace(ob):
|
||||
#progress('getting bounds of object(s)')
|
||||
|
||||
|
||||
maxx=maxy=maxz=-10000000
|
||||
minx=miny=minz=10000000
|
||||
|
||||
bb=ob.bound_box
|
||||
mw=ob.matrix_world
|
||||
|
||||
for coord in bb:
|
||||
#this can work badly with some imported curves, don't know why...
|
||||
#worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z))
|
||||
worldCoord = mw * Vector((coord[0], coord[1], coord[2]))
|
||||
minx=min(minx,worldCoord.x)
|
||||
miny=min(miny,worldCoord.y)
|
||||
minz=min(minz,worldCoord.z)
|
||||
maxx=max(maxx,worldCoord.x)
|
||||
maxy=max(maxy,worldCoord.y)
|
||||
maxz=max(maxz,worldCoord.z)
|
||||
|
||||
#progress(time.time()-t)
|
||||
return minx,miny,minz,maxx,maxy,maxz
|
||||
|
||||
def getBoundsSpline(s):
|
||||
#progress('getting bounds of object(s)')
|
||||
|
||||
|
||||
maxx=maxy=maxz=-10000000
|
||||
minx=miny=minz=10000000
|
||||
|
||||
|
||||
|
||||
|
||||
for p in s.points:
|
||||
#this can work badly with some imported curves, don't know why...
|
||||
#worldCoord = mw * Vector((coord[0]/ob.scale.x, coord[1]/ob.scale.y, coord[2]/ob.scale.z))
|
||||
|
||||
minx=min(minx,p.co.x)
|
||||
miny=min(miny,p.co.y)
|
||||
minz=min(minz,p.co.z)
|
||||
maxx=max(maxx,p.co.x)
|
||||
maxy=max(maxy,p.co.y)
|
||||
maxz=max(maxz,p.co.z)
|
||||
for p in s.bezier_points:
|
||||
minx=min(minx,p.co.x)
|
||||
miny=min(miny,p.co.y)
|
||||
minz=min(minz,p.co.z)
|
||||
maxx=max(maxx,p.co.x)
|
||||
maxy=max(maxy,p.co.y)
|
||||
maxz=max(maxz,p.co.z)
|
||||
#progress(time.time()-t)
|
||||
return minx,miny,minz,maxx,maxy,maxz
|
||||
|
||||
def getInstances(obs):
|
||||
instanceindices=[]
|
||||
data=[]
|
||||
dataindices=[]
|
||||
counti=0
|
||||
#dataindex=0
|
||||
for ob in obs:
|
||||
if not ob.data in data:# or 1:
|
||||
data.append(ob.data)
|
||||
instanceindices.append(counti)
|
||||
dataindices.append(counti)
|
||||
#dataindex+=1
|
||||
|
||||
else:
|
||||
i = data.index(ob.data)
|
||||
#print(i);
|
||||
instanceindices.append(instanceindices[dataindices[i]])
|
||||
counti+=1
|
||||
print('number of original shapes',str(len(data)))
|
||||
print(instanceindices)
|
||||
return instanceindices
|
||||
|
||||
def prepareCurves(obs, set):
|
||||
packobs=[]
|
||||
zoffset=0
|
||||
instanceindices=getInstances(obs)
|
||||
instanceindex=0
|
||||
e=mathutils.Euler((math.pi/2,0,0))
|
||||
|
||||
for ob in obs:
|
||||
|
||||
if ob.type=='CURVE':
|
||||
|
||||
oldloc=ob.location.copy()
|
||||
|
||||
if instanceindices[instanceindex]==instanceindex:
|
||||
|
||||
|
||||
activate(ob)
|
||||
bpy.ops.object.duplicate()
|
||||
packob=bpy.context.active_object
|
||||
#bpy.ops.object.rotation_clear()
|
||||
simplify=True
|
||||
thickness=0.1
|
||||
if simplify:
|
||||
c=packob.data
|
||||
if len(c.splines)>0:
|
||||
maxbounds=-10000
|
||||
maxc=0
|
||||
for i in range(0,len(c.splines)):
|
||||
minx,miny,minz,maxx,maxy,maxz=getBoundsSpline(c.splines[i])
|
||||
if maxx-minx+maxy-miny>maxbounds:
|
||||
maxc=i
|
||||
maxbounds= maxx-minx+maxy-miny
|
||||
for i in range(len(c.splines)-1,-1,-1):
|
||||
if i!=maxc:
|
||||
c.splines.remove(c.splines[i])
|
||||
doconvert=False
|
||||
for s in c.splines:
|
||||
if s.type!='POLY':
|
||||
doconvert=True
|
||||
if doconvert:
|
||||
c.dimensions = '3D'
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
bpy.ops.object.convert(target='CURVE')
|
||||
|
||||
bpy.ops.curve.simplify(error = 0.001)
|
||||
#delete packob here?
|
||||
bpy.context.scene.objects.unlink(packob)
|
||||
packob=bpy.context.active_object
|
||||
activate(packob)
|
||||
for s in packob.data.splines:
|
||||
s.use_cyclic_u=True
|
||||
|
||||
if min(maxx-minx,maxy-miny)<0.1:
|
||||
thickness=min(maxx-minx,maxy-miny)
|
||||
packob.data.dimensions = '2D'
|
||||
|
||||
|
||||
bpy.context.active_object.rotation_euler.rotate(e)
|
||||
#packob.rotation_euler=(math.pi/2,0,0)
|
||||
|
||||
|
||||
#bpy.ops.object.origin_set(type='ORIGIN_CENTER_OF_MASS')
|
||||
newloc=packob.location.copy()
|
||||
#print(newloc-oldloc)
|
||||
|
||||
bpy.ops.object.convert(target='MESH')
|
||||
activate(packob)
|
||||
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
|
||||
#print(packob.name)
|
||||
#print(bpy.context.active_object)
|
||||
bpy.ops.object.modifier_add(type='SOLIDIFY')
|
||||
bpy.context.object.modifiers["Solidify"].thickness = thickness
|
||||
bpy.ops.object.modifier_apply(apply_as='DATA', modifier="Solidify")
|
||||
#bpy.ops.object.transform_apply(location=False, rotation=True, scale=True)
|
||||
else:
|
||||
print(instanceindex)
|
||||
source_packob=packobs[instanceindices[instanceindex]][0]
|
||||
|
||||
activate(source_packob)
|
||||
bpy.ops.object.duplicate(linked=True)
|
||||
packob=bpy.context.active_object
|
||||
|
||||
packob.rotation_euler=(math.pi/2,-ob.rotation_euler.z,0)
|
||||
#packob.rotation_euler.rotate()
|
||||
#packob.rotation_euler.rotate(e)
|
||||
|
||||
packob['source']=ob.name
|
||||
if set.startConditions=='TWEAK' or set.startConditions=='FIXED':
|
||||
packob.location=(oldloc.x,0,oldloc.y)
|
||||
if set.startConditions=='NEW':
|
||||
packob.location=(0,0,0)
|
||||
bpy.ops.object.location_clear()
|
||||
packob.rotation_euler=(math.pi/2,0,0)
|
||||
minx,maxx,miny,maxy,minz,maxz=getBoundsWorldspace(packob)
|
||||
packob.location.x=-minx
|
||||
packob.location.z= -miny+zoffset
|
||||
zoffset+= maxy-miny
|
||||
|
||||
#bpy.ops.object.editmode_toggle()
|
||||
#bpy.ops.mesh.separate(type='LOOSE')
|
||||
#bpy.ops.object.editmode_toggle()
|
||||
packobs.append((packob,ob, newloc-oldloc))
|
||||
instanceindex+=1
|
||||
return packobs
|
||||
|
||||
|
||||
def doGameObs(context):
|
||||
#getIslands(bpy.context.active_object)
|
||||
obs=bpy.context.selected_objects
|
||||
origscene=bpy.context.scene
|
||||
import_scene('GPack')
|
||||
set=bpy.context.scene.gpacker_settings
|
||||
|
||||
packobs=prepareCurves(obs,set)
|
||||
gobs=[]
|
||||
for data in packobs:
|
||||
ob=data[0]
|
||||
GameDropOb(ob,set.initialmargin, set.enablerotation)
|
||||
for data in packobs:
|
||||
data[0].select=True
|
||||
bpy.ops.group.create()
|
||||
print('done')
|
||||
|
||||
bpy.ops.object.make_links_scene(scene='GPack')
|
||||
bpy.ops.object.delete(use_global=False)
|
||||
bpy.context.window.screen.scene = bpy.data.scenes['GPack']
|
||||
|
||||
bpy.ops.view3d.viewnumpad(type='CAMERA')
|
||||
bpy.context.space_data.viewport_shade = 'MATERIAL'
|
||||
#pass data to game:
|
||||
for ob in bpy.data.scenes['GPack'].objects:
|
||||
if ob.game.properties.get('startconditions')!=None:
|
||||
ob.game.properties['startconditions'].value = set.startConditions
|
||||
ob.game.properties['doobjects'].value = True
|
||||
ob.game.properties['xsize'].value = set.xsize
|
||||
ob.game.properties['ysize'].value = set.ysize
|
||||
|
||||
bpy.ops.view3d.game_start()
|
||||
for s in bpy.data.scenes:
|
||||
s.gpacker_settings.doobjects=False
|
||||
print('repack')
|
||||
|
||||
if set.apply:
|
||||
for data in packobs:
|
||||
print(data[0].location,data[1].location)
|
||||
data[1].location.x=data[0].location.x
|
||||
data[1].location.y=data[0].location.z
|
||||
data[1].rotation_euler.z=-data[0].rotation_euler.y
|
||||
|
||||
#bpy.context.scene.objects.unlink(data[0])
|
||||
for s in bpy.data.scenes:
|
||||
s.gpacker_settings.apply=False
|
||||
bpy.context.window.screen.scene = origscene
|
||||
bpy.data.scenes.remove(bpy.data.scenes['GPack'])
|
||||
for ob in obs:
|
||||
ob.select=True
|
||||
|
||||
|
||||
#####################################################################
|
||||
# Import Functions
|
||||
|
||||
def import_scene(obname):
|
||||
opath = "//data.blend\\Scene\\" + obname
|
||||
s = os.sep
|
||||
for p in bpy.utils.script_paths():
|
||||
fname= p + '%saddons%sGPack%spack_scene.blend' % (s,s,s)
|
||||
dpath = p + \
|
||||
'%saddons%sGPack%spack_scene.blend\\Scene\\' % (s, s, s)
|
||||
if os.path.isfile(fname):
|
||||
break
|
||||
# DEBUG
|
||||
#print('import_object: ' + opath)
|
||||
print(dpath,opath)
|
||||
result = bpy.ops.wm.append(
|
||||
filepath=opath,
|
||||
filename=obname,
|
||||
directory=dpath,
|
||||
filemode=1,
|
||||
link=False,
|
||||
autoselect=True,
|
||||
active_layer=True,
|
||||
instance_groups=True
|
||||
)
|
||||
print(result)
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class GPackUVOperator(bpy.types.Operator):
|
||||
"""Tooltip"""
|
||||
bl_idname = "object.gpack_uv"
|
||||
bl_label = "Gravity Pack UVs"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects)>0
|
||||
|
||||
def execute(self, context):
|
||||
doGameUV(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
class GPackCurvesOperator(bpy.types.Operator):
|
||||
"""Tooltip"""
|
||||
bl_idname = "object.gpack"
|
||||
bl_label = "Gravity Pack Curves"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return len(context.selected_objects)>0
|
||||
|
||||
def execute(self, context):
|
||||
doGameObs(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class GPackSettings(bpy.types.PropertyGroup):
|
||||
|
||||
|
||||
#lpgroup = bpy.props.StringProperty(name="low poly group", default="")
|
||||
#hpgroup = bpy.props.StringProperty(name="high poly group", default="")
|
||||
apply = bpy.props.BoolProperty(name="apply",description="", default=False)
|
||||
doobjects = bpy.props.BoolProperty(name="doobjects",description="", default=False)
|
||||
|
||||
startConditions = bpy.props.EnumProperty(name='start state', items=(('NEW','Drop All','all parts are dropped into the layout'),('FIXED','Fixed','All objects are still in beginning, just tweak the extra additions'),('TWEAK','Tweak','start from current state, position objects before to drop properly')),
|
||||
description='start conditions',
|
||||
default='TWEAK')
|
||||
xsize = bpy.props.FloatProperty(name="X-sheet-size",description="", default=1)
|
||||
ysize = bpy.props.FloatProperty(name="Y-size",description="", default=1)
|
||||
initialmargin = bpy.props.FloatProperty(name="initial margin",description="", default=0.003)
|
||||
enablerotation = bpy.props.BoolProperty(name="rotation",description="", default=True)
|
||||
|
||||
class GPackCurvesPanel(bpy.types.Panel):
|
||||
"""Creates a Panel in the Object properties window"""
|
||||
bl_space_type = 'VIEW_3D'
|
||||
bl_region_type = 'TOOLS'
|
||||
bl_label = "Gravity Packer"
|
||||
bl_idname = "WORLD_PT_GPACKER"
|
||||
|
||||
bl_context = "objectmode"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_category = "Tools"
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
obj = bpy.context.active_object
|
||||
#s=bpy.context.scene
|
||||
s=bpy.context.scene.gpacker_settings
|
||||
row = layout.row()
|
||||
layout.operator("object.gpack")
|
||||
layout.operator("object.gpack_uv")
|
||||
#layout.prop_search(s, "lpgroup", bpy.data, "groups")
|
||||
#layout.prop_search(s, "hpgroup", bpy.data, "groups")
|
||||
|
||||
layout.prop(s,'startConditions')
|
||||
layout.prop(s,'xsize')
|
||||
layout.prop(s,'ysize')
|
||||
layout.prop(s,'initialmargin')
|
||||
layout.prop(s,'enablerotation')
|
||||
|
||||
#layout.prop(s,'pass_combined')
|
||||
|
||||
# separate UV's????
|
||||
# class GPackUVPanel(bpy.types.Panel):
|
||||
# '''Creates a Panel in the Object properties window"""
|
||||
# bl_label = "Gravity Packer"
|
||||
# bl_idname = "WORLD_PT_GPACKER"
|
||||
# bl_space_type = 'PROPERTIES'
|
||||
# bl_region_type = 'WINDOW'
|
||||
# bl_context = "object"
|
||||
#
|
||||
#
|
||||
# def draw(self, context):
|
||||
# layout = self.layout
|
||||
#
|
||||
# obj = bpy.context.active_object
|
||||
# #s=bpy.context.scene
|
||||
# s=bpy.context.scene.gpacker_settings
|
||||
# row = layout.row()
|
||||
# layout.operator("object.gpack_uv")
|
||||
# #layout.prop_search(s, "lpgroup", bpy.data, "groups")
|
||||
# #layout.prop_search(s, "hpgroup", bpy.data, "groups")
|
||||
#
|
||||
# layout.prop(s,'startConditions')
|
||||
# layout.prop(s,'xsize')
|
||||
# layout.prop(s,'ysize')
|
||||
# layout.prop(s,'initialmargin')
|
||||
#
|
||||
#
|
||||
# #layout.prop(s,'pass_combined')
|
||||
|
||||
def register():
|
||||
s = bpy.types.Scene
|
||||
bpy.utils.register_class(GPackUVOperator)
|
||||
bpy.utils.register_class(GPackCurvesOperator)
|
||||
bpy.utils.register_class(GPackSettings)
|
||||
bpy.utils.register_class(GPackCurvesPanel)
|
||||
s.gpacker_settings = bpy.props.PointerProperty(type= GPackSettings)
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(GPackUVOperator)
|
||||
bpy.utils.unregister_class(GPackCurvesOperator)
|
||||
bpy.utils.unregister_class(GPackSettings)
|
||||
bpy.utils.unregister_class(GPackCurvesPanel)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
|
||||
|
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
Plik binarny nie jest wyświetlany.
|
@ -23,15 +23,19 @@ import bgl
|
|||
import bl_operators
|
||||
import blf
|
||||
import bpy
|
||||
import bpy.ops
|
||||
import math
|
||||
import numpy
|
||||
import os
|
||||
import pickle
|
||||
import shutil
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
try:
|
||||
import shapely
|
||||
except ImportError:
|
||||
|
@ -46,23 +50,22 @@ from bpy.props import *
|
|||
from bpy.types import Menu, Operator, UIList, AddonPreferences
|
||||
from bpy_extras.object_utils import object_data_add
|
||||
from cam import ui, ops, curvecamtools, curvecamequation, curvecamcreate, utils, simple, \
|
||||
polygon_utils_cam # , post_processors
|
||||
polygon_utils_cam, autoupdate, basrelief # , post_processors
|
||||
from mathutils import *
|
||||
from shapely import geometry as sgeometry
|
||||
|
||||
from cam.ui import *
|
||||
|
||||
|
||||
from cam.version import __version__
|
||||
|
||||
bl_info = {
|
||||
"name": "CAM - gcode generation tools",
|
||||
"author": "Vilem Novak",
|
||||
"version": (0, 9, 3),
|
||||
"blender": (2, 80, 0),
|
||||
"version":(0,9,10),
|
||||
"blender": (3, 6, 0),
|
||||
"location": "Properties > render",
|
||||
"description": "Generate machining paths for CNC",
|
||||
"warning": "there is no warranty for the produced gcode by now",
|
||||
"wiki_url": "https://github.com/vilemduha/blendercam/wiki",
|
||||
"warning": "",
|
||||
"doc_url": "https://blendercam.com/",
|
||||
"tracker_url": "",
|
||||
"category": "Scene"}
|
||||
|
||||
|
@ -71,8 +74,10 @@ import cam.constants
|
|||
was_hidden_dict = {}
|
||||
|
||||
def updateMachine(self, context):
|
||||
global _IS_LOADING_DEFAULTS
|
||||
print('update machine ')
|
||||
utils.addMachineAreaObject()
|
||||
if not _IS_LOADING_DEFAULTS:
|
||||
utils.addMachineAreaObject()
|
||||
|
||||
|
||||
def updateMaterial(self, context):
|
||||
|
@ -83,6 +88,7 @@ def updateMaterial(self, context):
|
|||
def updateOperation(self, context):
|
||||
scene = context.scene
|
||||
ao = scene.cam_operations[scene.cam_active_operation]
|
||||
operationValid(self, context)
|
||||
|
||||
if ao.hide_all_others:
|
||||
for _ao in scene.cam_operations:
|
||||
|
@ -117,6 +123,7 @@ def updateOperation(self, context):
|
|||
print(e)
|
||||
|
||||
|
||||
|
||||
class CamAddonPreferences(AddonPreferences):
|
||||
# this must match the addon name, use '__package__'
|
||||
# when defining this in a submodule of a python package.
|
||||
|
@ -127,12 +134,72 @@ class CamAddonPreferences(AddonPreferences):
|
|||
default=False,
|
||||
)
|
||||
|
||||
update_source: bpy.props.StringProperty(
|
||||
name="Source of updates for the addon",
|
||||
description="This can be either a github repo link in which case it will download the latest release on there, "
|
||||
"or an api link like https://api.github.com/repos/<author>/blendercam/commits to get from a github repository",
|
||||
default="https://github.com/pppalain/blendercam",
|
||||
)
|
||||
|
||||
last_update_check: IntProperty(
|
||||
name="Last update time",
|
||||
default=0
|
||||
)
|
||||
|
||||
last_commit_hash: StringProperty(
|
||||
name="Hash of last commit from updater",
|
||||
default=""
|
||||
)
|
||||
|
||||
|
||||
just_updated: BoolProperty(
|
||||
name="Set to true on update or initial install",
|
||||
default=True
|
||||
)
|
||||
|
||||
new_version_available: StringProperty(
|
||||
name="Set to new version name if one is found",
|
||||
default=""
|
||||
)
|
||||
|
||||
|
||||
|
||||
default_interface_level: bpy.props.EnumProperty(
|
||||
name="Interface level in new file",
|
||||
description="Choose visible options",
|
||||
items=[('0', "Basic", "Only show essential options"),
|
||||
('1', "Advanced", "Show advanced options"),
|
||||
('2', "Complete", "Show all options"),
|
||||
('3', "Experimental", "Show experimental options")],
|
||||
default='3',
|
||||
)
|
||||
|
||||
default_machine_preset: bpy.props.StringProperty(
|
||||
name="Machine preset in new file",
|
||||
description="So that machine preset choice persists between files",
|
||||
default='',
|
||||
)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Use experimental features when you want to help development of Blender CAM:")
|
||||
|
||||
layout.prop(self, "experimental")
|
||||
layout.prop(self, "update_source")
|
||||
layout.label(text="Choose a preset update source")
|
||||
|
||||
UPDATE_SOURCES=[("https://github.com/vilemduha/blendercam", "Stable", "Stable releases (github.com/vilemduja/blendercam)"),
|
||||
("https://github.com/pppalain/blendercam", "Unstable", "Unstable releases (github.com/pppalain/blendercam)"),
|
||||
# comments for searching in github actions release script to automatically set this repo
|
||||
# if required
|
||||
## REPO ON NEXT LINE
|
||||
("https://api.github.com/repos/pppalain/blendercam/commits","Direct from git (may not work)","Get from git commits directly"),
|
||||
## REPO ON PREV LINE
|
||||
("","None","Don't do auto update"),
|
||||
]
|
||||
grid=layout.grid_flow(align=True)
|
||||
for (url,short,long) in UPDATE_SOURCES:
|
||||
op=grid.operator("render.cam_set_update_source",text=short)
|
||||
op.new_source=url
|
||||
|
||||
class machineSettings(bpy.types.PropertyGroup):
|
||||
"""stores all data for machines"""
|
||||
|
@ -284,41 +351,56 @@ class import_settings(bpy.types.PropertyGroup):
|
|||
max_segment_size: FloatProperty(name="", description="Only Segments bigger then this value get subdivided",
|
||||
default=0.001, min=0.0001, max=1.0, unit="LENGTH")
|
||||
|
||||
|
||||
def operationValid(self, context):
|
||||
o = self
|
||||
o.changed = True
|
||||
o.valid = True
|
||||
invalidmsg = "Operation has no valid data input\n"
|
||||
o.info.warnings = ""
|
||||
o = bpy.context.scene.cam_operations[bpy.context.scene.cam_active_operation]
|
||||
def isValid(o,context):
|
||||
valid=True
|
||||
if o.geometry_source == 'OBJECT':
|
||||
if o.object_name not in bpy.data.objects:
|
||||
o.valid = False
|
||||
o.info.warnings = invalidmsg
|
||||
valid = False
|
||||
if o.geometry_source == 'COLLECTION':
|
||||
if o.collection_name not in bpy.data.collections:
|
||||
o.valid = False
|
||||
o.info.warnings = invalidmsg
|
||||
valid = False
|
||||
elif len(bpy.data.collections[o.collection_name].objects) == 0:
|
||||
o.valid = False
|
||||
o.info.warnings = invalidmsg
|
||||
valid = False
|
||||
|
||||
if o.geometry_source == 'IMAGE':
|
||||
if o.source_image_name not in bpy.data.images:
|
||||
o.valid = False
|
||||
o.info.warnings = invalidmsg
|
||||
valid = False
|
||||
return valid
|
||||
|
||||
def operationValid(self, context):
|
||||
scene=context.scene
|
||||
o = scene.cam_operations[scene.cam_active_operation]
|
||||
o.changed = True
|
||||
o.valid = isValid(o,context)
|
||||
invalidmsg = "Invalid source object for operation.\n"
|
||||
if o.valid:
|
||||
o.info.warnings = ""
|
||||
else:
|
||||
o.info.warnings = invalidmsg
|
||||
|
||||
if o.geometry_source == 'IMAGE':
|
||||
o.optimisation.use_exact = False
|
||||
o.update_offsetimage_tag = True
|
||||
o.update_zbufferimage_tag = True
|
||||
print('validity ')
|
||||
|
||||
def isChainValid(chain,context):
|
||||
s = context.scene
|
||||
if len(chain.operations)==0:
|
||||
return (False,"")
|
||||
for cho in chain.operations:
|
||||
found_op = None
|
||||
for so in s.cam_operations:
|
||||
if so.name == cho.name:
|
||||
found_op= so
|
||||
if found_op == None:
|
||||
return (False,f"Couldn't find operation {cho.name}")
|
||||
if cam.isValid(found_op,context) is False:
|
||||
return (False,f"Operation {found_op.name} is not valid")
|
||||
return (True,"")
|
||||
|
||||
# print(o.valid)
|
||||
|
||||
def updateOperationValid(self, context):
|
||||
operationValid(self, context)
|
||||
updateOperation(self, context)
|
||||
|
||||
|
||||
|
@ -802,7 +884,7 @@ class camOperation(bpy.types.PropertyGroup):
|
|||
update=updateRest)
|
||||
|
||||
# feeds
|
||||
feedrate: FloatProperty(name="Feedrate", description="Feedrate", min=0.00005, max=50.0, default=1.0,
|
||||
feedrate: FloatProperty(name="Feedrate", description="Feedrate in units per minute", min=0.00005, max=50.0, default=1.0,
|
||||
precision=cam.constants.PRECISION, unit="LENGTH", update=updateChipload)
|
||||
plunge_feedrate: FloatProperty(name="Plunge speed ", description="% of feedrate", min=0.1, max=100.0, default=50.0,
|
||||
precision=1, subtype='PERCENTAGE', update=updateRest)
|
||||
|
@ -1009,28 +1091,27 @@ class camChain(bpy.types.PropertyGroup): # chain is just a set of operations wh
|
|||
computing: bpy.props.BoolProperty(name="Computing right now", description="", default=False)
|
||||
operations: bpy.props.CollectionProperty(type=opReference) # this is to hold just operation names.
|
||||
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def check_operations_on_load(context):
|
||||
"""checks any broken computations on load and reset them."""
|
||||
s = bpy.context.scene
|
||||
for o in s.cam_operations:
|
||||
if o.computing:
|
||||
o.computing = False
|
||||
|
||||
class CAM_CUTTER_MT_presets(Menu):
|
||||
bl_label = "Cutter presets"
|
||||
preset_subdir = "cam_cutters"
|
||||
preset_operator = "script.execute_preset"
|
||||
draw = Menu.draw_preset
|
||||
|
||||
|
||||
class CAM_MACHINE_MT_presets(Menu):
|
||||
bl_label = "Machine presets"
|
||||
preset_subdir = "cam_machines"
|
||||
preset_operator = "script.execute_preset"
|
||||
draw = Menu.draw_preset
|
||||
|
||||
@classmethod
|
||||
def post_cb(cls,context):
|
||||
name = cls.bl_label
|
||||
filepath = bpy.utils.preset_find(name,
|
||||
cls.preset_subdir,
|
||||
display_name=True,
|
||||
ext=".py")
|
||||
context.preferences.addons['cam'].preferences.default_machine_preset=filepath
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
class AddPresetCamCutter(bl_operators.presets.AddPresetBase, Operator):
|
||||
"""Add a Cutter Preset"""
|
||||
|
@ -1133,6 +1214,42 @@ class BLENDERCAM_ENGINE(bpy.types.RenderEngine):
|
|||
bl_idname = 'BLENDERCAM_RENDER'
|
||||
bl_label = "Cam"
|
||||
|
||||
_IS_LOADING_DEFAULTS=False
|
||||
|
||||
@bpy.app.handlers.persistent
|
||||
def check_operations_on_load(context):
|
||||
global _IS_LOADING_DEFAULTS
|
||||
"""checks any broken computations on load and reset them."""
|
||||
s = bpy.context.scene
|
||||
for o in s.cam_operations:
|
||||
if o.computing:
|
||||
o.computing = False
|
||||
# set interface level to previously used level for a new file
|
||||
if not bpy.data.filepath:
|
||||
_IS_LOADING_DEFAULTS=True
|
||||
s.interface.level = bpy.context.preferences.addons['cam'].preferences.default_interface_level
|
||||
machine_preset=bpy.context.preferences.addons['cam'].preferences.machine_preset=bpy.context.preferences.addons['cam'].preferences.default_machine_preset
|
||||
if len(machine_preset)>0:
|
||||
print("Loading preset:",machine_preset)
|
||||
# load last used machine preset
|
||||
bpy.ops.script.execute_preset(filepath=machine_preset,menu_idname="CAM_MACHINE_MT_presets")
|
||||
_IS_LOADING_DEFAULTS=False
|
||||
# check for updated version of the plugin
|
||||
bpy.ops.render.cam_check_updates()
|
||||
# copy presets if not there yet
|
||||
if bpy.context.preferences.addons['cam'].preferences.just_updated:
|
||||
preset_source_path = Path(__file__).parent / 'presets'
|
||||
preset_target_path = Path(bpy.utils.script_path_user()) / 'presets'
|
||||
|
||||
def copy_if_not_exists(src,dst):
|
||||
if Path(dst).exists()==False:
|
||||
shutil.copy2(src,dst)
|
||||
shutil.copytree(preset_source_path,preset_target_path,copy_function=copy_if_not_exists,dirs_exist_ok=True)
|
||||
|
||||
bpy.context.preferences.addons['cam'].preferences.just_updated=False
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
|
||||
|
||||
def get_panels(): # convenience function for bot register and unregister functions
|
||||
# types = bpy.types
|
||||
|
@ -1285,6 +1402,7 @@ def compatible_panels():
|
|||
t.MATERIAL_PT_strand,
|
||||
t.MATERIAL_PT_options,
|
||||
t.MATERIAL_PT_shadow,
|
||||
|
||||
t.MATERIAL_PT_transp_game,
|
||||
t.MATERIAL_PT_volume_density,
|
||||
t.MATERIAL_PT_volume_shading,
|
||||
|
@ -1340,6 +1458,9 @@ def compatible_panels():
|
|||
|
||||
|
||||
classes = [
|
||||
autoupdate.UpdateSourceOperator,
|
||||
autoupdate.Updater,
|
||||
autoupdate.UpdateChecker,
|
||||
ui.CAM_UL_operations,
|
||||
ui.CAM_UL_chains,
|
||||
opReference,
|
||||
|
@ -1347,7 +1468,6 @@ classes = [
|
|||
machineSettings,
|
||||
CamAddonPreferences,
|
||||
import_settings,
|
||||
|
||||
ui.CAM_INTERFACE_Panel,
|
||||
ui.CAM_INTERFACE_Properties,
|
||||
ui.CAM_CHAINS_Panel,
|
||||
|
@ -1443,7 +1563,6 @@ classes = [
|
|||
|
||||
|
||||
def register():
|
||||
|
||||
for p in classes:
|
||||
bpy.utils.register_class(p)
|
||||
|
||||
|
@ -1471,6 +1590,8 @@ def register():
|
|||
|
||||
bpy.types.Scene.interface = bpy.props.PointerProperty(type=CAM_INTERFACE_Properties)
|
||||
|
||||
basrelief.register()
|
||||
|
||||
|
||||
def unregister():
|
||||
for p in classes:
|
||||
|
@ -1487,3 +1608,4 @@ def unregister():
|
|||
del s.cam_text
|
||||
del s.cam_pack
|
||||
del s.cam_slice
|
||||
basrelief.unregister()
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
import bpy
|
||||
import sys
|
||||
|
||||
import types
|
||||
|
||||
@types.coroutine
|
||||
def progress_async(text, n=None,value_type='%'):
|
||||
"""function for reporting during the script, works for background operations in the header."""
|
||||
throw_exception=yield ('progress',{'text':text,'n':n,"value_type":value_type})
|
||||
if throw_exception is not None:
|
||||
raise throw_exception
|
||||
|
||||
class AsyncCancelledException(Exception):
|
||||
pass
|
||||
|
||||
class AsyncOperatorMixin:
|
||||
|
||||
def __init__(self):
|
||||
self.timer=None
|
||||
self.coroutine=None
|
||||
self._is_cancelled=False
|
||||
|
||||
def modal(self,context,event):
|
||||
if event.type == 'TIMER':
|
||||
try:
|
||||
if self.tick(context):
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
context.window_manager.event_timer_remove(self.timer)
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
return {'FINISHED'}
|
||||
except Exception as e:
|
||||
context.window_manager.event_timer_remove(self.timer)
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
self.report({'ERROR'},str(e))
|
||||
return {'FINISHED'}
|
||||
elif event.type == 'ESC':
|
||||
self._is_cancelled=True
|
||||
self.tick(context)
|
||||
context.window_manager.event_timer_remove(self.timer)
|
||||
bpy.context.workspace.status_text_set(None)
|
||||
return {'FINISHED'}
|
||||
if 'BLOCKING' in self.bl_options:
|
||||
return {'RUNNING_MODAL'}
|
||||
else:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def show_progress(self,context,text, n,value_type):
|
||||
if n is not None:
|
||||
progress_text = f"{text}: {n:.2f}{value_type}"
|
||||
else:
|
||||
progress_text = f"{text}"
|
||||
bpy.context.workspace.status_text_set(progress_text + " (Press ESC to cancel)")
|
||||
sys.stdout.write(f"Progress: {progress_text}\n")
|
||||
sys.stdout.flush()
|
||||
|
||||
def tick(self,context):
|
||||
if self.coroutine==None:
|
||||
self.coroutine=self.execute_async(context)
|
||||
try:
|
||||
if self._is_cancelled:
|
||||
(msg,args)=self.coroutine.send(AsyncCancelledException("Cancelled with ESC key"))
|
||||
raise StopIteration
|
||||
else:
|
||||
(msg,args)=self.coroutine.send(None)
|
||||
if msg=='progress':
|
||||
self.show_progress(context,**args)
|
||||
else:
|
||||
sys.stdout.write(f"{msg},{args}")
|
||||
return True
|
||||
except StopIteration:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
if bpy.app.background:
|
||||
# running in background - don't run as modal,
|
||||
# otherwise tests all fail
|
||||
while self.tick(context)==True:
|
||||
pass
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
self.timer=context.window_manager.event_timer_add(.001, window=context.window)
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
class AsyncTestOperator(bpy.types.Operator,AsyncOperatorMixin):
|
||||
"""test async operator"""
|
||||
bl_idname = "object.cam_async_test_operator"
|
||||
bl_label = "Test operator for async stuff"
|
||||
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
||||
|
||||
async def execute_async(self,context):
|
||||
for x in range(100):
|
||||
await progress_async("Async test:",x)
|
||||
|
||||
#bpy.utils.register_class(AsyncTestOperator)
|
|
@ -0,0 +1,164 @@
|
|||
from datetime import date
|
||||
from cam.version import __version__ as current_version
|
||||
from urllib.request import urlopen
|
||||
import json
|
||||
import pathlib
|
||||
import zipfile
|
||||
import bpy
|
||||
import re
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import calendar
|
||||
|
||||
class UpdateChecker(bpy.types.Operator):
|
||||
"""check for updates"""
|
||||
bl_idname = "render.cam_check_updates"
|
||||
bl_label = "Check for updates in blendercam plugin"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check
|
||||
today=date.today().toordinal()
|
||||
update_source = bpy.context.preferences.addons['cam'].preferences.update_source
|
||||
match = re.match(r"https://github.com/([^/]+/[^/]+)",update_source)
|
||||
if match:
|
||||
update_source = f"https://api.github.com/repos/{match.group(1)}/releases"
|
||||
|
||||
print(f"update check: {update_source}")
|
||||
if update_source=="None" or len(update_source)==0:
|
||||
return {'FINISHED'}
|
||||
|
||||
bpy.context.preferences.addons['cam'].preferences.new_version_available = ""
|
||||
bpy.ops.wm.save_userpref()
|
||||
# get list of releases from github release
|
||||
if update_source.endswith("/releases"):
|
||||
with urlopen(update_source,timeout=2.0) as response:
|
||||
body = response.read().decode("UTF-8")
|
||||
# find the tag name
|
||||
release_list=json.loads(body)
|
||||
if len(release_list) > 0:
|
||||
release = release_list[0]
|
||||
tag = release["tag_name"]
|
||||
print(f"Found release: {tag}")
|
||||
match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)",tag)
|
||||
if match:
|
||||
version_num = tuple(map(int,match.groups()))
|
||||
print(f"Found version: {version_num}")
|
||||
bpy.context.preferences.addons['cam'].preferences.last_update_check = today
|
||||
|
||||
if version_num > current_version:
|
||||
bpy.context.preferences.addons['cam'].preferences.new_version_available = ".".join(
|
||||
[str(x) for x in version_num])
|
||||
bpy.ops.wm.save_userpref()
|
||||
elif update_source.endswith("/commits"):
|
||||
with urlopen(update_source+"?per_page=1",timeout=2) as response:
|
||||
body = response.read().decode("UTF-8")
|
||||
# find the tag name
|
||||
commit_list=json.loads(body)
|
||||
commit_sha=commit_list[0]['sha']
|
||||
commit_date=commit_list[0]['commit']['author']['date']
|
||||
if bpy.context.preferences.addons['cam'].preferences.last_commit_hash != commit_sha:
|
||||
bpy.context.preferences.addons['cam'].preferences.new_version_available=commit_date
|
||||
bpy.ops.wm.save_userpref()
|
||||
return {'FINISHED'}
|
||||
|
||||
class Updater(bpy.types.Operator):
|
||||
"""update to newer version if possible """
|
||||
bl_idname = "render.cam_update_now"
|
||||
bl_label = "Update"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
print("update check")
|
||||
last_update_check = bpy.context.preferences.addons['cam'].preferences.last_update_check
|
||||
today=date.today().toordinal()
|
||||
update_source = bpy.context.preferences.addons['cam'].preferences.update_source
|
||||
if update_source=="None" or len(update_source)==0:
|
||||
return {'FINISHED'}
|
||||
match = re.match(r"https://github.com/([^/]+/[^/]+)",update_source)
|
||||
if match:
|
||||
update_source = f"https://api.github.com/repos/{match.group(1)}/releases"
|
||||
|
||||
# get list of releases from github release
|
||||
if update_source.endswith("/releases"):
|
||||
with urlopen(update_source,timeout=2) as response:
|
||||
body = response.read().decode("UTF-8")
|
||||
# find the tag name
|
||||
release_list=json.loads(body)
|
||||
if len(release_list) > 0:
|
||||
release = release_list[0]
|
||||
tag = release["tag_name"]
|
||||
print(f"Found release: {tag}")
|
||||
match = re.match(r".*(\d+)\.(\s*\d+)\.(\s*\d+)",tag)
|
||||
if match:
|
||||
version_num = tuple(map(int,match.groups()))
|
||||
print(f"Found version: {version_num}")
|
||||
bpy.context.preferences.addons['cam'].preferences.last_update_check = today
|
||||
bpy.ops.wm.save_userpref()
|
||||
|
||||
if version_num > current_version:
|
||||
print("Version is newer, downloading source")
|
||||
zip_url = release["zipball_url"]
|
||||
self.install_zip_from_url(zip_url)
|
||||
return {'FINISHED'}
|
||||
elif update_source.endswith("/commits"):
|
||||
with urlopen(update_source+"?per_page=1",timeout=2) as response:
|
||||
body = response.read().decode("UTF-8")
|
||||
# find the tag name
|
||||
commit_list=json.loads(body)
|
||||
commit_sha=commit_list[0]['sha']
|
||||
if bpy.context.preferences.addons['cam'].preferences.last_commit_hash != commit_sha:
|
||||
# get zipball from this commit
|
||||
zip_url = update_source.replace("/commits",f"/zipball/{commit_sha}")
|
||||
self.install_zip_from_url(zip_url)
|
||||
bpy.context.preferences.addons['cam'].preferences.last_commit_hash = commit_sha
|
||||
bpy.ops.wm.save_userpref()
|
||||
return {'FINISHED'}
|
||||
|
||||
def install_zip_from_url(self,zip_url):
|
||||
with urlopen(zip_url) as zip_response:
|
||||
zip_body=zip_response.read()
|
||||
buffer= io.BytesIO(zip_body)
|
||||
zf=zipfile.ZipFile(buffer,mode='r')
|
||||
files=zf.infolist()
|
||||
cam_addon_path = pathlib.Path(__file__).parent
|
||||
for fileinfo in files:
|
||||
filename=fileinfo.filename
|
||||
if fileinfo.is_dir() == False:
|
||||
path_pos=filename.replace("\\","/").find("/scripts/addons/cam/")
|
||||
if path_pos!=-1:
|
||||
relative_path=filename[path_pos+len("/scripts/addons/cam/"):]
|
||||
out_path = cam_addon_path / relative_path
|
||||
print(out_path)
|
||||
# check folder exists
|
||||
out_path.parent.mkdir(parents=True,exist_ok=True)
|
||||
with zf.open(filename,"r") as in_file, open(out_path,"wb") as out_file:
|
||||
time_struct=(*fileinfo.date_time,0,0,0)
|
||||
mtime=calendar.timegm(time_struct)
|
||||
out_file.write(in_file.read())
|
||||
os.utime(out_path,times=(mtime,mtime))
|
||||
# TODO: check for newer times
|
||||
# TODO: what about if a file is deleted...
|
||||
# updated everything, now mark as updated and reload scripts
|
||||
bpy.context.preferences.addons['cam'].preferences.just_updated=True
|
||||
bpy.context.preferences.addons['cam'].preferences.new_version_available = ""
|
||||
bpy.ops.wm.save_userpref()
|
||||
# unload ourself from python module system
|
||||
delete_list=[]
|
||||
for m in sys.modules.keys():
|
||||
if m.startswith("cam.") or m=='cam':
|
||||
delete_list.append(m)
|
||||
for d in delete_list:
|
||||
del sys.modules[d]
|
||||
bpy.ops.script.reload()
|
||||
|
||||
class UpdateSourceOperator(bpy.types.Operator):
|
||||
bl_idname = "render.cam_set_update_source"
|
||||
bl_label = "Set blendercam update source"
|
||||
|
||||
new_source: bpy.props.StringProperty(default='')
|
||||
def execute(self,context):
|
||||
bpy.context.preferences.addons['cam'].preferences.update_source=self.new_source
|
||||
bpy.ops.wm.save_userpref()
|
||||
return {'FINISHED'}
|
|
@ -6,18 +6,6 @@ from math import *
|
|||
from bpy.props import *
|
||||
|
||||
|
||||
bl_info = {
|
||||
"name": "Bas relief",
|
||||
"author": "Vilem Novak",
|
||||
"version": (0, 1, 0),
|
||||
"blender": (2, 80, 0),
|
||||
"location": "Properties > render",
|
||||
"description": "Converts zbuffer image to bas relief.",
|
||||
"warning": "there is no warranty. needs Numpy library installed in blender python directory.",
|
||||
"wiki_url": "blendercam.blogspot.com",
|
||||
"tracker_url": "",
|
||||
"category": "Scene"}
|
||||
|
||||
##////////////////////////////////////////////////////////////////////
|
||||
#// Full Multigrid Algorithm for solving partial differential equations
|
||||
#//////////////////////////////////////////////////////////////////////
|
||||
|
@ -596,111 +584,144 @@ def imagetonumpy(i):
|
|||
print('\ntime of image to numpy '+str(time.time()-t))
|
||||
return na
|
||||
|
||||
def tonemap(i):
|
||||
maxheight=i.max()
|
||||
def tonemap(i,exponent):
|
||||
# if depth buffer never got written it gets set
|
||||
# to a great big value (10000000000.0)
|
||||
# filter out anything within an order of magnitude of it
|
||||
# so we only have things that are actually drawn
|
||||
maxheight=i.max(where=i<1000000000.0,initial=0)
|
||||
minheight=i.min()
|
||||
i[:]=numpy.clip(i,minheight,maxheight)
|
||||
|
||||
i[:]=((i-minheight))/(maxheight-minheight)
|
||||
i[:]**=exponent
|
||||
|
||||
def vert(column, row, z,XYscaling,Zscaling):
|
||||
""" Create a single vert """
|
||||
return column * XYscaling, row * XYscaling, z * Zscaling
|
||||
""" Create a single vert """
|
||||
return column * XYscaling, row * XYscaling, z * Zscaling
|
||||
|
||||
def buildMesh(mesh_z,br):
|
||||
global rows
|
||||
global size
|
||||
scale=1
|
||||
scalez=1
|
||||
decimateRatio= br.decimate_ratio #get variable from interactive table
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for object in bpy.data.objects:
|
||||
if re.search("BasReliefMesh",str(object)):
|
||||
bpy.data.objects.remove(object)
|
||||
print("old basrelief removed")
|
||||
|
||||
global rows
|
||||
global size
|
||||
scale=1
|
||||
scalez=1
|
||||
decimateRatio= br.decimate_ratio #get variable from interactive table
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
for object in bpy.data.objects:
|
||||
if re.search("BasReliefMesh",str(object)):
|
||||
bpy.data.objects.remove(object)
|
||||
print("old basrelief removed")
|
||||
|
||||
|
||||
|
||||
print("Building mesh")
|
||||
numY = mesh_z.shape[1]
|
||||
numX = mesh_z.shape[0]
|
||||
print(numX,numY)
|
||||
|
||||
verts = list()
|
||||
faces = list()
|
||||
|
||||
for i, row in enumerate(mesh_z):
|
||||
for j, col in enumerate(row):
|
||||
verts.append(vert(i, j, col,scale,scalez))
|
||||
|
||||
print("Building mesh")
|
||||
numY = mesh_z.shape[1]
|
||||
numX = mesh_z.shape[0]
|
||||
print(numX,numY)
|
||||
|
||||
verts = list()
|
||||
faces = list()
|
||||
|
||||
for i, row in enumerate(mesh_z):
|
||||
for j, col in enumerate(row):
|
||||
verts.append(vert(i, j, col,scale,scalez))
|
||||
|
||||
count = 0
|
||||
for i in range (0, numY *(numX-1)):
|
||||
if count < numY-1:
|
||||
A = i # the first vertex
|
||||
B = i+1 # the second vertex
|
||||
C = (i+numY)+1 # the third vertex
|
||||
D = (i+numY) # the fourth vertex
|
||||
count = 0
|
||||
for i in range (0, numY *(numX-1)):
|
||||
if count < numY-1:
|
||||
A = i # the first vertex
|
||||
B = i+1 # the second vertex
|
||||
C = (i+numY)+1 # the third vertex
|
||||
D = (i+numY) # the fourth vertex
|
||||
|
||||
face = (A,B,C,D)
|
||||
faces.append(face)
|
||||
count = count + 1
|
||||
else:
|
||||
count = 0
|
||||
face = (A,B,C,D)
|
||||
faces.append(face)
|
||||
count = count + 1
|
||||
else:
|
||||
count = 0
|
||||
|
||||
# Create Mesh Datablock
|
||||
mesh = bpy.data.meshes.new("displacement")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
# Create Mesh Datablock
|
||||
mesh = bpy.data.meshes.new("displacement")
|
||||
mesh.from_pydata(verts, [], faces)
|
||||
|
||||
mesh.update()
|
||||
mesh.update()
|
||||
|
||||
# make object from mesh
|
||||
new_object = bpy.data.objects.new('BasReliefMesh', mesh)
|
||||
scene = bpy.context.scene
|
||||
scene.collection.objects.link(new_object)
|
||||
# make object from mesh
|
||||
new_object = bpy.data.objects.new('BasReliefMesh', mesh)
|
||||
scene = bpy.context.scene
|
||||
scene.collection.objects.link(new_object)
|
||||
|
||||
#mesh object is made - preparing to decimate.
|
||||
ob=bpy.data.objects['BasReliefMesh']
|
||||
ob.select_set(True)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
bpy.context.active_object.dimensions= (br.widthmm/1000,br.heightmm/1000,br.thicknessmm/1000)
|
||||
bpy.context.active_object.location= (float(br.justifyx)*br.widthmm/1000,float(br.justifyy)*br.heightmm/1000,float(br.justifyz)*br.thicknessmm/1000)
|
||||
#mesh object is made - preparing to decimate.
|
||||
ob=bpy.data.objects['BasReliefMesh']
|
||||
ob.select_set(True)
|
||||
bpy.context.view_layer.objects.active = ob
|
||||
bpy.context.active_object.dimensions= (br.widthmm/1000,br.heightmm/1000,br.thicknessmm/1000)
|
||||
bpy.context.active_object.location= (float(br.justifyx)*br.widthmm/1000,float(br.justifyy)*br.heightmm/1000,float(br.justifyz)*br.thicknessmm/1000)
|
||||
|
||||
|
||||
print("faces:" + str(len(ob.data.polygons)))
|
||||
print("vertices:" + str(len(ob.data.vertices)))
|
||||
if decimateRatio > 0.95:
|
||||
print("skipping decimate ratio > 0.95")
|
||||
else:
|
||||
m = ob.modifiers.new(name="Foo", type='DECIMATE')
|
||||
m.ratio=decimateRatio
|
||||
print("decimating with ratio:"+str(decimateRatio))
|
||||
bpy.ops.object.modifier_apply({"object" : ob}, modifier=m.name)
|
||||
print("decimated")
|
||||
print("faces:" + str(len(ob.data.polygons)))
|
||||
print("vertices:" + str(len(ob.data.vertices)))
|
||||
print("faces:" + str(len(ob.data.polygons)))
|
||||
print("vertices:" + str(len(ob.data.vertices)))
|
||||
if decimateRatio > 0.95:
|
||||
print("skipping decimate ratio > 0.95")
|
||||
else:
|
||||
m = ob.modifiers.new(name="Foo", type='DECIMATE')
|
||||
m.ratio=decimateRatio
|
||||
print("decimating with ratio:"+str(decimateRatio))
|
||||
bpy.ops.object.modifier_apply(modifier=m.name)
|
||||
print("decimated")
|
||||
print("faces:" + str(len(ob.data.polygons)))
|
||||
print("vertices:" + str(len(ob.data.vertices)))
|
||||
|
||||
# Switches to cycles render to CYCLES to render the sceen then switches it back to BLENDERCAM_RENDER for basRelief
|
||||
def renderScene(width,height,bit_diameter,passes_per_radius,make_nodes,view_layer):
|
||||
print("rendering scene")
|
||||
scene = bpy.context.scene
|
||||
# make sure we're in object mode or else bad things happen
|
||||
if bpy.context.active_object:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
def renderScene(width,height,bit_diameter,passes_per_radius):
|
||||
print("rendering scene")
|
||||
bpy.context.scene.render.engine = 'CYCLES'
|
||||
scene = bpy.context.scene
|
||||
# Set render resolution
|
||||
passes=bit_diameter/(2*passes_per_radius)
|
||||
x=round(width/passes)
|
||||
y=round(height/passes)
|
||||
print(x,y,passes)
|
||||
scene.render.resolution_x = x
|
||||
scene.render.resolution_y = y
|
||||
scene.render.resolution_percentage = 100
|
||||
bpy.ops.render.render(animation=False, write_still=False, use_viewport=True, layer="", scene="")
|
||||
bpy.context.scene.render.engine = 'BLENDERCAM_RENDER'
|
||||
print("done rendering")
|
||||
|
||||
|
||||
scene.render.engine = 'CYCLES'
|
||||
our_viewer=None
|
||||
our_renderer=None
|
||||
if make_nodes:
|
||||
# make depth render node and viewer node
|
||||
if scene.use_nodes==False:
|
||||
scene.use_nodes=True
|
||||
node_tree=scene.node_tree
|
||||
nodes=node_tree.nodes
|
||||
our_viewer=node_tree.nodes.new(type = 'CompositorNodeViewer')
|
||||
our_viewer.label="CAM_basrelief_viewer"
|
||||
our_renderer=node_tree.nodes.new(type= 'CompositorNodeRLayers')
|
||||
our_renderer.label="CAM_basrelief_renderlayers"
|
||||
our_renderer.layer=view_layer
|
||||
node_tree.links.new(our_renderer.outputs[our_renderer.outputs.find('Depth')],our_viewer.inputs[our_viewer.inputs.find("Image")])
|
||||
scene.view_layers[view_layer].use_pass_z=True
|
||||
# set our viewer as active so that it is what gets rendered to viewer node image
|
||||
nodes.active=our_viewer
|
||||
|
||||
# Set render resolution
|
||||
passes=bit_diameter/(2*passes_per_radius)
|
||||
x=round(width/passes)
|
||||
y=round(height/passes)
|
||||
print(x,y,passes)
|
||||
scene.render.resolution_x = x
|
||||
scene.render.resolution_y = y
|
||||
scene.render.resolution_percentage = 100
|
||||
bpy.ops.render.render(animation=False, write_still=False, use_viewport=True, layer="", scene="")
|
||||
if our_renderer is not None:
|
||||
nodes.remove(our_renderer)
|
||||
if our_viewer is not None:
|
||||
nodes.remove(our_viewer)
|
||||
bpy.context.scene.render.engine = 'BLENDERCAM_RENDER'
|
||||
print("done rendering")
|
||||
|
||||
|
||||
def problemAreas(br):
|
||||
t=time.time()
|
||||
|
||||
i=bpy.data.images[br.source_image_name]
|
||||
i=bpy.data.images["Viewer Node"]
|
||||
if br.use_image_source:
|
||||
i=bpy.data.images[br.source_image_name]
|
||||
else:
|
||||
i=bpy.data.images["Viewer Node"]
|
||||
silh_thres=br.silhouette_threshold
|
||||
recover_silh=br.recover_silhouettes
|
||||
silh_scale=br.silhouette_scale
|
||||
|
@ -724,7 +745,7 @@ def problemAreas(br):
|
|||
if br.gradient_scaling_mask_use:
|
||||
mask=imagetonumpy(m)
|
||||
#put image to scale
|
||||
tonemap(nar)
|
||||
tonemap(nar,br.depth_exponent)
|
||||
nar=1-nar# reverse z buffer+ add something
|
||||
print(nar.min(),nar.max())
|
||||
gx=nar.copy()
|
||||
|
@ -785,8 +806,10 @@ def problemAreas(br):
|
|||
def relief(br):
|
||||
t=time.time()
|
||||
|
||||
i=bpy.data.images[br.source_image_name]
|
||||
i=bpy.data.images["Viewer Node"]
|
||||
if br.use_image_source:
|
||||
i=bpy.data.images[br.source_image_name]
|
||||
else:
|
||||
i=bpy.data.images["Viewer Node"]
|
||||
silh_thres=br.silhouette_threshold
|
||||
recover_silh=br.recover_silhouettes
|
||||
silh_scale=br.silhouette_scale
|
||||
|
@ -810,9 +833,12 @@ def relief(br):
|
|||
if br.gradient_scaling_mask_use:
|
||||
mask=imagetonumpy(m)
|
||||
#put image to scale
|
||||
tonemap(nar)
|
||||
tonemap(nar,br.depth_exponent)
|
||||
nar=1-nar# reverse z buffer+ add something
|
||||
print(nar.min(),nar.max())
|
||||
print("Range:",nar.min(),nar.max())
|
||||
if nar.min() - nar.max() ==0:
|
||||
raise ReliefError("Input image is blank - check you have the correct view layer or input image set.")
|
||||
|
||||
gx=nar.copy()
|
||||
gx.fill(0)
|
||||
gx[:-1,:]=nar[1:,:]-nar[:-1,:]
|
||||
|
@ -923,7 +949,7 @@ def relief(br):
|
|||
|
||||
solve_pde_multigrid( divg, target ,vcycleiterations, linbcgiterations, smoothiterations, mins, levels, useplanar, planar)
|
||||
|
||||
tonemap(target)
|
||||
tonemap(target,1)
|
||||
|
||||
buildMesh(target,br)
|
||||
|
||||
|
@ -935,8 +961,9 @@ def relief(br):
|
|||
|
||||
|
||||
class BasReliefsettings(bpy.types.PropertyGroup):
|
||||
use_image_source: bpy.props.BoolProperty(name="Use image source",description="", default=False)
|
||||
source_image_name: bpy.props.StringProperty(name='Image source', description='image source')
|
||||
# output_image_name: bpy.props.StringProperty(name='Image target', description='image output name')
|
||||
view_layer_name: bpy.props.StringProperty(name='View layer source',description='Make a bas-relief from whatever is on this view layer')
|
||||
bit_diameter: FloatProperty(name="Diameter of ball end in mm", description="Diameter of bit which will be used for carving", min=0.01, max=50.0, default=3.175, precision=PRECISION)
|
||||
pass_per_radius: bpy.props.IntProperty(name="Passes per radius", description="Amount of passes per radius\n(more passes, more mesh precision)",default=2, min=1, max=10)
|
||||
widthmm: bpy.props.IntProperty(name="Desired width in mm", default=200, min=5, max=4000)
|
||||
|
@ -946,7 +973,9 @@ class BasReliefsettings(bpy.types.PropertyGroup):
|
|||
justifyx: bpy.props.EnumProperty(name="X",items=[('1', 'Left','', 0),('-0.5', 'Centered','', 1),('-1', 'Right','', 2)],default='-1')
|
||||
justifyy: bpy.props.EnumProperty(name="Y",items=[('1', 'Bottom','', 0),('-0.5', 'Centered','', 2),('-1', 'Top','', 1),],default='-1')
|
||||
justifyz: bpy.props.EnumProperty(name="Z",items=[('-1', 'Below 0','', 0),('-0.5', 'Centered','', 2),('1', 'Above 0','', 1),],default='-1')
|
||||
|
||||
|
||||
depth_exponent: FloatProperty(name="Depth exponent", description="Initial depth map is taken to this power. Higher = sharper relief",min=0.5,max=10.0,default=1.0,precision=PRECISION)
|
||||
|
||||
silhouette_threshold: FloatProperty(name="Silhouette threshold", description="Silhouette threshold", min=0.000001, max=1.0, default=0.003, precision=PRECISION)
|
||||
recover_silhouettes: bpy.props.BoolProperty(name="Recover silhouettes",description="", default=True)
|
||||
silhouette_scale: FloatProperty(name="Silhouette scale", description="Silhouette scale", min=0.000001, max=5.0, default=0.3, precision=PRECISION)
|
||||
|
@ -999,8 +1028,12 @@ class BASRELIEF_Panel(bpy.types.Panel):
|
|||
#cutter preset
|
||||
layout.operator("scene.calculate_bas_relief", text="Calculate relief")
|
||||
layout.prop(br,'advanced')
|
||||
layout.prop_search(br,'source_image_name', bpy.data, "images")
|
||||
# layout.prop(br,'output_image_name')
|
||||
layout.prop(br,'use_image_source')
|
||||
if br.use_image_source:
|
||||
layout.prop_search(br,'source_image_name', bpy.data, "images")
|
||||
else:
|
||||
layout.prop_search(br,'view_layer_name',bpy.context.scene,"view_layers")
|
||||
layout.prop(br,'depth_exponent')
|
||||
layout.label(text="Project parameters")
|
||||
layout.prop(br,'bit_diameter')
|
||||
layout.prop(br,'pass_per_radius')
|
||||
|
@ -1012,7 +1045,7 @@ class BASRELIEF_Panel(bpy.types.Panel):
|
|||
layout.prop(br,'justifyx')
|
||||
layout.prop(br,'justifyy')
|
||||
layout.prop(br,'justifyz')
|
||||
|
||||
|
||||
layout.label(text="Silhouette")
|
||||
layout.prop(br,'silhouette_threshold')
|
||||
layout.prop(br,'recover_silhouettes')
|
||||
|
@ -1050,6 +1083,9 @@ class BASRELIEF_Panel(bpy.types.Panel):
|
|||
#if br.scale_down_before_use:
|
||||
# layout.prop(br,'scale_down_before')
|
||||
|
||||
class ReliefError(Exception):
|
||||
pass
|
||||
|
||||
class DoBasRelief(bpy.types.Operator):
|
||||
"""calculate Bas relief"""
|
||||
bl_idname = "scene.calculate_bas_relief"
|
||||
|
@ -1058,17 +1094,23 @@ class DoBasRelief(bpy.types.Operator):
|
|||
|
||||
processes=[]
|
||||
|
||||
#@classmethod
|
||||
#def poll(cls, context):
|
||||
# return context.active_object is not None
|
||||
|
||||
def execute(self, context):
|
||||
s=bpy.context.scene
|
||||
br=s.basreliefsettings
|
||||
if not br.use_image_source and br.view_layer_name=="":
|
||||
br.view_layer_name=bpy.context.view_layer.name
|
||||
|
||||
renderScene(br.widthmm,br.heightmm,br.bit_diameter,br.pass_per_radius)
|
||||
|
||||
relief(br)
|
||||
try:
|
||||
renderScene(br.widthmm,br.heightmm,br.bit_diameter,br.pass_per_radius,not br.use_image_source,br.view_layer_name)
|
||||
except ReliefError as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
return {"CANCELLED"}
|
||||
|
||||
try:
|
||||
relief(br)
|
||||
except ReliefError as e:
|
||||
self.report({"ERROR"}, str(e))
|
||||
return {"CANCELLED"}
|
||||
return {'FINISHED'}
|
||||
|
||||
class ProblemAreas(bpy.types.Operator):
|
||||
|
@ -1110,5 +1152,3 @@ def unregister():
|
|||
s=bpy.types.Scene
|
||||
del s.basreliefsettings
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
|
@ -25,6 +25,7 @@ from shapely.geometry import polygon as spolygon
|
|||
from shapely import geometry as sgeometry
|
||||
from cam import polygon_utils_cam
|
||||
from cam.simple import *
|
||||
from cam.exception import CamException
|
||||
import math
|
||||
|
||||
|
||||
|
@ -970,7 +971,6 @@ def restoreVisibility(o, storage):
|
|||
|
||||
|
||||
def meshFromCurve(o, use_modifiers=False):
|
||||
# print(o.name,o)
|
||||
activate(o)
|
||||
bpy.ops.object.duplicate()
|
||||
|
||||
|
@ -980,6 +980,9 @@ def meshFromCurve(o, use_modifiers=False):
|
|||
|
||||
if co.type == 'FONT': # support for text objects is only and only here, just convert them to curves.
|
||||
bpy.ops.object.convert(target='CURVE', keep_original=False)
|
||||
elif co.type != 'CURVE': # curve must be a curve...
|
||||
bpy.ops.object.delete() # delete temporary object
|
||||
raise CamException("Source curve object must be of type CURVE")
|
||||
co.data.dimensions = '3D'
|
||||
co.data.bevel_depth = 0
|
||||
co.data.extrude = 0
|
||||
|
|
|
@ -266,6 +266,9 @@ def prepareBulletCollision(o):
|
|||
if active_collection in collisionob.users_collection:
|
||||
active_collection.objects.unlink(collisionob)
|
||||
|
||||
# call this twice or else blender doesn't update physics. No idea why, but just leave
|
||||
# this double call to avoid bad things
|
||||
getCutterBullet(o)
|
||||
getCutterBullet(o)
|
||||
|
||||
# machine objects scaling up to simulation scale
|
||||
|
|
|
@ -179,7 +179,7 @@ class CamCurveOvercuts(bpy.types.Operator):
|
|||
for s in shapes.geoms:
|
||||
s = shapely.geometry.polygon.orient(s, 1)
|
||||
if s.boundary.geom_type == 'LineString':
|
||||
from shapely import MultiLineString
|
||||
from shapely.geometry import MultiLineString
|
||||
loops = MultiLineString([s.boundary])
|
||||
else:
|
||||
loops = s.boundary
|
||||
|
|
|
@ -40,6 +40,8 @@ from cam.collision import *
|
|||
from cam import simple
|
||||
from cam.simple import *
|
||||
|
||||
from cam.async_op import progress_async
|
||||
|
||||
from cam import bridges
|
||||
from cam.bridges import *
|
||||
|
||||
|
@ -512,7 +514,7 @@ def exportGcodePath(filename, vertslist, operations):
|
|||
print(time.time() - t)
|
||||
|
||||
|
||||
def getPath(context, operation): # should do all path calculations.
|
||||
async def getPath(context, operation): # should do all path calculations.
|
||||
t = time.process_time()
|
||||
# print('ahoj0')
|
||||
if shapely.speedups.available:
|
||||
|
@ -541,19 +543,19 @@ def getPath(context, operation): # should do all path calculations.
|
|||
print(operation.machine_axes)
|
||||
|
||||
if operation.machine_axes == '3':
|
||||
getPath3axis(context, operation)
|
||||
await getPath3axis(context, operation)
|
||||
|
||||
elif (operation.machine_axes == '5' and operation.strategy5axis == 'INDEXED') or (
|
||||
operation.machine_axes == '4' and operation.strategy4axis == 'INDEXED'):
|
||||
# 5 axis operations are now only 3 axis operations that get rotated...
|
||||
operation.orientation = prepareIndexed(operation) # TODO RENAME THIS
|
||||
|
||||
getPath3axis(context, operation) # TODO RENAME THIS
|
||||
await getPath3axis(context, operation) # TODO RENAME THIS
|
||||
|
||||
cleanupIndexed(operation) # TODO RENAME THIS
|
||||
# transform5axisIndexed
|
||||
elif operation.machine_axes == '4':
|
||||
getPath4axis(context, operation)
|
||||
await getPath4axis(context, operation)
|
||||
|
||||
# export gcode if automatic.
|
||||
if operation.auto_export:
|
||||
|
@ -602,22 +604,23 @@ def checkMemoryLimit(o):
|
|||
|
||||
# this is the main function.
|
||||
# FIXME: split strategies into separate file!
|
||||
def getPath3axis(context, operation):
|
||||
async def getPath3axis(context, operation):
|
||||
s = bpy.context.scene
|
||||
o = operation
|
||||
utils.getBounds(o)
|
||||
tw = time.time()
|
||||
|
||||
if o.strategy == 'CUTOUT':
|
||||
strategy.cutout(o)
|
||||
await strategy.cutout(o)
|
||||
|
||||
elif o.strategy == 'CURVE':
|
||||
strategy.curve(o)
|
||||
await strategy.curve(o)
|
||||
|
||||
elif o.strategy == 'PROJECTED_CURVE':
|
||||
strategy.proj_curve(s, o)
|
||||
await strategy.proj_curve(s, o)
|
||||
|
||||
elif o.strategy == 'POCKET':
|
||||
strategy.pocket(o)
|
||||
await strategy.pocket(o)
|
||||
|
||||
elif o.strategy in ['PARALLEL', 'CROSS', 'BLOCK', 'SPIRAL', 'CIRCLES', 'OUTLINEFILL', 'CARVE', 'PENCIL', 'CRAZY']:
|
||||
|
||||
|
@ -625,16 +628,16 @@ def getPath3axis(context, operation):
|
|||
pathSamples = []
|
||||
ob = bpy.data.objects[o.curve_object]
|
||||
pathSamples.extend(curveToChunks(ob))
|
||||
pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
pathSamples = chunksRefine(pathSamples, o)
|
||||
elif o.strategy == 'PENCIL':
|
||||
prepareArea(o)
|
||||
await prepareArea(o)
|
||||
utils.getAmbient(o)
|
||||
pathSamples = getOffsetImageCavities(o, o.offset_image)
|
||||
pathSamples = limitChunks(pathSamples, o)
|
||||
pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
elif o.strategy == 'CRAZY':
|
||||
prepareArea(o)
|
||||
await prepareArea(o)
|
||||
# pathSamples = crazyStrokeImage(o)
|
||||
# this kind of worked and should work:
|
||||
millarea = o.zbuffer_image < o.minz + 0.000001
|
||||
|
@ -642,22 +645,21 @@ def getPath3axis(context, operation):
|
|||
|
||||
pathSamples = crazyStrokeImageBinary(o, millarea, avoidarea)
|
||||
#####
|
||||
pathSamples = utils.sortChunks(pathSamples, o)
|
||||
pathSamples = await utils.sortChunks(pathSamples, o)
|
||||
pathSamples = chunksRefine(pathSamples, o)
|
||||
|
||||
else:
|
||||
print("PARALLEL")
|
||||
if o.strategy == 'OUTLINEFILL':
|
||||
utils.getOperationSilhouete(o)
|
||||
|
||||
pathSamples = getPathPattern(o)
|
||||
|
||||
if o.strategy == 'OUTLINEFILL':
|
||||
pathSamples = utils.sortChunks(pathSamples, o)
|
||||
pathSamples = await utils.sortChunks(pathSamples, o)
|
||||
# have to be sorted once before, because of the parenting inside of samplechunks
|
||||
|
||||
if o.strategy in ['BLOCK', 'SPIRAL', 'CIRCLES']:
|
||||
pathSamples = utils.connectChunksLow(pathSamples, o)
|
||||
pathSamples = await utils.connectChunksLow(pathSamples, o)
|
||||
|
||||
# print (minz)
|
||||
|
||||
|
@ -665,7 +667,7 @@ def getPath3axis(context, operation):
|
|||
layers = strategy.getLayers(o, o.maxz, o.min.z)
|
||||
|
||||
print("SAMPLE", o.name)
|
||||
chunks.extend(utils.sampleChunks(o, pathSamples, layers))
|
||||
chunks.extend(await utils.sampleChunks(o, pathSamples, layers))
|
||||
print("SAMPLE OK")
|
||||
if o.strategy == 'PENCIL': # and bpy.app.debug_value==-3:
|
||||
chunks = chunksCoherency(chunks)
|
||||
|
@ -673,9 +675,9 @@ def getPath3axis(context, operation):
|
|||
|
||||
if o.strategy in ['PARALLEL', 'CROSS', 'PENCIL', 'OUTLINEFILL']: # and not o.movement.parallel_step_back:
|
||||
print('sorting')
|
||||
chunks = utils.sortChunks(chunks, o)
|
||||
chunks = await utils.sortChunks(chunks, o)
|
||||
if o.strategy == 'OUTLINEFILL':
|
||||
chunks = utils.connectChunksLow(chunks, o)
|
||||
chunks = await utils.connectChunksLow(chunks, o)
|
||||
if o.movement.ramp:
|
||||
for ch in chunks:
|
||||
ch.rampZigZag(ch.zstart, ch.points[0][2], o)
|
||||
|
@ -694,7 +696,7 @@ def getPath3axis(context, operation):
|
|||
elif o.strategy == 'WATERLINE' and o.optimisation.use_opencamlib:
|
||||
utils.getAmbient(o)
|
||||
chunks = []
|
||||
oclGetWaterline(o, chunks)
|
||||
await oclGetWaterline(o, chunks)
|
||||
chunks = limitChunks(chunks, o)
|
||||
if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CW') or (
|
||||
o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CCW'):
|
||||
|
@ -704,10 +706,9 @@ def getPath3axis(context, operation):
|
|||
|
||||
elif o.strategy == 'WATERLINE' and not o.optimisation.use_opencamlib:
|
||||
topdown = True
|
||||
tw = time.time()
|
||||
chunks = []
|
||||
progress('retrieving object slices')
|
||||
prepareArea(o)
|
||||
await progress_async('retrieving object slices')
|
||||
await prepareArea(o)
|
||||
layerstep = 1000000000
|
||||
if o.use_layers:
|
||||
layerstep = math.floor(o.stepdown / o.slice_detail)
|
||||
|
@ -719,7 +720,7 @@ def getPath3axis(context, operation):
|
|||
layerend = o.min.z #
|
||||
layers = [[layerstart, layerend]]
|
||||
#######################
|
||||
nslices = ceil(abs(o.minz / o.slice_detail))
|
||||
nslices = ceil(abs((o.minz-o.maxz) / o.slice_detail))
|
||||
lastslice = spolygon.Polygon() # polyversion
|
||||
layerstepinc = 0
|
||||
|
||||
|
@ -778,7 +779,7 @@ def getPath3axis(context, operation):
|
|||
# project paths TODO: path projection during waterline is not working
|
||||
if o.waterline_project:
|
||||
nchunks = chunksRefine(nchunks, o)
|
||||
nchunks = utils.sampleChunks(o, nchunks, layers)
|
||||
nchunks = await utils.sampleChunks(o, nchunks, layers)
|
||||
|
||||
nchunks = limitChunks(nchunks, o, force=True)
|
||||
#########################
|
||||
|
@ -819,14 +820,14 @@ def getPath3axis(context, operation):
|
|||
i += 1
|
||||
|
||||
percent = int(h / nslices * 100)
|
||||
progress('waterline layers ', percent)
|
||||
await progress_async('waterline layers ', percent)
|
||||
lastslice = poly
|
||||
|
||||
if (o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CCW') or (
|
||||
o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CW'):
|
||||
for chunk in slicechunks:
|
||||
chunk.points.reverse()
|
||||
slicechunks = utils.sortChunks(slicechunks, o)
|
||||
slicechunks = await utils.sortChunks(slicechunks, o)
|
||||
if topdown:
|
||||
slicechunks.reverse()
|
||||
# project chunks in between
|
||||
|
@ -835,17 +836,17 @@ def getPath3axis(context, operation):
|
|||
if topdown:
|
||||
chunks.reverse()
|
||||
|
||||
print(time.time() - tw)
|
||||
strategy.chunksToMesh(chunks, o)
|
||||
|
||||
elif o.strategy == 'DRILL':
|
||||
strategy.drill(o)
|
||||
await strategy.drill(o)
|
||||
|
||||
elif o.strategy == 'MEDIAL_AXIS':
|
||||
strategy.medial_axis(o)
|
||||
await strategy.medial_axis(o)
|
||||
await progress_async(f"Done",time.time() - tw,"s")
|
||||
|
||||
|
||||
def getPath4axis(context, operation):
|
||||
async def getPath4axis(context, operation):
|
||||
o = operation
|
||||
utils.getBounds(o)
|
||||
if o.strategy4axis in ['PARALLELR', 'PARALLEL', 'HELIX', 'CROSS']:
|
||||
|
@ -856,5 +857,5 @@ def getPath4axis(context, operation):
|
|||
|
||||
layers = strategy.getLayers(o, 0, depth)
|
||||
|
||||
chunks.extend(utils.sampleChunksNAxis(o, path_samples, layers))
|
||||
chunks.extend(await utils.sampleChunksNAxis(o, path_samples, layers))
|
||||
strategy.chunksToMesh(chunks, o)
|
||||
|
|
|
@ -33,14 +33,12 @@ from cam.simple import *
|
|||
from cam import chunk
|
||||
from cam.chunk import *
|
||||
from cam import simulation
|
||||
|
||||
from cam.async_op import progress_async
|
||||
|
||||
def getCircle(r, z):
|
||||
car = numpy.array(0, dtype=float)
|
||||
car = numpy.full(shape=(r*2,r*2),fill_value=-10,dtype=numpy.double)
|
||||
res = 2 * r
|
||||
m = r
|
||||
car.resize(r * 2, r * 2)
|
||||
car.fill(-10)
|
||||
v = mathutils.Vector((0, 0, 0))
|
||||
for a in range(0, res):
|
||||
v.x = (a + 0.5 - m)
|
||||
|
@ -52,11 +50,9 @@ def getCircle(r, z):
|
|||
|
||||
|
||||
def getCircleBinary(r):
|
||||
car = numpy.array((False), dtype=bool)
|
||||
car = numpy.full(shape=(r*2,r*2),fill_value=False,dtype=bool)
|
||||
res = 2 * r
|
||||
m = r
|
||||
car.resize(r * 2, r * 2)
|
||||
car.fill(False)
|
||||
v = mathutils.Vector((0, 0, 0))
|
||||
for a in range(0, res):
|
||||
v.x = (a + 0.5 - m)
|
||||
|
@ -118,10 +114,7 @@ def imagetonumpy(i):
|
|||
|
||||
width = i.size[0]
|
||||
height = i.size[1]
|
||||
na = numpy.array((0.1), dtype=float)
|
||||
|
||||
size = width * height
|
||||
na.resize(size * 4)
|
||||
na = numpy.full(shape=(width*height*4,),fill_value=-10,dtype=numpy.double)
|
||||
|
||||
p = i.pixels[:]
|
||||
# these 2 lines are about 15% faster than na[:]=i.pixels[:].... whyyyyyyyy!!?!?!?!?!
|
||||
|
@ -135,11 +128,10 @@ def imagetonumpy(i):
|
|||
return na
|
||||
|
||||
|
||||
def offsetArea(o, samples):
|
||||
async def offsetArea(o, samples):
|
||||
""" offsets the whole image with the cutter + skin offsets """
|
||||
if o.update_offsetimage_tag:
|
||||
minx, miny, minz, maxx, maxy, maxz = o.min.x, o.min.y, o.min.z, o.max.x, o.max.y, o.max.z
|
||||
o.offset_image.fill(-10)
|
||||
|
||||
sourceArray = samples
|
||||
cutterArray = simulation.getCutterArray(o, o.optimisation.pixsize)
|
||||
|
@ -149,6 +141,7 @@ def offsetArea(o, samples):
|
|||
width = len(sourceArray)
|
||||
height = len(sourceArray[0])
|
||||
cwidth = len(cutterArray)
|
||||
o.offset_image= numpy.full(shape=(width,height),fill_value=-10,dtype=numpy.float)
|
||||
|
||||
t = time.time()
|
||||
|
||||
|
@ -162,25 +155,17 @@ def offsetArea(o, samples):
|
|||
for x in range(0, cwidth): # cwidth):
|
||||
text = "Offsetting depth " + str(int(x * 100 / cwidth))
|
||||
# o.operator.report({"INFO"}, text)
|
||||
simple.progress('offset ', int(x * 100 / cwidth))
|
||||
await progress_async('offset depth image', int(x * 100 / cwidth))
|
||||
for y in range(0, cwidth):
|
||||
# TODO:OPTIMIZE THIS - this can run much faster when the areas won't be created each run????
|
||||
# tests dont work now
|
||||
if cutterArray[x, y] > -10:
|
||||
# i+=1
|
||||
# progress(i)
|
||||
# winner
|
||||
numpy.maximum(sourceArray[x: width - cwidth + x, y: height - cwidth + y] + cutterArray[x, y],
|
||||
comparearea, comparearea)
|
||||
# contest of performance
|
||||
|
||||
o.offset_image[m: width - cwidth + m, m:height - cwidth + m] = comparearea
|
||||
# progress('offseting done')
|
||||
|
||||
simple.progress('\ntime ' + str(time.time() - t))
|
||||
print('\nOffset image time ' + str(time.time() - t))
|
||||
|
||||
o.update_offsetimage_tag = False
|
||||
# progress('doing offsetimage')
|
||||
return o.offset_image
|
||||
|
||||
|
||||
|
@ -355,9 +340,7 @@ def crazyPath(o):
|
|||
resx = ceil(sx / o.optimisation.simulation_detail) + 2 * o.borderwidth
|
||||
resy = ceil(sy / o.optimisation.simulation_detail) + 2 * o.borderwidth
|
||||
|
||||
o.millimage = numpy.array((0.1), dtype=float)
|
||||
o.millimage.resize(resx, resy)
|
||||
o.millimage.fill(0)
|
||||
o.millimage = numpy.full(shape=(resx,resy),fill_value=0.,dtype=numpy.float)
|
||||
o.cutterArray = -simulation.getCutterArray(o, o.optimisation.simulation_detail) # getting inverted cutter
|
||||
|
||||
|
||||
|
@ -367,9 +350,7 @@ def buildStroke(start, end, cutterArray):
|
|||
size_y = abs(end[1] - start[1]) + cutterArray.size[0]
|
||||
r = cutterArray.size[0] / 2
|
||||
|
||||
strokeArray = numpy.array((0), dtype=float)
|
||||
strokeArray.resize(size_x, size_y)
|
||||
strokeArray.fill(-10)
|
||||
strokeArray = numpy.full(shape=(size_x,size_y),fill_value=-10.0,dtype=numpy.float)
|
||||
samplesx = numpy.round(numpy.linspace(start[0], end[0], strokelength))
|
||||
samplesy = numpy.round(numpy.linspace(start[1], end[1], strokelength))
|
||||
samplesz = numpy.round(numpy.linspace(start[2], end[2], strokelength))
|
||||
|
@ -1041,7 +1022,7 @@ def renderSampleImage(o):
|
|||
t = time.time()
|
||||
simple.progress('getting zbuffer')
|
||||
# print(o.zbuffer_image)
|
||||
|
||||
o.update_offsetimage_tag = True
|
||||
if o.geometry_source == 'OBJECT' or o.geometry_source == 'COLLECTION':
|
||||
pixsize = o.optimisation.pixsize
|
||||
|
||||
|
@ -1060,7 +1041,12 @@ def renderSampleImage(o):
|
|||
if not o.update_zbufferimage_tag:
|
||||
try:
|
||||
i = bpy.data.images.load(iname)
|
||||
if i.size[0] != resx or i.size[1] != resy:
|
||||
print("Z buffer size changed:",i.size,resx,resy)
|
||||
o.update_zbufferimage_tag = True
|
||||
|
||||
except:
|
||||
|
||||
o.update_zbufferimage_tag = True
|
||||
if o.update_zbufferimage_tag:
|
||||
s = bpy.context.scene
|
||||
|
@ -1068,29 +1054,30 @@ def renderSampleImage(o):
|
|||
# prepare nodes first
|
||||
s.use_nodes = True
|
||||
n = s.node_tree
|
||||
r = s.render
|
||||
r.resolution_x = resx
|
||||
r.resolution_y = resy
|
||||
r.engine = 'BLENDER_EEVEE'
|
||||
|
||||
n.links.clear()
|
||||
n.nodes.clear()
|
||||
n1 = n.nodes.new('CompositorNodeRLayers')
|
||||
s.view_layers[n1.layer].use_pass_z=True
|
||||
n2 = n.nodes.new('CompositorNodeViewer')
|
||||
n3 = n.nodes.new('CompositorNodeComposite')
|
||||
n.links.new(n1.outputs['Depth'], n2.inputs['Image'])
|
||||
n.links.new(n1.outputs['Depth'], n3.inputs['Image'])
|
||||
n.links.new(n1.outputs[n1.outputs.find('Depth')], n2.inputs[n2.inputs.find('Image')])
|
||||
n.links.new(n1.outputs[n1.outputs.find('Depth')], n3.inputs[n3.inputs.find('Image')])
|
||||
|
||||
n.nodes.active = n2
|
||||
###################
|
||||
|
||||
r = s.render
|
||||
r.resolution_x = resx
|
||||
r.resolution_y = resy
|
||||
|
||||
# resize operation image
|
||||
o.offset_image.resize((resx, resy))
|
||||
o.offset_image.fill(-10)
|
||||
o.offset_image= numpy.full(shape=(resx,resy),fill_value=-10,dtype=numpy.double)
|
||||
|
||||
# various settings for faster render
|
||||
r.resolution_percentage = 100
|
||||
|
||||
r.engine = 'BLENDER_EEVEE'
|
||||
ff = r.image_settings.file_format
|
||||
cm = r.image_settings.color_mode
|
||||
r.image_settings.file_format = 'OPEN_EXR'
|
||||
|
@ -1158,7 +1145,7 @@ def renderSampleImage(o):
|
|||
sy = 0
|
||||
ey = i.size[1]
|
||||
|
||||
o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth)
|
||||
#o.offset_image.resize(ex - sx + 2 * o.borderwidth, ey - sy + 2 * o.borderwidth)
|
||||
|
||||
o.optimisation.pixsize = o.source_image_size_x / i.size[0]
|
||||
simple.progress('pixel size in the image source', o.optimisation.pixsize)
|
||||
|
@ -1166,14 +1153,11 @@ def renderSampleImage(o):
|
|||
rawimage = imagetonumpy(i)
|
||||
maxa = numpy.max(rawimage)
|
||||
mina = numpy.min(rawimage)
|
||||
a = numpy.array((1.0, 1.0))
|
||||
a.resize(2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1])
|
||||
neg = o.source_image_scale_z < 0
|
||||
if o.strategy == 'WATERLINE': # waterline strategy needs image border to have ok ambient.
|
||||
a.fill(1 - neg)
|
||||
|
||||
a = numpy.full(shape=(2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]),fill_value=1-neg,dtype=numpy.float)
|
||||
else: # other operations like parallel need to reach the border
|
||||
a.fill(neg) #
|
||||
a = numpy.full(shape=(2 * o.borderwidth + i.size[0], 2 * o.borderwidth + i.size[1]),fill_value=neg,dtype=numpy.float)
|
||||
# 2*o.borderwidth
|
||||
a[o.borderwidth:-o.borderwidth, o.borderwidth:-o.borderwidth] = rawimage
|
||||
a = a[sx:ex + o.borderwidth * 2, sy:ey + o.borderwidth * 2]
|
||||
|
@ -1207,7 +1191,7 @@ def renderSampleImage(o):
|
|||
|
||||
# return numpy.array([])
|
||||
|
||||
def prepareArea(o):
|
||||
async def prepareArea(o):
|
||||
# if not o.use_exact:
|
||||
renderSampleImage(o)
|
||||
samples = o.zbuffer_image
|
||||
|
@ -1225,5 +1209,5 @@ def prepareArea(o):
|
|||
if o.update_offsetimage_tag:
|
||||
if o.inverse:
|
||||
samples = numpy.maximum(samples, o.min.z - 0.00001)
|
||||
offsetArea(o, samples)
|
||||
await offsetArea(o, samples)
|
||||
numpysave(o.offset_image, iname)
|
||||
|
|
|
@ -162,7 +162,8 @@ class Creator(iso.Creator):
|
|||
|
||||
def feed(self, x=None, y=None, z=None, a=None, b=None, c=None):
|
||||
|
||||
if self.same_xyz(x, y, z): return
|
||||
(x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z)
|
||||
if axis_count==0: return
|
||||
|
||||
self.write_blocknum()
|
||||
|
||||
|
|
|
@ -548,7 +548,8 @@ class Creator(nc.Creator):
|
|||
## Moves
|
||||
|
||||
def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None ):
|
||||
if self.same_xyz(x, y, z, a, b, c): return
|
||||
(x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z, a, b, c)
|
||||
if axis_count==0: return
|
||||
self.on_move()
|
||||
|
||||
if self.g0123_modal:
|
||||
|
@ -610,7 +611,8 @@ class Creator(nc.Creator):
|
|||
self.write('\n')
|
||||
|
||||
def feed(self, x=None, y=None, z=None, a=None, b=None, c=None):
|
||||
if self.same_xyz(x, y, z, a, b, c): return
|
||||
(x, y, z, a, b, c,axis_count)=self.filter_xyz(x, y, z, a, b, c)
|
||||
if axis_count==0: return
|
||||
self.on_move()
|
||||
if self.g0123_modal:
|
||||
if self.prev_g0123 != self.FEED():
|
||||
|
@ -674,26 +676,22 @@ class Creator(nc.Creator):
|
|||
self.write_misc()
|
||||
self.write('\n')
|
||||
|
||||
def same_xyz(self, x=None, y=None, z=None, a=None, b=None, c=None):
|
||||
if (x != None):
|
||||
if (self.fmt.string(x + self.shift_x)) != (self.fmt.string(self.x)):
|
||||
return False
|
||||
if (y != None):
|
||||
if (self.fmt.string(y + self.shift_y)) != (self.fmt.string(self.y)):
|
||||
return False
|
||||
if (z != None):
|
||||
if (self.fmt.string(z + self.shift_z)) != (self.fmt.string(self.z)):
|
||||
return False
|
||||
if (a != None):
|
||||
if (self.fmt.string(a)) != (self.fmt.string(self.a)):
|
||||
return False
|
||||
if (b != None):
|
||||
if (self.fmt.string(b)) != (self.fmt.string(self.b)):
|
||||
return False
|
||||
if (c != None):
|
||||
if (self.fmt.string(c)) != (self.fmt.string(self.c)):
|
||||
return False
|
||||
return True
|
||||
def filter_xyz(self, x=None, y=None, z=None, a=None, b=None, c=None):
|
||||
""" Check if x,y,z,a,b,c are the same and set them to None if they are
|
||||
return value = (x,y,z,a,b,c,count) where count is the number of
|
||||
axis moves left.
|
||||
"""
|
||||
rv = [x,y,z,a,b,c,0]
|
||||
comparisons = ((x,self.shift_x,self.x),(y,self.shift_y,self.y),(z,self.shift_z,self.z),
|
||||
(a,0,self.a),(b,0,self.b),(c,0,self.c))
|
||||
|
||||
for i,(new_val,shift,current_val) in enumerate(comparisons):
|
||||
if new_val is not None:
|
||||
if self.fmt.string(new_val+shift) == self.fmt.string(current_val):
|
||||
rv[i]=None
|
||||
else:
|
||||
rv[6]+=1
|
||||
return rv
|
||||
|
||||
|
||||
def get_quadrant(self, dx, dy):
|
||||
|
@ -732,7 +730,8 @@ class Creator(nc.Creator):
|
|||
return angle_e - angle_s
|
||||
|
||||
def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None):
|
||||
if self.same_xyz(x, y, z): return
|
||||
(x,y,z,_,_,_,axis_count) = self.filter_xyz(x,y,z)
|
||||
if axis_count==0: return
|
||||
|
||||
if self.output_arcs_as_lines or (self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z))):
|
||||
# split the helical arc into little line feed moves
|
||||
|
|
|
@ -13,9 +13,12 @@ import mathutils
|
|||
import math
|
||||
from cam.simple import activate
|
||||
from cam.exception import *
|
||||
from cam.async_op import progress_async
|
||||
|
||||
OCL_SCALE = 1000.0
|
||||
|
||||
_PREVIOUS_OCL_MESH=None
|
||||
|
||||
def get_oclSTL(operation):
|
||||
me = None
|
||||
oclSTL = ocl.STLSurf()
|
||||
|
@ -37,9 +40,9 @@ def get_oclSTL(operation):
|
|||
return oclSTL
|
||||
|
||||
|
||||
def ocl_sample(operation, chunks):
|
||||
async def ocl_sample(operation, chunks,use_cached_mesh = False):
|
||||
global _PREVIOUS_OCL_MESH
|
||||
|
||||
oclSTL = get_oclSTL(operation)
|
||||
|
||||
op_cutter_type = operation.cutter_type
|
||||
op_cutter_diameter = operation.cutter_diameter
|
||||
|
@ -48,7 +51,7 @@ def ocl_sample(operation, chunks):
|
|||
if op_cutter_type == "VCARVE":
|
||||
cutter_length = (op_cutter_diameter/math.tan(op_cutter_tip_angle))/2
|
||||
else:
|
||||
cutter_length = 10
|
||||
cutter_length = 10
|
||||
|
||||
cutter = None
|
||||
|
||||
|
@ -70,13 +73,18 @@ def ocl_sample(operation, chunks):
|
|||
quit()
|
||||
|
||||
bdc = ocl.BatchDropCutter()
|
||||
if use_cached_mesh and _PREVIOUS_OCL_MESH is not None:
|
||||
oclSTL=_PREVIOUS_OCL_MESH
|
||||
else:
|
||||
oclSTL = get_oclSTL(operation)
|
||||
_PREVIOUS_OCL_MESH=oclSTL
|
||||
bdc.setSTL(oclSTL)
|
||||
bdc.setCutter(cutter)
|
||||
|
||||
for chunk in chunks:
|
||||
for coord in chunk.points:
|
||||
bdc.appendPoint(ocl.CLPoint(coord[0] * 1000, coord[1] * 1000, op_minz * 1000))
|
||||
|
||||
await progress_async("OpenCAMLib sampling")
|
||||
bdc.run()
|
||||
|
||||
cl_points = bdc.getCLPoints()
|
||||
|
|
|
@ -15,6 +15,7 @@ from cam.collision import BULLET_SCALE
|
|||
from cam import simple
|
||||
from cam.chunk import camPathChunk
|
||||
from cam.simple import *
|
||||
from cam.async_op import progress_async
|
||||
from shapely import geometry as sgeometry
|
||||
from .oclSample import get_oclSTL
|
||||
|
||||
|
@ -78,25 +79,24 @@ def exportModelsToSTL(operation):
|
|||
file_number += 1
|
||||
|
||||
|
||||
def oclSamplePoints(operation, points):
|
||||
samples = ocl_sample(operation, points)
|
||||
async def oclSamplePoints(operation, points):
|
||||
samples = await ocl_sample(operation, points)
|
||||
pointSamplesFromOCL(points, samples)
|
||||
|
||||
|
||||
def oclSample(operation, chunks):
|
||||
samples = ocl_sample(operation, chunks)
|
||||
async def oclSample(operation, chunks):
|
||||
samples = await ocl_sample(operation, chunks)
|
||||
chunkPointSamplesFromOCL(chunks, samples)
|
||||
|
||||
|
||||
def oclResampleChunks(operation, chunks_to_resample):
|
||||
async def oclResampleChunks(operation, chunks_to_resample,use_cached_mesh):
|
||||
tmp_chunks = list()
|
||||
tmp_chunks.append(camPathChunk(inpoints=[]))
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
for p_index in range(i_start, i_start + i_length):
|
||||
tmp_chunks[0].append(chunk.points[p_index])
|
||||
|
||||
|
||||
samples = ocl_sample(operation, tmp_chunks)
|
||||
samples = await ocl_sample(operation, tmp_chunks,use_cached_mesh=use_cached_mesh)
|
||||
|
||||
sample_index = 0
|
||||
for chunk, i_start, i_length in chunks_to_resample:
|
||||
|
@ -127,7 +127,7 @@ def oclGetMedialAxis(operation, chunks):
|
|||
waterlineChunksFromOCL(operation, chunks)
|
||||
|
||||
|
||||
def oclGetWaterline(operation, chunks):
|
||||
async def oclGetWaterline(operation, chunks):
|
||||
layers = oclWaterlineLayerHeights(operation)
|
||||
oclSTL = get_oclSTL(operation)
|
||||
|
||||
|
@ -155,8 +155,8 @@ def oclGetWaterline(operation, chunks):
|
|||
waterline.setSTL(oclSTL)
|
||||
waterline.setCutter(cutter)
|
||||
waterline.setSampling(0.1)#TODO: add sampling setting to UI
|
||||
for height in layers:
|
||||
print(str(height) + '\n')
|
||||
for count,height in enumerate(layers):
|
||||
await progress_async("Waterline",int((100*count)/len(layers)))
|
||||
waterline.reset()
|
||||
waterline.setZ(height * OCL_SCALE)
|
||||
waterline.run2()
|
||||
|
|
|
@ -28,9 +28,13 @@ from bpy_extras.io_utils import ImportHelper
|
|||
|
||||
import subprocess, os, threading
|
||||
from cam import utils, pack, polygon_utils_cam, simple, gcodepath, bridges, simulation
|
||||
from cam.async_op import AsyncOperatorMixin,AsyncCancelledException
|
||||
import shapely
|
||||
import mathutils
|
||||
import math
|
||||
import textwrap
|
||||
import traceback
|
||||
|
||||
import cam
|
||||
from cam.exception import *
|
||||
|
||||
|
@ -142,66 +146,90 @@ class KillPathsBackground(bpy.types.Operator):
|
|||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CalculatePath(bpy.types.Operator):
|
||||
async def _calc_path(operator,context):
|
||||
s = bpy.context.scene
|
||||
o = s.cam_operations[s.cam_active_operation]
|
||||
if o.geometry_source == 'OBJECT':
|
||||
ob = bpy.data.objects[o.object_name]
|
||||
ob.hide_set(False)
|
||||
if o.geometry_source == 'COLLECTION':
|
||||
obc = bpy.data.collections[o.collection_name]
|
||||
for ob in obc.objects:
|
||||
ob.hide_set(False)
|
||||
if o.strategy == "CARVE":
|
||||
curvob = bpy.data.objects[o.curve_object]
|
||||
curvob.hide_set(False)
|
||||
'''if o.strategy == 'WATERLINE':
|
||||
ob = bpy.data.objects[o.object_name]
|
||||
ob.select_set(True)
|
||||
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)'''
|
||||
if bpy.context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
path = bpy.data.objects.get('cam_path_{}'.format(o.name))
|
||||
if path:
|
||||
path.select_set(state=True)
|
||||
bpy.ops.object.delete()
|
||||
|
||||
if not o.valid:
|
||||
operator.report({'ERROR_INVALID_INPUT'}, "Operation can't be performed, see warnings for info")
|
||||
progress_async("Operation can't be performed, see warnings for info")
|
||||
return {'FINISHED',False}
|
||||
|
||||
#check for free movement height < maxz and return with error
|
||||
if(o.movement.free_height < o.maxz):
|
||||
operator.report({'ERROR_INVALID_INPUT'}, "Free movement height is less than Operation depth start \n correct and try again.")
|
||||
progress_async("Operation can't be performed, see warnings for info")
|
||||
return {'FINISHED',False}
|
||||
|
||||
if o.computing:
|
||||
return {'FINISHED',False}
|
||||
|
||||
o.operator = operator
|
||||
|
||||
if o.use_layers:
|
||||
o.movement.parallel_step_back = False
|
||||
try:
|
||||
print("Get path:",context)
|
||||
await gcodepath.getPath(context, o)
|
||||
print("Got path:",context)
|
||||
except CamException as e:
|
||||
traceback.print_tb(e.__traceback__)
|
||||
error_str="\n".join(textwrap.wrap(str(e),width=80))
|
||||
operator.report({'ERROR'},error_str)
|
||||
return {'FINISHED',False}
|
||||
except AsyncCancelledException as e:
|
||||
return {'CANCELLED',False}
|
||||
except Exception as e:
|
||||
print("FAIL",e)
|
||||
traceback.print_tb(e.__traceback__)
|
||||
operator.report({'ERROR'},str(e))
|
||||
return {'FINISHED',False}
|
||||
coll = bpy.data.collections.get('RigidBodyWorld')
|
||||
if coll:
|
||||
bpy.data.collections.remove(coll)
|
||||
|
||||
return {'FINISHED',True}
|
||||
|
||||
|
||||
class CalculatePath(bpy.types.Operator,AsyncOperatorMixin):
|
||||
"""calculate CAM paths"""
|
||||
bl_idname = "object.calculate_cam_path"
|
||||
bl_label = "Calculate CAM paths"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
# this property was actually ignored, so removing it in 0.3
|
||||
# operation= StringProperty(name="Operation", description="Specify the operation to calculate",default='Operation')
|
||||
|
||||
def execute(self, context):
|
||||
print("CALCULATE")
|
||||
# getIslands(context.object)
|
||||
s = bpy.context.scene
|
||||
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls,context):
|
||||
s = context.scene
|
||||
o = s.cam_operations[s.cam_active_operation]
|
||||
if o.geometry_source == 'OBJECT':
|
||||
ob = bpy.data.objects[o.object_name]
|
||||
ob.hide_set(False)
|
||||
if o.geometry_source == 'COLLECTION':
|
||||
obc = bpy.data.collections[o.collection_name]
|
||||
for ob in obc.objects:
|
||||
ob.hide_set(False)
|
||||
if o.strategy == "CARVE":
|
||||
curvob = bpy.data.objects[o.curve_object]
|
||||
curvob.hide_set(False)
|
||||
print(bpy.context.mode)
|
||||
if bpy.context.mode != 'OBJECT':
|
||||
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
path = bpy.data.objects.get('cam_path_{}'.format(o.name))
|
||||
if path:
|
||||
path.select_set(state=True)
|
||||
bpy.ops.object.delete()
|
||||
if o is not None:
|
||||
if cam.isValid(o,context):
|
||||
return True
|
||||
return False
|
||||
|
||||
if not o.valid:
|
||||
self.report({'ERROR_INVALID_INPUT'}, "Operation can't be performed, see warnings for info")
|
||||
print("Operation can't be performed, see warnings for info")
|
||||
return {'CANCELLED'}
|
||||
|
||||
#check for free movement height < maxz and return with error
|
||||
if(o.movement.free_height < o.maxz):
|
||||
self.report({'ERROR_INVALID_INPUT'}, "Free movement height is less than Operation depth start \n correct and try again.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
if o.computing:
|
||||
return {'FINISHED'}
|
||||
|
||||
o.operator = self
|
||||
|
||||
if o.use_layers:
|
||||
o.movement.parallel_step_back = False
|
||||
try:
|
||||
gcodepath.getPath(context, o)
|
||||
except CamException as e:
|
||||
self.report({'ERROR'},str(e))
|
||||
return {'CANCELLED'}
|
||||
coll = bpy.data.collections.get('RigidBodyWorld')
|
||||
if coll:
|
||||
bpy.data.collections.remove(coll)
|
||||
|
||||
return {'FINISHED'}
|
||||
async def execute_async(self, context):
|
||||
(retval,success) = await _calc_path(self,context)
|
||||
return retval
|
||||
|
||||
|
||||
class PathsAll(bpy.types.Operator):
|
||||
|
@ -270,26 +298,38 @@ def getChainOperations(chain):
|
|||
return chop
|
||||
|
||||
|
||||
class PathsChain(bpy.types.Operator):
|
||||
class PathsChain(bpy.types.Operator,AsyncOperatorMixin):
|
||||
"""calculate a chain and export the gcode alltogether. """
|
||||
bl_idname = "object.calculate_cam_paths_chain"
|
||||
bl_label = "Calculate CAM paths in current chain and export chain gcode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
||||
|
||||
def execute(self, context):
|
||||
s = bpy.context.scene
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
s = context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
return cam.isChainValid(chain,context)[0]
|
||||
|
||||
async def execute_async(self, context):
|
||||
s = context.scene
|
||||
bpy.ops.object.mode_set(mode='OBJECT') # force object mode
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
chainops = getChainOperations(chain)
|
||||
meshes = []
|
||||
|
||||
# if len(chainops)<4:
|
||||
for i in range(0, len(chainops)):
|
||||
s.cam_active_operation = s.cam_operations.find(chainops[i].name)
|
||||
bpy.ops.object.calculate_cam_path()
|
||||
try:
|
||||
for i in range(0, len(chainops)):
|
||||
s.cam_active_operation = s.cam_operations.find(chainops[i].name)
|
||||
self.report({'INFO'},f"Calculating path: {chainops[i].name}")
|
||||
result,success=await _calc_path(self,context)
|
||||
if not success and 'FINISHED' in result:
|
||||
self.report({'ERROR'},f"Couldn't calculate path: {chainops[i].name}")
|
||||
except Exception as e:
|
||||
print("FAIL",e)
|
||||
traceback.print_tb(e.__traceback__)
|
||||
operator.report({'ERROR'},str(e))
|
||||
return {'FINISHED'}
|
||||
|
||||
for o in chainops:
|
||||
# bpy.ops.object.calculate_cam_paths_background()
|
||||
meshes.append(bpy.data.objects["cam_path_{}".format(o.name)].data)
|
||||
gcodepath.exportGcodePath(chain.filename, meshes, chainops)
|
||||
return {'FINISHED'}
|
||||
|
@ -301,6 +341,12 @@ class PathExportChain(bpy.types.Operator):
|
|||
bl_label = "Export CAM paths in current chain as gcode"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
s = context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
return cam.isChainValid(chain,context)[0]
|
||||
|
||||
def execute(self, context):
|
||||
s = bpy.context.scene
|
||||
|
||||
|
@ -335,27 +381,30 @@ class PathExport(bpy.types.Operator):
|
|||
return {'FINISHED'}
|
||||
|
||||
|
||||
class CAMSimulate(bpy.types.Operator):
|
||||
class CAMSimulate(bpy.types.Operator,AsyncOperatorMixin):
|
||||
"""simulate CAM operation
|
||||
this is performed by: creating an image, painting Z depth of the brush substractively.
|
||||
Works only for some operations, can not be used for 4-5 axis."""
|
||||
bl_idname = "object.cam_simulate"
|
||||
bl_label = "CAM simulation"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
||||
|
||||
operation: StringProperty(name="Operation",
|
||||
description="Specify the operation to calculate", default='Operation')
|
||||
|
||||
def execute(self, context):
|
||||
async def execute_async(self, context):
|
||||
s = bpy.context.scene
|
||||
operation = s.cam_operations[s.cam_active_operation]
|
||||
|
||||
operation_name = "cam_path_{}".format(operation.name)
|
||||
|
||||
if operation_name in bpy.data.objects:
|
||||
simulation.doSimulation(operation_name, [operation])
|
||||
try:
|
||||
await simulation.doSimulation(operation_name, [operation])
|
||||
except AsyncCancelledException as e:
|
||||
return {'CANCELLED'}
|
||||
else:
|
||||
print('no computed path to simulate')
|
||||
self.report({'ERROR'},'no computed path to simulate')
|
||||
return {'FINISHED'}
|
||||
return {'FINISHED'}
|
||||
|
||||
|
@ -364,17 +413,23 @@ class CAMSimulate(bpy.types.Operator):
|
|||
layout.prop_search(self, "operation", bpy.context.scene, "cam_operations")
|
||||
|
||||
|
||||
class CAMSimulateChain(bpy.types.Operator):
|
||||
class CAMSimulateChain(bpy.types.Operator, AsyncOperatorMixin):
|
||||
"""simulate CAM chain, compared to single op simulation just writes into one image and thus enables
|
||||
to see how ops work together."""
|
||||
bl_idname = "object.cam_simulate_chain"
|
||||
bl_label = "CAM simulation"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
bl_options = {'REGISTER', 'UNDO','BLOCKING'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
s = context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
return cam.isChainValid(chain,context)[0]
|
||||
|
||||
operation: StringProperty(name="Operation",
|
||||
description="Specify the operation to calculate", default='Operation')
|
||||
|
||||
def execute(self, context):
|
||||
async def execute_async(self, context):
|
||||
s = bpy.context.scene
|
||||
chain = s.cam_chains[s.cam_active_chain]
|
||||
chainops = getChainOperations(chain)
|
||||
|
@ -385,7 +440,10 @@ class CAMSimulateChain(bpy.types.Operator):
|
|||
canSimulate = True # force true
|
||||
print("operation name " + str(operation.name))
|
||||
if canSimulate:
|
||||
simulation.doSimulation(chain.name, chainops)
|
||||
try:
|
||||
await simulation.doSimulation(chain.name, chainops)
|
||||
except AsyncCancelledException as e:
|
||||
return {'CANCELLED'}
|
||||
else:
|
||||
print('no computed path to simulate')
|
||||
return {'FINISHED'}
|
||||
|
|
|
@ -31,6 +31,7 @@ import numpy as np
|
|||
|
||||
from cam import simple
|
||||
from cam import image_utils
|
||||
from cam.async_op import progress_async
|
||||
|
||||
|
||||
def createSimulationObject(name, operations, i):
|
||||
|
@ -89,13 +90,13 @@ def createSimulationObject(name, operations, i):
|
|||
bpy.ops.object.shade_smooth()
|
||||
|
||||
|
||||
def doSimulation(name, operations):
|
||||
async def doSimulation(name, operations):
|
||||
"""perform simulation of operations. Currently only for 3 axis"""
|
||||
for o in operations:
|
||||
utils.getOperationSources(o)
|
||||
limits = utils.getBoundsMultiple(
|
||||
operations) # this is here because some background computed operations still didn't have bounds data
|
||||
i = generateSimulationImage(operations, limits)
|
||||
i = await generateSimulationImage(operations, limits)
|
||||
# cp = simple.getCachePath(operations[0])[:-len(operations[0].name)] + name
|
||||
cp = simple.getSimulationPath()+name
|
||||
print('cp=', cp)
|
||||
|
@ -106,7 +107,7 @@ def doSimulation(name, operations):
|
|||
createSimulationObject(name, operations, i)
|
||||
|
||||
|
||||
def generateSimulationImage(operations, limits):
|
||||
async def generateSimulationImage(operations, limits):
|
||||
minx, miny, minz, maxx, maxy, maxz = limits
|
||||
# print(minx,miny,minz,maxx,maxy,maxz)
|
||||
sx = maxx - minx
|
||||
|
@ -119,11 +120,13 @@ def generateSimulationImage(operations, limits):
|
|||
resy = math.ceil(sy / simulation_detail) + 2 * borderwidth
|
||||
|
||||
# create array in which simulation happens, similar to an image to be painted in.
|
||||
si = np.array(0.1, dtype=float)
|
||||
si.resize(resx, resy)
|
||||
si.fill(maxz)
|
||||
si = np.full(shape=(resx,resy),fill_value=maxz,dtype=np.float)
|
||||
|
||||
for o in operations:
|
||||
num_operations=len(operations)
|
||||
|
||||
start_time=time.time()
|
||||
|
||||
for op_count,o in enumerate(operations):
|
||||
ob = bpy.data.objects["cam_path_{}".format(o.name)]
|
||||
m = ob.data
|
||||
verts = m.vertices
|
||||
|
@ -141,10 +144,6 @@ def generateSimulationImage(operations, limits):
|
|||
else:
|
||||
shapek = m.shape_keys.key_blocks[kname]
|
||||
shapek.data[0].co = (0.0, 0, 0)
|
||||
# print(len(shapek.data))
|
||||
# print(len(verts_rotations))
|
||||
|
||||
# print(r)
|
||||
|
||||
totalvolume = 0.0
|
||||
|
||||
|
@ -161,8 +160,8 @@ def generateSimulationImage(operations, limits):
|
|||
for i, vert in enumerate(verts):
|
||||
if perc != int(100 * i / vtotal):
|
||||
perc = int(100 * i / vtotal)
|
||||
simple.progress('simulation', perc)
|
||||
# progress('simulation ',int(100*i/l))
|
||||
total_perc = (perc+ op_count*100) / num_operations
|
||||
await progress_async(f'Simulation',int(total_perc))
|
||||
|
||||
if i > 0:
|
||||
volume = 0
|
||||
|
@ -279,6 +278,7 @@ def generateSimulationImage(operations, limits):
|
|||
si = si[borderwidth:-borderwidth, borderwidth:-borderwidth]
|
||||
si += -minz
|
||||
|
||||
await progress_async("Simulated:",time.time()-start_time,'s')
|
||||
return si
|
||||
|
||||
|
||||
|
@ -288,9 +288,7 @@ def getCutterArray(operation, pixsize):
|
|||
r = operation.cutter_diameter / 2 + operation.skin # /operation.pixsize
|
||||
res = math.ceil((r * 2) / pixsize)
|
||||
m = res / 2.0
|
||||
car = np.array((0), dtype=float)
|
||||
car.resize(res, res)
|
||||
car.fill(-10)
|
||||
car = np.full(shape=(res,res),fill_value=-10.0,dtype=np.float)
|
||||
|
||||
v = mathutils.Vector((0, 0, 0))
|
||||
ps = pixsize
|
||||
|
|
|
@ -50,7 +50,7 @@ SHAPELY = True
|
|||
|
||||
|
||||
# cutout strategy is completely here:
|
||||
def cutout(o):
|
||||
async def cutout(o):
|
||||
max_depth = checkminz(o)
|
||||
cutter_angle = math.radians(o.cutter_tip_angle / 2)
|
||||
c_offset = o.cutter_diameter / 2 # cutter ofset
|
||||
|
@ -117,7 +117,7 @@ def cutout(o):
|
|||
if not o.dont_merge:
|
||||
parentChildPoly(chunksFromCurve, chunksFromCurve, o)
|
||||
if o.outlines_count == 1:
|
||||
chunksFromCurve = utils.sortChunks(chunksFromCurve, o)
|
||||
chunksFromCurve = await utils.sortChunks(chunksFromCurve, o)
|
||||
|
||||
if (o.movement.type == 'CLIMB' and o.movement.spindle_rotation == 'CCW') or (
|
||||
o.movement.type == 'CONVENTIONAL' and o.movement.spindle_rotation == 'CW'):
|
||||
|
@ -204,16 +204,16 @@ def cutout(o):
|
|||
chunksToMesh(chunks, o)
|
||||
|
||||
|
||||
def curve(o):
|
||||
async def curve(o):
|
||||
print('operation: curve')
|
||||
pathSamples = []
|
||||
utils.getOperationSources(o)
|
||||
if not o.onlycurves:
|
||||
o.info.warnings += 'at least one of assigned objects is not a curve\n'
|
||||
raise CamException("All objects must be curves for this operation.")
|
||||
|
||||
for ob in o.objects:
|
||||
pathSamples.extend(curveToChunks(ob)) # make the chunks from curve here
|
||||
pathSamples = utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
pathSamples = await utils.sortChunks(pathSamples, o) # sort before sampling
|
||||
pathSamples = chunksRefine(pathSamples, o) # simplify
|
||||
|
||||
# layers here
|
||||
|
@ -246,7 +246,7 @@ def curve(o):
|
|||
chunksToMesh(pathSamples, o)
|
||||
|
||||
|
||||
def proj_curve(s, o):
|
||||
async def proj_curve(s, o):
|
||||
print('operation: projected curve')
|
||||
pathSamples = []
|
||||
chunks = []
|
||||
|
@ -257,8 +257,7 @@ def proj_curve(s, o):
|
|||
|
||||
from cam import chunk
|
||||
if targetCurve.type != 'CURVE':
|
||||
o.info.warnings += 'Projection target and source have to be curve objects!\n '
|
||||
return
|
||||
raise CamException('Projection target and source have to be curve objects!')
|
||||
|
||||
if 1:
|
||||
extend_up = 0.1
|
||||
|
@ -297,7 +296,7 @@ def proj_curve(s, o):
|
|||
chunksToMesh(chunks, o)
|
||||
|
||||
|
||||
def pocket(o):
|
||||
async def pocket(o):
|
||||
print('operation: pocket')
|
||||
scene = bpy.context.scene
|
||||
|
||||
|
@ -361,7 +360,7 @@ def pocket(o):
|
|||
for ch in chunksFromCurve:
|
||||
ch.points.reverse()
|
||||
|
||||
chunksFromCurve = utils.sortChunks(chunksFromCurve, o)
|
||||
chunksFromCurve = await utils.sortChunks(chunksFromCurve, o)
|
||||
|
||||
chunks = []
|
||||
layers = getLayers(o, o.maxz, checkminz(o))
|
||||
|
@ -477,7 +476,7 @@ def pocket(o):
|
|||
if o.first_down:
|
||||
if o.pocket_option == "OUTSIDE":
|
||||
chunks.reverse()
|
||||
chunks = utils.sortChunks(chunks, o)
|
||||
chunks = await utils.sortChunks(chunks, o)
|
||||
|
||||
if o.pocketToCurve: # make curve instead of a path
|
||||
simple.join_multiple("3dpocket")
|
||||
|
@ -486,7 +485,7 @@ def pocket(o):
|
|||
chunksToMesh(chunks, o) # make normal pocket path
|
||||
|
||||
|
||||
def drill(o):
|
||||
async def drill(o):
|
||||
print('operation: Drill')
|
||||
chunks = []
|
||||
for ob in o.objects:
|
||||
|
@ -576,11 +575,11 @@ def drill(o):
|
|||
newchunk.setZ(o.maxz)
|
||||
chunklayers.append(newchunk)
|
||||
|
||||
chunklayers = utils.sortChunks(chunklayers, o)
|
||||
chunklayers = await utils.sortChunks(chunklayers, o)
|
||||
chunksToMesh(chunklayers, o)
|
||||
|
||||
|
||||
def medial_axis(o):
|
||||
async def medial_axis(o):
|
||||
print('operation: Medial Axis')
|
||||
|
||||
simple.remove_multiple("medialMesh")
|
||||
|
@ -610,8 +609,7 @@ def medial_axis(o):
|
|||
elif o.cutter_type == 'BALLNOSE':
|
||||
maxdepth = - new_cutter_diameter / 2 - o.skin
|
||||
else:
|
||||
o.info.warnings += 'Only Ballnose, V-carve cutters\n are supported'
|
||||
return
|
||||
raise CamException("Only Ballnose and V-carve cutters are supported for meial axis.")
|
||||
# remember resolutions of curves, to refine them,
|
||||
# otherwise medial axis computation yields too many branches in curved parts
|
||||
resolutions_before = []
|
||||
|
@ -740,7 +738,7 @@ def medial_axis(o):
|
|||
oi += 1
|
||||
|
||||
# bpy.ops.object.join()
|
||||
chunks = utils.sortChunks(chunks, o)
|
||||
chunks = await utils.sortChunks(chunks, o)
|
||||
|
||||
layers = getLayers(o, o.maxz, o.min.z)
|
||||
|
||||
|
@ -754,7 +752,7 @@ def medial_axis(o):
|
|||
chunklayers.append(newchunk)
|
||||
|
||||
if o.first_down:
|
||||
chunklayers = utils.sortChunks(chunklayers, o)
|
||||
chunklayers = await utils.sortChunks(chunklayers, o)
|
||||
|
||||
if o.add_mesh_for_medial: # make curve instead of a path
|
||||
simple.join_multiple("medialMesh")
|
||||
|
@ -771,6 +769,10 @@ def getLayers(operation, startdepth, enddepth):
|
|||
"""returns a list of layers bounded by startdepth and enddepth
|
||||
uses operation.stepdown to determine number of layers.
|
||||
"""
|
||||
if startdepth < enddepth:
|
||||
raise CamException("Start depth is lower than end depth. "
|
||||
"If you have set a custom depth end, it must be lower than depth start, "
|
||||
"and should usually be negative. Set this in the CAM Operation Area panel.")
|
||||
if operation.use_layers:
|
||||
layers = []
|
||||
n = math.ceil((startdepth - enddepth) / operation.stepdown)
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
import tempfile
|
||||
import sys
|
||||
import subprocess
|
||||
import pathlib
|
||||
|
||||
INSTALL_CODE=f"""
|
||||
import bpy
|
||||
bpy.ops.preferences.addon_install(filepath='{sys.argv[1]}')
|
||||
bpy.ops.preferences.addon_enable(module='cam')
|
||||
bpy.ops.wm.save_userpref()
|
||||
"""
|
||||
|
||||
NUM_RETRIES=10
|
||||
|
||||
with tempfile.TemporaryDirectory() as td:
|
||||
file=pathlib.Path(td,"install.py")
|
||||
file.write_text(INSTALL_CODE)
|
||||
command = f'blender -b -P "{str(file)}"'
|
||||
# blender 4.0 installing addon crashes sometimes on mac github actions...
|
||||
for x in range(NUM_RETRIES):
|
||||
try:
|
||||
subprocess.run(command, shell=True, check=True,capture_output=True)
|
||||
sys.exit(0)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Install addon failed, retrying:",e)
|
||||
for line in e.stderr:
|
||||
if line.startswith("Writing: "):
|
||||
crash_file=pathlib.Path(line[len("Writing: "):])
|
||||
if crash_file.exists():
|
||||
print("Crash log:\n================")
|
||||
print(crash_file.read_text())
|
||||
print("============================")
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
(Created with grbl post processor 2023/07/30 18:07)
|
||||
(Created with grbl post processor 2024/01/13 06:53)
|
||||
G21
|
||||
(G-code generated with BlenderCAM and NC library)
|
||||
G17G90
|
||||
|
@ -138,9 +138,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -203,9 +203,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -399,9 +399,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -464,9 +464,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -660,9 +660,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -725,9 +725,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -921,9 +921,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -986,9 +986,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1182,9 +1182,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -1247,9 +1247,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1443,9 +1443,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -1508,9 +1508,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1704,9 +1704,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -1769,9 +1769,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1965,9 +1965,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -2030,9 +2030,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -2226,9 +2226,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -2291,9 +2291,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -2487,9 +2487,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -2552,9 +2552,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -2655,9 +2655,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -2720,9 +2720,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -2916,9 +2916,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -2981,9 +2981,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -3177,9 +3177,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -3242,9 +3242,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -3438,9 +3438,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -3503,9 +3503,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -3699,9 +3699,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -3764,9 +3764,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -3960,9 +3960,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -4025,9 +4025,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -4221,9 +4221,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -4286,9 +4286,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -4482,9 +4482,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -4547,9 +4547,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -4743,9 +4743,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -4808,9 +4808,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -5004,9 +5004,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -5069,9 +5069,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -5299,9 +5299,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -5364,9 +5364,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -5560,9 +5560,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -5625,9 +5625,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -5821,9 +5821,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -5886,9 +5886,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -6082,9 +6082,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6147,9 +6147,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -6343,9 +6343,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6408,9 +6408,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -6604,9 +6604,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6669,9 +6669,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -6865,9 +6865,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6930,9 +6930,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -7126,9 +7126,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -7191,9 +7191,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -7387,9 +7387,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -7452,9 +7452,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -7648,9 +7648,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -7713,9 +7713,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
(Created with grbl post processor 2023/07/30 18:07)
|
||||
(Created with grbl post processor 2024/01/13 06:53)
|
||||
G21
|
||||
(G-code generated with BlenderCAM and NC library)
|
||||
G17G90
|
||||
|
@ -138,9 +138,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -203,9 +203,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -306,9 +306,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -371,9 +371,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -601,9 +601,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -666,9 +666,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -927,9 +927,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -992,9 +992,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1095,9 +1095,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -1160,9 +1160,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -1390,9 +1390,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -1455,9 +1455,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -1716,9 +1716,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -1781,9 +1781,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -1884,9 +1884,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -1949,9 +1949,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -2179,9 +2179,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -2244,9 +2244,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -2505,9 +2505,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -2570,9 +2570,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -2673,9 +2673,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -2738,9 +2738,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -2968,9 +2968,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -3033,9 +3033,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -3294,9 +3294,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -3359,9 +3359,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -3462,9 +3462,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -3527,9 +3527,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -3757,9 +3757,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -3822,9 +3822,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -4083,9 +4083,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -4148,9 +4148,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -4251,9 +4251,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -4316,9 +4316,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -4546,9 +4546,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -4611,9 +4611,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -4872,9 +4872,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -4937,9 +4937,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -5040,9 +5040,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -5105,9 +5105,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -5335,9 +5335,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -5400,9 +5400,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -5661,9 +5661,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -5726,9 +5726,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -5829,9 +5829,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -5894,9 +5894,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -6124,9 +6124,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6189,9 +6189,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -6450,9 +6450,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -6515,9 +6515,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -6618,9 +6618,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -6683,9 +6683,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -6913,9 +6913,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -6978,9 +6978,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
@ -7239,9 +7239,9 @@ X82.064Y-130.656
|
|||
X82.027Y-130.659
|
||||
X81.99Y-130.661
|
||||
X81.953Y-130.663
|
||||
X81.917Y-130.663
|
||||
X81.917
|
||||
X-18.082
|
||||
X-18.119Y-130.663
|
||||
X-18.119
|
||||
X-18.156Y-130.661
|
||||
X-18.193Y-130.659
|
||||
X-18.229Y-130.656
|
||||
|
@ -7304,9 +7304,9 @@ X-19.576Y-129.309
|
|||
X-19.579Y-129.272
|
||||
X-19.582Y-129.236
|
||||
X-19.583Y-129.199
|
||||
X-19.583Y-129.162
|
||||
Y-129.162
|
||||
Y-29.162
|
||||
X-19.583Y-29.125
|
||||
Y-29.125
|
||||
X-19.582Y-29.088
|
||||
X-19.579Y-29.052
|
||||
X-19.576Y-29.015
|
||||
|
@ -7407,9 +7407,9 @@ X-75.19Y19.674
|
|||
X-75.227Y19.671
|
||||
X-75.264Y19.668
|
||||
X-75.301Y19.667
|
||||
X-75.337Y19.667
|
||||
X-75.337
|
||||
X-175.337
|
||||
X-175.374Y19.667
|
||||
X-175.374
|
||||
X-175.411Y19.668
|
||||
X-175.448Y19.671
|
||||
X-175.484Y19.674
|
||||
|
@ -7472,9 +7472,9 @@ X-176.831Y21.021
|
|||
X-176.834Y21.057
|
||||
X-176.837Y21.094
|
||||
X-176.838Y21.131
|
||||
X-176.838Y21.168
|
||||
Y21.168
|
||||
Y121.168
|
||||
X-176.838Y121.204
|
||||
Y121.204
|
||||
X-176.837Y121.241
|
||||
X-176.834Y121.278
|
||||
X-176.831Y121.315
|
||||
|
@ -7702,9 +7702,9 @@ X75.19Y130.656
|
|||
X75.227Y130.659
|
||||
X75.264Y130.661
|
||||
X75.301Y130.663
|
||||
X75.337Y130.663
|
||||
X75.337
|
||||
X175.337
|
||||
X175.374Y130.663
|
||||
X175.374
|
||||
X175.411Y130.661
|
||||
X175.448Y130.659
|
||||
X175.484Y130.656
|
||||
|
@ -7767,9 +7767,9 @@ X176.831Y129.309
|
|||
X176.834Y129.272
|
||||
X176.837Y129.236
|
||||
X176.838Y129.199
|
||||
X176.838Y129.162
|
||||
Y129.162
|
||||
Y29.162
|
||||
X176.838Y29.125
|
||||
Y29.125
|
||||
X176.837Y29.088
|
||||
X176.834Y29.052
|
||||
X176.831Y29.015
|
||||
|
|
|
@ -0,0 +1,810 @@
|
|||
(Created with grbl post processor 2024/01/11 10:02)
|
||||
G21
|
||||
(G-code generated with BlenderCAM and NC library)
|
||||
G17G90
|
||||
(Tool: D = 3.0 mm type END flutes 2)
|
||||
S12000M03
|
||||
G00 Z2.0
|
||||
|
||||
G0X0Y0Z2
|
||||
X-98Y99
|
||||
G1Z-3.1F500
|
||||
Y-99.999F1000
|
||||
X-96
|
||||
Y99
|
||||
X-94
|
||||
Y-99.999
|
||||
X-92
|
||||
Y99
|
||||
X-90
|
||||
Y-99.999
|
||||
X-88
|
||||
Y99
|
||||
X-86
|
||||
Y-99.999
|
||||
X-84
|
||||
Y99
|
||||
X-82
|
||||
Y-99.999
|
||||
X-80
|
||||
Y99
|
||||
X-78
|
||||
Y-99.999
|
||||
X-76
|
||||
Y99
|
||||
X-74
|
||||
Y-99.999
|
||||
X-72
|
||||
Y99
|
||||
X-70
|
||||
Y-99.999
|
||||
X-68
|
||||
Y99
|
||||
X-66
|
||||
Y-99.999
|
||||
X-64
|
||||
Y99
|
||||
X-62
|
||||
Y-99.999
|
||||
X-60
|
||||
Y99
|
||||
X-58
|
||||
Y-99.999
|
||||
X-56
|
||||
Y99
|
||||
X-54
|
||||
Y-99.999
|
||||
X-52
|
||||
Y99
|
||||
X-50
|
||||
Y-99.999
|
||||
X-48
|
||||
Y99
|
||||
X-46
|
||||
Y-99.999
|
||||
X-44
|
||||
Y99
|
||||
X-42
|
||||
Y-99.999
|
||||
X-40
|
||||
Y99
|
||||
X-38
|
||||
Y-99.999
|
||||
X-36
|
||||
Y99
|
||||
X-34
|
||||
Y-99.999
|
||||
X-32
|
||||
Y99
|
||||
X-30
|
||||
Y-99.999
|
||||
X-28
|
||||
Y99
|
||||
X-26
|
||||
Y-99.999
|
||||
X-24
|
||||
Y99
|
||||
X-22
|
||||
Y-99.999
|
||||
X-20
|
||||
Y99
|
||||
X-18
|
||||
Y-99.999
|
||||
X-16
|
||||
Y99
|
||||
X-14
|
||||
Y-99.999
|
||||
X-12
|
||||
Y99
|
||||
X-10
|
||||
Y-99.999
|
||||
X-8
|
||||
Y99
|
||||
X-6
|
||||
Y-99.999
|
||||
X-4
|
||||
Y99
|
||||
X-2
|
||||
Y-99.999
|
||||
X0
|
||||
Y99
|
||||
X2
|
||||
Y-99.999
|
||||
X4
|
||||
Y99
|
||||
X6
|
||||
Y-99.999
|
||||
X8
|
||||
Y99
|
||||
X10
|
||||
Y-99.999
|
||||
X12
|
||||
Y99
|
||||
X14
|
||||
Y-99.999
|
||||
X16
|
||||
Y99
|
||||
X18
|
||||
Y-99.999
|
||||
X20
|
||||
Y99
|
||||
X22
|
||||
Y-99.999
|
||||
X24
|
||||
Y99
|
||||
X26
|
||||
Y-99.999
|
||||
X28
|
||||
Y99
|
||||
X30
|
||||
Y-99.999
|
||||
X32
|
||||
Y99
|
||||
X34
|
||||
Y-99.999
|
||||
X36
|
||||
Y99
|
||||
X38
|
||||
Y-99.999
|
||||
X40
|
||||
Y99
|
||||
X42
|
||||
Y-99.999
|
||||
X44
|
||||
Y99
|
||||
X46
|
||||
Y-99.999
|
||||
X48
|
||||
Y99
|
||||
X50
|
||||
Y-99.999
|
||||
X52
|
||||
Y99
|
||||
X54
|
||||
Y-99.999
|
||||
X56
|
||||
Y99
|
||||
X58
|
||||
Y-99.999
|
||||
X60
|
||||
Y99
|
||||
X62
|
||||
Y-99.999
|
||||
X64
|
||||
Y99
|
||||
X66
|
||||
Y-99.999
|
||||
X68
|
||||
Y99
|
||||
X70
|
||||
Y-99.999
|
||||
X72
|
||||
Y99
|
||||
X74
|
||||
Y-99.999
|
||||
X76
|
||||
Y99
|
||||
X78
|
||||
Y-99.999
|
||||
X80
|
||||
Y99
|
||||
X82
|
||||
Y-99.999
|
||||
X84
|
||||
Y99
|
||||
X86
|
||||
Y-99.999
|
||||
X88
|
||||
Y99
|
||||
X90
|
||||
Y-99.999
|
||||
X92
|
||||
Y99
|
||||
X94
|
||||
Y-99.999
|
||||
X96
|
||||
Y99
|
||||
X98
|
||||
Y-99.999
|
||||
G0Z2
|
||||
X-98
|
||||
G1Z-6.1F500
|
||||
Y99F1000
|
||||
X-96
|
||||
Y-99.999
|
||||
X-94
|
||||
Y99
|
||||
X-92
|
||||
Y-99.999
|
||||
X-90
|
||||
Y99
|
||||
X-88
|
||||
Y-99.999
|
||||
X-86
|
||||
Y99
|
||||
X-84
|
||||
Y-99.999
|
||||
X-82
|
||||
Y99
|
||||
X-80
|
||||
Y-99.999
|
||||
X-78
|
||||
Y99
|
||||
X-76
|
||||
Y-99.999
|
||||
X-74
|
||||
Y99
|
||||
X-72
|
||||
Y-99.999
|
||||
X-70
|
||||
Y99
|
||||
X-68
|
||||
Y-99.999
|
||||
X-66
|
||||
Y99
|
||||
X-64
|
||||
Y-99.999
|
||||
X-62
|
||||
Y99
|
||||
X-60
|
||||
Y-99.999
|
||||
X-58
|
||||
Y99
|
||||
X-56
|
||||
Y-99.999
|
||||
X-54
|
||||
Y99
|
||||
X-52
|
||||
Y-99.999
|
||||
X-50
|
||||
Y99
|
||||
X-48
|
||||
Y-99.999
|
||||
X-46
|
||||
Y99
|
||||
X-44
|
||||
Y-99.999
|
||||
X-42
|
||||
Y99
|
||||
X-40
|
||||
Y-99.999
|
||||
X-38
|
||||
Y99
|
||||
X-36
|
||||
Y-99.999
|
||||
X-34
|
||||
Y99
|
||||
X-32
|
||||
Y-99.999
|
||||
X-30
|
||||
Y99
|
||||
X-28
|
||||
Y-99.999
|
||||
X-26
|
||||
Y99
|
||||
X-24
|
||||
Y-99.999
|
||||
X-22
|
||||
Y99
|
||||
X-20
|
||||
Y-99.999
|
||||
X-18
|
||||
Y99
|
||||
X-16
|
||||
Y-99.999
|
||||
X-14
|
||||
Y99
|
||||
X-12
|
||||
Y-99.999
|
||||
X-10
|
||||
Y99
|
||||
X-8
|
||||
Y-99.999
|
||||
X-6
|
||||
Y99
|
||||
X-4
|
||||
Y-99.999
|
||||
X-2
|
||||
Y99
|
||||
X0
|
||||
Y-99.999
|
||||
X2
|
||||
Y99
|
||||
X4
|
||||
Y-99.999
|
||||
X6
|
||||
Y99
|
||||
X8
|
||||
Y-99.999
|
||||
X10
|
||||
Y99
|
||||
X12
|
||||
Y-99.999
|
||||
X14
|
||||
Y99
|
||||
X16
|
||||
Y-99.999
|
||||
X18
|
||||
Y99
|
||||
X20
|
||||
Y-99.999
|
||||
X22
|
||||
Y99
|
||||
X24
|
||||
Y-99.999
|
||||
X26
|
||||
Y99
|
||||
X28
|
||||
Y-99.999
|
||||
X30
|
||||
Y99
|
||||
X32
|
||||
Y-99.999
|
||||
X34
|
||||
Y99
|
||||
X36
|
||||
Y-99.999
|
||||
X38
|
||||
Y99
|
||||
X40
|
||||
Y-99.999
|
||||
X42
|
||||
Y99
|
||||
X44
|
||||
Y-99.999
|
||||
X46
|
||||
Y99
|
||||
X48
|
||||
Y-99.999
|
||||
X50
|
||||
Y99
|
||||
X52
|
||||
Y-99.999
|
||||
X54
|
||||
Y99
|
||||
X56
|
||||
Y-99.999
|
||||
X58
|
||||
Y99
|
||||
X60
|
||||
Y-99.999
|
||||
X62
|
||||
Y99
|
||||
X64
|
||||
Y-99.999
|
||||
X66
|
||||
Y99
|
||||
X68
|
||||
Y-99.999
|
||||
X70
|
||||
Y99
|
||||
X72
|
||||
Y-99.999
|
||||
X74
|
||||
Y99
|
||||
X76
|
||||
Y-99.999
|
||||
X78
|
||||
Y99
|
||||
X80
|
||||
Y-99.999
|
||||
X82
|
||||
Y99
|
||||
X84
|
||||
Y-99.999
|
||||
X86
|
||||
Y99
|
||||
X88
|
||||
Y-99.999
|
||||
X90
|
||||
Y99
|
||||
X92
|
||||
Y-99.999
|
||||
X94
|
||||
Y99
|
||||
X96
|
||||
Y-99.999
|
||||
X98
|
||||
Y99
|
||||
G0Z2
|
||||
X-98
|
||||
G1Z-9.1F500
|
||||
Y-99.999F1000
|
||||
X-96
|
||||
Y99
|
||||
X-94
|
||||
Y-99.999
|
||||
X-92
|
||||
Y99
|
||||
X-90
|
||||
Y-99.999
|
||||
X-88
|
||||
Y99
|
||||
X-86
|
||||
Y-99.999
|
||||
X-84
|
||||
Y99
|
||||
X-82
|
||||
Y-99.999
|
||||
X-80
|
||||
Y99
|
||||
X-78
|
||||
Y-99.999
|
||||
X-76
|
||||
Y99
|
||||
X-74
|
||||
Y-99.999
|
||||
X-72
|
||||
Y99
|
||||
X-70
|
||||
Y-99.999
|
||||
X-68
|
||||
Y99
|
||||
X-66
|
||||
Y-99.999
|
||||
X-64
|
||||
Y99
|
||||
X-62
|
||||
Y-99.999
|
||||
X-60
|
||||
Y99
|
||||
X-58
|
||||
Y-99.999
|
||||
X-56
|
||||
Y99
|
||||
X-54
|
||||
Y-99.999
|
||||
X-52
|
||||
Y99
|
||||
X-50
|
||||
Y-99.999
|
||||
X-48
|
||||
Y99
|
||||
X-46
|
||||
Y-99.999
|
||||
X-44
|
||||
Y99
|
||||
X-42
|
||||
Y-99.999
|
||||
X-40
|
||||
Y99
|
||||
X-38
|
||||
Y-99.999
|
||||
X-36
|
||||
Y99
|
||||
X-34
|
||||
Y-99.999
|
||||
X-32
|
||||
Y99
|
||||
X-30
|
||||
Y-99.999
|
||||
X-28
|
||||
Y99
|
||||
X-26
|
||||
Y-99.999
|
||||
X-24
|
||||
Y99
|
||||
X-22
|
||||
Y-99.999
|
||||
X-20
|
||||
Y99
|
||||
X-18
|
||||
Y-99.999
|
||||
X-16
|
||||
Y99
|
||||
X-14
|
||||
Y-99.999
|
||||
X-12
|
||||
Y99
|
||||
X-10
|
||||
Y-99.999
|
||||
X-8
|
||||
Y99
|
||||
X-6
|
||||
Y-99.999
|
||||
X-4
|
||||
Y99
|
||||
X-2
|
||||
Y-99.999
|
||||
X0
|
||||
Y99
|
||||
X2
|
||||
Y-99.999
|
||||
X4
|
||||
Y99
|
||||
X6
|
||||
Y-99.999
|
||||
X8
|
||||
Y99
|
||||
X10
|
||||
Y-99.999
|
||||
X12
|
||||
Y99
|
||||
X14
|
||||
Y-99.999
|
||||
X16
|
||||
Y99
|
||||
X18
|
||||
Y-99.999
|
||||
X20
|
||||
Y99
|
||||
X22
|
||||
Y-99.999
|
||||
X24
|
||||
Y99
|
||||
X26
|
||||
Y-99.999
|
||||
X28
|
||||
Y99
|
||||
X30
|
||||
Y-99.999
|
||||
X32
|
||||
Y99
|
||||
X34
|
||||
Y-99.999
|
||||
X36
|
||||
Y99
|
||||
X38
|
||||
Y-99.999
|
||||
X40
|
||||
Y99
|
||||
X42
|
||||
Y-99.999
|
||||
X44
|
||||
Y99
|
||||
X46
|
||||
Y-99.999
|
||||
X48
|
||||
Y99
|
||||
X50
|
||||
Y-99.999
|
||||
X52
|
||||
Y99
|
||||
X54
|
||||
Y-99.999
|
||||
X56
|
||||
Y99
|
||||
X58
|
||||
Y-99.999
|
||||
X60
|
||||
Y99
|
||||
X62
|
||||
Y-99.999
|
||||
X64
|
||||
Y99
|
||||
X66
|
||||
Y-99.999
|
||||
X68
|
||||
Y99
|
||||
X70
|
||||
Y-99.999
|
||||
X72
|
||||
Y99
|
||||
X74
|
||||
Y-99.999
|
||||
X76
|
||||
Y99
|
||||
X78
|
||||
Y-99.999
|
||||
X80
|
||||
Y99
|
||||
X82
|
||||
Y-99.999
|
||||
X84
|
||||
Y99
|
||||
X86
|
||||
Y-99.999
|
||||
X88
|
||||
Y99
|
||||
X90
|
||||
Y-99.999
|
||||
X92
|
||||
Y99
|
||||
X94
|
||||
Y-99.999
|
||||
X96
|
||||
Y99
|
||||
X98
|
||||
Y-99.999
|
||||
G0Z2
|
||||
X-98
|
||||
G1Z-10F500
|
||||
Y99F1000
|
||||
X-96
|
||||
Y-99.999
|
||||
X-94
|
||||
Y99
|
||||
X-92
|
||||
Y-99.999
|
||||
X-90
|
||||
Y99
|
||||
X-88
|
||||
Y-99.999
|
||||
X-86
|
||||
Y99
|
||||
X-84
|
||||
Y-99.999
|
||||
X-82
|
||||
Y99
|
||||
X-80
|
||||
Y-99.999
|
||||
X-78
|
||||
Y99
|
||||
X-76
|
||||
Y-99.999
|
||||
X-74
|
||||
Y99
|
||||
X-72
|
||||
Y-99.999
|
||||
X-70
|
||||
Y99
|
||||
X-68
|
||||
Y-99.999
|
||||
X-66
|
||||
Y99
|
||||
X-64
|
||||
Y-99.999
|
||||
X-62
|
||||
Y99
|
||||
X-60
|
||||
Y-99.999
|
||||
X-58
|
||||
Y99
|
||||
X-56
|
||||
Y-99.999
|
||||
X-54
|
||||
Y99
|
||||
X-52
|
||||
Y-99.999
|
||||
X-50
|
||||
Y99
|
||||
X-48
|
||||
Y-99.999
|
||||
X-46
|
||||
Y99
|
||||
X-44
|
||||
Y-99.999
|
||||
X-42
|
||||
Y99
|
||||
X-40
|
||||
Y-99.999
|
||||
X-38
|
||||
Y99
|
||||
X-36
|
||||
Y-99.999
|
||||
X-34
|
||||
Y99
|
||||
X-32
|
||||
Y-99.999
|
||||
X-30
|
||||
Y99
|
||||
X-28
|
||||
Y-99.999
|
||||
X-26
|
||||
Y99
|
||||
X-24
|
||||
Y-99.999
|
||||
X-22
|
||||
Y99
|
||||
X-20
|
||||
Y-99.999
|
||||
X-18
|
||||
Y99
|
||||
X-16
|
||||
Y-99.999
|
||||
X-14
|
||||
Y99
|
||||
X-12
|
||||
Y-99.999
|
||||
X-10
|
||||
Y99
|
||||
X-8
|
||||
Y-99.999
|
||||
X-6
|
||||
Y99
|
||||
X-4
|
||||
Y-99.999
|
||||
X-2
|
||||
Y99
|
||||
X0
|
||||
Y-99.999
|
||||
X2
|
||||
Y99
|
||||
X4
|
||||
Y-99.999
|
||||
X6
|
||||
Y99
|
||||
X8
|
||||
Y-99.999
|
||||
X10
|
||||
Y99
|
||||
X12
|
||||
Y-99.999
|
||||
X14
|
||||
Y99
|
||||
X16
|
||||
Y-99.999
|
||||
X18
|
||||
Y99
|
||||
X20
|
||||
Y-99.999
|
||||
X22
|
||||
Y99
|
||||
X24
|
||||
Y-99.999
|
||||
X26
|
||||
Y99
|
||||
X28
|
||||
Y-99.999
|
||||
X30
|
||||
Y99
|
||||
X32
|
||||
Y-99.999
|
||||
X34
|
||||
Y99
|
||||
X36
|
||||
Y-99.999
|
||||
X38
|
||||
Y99
|
||||
X40
|
||||
Y-99.999
|
||||
X42
|
||||
Y99
|
||||
X44
|
||||
Y-99.999
|
||||
X46
|
||||
Y99
|
||||
X48
|
||||
Y-99.999
|
||||
X50
|
||||
Y99
|
||||
X52
|
||||
Y-99.999
|
||||
X54
|
||||
Y99
|
||||
X56
|
||||
Y-99.999
|
||||
X58
|
||||
Y99
|
||||
X60
|
||||
Y-99.999
|
||||
X62
|
||||
Y99
|
||||
X64
|
||||
Y-99.999
|
||||
X66
|
||||
Y99
|
||||
X68
|
||||
Y-99.999
|
||||
X70
|
||||
Y99
|
||||
X72
|
||||
Y-99.999
|
||||
X74
|
||||
Y99
|
||||
X76
|
||||
Y-99.999
|
||||
X78
|
||||
Y99
|
||||
X80
|
||||
Y-99.999
|
||||
X82
|
||||
Y99
|
||||
X84
|
||||
Y-99.999
|
||||
X86
|
||||
Y99
|
||||
X88
|
||||
Y-99.999
|
||||
X90
|
||||
Y99
|
||||
X92
|
||||
Y-99.999
|
||||
X94
|
||||
Y99
|
||||
X96
|
||||
Y-99.999
|
||||
X98
|
||||
Y99
|
||||
G0Z2
|
||||
|
Plik diff jest za duży
Load Diff
Plik diff jest za duży
Load Diff
|
@ -1,4 +1,4 @@
|
|||
(Created with grbl post processor 2023/07/06 16:58)
|
||||
(Created with grbl post processor 2024/01/11 09:36)
|
||||
G21
|
||||
(G-code generated with BlenderCAM and NC library)
|
||||
G17G90
|
Plik binarny nie jest wyświetlany.
|
@ -1,4 +1,4 @@
|
|||
(Created with grbl post processor 2023/07/14 18:33)
|
||||
(Created with grbl post processor 2024/01/13 06:54)
|
||||
G21
|
||||
(G-code generated with BlenderCAM and NC library)
|
||||
G17G90
|
||||
|
@ -315,8 +315,8 @@ X50.435Y66.262
|
|||
X50.411Y66.259
|
||||
X50.387Y66.256
|
||||
X49.19Y66.164
|
||||
X49.166Y66.164
|
||||
X49.141Y66.164
|
||||
X49.166
|
||||
X49.141
|
||||
X49.116Y66.166
|
||||
X49.092Y66.169
|
||||
X49.068Y66.173
|
||||
|
@ -382,7 +382,7 @@ X23.148Y71.158
|
|||
X22.256Y71.018
|
||||
X22.231Y71.015
|
||||
X22.206Y71.014
|
||||
X22.182Y71.014
|
||||
X22.182
|
||||
X22.157Y71.015
|
||||
X22.132Y71.017
|
||||
X22.108Y71.021
|
||||
|
@ -703,7 +703,7 @@ X-30.69Y-60.319
|
|||
X-30.715Y-60.323
|
||||
X-30.74Y-60.325
|
||||
X-30.956Y-60.336
|
||||
X-30.982Y-60.336
|
||||
X-30.982
|
||||
X-31.17Y-60.325
|
||||
X-31.185Y-60.324
|
||||
X-31.2Y-60.322
|
||||
|
@ -737,7 +737,7 @@ X-32.52Y-59.045
|
|||
X-32.549Y-58.849
|
||||
X-32.552Y-58.823
|
||||
X-32.553Y-58.797
|
||||
X-32.553Y-58.771
|
||||
Y-58.771
|
||||
X-32.552Y-58.745
|
||||
X-32.549Y-58.719
|
||||
X-32.545Y-58.693
|
||||
|
@ -839,7 +839,7 @@ X-45.472Y-65.094
|
|||
X-45.496Y-65.097
|
||||
X-45.521Y-65.1
|
||||
X-45.547Y-65.101
|
||||
X-45.572Y-65.101
|
||||
X-45.572
|
||||
X-45.597Y-65.099
|
||||
X-45.622Y-65.097
|
||||
X-45.647Y-65.093
|
||||
|
@ -884,7 +884,7 @@ X-64.004Y-70.885
|
|||
X-64.028Y-70.888
|
||||
X-64.052Y-70.891
|
||||
X-64.077Y-70.892
|
||||
X-64.101Y-70.892
|
||||
X-64.101
|
||||
X-64.126Y-70.891
|
||||
X-64.15Y-70.888
|
||||
X-64.174Y-70.885
|
||||
|
@ -1299,8 +1299,8 @@ X-68.454Y-91.499
|
|||
X-68.478Y-91.503
|
||||
X-68.502Y-91.506
|
||||
X-69.726Y-91.583
|
||||
X-69.75Y-91.583
|
||||
X-69.775Y-91.583
|
||||
X-69.75
|
||||
X-69.775
|
||||
X-69.799Y-91.581
|
||||
X-69.823Y-91.577
|
||||
X-69.846Y-91.573
|
||||
|
@ -1392,7 +1392,7 @@ X-66.066Y-75.27
|
|||
X-66.091Y-75.274
|
||||
X-66.115Y-75.277
|
||||
X-67.339Y-75.363
|
||||
X-67.363Y-75.363
|
||||
X-67.363
|
||||
X-67.388Y-75.362
|
||||
X-67.412Y-75.361
|
||||
X-67.436Y-75.358
|
||||
|
@ -1446,7 +1446,7 @@ X-74.198Y-76.539
|
|||
X-74.222Y-76.543
|
||||
X-74.246Y-76.546
|
||||
X-74.271Y-76.547
|
||||
X-74.295Y-76.547
|
||||
X-74.295
|
||||
X-74.319Y-76.546
|
||||
X-74.343Y-76.544
|
||||
X-74.367Y-76.54
|
||||
|
@ -1484,8 +1484,8 @@ X-75.469Y-74.598
|
|||
X-75.489Y-74.473
|
||||
X-75.492Y-74.447
|
||||
X-75.5Y-74.325
|
||||
X-75.5Y-74.301
|
||||
X-75.5Y-74.277
|
||||
Y-74.301
|
||||
Y-74.277
|
||||
X-75.498Y-74.253
|
||||
X-75.495Y-74.23
|
||||
X-75.491Y-74.206
|
||||
|
@ -1531,8 +1531,8 @@ X-15.618Y58.768
|
|||
X-15.656Y59.003
|
||||
X-15.682Y59.237
|
||||
X-15.684Y59.262
|
||||
X-15.684Y59.287
|
||||
X-15.684Y59.311
|
||||
Y59.287
|
||||
Y59.311
|
||||
X-15.682Y59.336
|
||||
X-15.679Y59.36
|
||||
X-15.675Y59.385
|
||||
|
@ -1594,7 +1594,7 @@ X-39.35Y48.919
|
|||
X-39.374Y48.915
|
||||
X-39.397Y48.913
|
||||
X-40.697Y48.835
|
||||
X-40.721Y48.835
|
||||
X-40.721
|
||||
X-40.746Y48.836
|
||||
X-40.77Y48.838
|
||||
X-40.794Y48.842
|
||||
|
@ -1664,7 +1664,7 @@ X-52.705Y42.558
|
|||
X-52.729Y42.555
|
||||
X-52.754Y42.552
|
||||
X-52.778Y42.551
|
||||
X-52.803Y42.551
|
||||
X-52.803
|
||||
X-52.827Y42.552
|
||||
X-52.852Y42.554
|
||||
X-52.876Y42.558
|
||||
|
@ -1696,7 +1696,7 @@ X-54.798Y45.207
|
|||
X-54.801Y45.231
|
||||
X-54.804Y45.255
|
||||
X-54.805Y45.279
|
||||
X-54.805Y45.304
|
||||
Y45.304
|
||||
X-54.804Y45.328
|
||||
X-54.801Y45.352
|
||||
X-54.798Y45.376
|
||||
|
@ -1818,7 +1818,7 @@ X-49.996Y52.117
|
|||
X-49.275Y52.217
|
||||
X-49.251Y52.219
|
||||
X-49.227Y52.22
|
||||
X-49.204Y52.22
|
||||
X-49.204
|
||||
X-49.18Y52.219
|
||||
X-49.156Y52.217
|
||||
X-49.132Y52.213
|
||||
|
@ -1844,7 +1844,7 @@ X-49.231Y52.817
|
|||
X-49.235Y52.84
|
||||
X-49.314Y53.503
|
||||
X-49.316Y53.529
|
||||
X-49.316Y53.554
|
||||
Y53.554
|
||||
X-49.315Y53.578
|
||||
X-49.313Y53.603
|
||||
X-49.31Y53.627
|
||||
|
@ -1973,7 +1973,7 @@ X-34.653Y64.351
|
|||
X-34.678Y64.347
|
||||
X-34.702Y64.344
|
||||
X-34.727Y64.342
|
||||
X-34.752Y64.342
|
||||
X-34.752
|
||||
X-34.776Y64.343
|
||||
X-34.801Y64.345
|
||||
X-34.826Y64.348
|
||||
|
@ -2031,7 +2031,7 @@ X-37.517Y64.687
|
|||
X-37.666Y64.664
|
||||
X-37.69Y64.661
|
||||
X-37.838Y64.653
|
||||
X-37.862Y64.653
|
||||
X-37.862
|
||||
X-37.886Y64.654
|
||||
X-37.911Y64.657
|
||||
X-37.935Y64.66
|
||||
|
@ -2063,8 +2063,8 @@ X-38.345Y65.062
|
|||
X-38.349Y65.086
|
||||
X-38.352Y65.11
|
||||
X-38.429Y66.332
|
||||
X-38.429Y66.355
|
||||
X-38.429Y66.379
|
||||
Y66.355
|
||||
Y66.379
|
||||
X-38.427Y66.404
|
||||
X-38.424Y66.428
|
||||
X-38.419Y66.453
|
||||
|
@ -2114,7 +2114,7 @@ X22.072Y90.755
|
|||
X25.365Y91.234
|
||||
X28.509Y91.582
|
||||
X28.532Y91.583
|
||||
X28.555Y91.583
|
||||
X28.555
|
||||
X32.181Y91.486
|
||||
X35.608Y91.312
|
||||
X38.849Y91.061
|
||||
|
|
|
@ -2,6 +2,7 @@ import difflib
|
|||
import unittest
|
||||
import subprocess
|
||||
import os
|
||||
import sys
|
||||
|
||||
class BlenderCAMTest(unittest.TestCase):
|
||||
@classmethod
|
||||
|
@ -64,13 +65,20 @@ class BlenderCAMTest(unittest.TestCase):
|
|||
# Compare the generated and expected gcode for each operation
|
||||
for gcode_file in test_case['gcode_files']:
|
||||
with self.subTest(operation=f"{test_case['subdir_name']}/{gcode_file}"):
|
||||
try:
|
||||
generated = self.get_gcode_from_file(gcode_file[1:])
|
||||
expected = self.get_gcode_from_file(gcode_file)
|
||||
generated = self.get_gcode_from_file(gcode_file[1:])
|
||||
expected = self.get_gcode_from_file(gcode_file)
|
||||
if sys.platform=='darwin' and os.path.exists(gcode_file+".mac"):
|
||||
# bullet physics gives slightly different results on mac sometimes...
|
||||
# this is something we can't fix, so compare against mac generated test
|
||||
# file
|
||||
print("Using mac test file",len(expected),len(generated))
|
||||
expected = self.get_gcode_from_file(gcode_file+".mac")
|
||||
self.assertMultiLineEqual(generated, expected,
|
||||
msg = "\n"+self.get_diff(gcode_file[1:], gcode_file+".mac"))
|
||||
else:
|
||||
self.assertMultiLineEqual(generated, expected,
|
||||
msg = "\n"+self.get_diff(gcode_file[1:], gcode_file))
|
||||
finally:
|
||||
os.remove(gcode_file[1:]) # Cleanup generated file
|
||||
os.remove(gcode_file[1:]) # cleanup generated file unless test fails
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Add a test method for each test case to the TestCase class
|
||||
|
@ -78,4 +86,4 @@ if __name__ == '__main__':
|
|||
test_func = lambda self, tc=test_case: self.run_test_case(tc)
|
||||
setattr(BlenderCAMTest, f'test_{test_case["subdir_name"]}', test_func)
|
||||
|
||||
unittest.main()
|
||||
unittest.main()
|
||||
|
|
|
@ -3,6 +3,8 @@ import bpy
|
|||
from bpy.types import UIList
|
||||
from cam.ui_panels.buttons_panel import CAMButtonsPanel
|
||||
|
||||
import cam
|
||||
|
||||
|
||||
class CAM_UL_operations(UIList):
|
||||
def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):
|
||||
|
@ -37,6 +39,7 @@ class CAM_CHAINS_Panel(CAMButtonsPanel, bpy.types.Panel):
|
|||
bl_label = "CAM chains"
|
||||
bl_idname = "WORLD_PT_CAM_CHAINS"
|
||||
panel_interface_level = 1
|
||||
always_show_panel = True
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
@ -63,13 +66,14 @@ class CAM_CHAINS_Panel(CAMButtonsPanel, bpy.types.Panel):
|
|||
col.operator("scene.cam_chain_operation_down", icon='TRIA_DOWN', text="")
|
||||
|
||||
if not chain.computing:
|
||||
if chain.valid:
|
||||
pass
|
||||
layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode")
|
||||
layout.operator("object.cam_export_paths_chain", text="Export chain gcode")
|
||||
layout.operator("object.cam_simulate_chain", text="Simulate this chain")
|
||||
else:
|
||||
layout.label(text="chain invalid, can't compute")
|
||||
layout.operator("object.calculate_cam_paths_chain", text="Calculate chain paths & Export Gcode")
|
||||
layout.operator("object.cam_export_paths_chain", text="Export chain gcode")
|
||||
layout.operator("object.cam_simulate_chain", text="Simulate this chain")
|
||||
|
||||
valid,reason=cam.isChainValid(chain,context)
|
||||
if not valid:
|
||||
layout.label(icon="ERROR",text=f"Can't compute chain - reason:\n")
|
||||
layout.label(text=reason)
|
||||
else:
|
||||
layout.label(text='chain is currently computing')
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue