Porównaj commity

...

148 Commity

Autor SHA1 Wiadomość Data
Release robot a174dd7099 Version number 2024-01-20 16:11:58 +00:00
Alain Pelletier 74eb0cbb17
Merge pull request #253 from zver/fix-import-multilinestring
Fix import MultiLineString. Issue was detected with shapely 1.8.2 module
2024-01-20 12:10:07 -04:00
Alain Pelletier 407e5d8c46
Merge pull request #254 from pppalain/master
January 24 update
2024-01-20 12:07:24 -04:00
Release robot 7c5e846004 Version number 2024-01-17 20:00:57 +00:00
Alain Pelletier 737870b96e
Merge pull request #135 from joemarshall/silly_warnings
Add warnings for when you (I) do silly things
2024-01-17 14:24:15 -04:00
Joe Marshall 2e6ad35485 make UI respond immediately to operation validity etc. 2024-01-17 11:50:39 +00:00
Denis Klimov 5a5b02709f Fix import MultiLineString. Issue was detected with shapely 1.8.2 module 2024-01-17 09:27:49 +05:00
Release robot fba545940a Version number 2024-01-16 16:36:33 +00:00
Alain Pelletier 88f7b5fc0c
Merge pull request #134 from joemarshall/mac_bugfix
mac test bugfix bugfix
2024-01-16 12:35:21 -04:00
Joe Marshall e507f5e4c2 typo 2024-01-16 15:44:26 +00:00
Alain Pelletier 4f9c0d1f2d
Merge pull request #133 from joemarshall/mac_bugfix
Mac bugfixes
2024-01-14 09:26:43 -04:00
Joe Marshall 1bec1bca68 output crash log on blender segfault 2024-01-14 09:41:42 +00:00
Joe Marshall f1fd736d6e Merge branch 'mac_bugfix' of https://github.com/joemarshall/blendercam into mac_bugfix 2024-01-14 09:19:50 +00:00
Joe Marshall ac33091b5c reinstall blender on failure of install script on CI 2024-01-14 09:19:49 +00:00
Joe Marshall 80743420b3
fix rerun 2024-01-13 08:37:02 +00:00
Joe Marshall 283a40ea82 Merge branch 'mac_bugfix' of https://github.com/joemarshall/blendercam into mac_bugfix 2024-01-13 08:30:42 +00:00
Joe Marshall f80676fdb7 retry installation 2024-01-13 08:30:34 +00:00
Joe Marshall 033c6507ee
Update build_and_test.yaml 2024-01-13 08:25:18 +00:00
Joe Marshall cc0e1ce3c3 mac tests should work now 2024-01-13 08:22:34 +00:00
Joe Marshall e144901e9b
Update build_and_test.yaml 2024-01-13 08:14:35 +00:00
Joe Marshall 90055a4c48
Update build_and_test.yaml 2024-01-13 08:12:40 +00:00
Joe Marshall 8fcabf2099 get mac test file 2024-01-13 08:09:25 +00:00
Joe Marshall dfc9f7ffbb debug 2 2024-01-13 08:05:27 +00:00
Joe Marshall 6657769857 debug output full diff 2024-01-13 07:57:45 +00:00
Joe Marshall 838cc1f5f0 mac fixes 2024-01-13 07:53:50 +00:00
Joe Marshall 638d22c3e3 mac fixes 2024-01-13 07:48:03 +00:00
Joe Marshall 3563a853bf filter for identical values in iso moves 2024-01-13 07:02:20 +00:00
Alain Pelletier f8ab8c45dc
Merge pull request #131 from joemarshall/master
github actions workflow
2024-01-12 13:20:43 -04:00
Joe Marshall f1a6ee1f85
Update create_release.yaml 2024-01-12 16:15:11 +00:00
Joe Marshall 2fa08c3bfb
Update create_release.yaml 2024-01-12 16:13:48 +00:00
Release robot fbd4eccb1f Version number 2024-01-12 16:11:41 +00:00
Joe Marshall b63abfe007
Merge pull request #4 from joemarshall/autoupdate
Autoupdate
2024-01-12 16:06:15 +00:00
Joe Marshall 45dee9abcc copy presets on startup 2024-01-12 16:01:24 +00:00
Joe Marshall 8a91c0324c make pppalain/blendercam default repo for releases 2024-01-12 15:41:41 +00:00
Joe Marshall 3019120837 make everything live under cam addon 2024-01-12 15:34:50 +00:00
Joe Marshall fffef961da automatic updates 2024-01-12 15:24:30 +00:00
Joe Marshall f046f76447 preset update sources 2024-01-12 15:08:03 +00:00
Joe Marshall 093ff7288b get files from commit and set time right 2024-01-12 14:20:45 +00:00
Joe Marshall 924d20d4c7 autoupdate from git 2024-01-12 14:16:47 +00:00
Joe Marshall 1e05bcf7bb typo 2024-01-12 13:43:17 +00:00
Joe Marshall 9203d46952 autoupdate button 2024-01-12 13:41:57 +00:00
Joe Marshall a31b64961e autoupdate from panel only 2024-01-12 13:35:18 +00:00
Joe Marshall baa29586d7 autoupdate disable option 2024-01-12 13:16:36 +00:00
Joe Marshall 5d12e32a9b typo 2024-01-12 12:24:53 +00:00
Joe Marshall 7fae236a11 mtimes 2024-01-12 12:23:46 +00:00
Joe Marshall 94860361bc commit working version 2024-01-12 12:14:37 +00:00
Joe Marshall 302ea11809 working autoupdate except reload 2024-01-12 12:01:24 +00:00
Joe Marshall 7dcf78706a Merge branch 'master' of https://github.com/joemarshall/blendercam into autoupdate 2024-01-12 11:22:53 +00:00
Release robot 527241fe34 Version number 2024-01-12 11:02:52 +00:00
Joe Marshall 366e0aa7c7
Update create_release.yaml 2024-01-12 11:02:26 +00:00
Release robot 8e2ac449a3 Version number 2024-01-12 10:59:59 +00:00
Joe Marshall 3f90793a27
Update create_release.yaml 2024-01-12 10:59:31 +00:00
Joe Marshall c38bf30688
Merge pull request #3 from joemarshall/action_updates
update bl_info version manually
2024-01-12 10:53:54 +00:00
Joe Marshall e64861bd9f update bl_info version manually 2024-01-12 10:52:28 +00:00
Joe Marshall 9d1bbc8ce6 autoupdate initial 2024-01-12 10:41:43 +00:00
Joe Marshall fdf6a81617
Update create_release.yaml 2024-01-11 16:59:01 +00:00
Joe Marshall f786176bb4
Merge branch 'pppalain:master' into master 2024-01-11 15:59:46 +00:00
Joe Marshall 1d9cd3782c
Update create_release.yaml 2024-01-11 15:57:31 +00:00
Joe Marshall 3efe967498
Update create_release.yaml 2024-01-11 15:54:52 +00:00
Joe Marshall b1bb2719d2
Update create_release.yaml 2024-01-11 15:53:57 +00:00
Release robot 7482c7af6e Version number 2024-01-11 15:52:10 +00:00
Joe Marshall 98ad6282d6
Update create_release.yaml 2024-01-11 15:51:43 +00:00
Joe Marshall 06b80c365e
Update create_release.yaml 2024-01-11 15:49:38 +00:00
Release robot 596d056aaa Version number 2024-01-11 15:48:33 +00:00
Joe Marshall 4d4e5d6b0f
Update create_release.yaml 2024-01-11 15:48:09 +00:00
Joe Marshall 3d6a1d04bf
Update create_release.yaml 2024-01-11 15:42:43 +00:00
Joe Marshall 5b1e4d3105
Update create_release.yaml 2024-01-11 15:39:25 +00:00
Joe Marshall 47070b4555
Update create_release.yaml 2024-01-11 15:38:04 +00:00
Joe Marshall f7bda388bd
Update create_release.yaml 2024-01-11 15:36:58 +00:00
Joe Marshall 7543764590
Update create_release.yaml 2024-01-11 15:35:32 +00:00
Joe Marshall 3ba7be8b5c
Update create_release.yaml 2024-01-11 15:34:39 +00:00
Joe Marshall d00bb9e130
Update create_release.yaml 2024-01-11 15:30:31 +00:00
Joe Marshall d27f2cca28
Update create_release.yaml 2024-01-11 15:29:41 +00:00
Joe Marshall 6d4d2cead0
Update create_release.yaml 2024-01-11 15:28:22 +00:00
Joe Marshall 25a2818407
Update create_release.yaml 2024-01-11 15:27:00 +00:00
Joe Marshall 78c50efc46
Merge pull request #2 from joemarshall/actions
Actions
2024-01-11 15:26:17 +00:00
Joe Marshall 8203f8b59a Merge branch 'actions' of https://github.com/joemarshall/blendercam into actions 2024-01-11 15:24:06 +00:00
Joe Marshall e5cb5cebc9 version in separate file 2024-01-11 15:24:00 +00:00
Joe Marshall a85957dcb7
Create create_release.yaml 2024-01-11 15:23:33 +00:00
Joe Marshall 829b62b793 revert accuracy changes
because they don't fix mac tests anyway
2024-01-11 14:55:31 +00:00
Joe Marshall 21a0a5879c try with isclose 2024-01-11 14:39:12 +00:00
Joe Marshall 825e0cac5e fix for mac? 2024-01-11 14:20:43 +00:00
Joe Marshall 89ff885f1c
Update build_and_test.yaml 2024-01-11 13:34:24 +00:00
Joe Marshall 48a60f35a6
Update build_and_test.yaml 2024-01-11 13:32:06 +00:00
Joe Marshall 42de2b1728
Update test_suite.py 2024-01-11 13:28:03 +00:00
Joe Marshall 0a7bfc1a72
Update test_suite.py 2024-01-11 13:26:06 +00:00
Joe Marshall 322dd24db2
Update test_suite.py 2024-01-11 13:20:01 +00:00
Alain Pelletier fc93b62e8a
Merge pull request #130 from joemarshall/ocl_progress
progress for OCL waterline
2024-01-11 09:17:19 -04:00
Joe Marshall a923e955b8
Merge pull request #1 from joemarshall/master
Update test_suite.py
2024-01-11 13:16:04 +00:00
Joe Marshall 0d5e9f005b
Update test_suite.py 2024-01-11 13:14:19 +00:00
Joe Marshall 505f2e43f1
Update build_and_test.yaml 2024-01-11 13:05:12 +00:00
Joe Marshall fd55f11e4f
Update build_and_test.yaml 2024-01-11 13:03:11 +00:00
Joe Marshall f84920b40c
Update build_and_test.yaml 2024-01-11 13:00:40 +00:00
Joe Marshall a943809ec3
Update build_and_test.yaml 2024-01-11 12:58:50 +00:00
Joe Marshall a23b58606c
Update build_and_test.yaml 2024-01-11 12:54:04 +00:00
Joe Marshall 5a058afba7
Update build_and_test.yaml 2024-01-11 12:48:43 +00:00
Joe Marshall b107b2eed2
Update build_and_test.yaml 2024-01-11 12:47:13 +00:00
Joe Marshall a2ed453255
Update build_and_test.yaml 2024-01-11 12:39:16 +00:00
Joe Marshall 141cb3d17e
Update build_and_test.yaml 2024-01-11 12:36:26 +00:00
Joe Marshall db7bbb1bf1
Update build_and_test.yaml 2024-01-11 12:33:06 +00:00
Joe Marshall 481a670966
Update build_and_test.yaml 2024-01-11 12:32:06 +00:00
Joe Marshall 881e0d3437
Update build_and_test.yaml 2024-01-11 12:30:31 +00:00
Joe Marshall 02fd1f530c
Update build_and_test.yaml 2024-01-11 12:06:55 +00:00
Joe Marshall 0d400711a6
Update build_and_test.yaml 2024-01-11 12:00:49 +00:00
Joe Marshall 0661fd1de3
Update build_and_test.yaml 2024-01-11 11:56:42 +00:00
Joe Marshall ff5088114b
Update build_and_test.yaml 2024-01-11 11:48:20 +00:00
Joe Marshall 08dd7490a9
Update build_and_test.yaml 2024-01-11 11:40:23 +00:00
Joe Marshall d56d9c8e55
Update build_and_test.yaml 2024-01-11 11:35:05 +00:00
Joe Marshall f2df26479d
Update install_addon.py 2024-01-11 11:31:31 +00:00
Joe Marshall 61b529bbbf
add cache 2024-01-11 11:28:40 +00:00
Joe Marshall a4a86f1e56
addon installer for testing on github actions 2024-01-11 11:24:59 +00:00
Joe Marshall 6a4326e47f
Update build_and_test.yaml 2024-01-11 11:19:43 +00:00
Joe Marshall 9327bf3b2e
Update build_and_test.yaml 2024-01-11 11:14:45 +00:00
Joe Marshall 76ce1dda5d
Update build_and_test.yaml 2024-01-11 11:13:27 +00:00
Joe Marshall 7138c29d70
Update build_and_test.yaml 2024-01-11 11:12:19 +00:00
Joe Marshall 0bd654a8b6
Update build_and_test.yaml 2024-01-11 11:11:39 +00:00
Joe Marshall 8078f99e84
Update build_and_test.yaml 2024-01-11 11:10:52 +00:00
Joe Marshall a465ce9fa2
Update build_and_test.yaml 2024-01-11 11:05:36 +00:00
Joe Marshall 5cd56009eb
Update build_and_test.yaml 2024-01-11 11:04:06 +00:00
Joe Marshall eb4c0fa22e
typo 2024-01-11 11:03:27 +00:00
Joe Marshall ff4498012e
Create build_and_test.yaml 2024-01-11 11:02:35 +00:00
Joe Marshall d3ea1c1db2 fixed test settings for parallel tests 2024-01-11 10:15:37 +00:00
Joe Marshall 2b56fe70e6 fixed tests for progress changes 2024-01-11 10:11:08 +00:00
Joe Marshall 2c48304853 fix tests 2024-01-10 16:06:13 +00:00
Joe Marshall 4fc826fbde fix medial axis 2024-01-10 15:53:51 +00:00
Joe Marshall e0c3f30a89 ocl progress 2024-01-10 15:29:22 +00:00
Joe Marshall 69e5476966 progress for OCL waterline 2024-01-10 14:29:44 +00:00
Alain Pelletier b1a7f136ab
Merge pull request #129 from joemarshall/operation_progress
Operation progress
2024-01-10 09:25:38 -04:00
Joe Marshall 3f797afe2d Merge https://github.com/pppalain/blendercam into operation_progress 2024-01-10 11:19:25 +00:00
Joe Marshall f637370810 operation progress 2024-01-10 11:15:10 +00:00
Alain Pelletier 7c6f4ea254
Merge pull request #127 from joemarshall/np.full
use np.full instead of creating array and resizing
2024-01-09 12:15:39 -04:00
Alain Pelletier f846fb4243
Merge pull request #128 from abosafia/master
Waterline area fix
2024-01-09 12:15:07 -04:00
abosafia fd7ab2b10e Waterline area fix 2024-01-07 11:30:42 +02:00
Joe Marshall bf760e8e60 use np.full 2024-01-06 08:35:18 +00:00
Alain Pelletier 71d06d2c51
Merge pull request #126 from joemarshall/waterline_fix2
waterline bugfixes
2024-01-05 12:04:05 -04:00
Joe Marshall 374d8122df waterline fixes:
1) Make waterline work from z-start other than zero
2) Make waterline re-render depth cache if objects are resized / moved etc.
2024-01-05 15:59:56 +00:00
Alain Pelletier ddf7479022
Merge pull request #125 from abosafia/master
apply transform if waterline
2024-01-05 11:43:10 -04:00
abosafia baab210120 apply transform if waterline 2024-01-05 11:04:03 +02:00
Alain Pelletier 2f3fee765a
Merge pull request #124 from joemarshall/blender4
Blender4 fixes
2024-01-04 14:43:18 -04:00
Alain Pelletier 249508b2a3
Merge pull request #123 from joemarshall/persist_machine
changing machine preset changes default machine for new files
2024-01-04 14:39:49 -04:00
Joe Marshall 2058d61aec fix offsetArea for blender4 2024-01-04 16:47:26 +00:00
Joe Marshall 01259026af blender 4 fixes 2024-01-04 07:32:55 +00:00
Joe Marshall 85722763f4 changing machine preset changes default machine for new files
this means that if you setup a preset for your machine, when you create a new blender file,
it will be ready to run on your machine.
2024-01-03 07:56:25 +00:00
Alain Pelletier 41d8e33b6c
Merge pull request #122 from joemarshall/waterline_fix
Waterline fix
2023-12-31 11:45:14 -04:00
Joe Marshall 7996e70243 Merge branch 'waterline_fix' of https://github.com/joemarshall/blendercam into waterline_fix 2023-12-31 09:31:31 +00:00
Joe Marshall fb99710cab enable z pass on rendering depth images 2023-12-31 09:31:17 +00:00
Alain Pelletier a621b29cce
Merge pull request #121 from joemarshall/bas_relief_fixes
basrelief improvements
2023-12-28 11:50:38 -04:00
Joe Marshall 11e4a88c52 make basrelief work with depth maps 2023-12-28 07:17:23 +00:00
117 zmienionych plików z 4442 dodań i 4430 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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"

Plik diff jest za duży Load Diff

Wyświetl plik

@ -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.

Wyświetl plik

@ -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()

Wyświetl plik

@ -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)

Wyświetl plik

@ -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'}

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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)

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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'}

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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("============================")

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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