141 Commits

Author SHA1 Message Date
jdegenstein
9579757c98 debugger.py -> abspath to absolute 2024-08-22 12:34:28 -05:00
jdegenstein
5d8f651f97 widgets/editor.py -> abspath to absolute 2024-08-22 12:34:12 -05:00
jdegenstein
7f01d73f54 editor.py -> abspath to absolute 2024-08-22 12:33:40 -05:00
jdegenstein
d657da605a setup.py -> add numpy dep below v2 2024-08-22 12:27:06 -05:00
jdegenstein
5fdeda5076 pyinstaller-builds-actions-PIP-TAR.yml -> pip install self built macos arm64 nlopt 2024-06-05 08:45:57 -05:00
jdegenstein
ebdc391f3d Update pyinstaller-builds-actions-PIP-TAR.yml 2024-06-04 20:36:06 -05:00
jdegenstein
3ba85424c7 Update pyinstaller-builds-actions-PIP-TAR.yml -> use mainline pyinstaller-hooks-contrib 2024-06-04 14:09:54 -05:00
jdegenstein
069df10dd0 Update pyinstaller-builds-actions-PIP-TAR.yml 2024-06-01 15:20:44 -05:00
jdegenstein
c2ba027cd5 Update pyinstaller-builds-actions-PIP-TAR.yml 2024-06-01 09:54:33 -05:00
jdegenstein
9653933c2e CQ-editor.sh workaround from issue #13 2024-05-30 20:44:56 -05:00
jdegenstein
7878bad430 Update pyinstaller-builds-actions-PIP-TAR.yml 2024-05-30 15:21:05 -05:00
jdegenstein
80b9dd5bef pyinstaller-builds-actions-PIP-TAR.yml 2024-05-30 14:48:30 -05:00
jdegenstein
67d64f67a7 pyinstaller-builds-actions-PIP-TAR.yml -> attempt to add macos-arm64 2024-05-30 14:40:50 -05:00
jdegenstein
4ae685aba2 pyinstaller-builds-actions-PIP-TAR.yml -> Fix win path for use with bash 2024-05-30 14:20:24 -05:00
jdegenstein
189c37e284 pyinstaller-builds-actions-PIP-TAR.yml -> shell: bash --login {0} 2024-05-30 14:06:37 -05:00
jdegenstein
8b63d10dc5 Update pyinstaller-builds-actions-PIP-TAR.yml 2024-05-30 13:59:06 -05:00
jdegenstein
38feb5ddc4 pyinstaller-builds-actions-PIP-TAR.yml -> activate environment 2024-05-30 13:49:42 -05:00
jdegenstein
0254de2be7 Update cqgui_env.yml python 3.11 2024-05-30 13:15:30 -05:00
jdegenstein
33e813dad8 pyinstaller_pip.spec -> python 3.11 2024-05-30 13:14:42 -05:00
jdegenstein
5354369dcd environment.yml -> python=3.11 2024-05-30 13:14:04 -05:00
jdegenstein
20e8d623d0 pyinstaller-builds-actions-PIP-TAR.yml -> Updating actions and fixing MacOS/x86_64 builds 2024-05-30 13:13:05 -05:00
jdegenstein
f26f95396a pyinstaller-builds-actions-PIP-TAR.yml -> update some GH actions for node20 2024-04-12 09:46:11 -05:00
jdegenstein
6fe767c0a4 Delete pyinstaller.spec 2024-01-02 11:46:25 -06:00
jdegenstein
1bb94c1d76 Delete .github/workflows/pyinstaller-builds-actions.yml 2024-01-02 11:46:13 -06:00
jdegenstein
003b9eb3f5 Delete .github/workflows/pyinstaller-builds-actions-mmamba.yml 2024-01-02 11:46:06 -06:00
jdegenstein
143d7712cd Delete .github/workflows/pyinstaller-builds-actions-mmamba-TAR.yml 2024-01-02 11:45:55 -06:00
jdegenstein
aaf0d31b52 Delete .github/workflows/pyinstaller-builds-actions-build-PIP-TAR-pyinst5-6p.yml 2024-01-02 11:45:33 -06:00
jdegenstein
14d0477d1d README.md -> add bd_warehouse 2024-01-02 10:56:34 -06:00
jdegenstein
5b0504f22b pyinstaller_pip.spec -> add bd_warehouse 2024-01-02 10:55:24 -06:00
jdegenstein
3ed0deff41 pyinstaller-builds-actions-PIP-TAR.yml -> install bd_warehouse 2024-01-02 10:49:14 -06:00
jdegenstein
e00e40c7b0 Update pyinstaller-builds-actions-PIP-TAR.yml
back to python 3.10
2023-10-30 13:44:07 -05:00
jdegenstein
035b95c508 Update pyinstaller-builds-actions-PIP-TAR.yml
bump to Ubuntu-22.04 and python 3.11
2023-10-30 13:42:10 -05:00
jdegenstein
656426f645 Delete pyinstaller/extrahooks/hook-rtree.py
fixed on pyinstaller hooks contrib, this file did not produce working linux builds anyway
2023-10-26 13:40:46 -05:00
jdegenstein
ab6526c1fe Create hook-rtree.py 2023-10-20 15:19:04 -05:00
jdegenstein
d8c4e5bfac Create hook-py_lib3mf.py 2023-10-20 14:01:40 -05:00
jdegenstein
f52fc4dd42 remove getfullnameof in pyinstaller_pip.spec
changes in pyinstaller>=6.0.0 removed getfullnameof
2023-09-25 10:53:38 -05:00
jdegenstein
25d4c2c45c Swap front/back view directions to match with OCP CAD Viewer
also makes it match with many other CAD software
2023-07-05 16:41:13 -05:00
jdegenstein
e59c6ae7ba Add Release Info to README.md 2023-06-16 12:29:05 -05:00
jdegenstein
0c7237fecb GH Actions: Downgrade tartifact to @v1 for MacOS
due to shopt globstar invalid on MacOS
2023-06-15 11:13:42 -05:00
jdegenstein
a83d03c7ef GH Actions init-shell powershell for windows 2023-06-15 10:00:09 -05:00
jdegenstein
1b75bf8fc0 GH Actions to checkout@v3 and tartifact@v2
alehechka/upload-tartifact@v2
actions/checkout@v3

for Node16 compat
2023-06-15 09:49:20 -05:00
jdegenstein
21d729d758 Update actions to use setup-micromamba instead of prov mm
Update actions to use setup-micromamba instead of provision-with-micromamba
2023-06-15 09:37:19 -05:00
jdegenstein
1f546f4bc5 Update README.md 2023-05-08 14:09:54 -05:00
jdegenstein
56ca315097 testing py 3.11 2023-04-19 11:21:22 -05:00
jdegenstein
9a60cf7802 add unused parameters to show_object for compat with ocp_vscode 2023-04-12 10:49:10 -05:00
jdegenstein
151b8b8e36 fix missing import Quantity_TOC_RGB
Quantity_TOC_RGB as TOC_RGB for consistency
2023-03-19 12:19:03 -05:00
jdegenstein
0ba64024e0 change x-axis to "wine red" color
to create a difference between the axis and wires/edges
2023-03-13 13:39:27 -05:00
jdegenstein
a4b86630dc change to "wine red" x-axis 2023-03-13 13:37:25 -05:00
jdegenstein
55c23b19ff add doc for name and rand_color in show_object to README.md 2023-03-01 09:22:11 -06:00
jdegenstein
d9153abfca bump MacOS static build to pyinstaller>=5.6 2023-02-27 14:21:41 -06:00
jdegenstein
deca2f1b89 patched local hooks-contrib 2023-02-27 14:11:23 -06:00
jdegenstein
215c200aa1 install pyinstaller-hooks-contrib from GH
contains a fix for pyqtgraph builds
2023-02-27 14:04:53 -06:00
jdegenstein
c1f08af0de bump OCP static lib to python 3.10 2023-02-27 12:20:10 -06:00
jdegenstein
fba053d026 unpin casadi==3.5.5 to fix linux static builds 2023-02-27 11:54:53 -06:00
jdegenstein
be2989c424 pin all to python=3.10 2023-02-27 11:50:04 -06:00
jdegenstein
df863ae27c bump to python=3.10 to fix linux static build 2023-02-27 11:49:07 -06:00
jdegenstein
611d189051 bump python=3.10 linux static build 2023-02-27 11:45:33 -06:00
jdegenstein
653502e913 try unpin pyqtgraph==0.12.4 to fix linux static builds 2023-02-27 11:35:45 -06:00
jdegenstein
5e49adcf54 install jupyter-rfb on linux static build 2023-02-27 11:25:42 -06:00
jdegenstein
feef729143 try unpin ipython==8.4.0 to fix linux static builds 2023-02-27 11:17:43 -06:00
jdegenstein
3ef2ec4447 install pyopengl on linux static build 2023-02-21 12:10:29 -06:00
jdegenstein
0d1ee5d5af add qtbase5-dev qt5-qmake to linux static build 2023-02-21 12:05:12 -06:00
jdegenstein
c1e75a8b62 update to pyinstaller>=5.6 2023-02-21 11:40:45 -06:00
jdegenstein
b408429405 Unpin scipy from setup.py 2023-02-21 11:33:11 -06:00
jdegenstein
6a762801c1 pyinstall 5.6 remove --debug 2023-02-21 11:02:45 -06:00
jdegenstein
ba3f45ea25 Testing pyinstaller >=5.6 2023-02-21 10:53:40 -06:00
jdegenstein
9eca091fa3 test scipy<=1.9.1 in setup.py 2023-02-20 21:27:58 -06:00
jdegenstein
43c4708847 Testing unpinning jedi in setup.py 2023-02-20 09:22:13 -06:00
jdegenstein
e71a5f1c62 Update README
Update CQ pip install, and add more linux install info.
2023-02-17 13:32:41 -06:00
jdegenstein
6b762baf41 Merge pull request #6 from jdegenstein/mactrackpad
Change right click and drag to pan
2023-02-14 15:03:19 -06:00
jdegenstein
758afb5f18 Add discord link to readme 2023-02-11 13:41:56 -06:00
jdegenstein
cf43cee874 Update build123d install instruction 2023-02-11 13:31:16 -06:00
jdegenstein
29b2faeaa8 Change rand_color seed
seed(371353) - > seed(59798267586177) 
has better colors that are more distinguishable from one another
2023-02-10 19:32:34 -06:00
jdegenstein
9506307367 Add pip install instruction for build123d to README 2023-02-02 15:38:55 -06:00
jdegenstein
524a8dd6fd Change right click and drag to pan
Change right click and drag to pan (same as middle click and drag)
2023-02-02 15:25:27 -06:00
jdegenstein
bd12503e81 Additional info about build123d compat and more links 2023-01-23 11:29:18 -06:00
jdegenstein
2ba23c4659 build123d compat 2023-01-09 12:22:14 -06:00
jdegenstein
ee6a60705c remove debugging print 2023-01-09 12:18:23 -06:00
jdegenstein
a144b2e60a build123d compatibility improvements for file exporting
Removes the need to use .wrapped for the CQ-editor file exporter to work correctly. Also ran file through black formatter.
2023-01-09 12:16:49 -06:00
jdegenstein
7b49f25f94 Revert ubuntu-1804 changes to test ubuntu-2004 2022-11-03 13:57:23 -05:00
jdegenstein
2b3620b759 max verbosity 2022-11-01 21:14:08 -05:00
jdegenstein
afa20cd715 increase pip verbosity for cadquery and casadi install 2022-11-01 20:56:21 -05:00
jdegenstein
2d7fbeb08d changes for ubuntu1804
from:
casadi==3.5.5
to:
casadi
2022-11-01 20:46:50 -05:00
jdegenstein
ecaf68edac changes for ubuntu-1804
change from:
sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
to:
sudo apt install -y libblas-dev libblas3
2022-11-01 20:42:57 -05:00
jdegenstein
69d6bc368e testing ubuntu-18.04 2022-11-01 20:39:09 -05:00
jdegenstein
dbc90c2724 register rand_color for use in console 2022-10-28 23:36:26 -05:00
jdegenstein
fbbb1fd7b6 Update debugger.py 2022-10-28 23:33:57 -05:00
jdegenstein
73a5825f3f add rand_color 2022-10-28 22:02:32 -05:00
jdegenstein
fefb57c2b9 remove rand_color 2022-10-28 21:59:28 -05:00
jdegenstein
5e0d68e2ad remove rand_color 2022-10-28 21:58:20 -05:00
jdegenstein
7c8fa2b100 from .widgets.object_tree import ObjectTree, rand_color 2022-10-28 21:02:07 -05:00
jdegenstein
925aded53c add rand_color() helper function
and seed(371353) global
2022-10-28 20:05:51 -05:00
jdegenstein
973448697a move to ubuntu-22.04 from ubuntu-latest (20.04)
trying to get libstdc++.so.6.30 instead of ...28
2022-10-28 15:34:40 -05:00
jdegenstein
c5e0d73422 fix list(tuple) 2022-10-28 12:05:18 -05:00
jdegenstein
a7a376f9f5 point to pyinstaller_pip.spec on Linux/MacOS 2022-10-28 11:59:52 -05:00
jdegenstein
e436bd3d7e Re-enable Linux/MacOS on -PIP-TAR.yaml
try and emulate Windows installation process, wherever possible
2022-10-28 11:52:16 -05:00
jdegenstein
26b64607a5 hookspath=['pyinstaller/extrahooks/']to _pip.spec 2022-10-28 11:28:22 -05:00
jdegenstein
000fe986e0 Delete dup hook-casadi.py 2022-10-28 11:24:30 -05:00
jdegenstein
acf5639e76 create subdir and add hook-casasi.py to it
create subdir and add hook-casasi.py to it
2022-10-28 11:24:12 -05:00
jdegenstein
ebcb831ca9 Update pyinstaller_pip.spec
add hook-casadi.py
2022-10-28 10:23:58 -05:00
jdegenstein
fd47eadcdc Create hook-casadi.py
per discussion https://github.com/pyinstaller/pyinstaller/discussions/7199
2022-10-28 10:22:11 -05:00
jdegenstein
67fa19704e Update pyinstaller-builds-actions-PIP-TAR.yml
change from:
pip install --pre cadquery casadi==3.5.5
to:
pip install --pre git+https://github.com/cadquery/cadquery casadi==3.5.5
2022-10-28 10:18:01 -05:00
jdegenstein
272bede28f Update pyinstaller-builds-actions-PIP-TAR.yml
Remove: 
        pip install pipwin
        pipwin install numpy
Will probably break everything because we may need MKL-enabled numpy.
2022-10-28 08:56:08 -05:00
jdegenstein
9f4fa50c7c Update pyinstaller-builds-actions-PIP-TAR.yml
pip list
tree $PWD /F
2022-10-27 20:31:31 -05:00
jdegenstein
03e8575210 Update README.md
add pip install directions
2022-10-27 20:17:35 -05:00
jdegenstein
0087803ee4 Update pyinstaller-builds-actions-PIP-TAR.yml
unpin ipopt==3.4.10
2022-10-27 19:45:28 -05:00
jdegenstein
72e814db3b Update pyinstaller_pip.spec 2022-10-27 19:34:34 -05:00
jdegenstein
466d1e7534 Update pyinstaller-builds-actions-PIP-TAR.yml
pin ipopt==3.4.10 casadi==3.5.5
2022-10-27 19:33:50 -05:00
jdegenstein
3f3e22c2f1 Update pyinstaller_pip.spec
remove (occt_dir, 'opencascade') from _pip.spec
2022-10-27 16:51:32 -05:00
jdegenstein
aa90e48968 Create pyinstaller_pip.spec 2022-10-27 16:40:50 -05:00
jdegenstein
0e29e3091b Update pyinstaller-builds-actions-PIP-TAR.yml
point to new spec file
2022-10-27 16:37:33 -05:00
jdegenstein
66e620a877 Update pyinstaller-builds-actions-PIP-TAR.yml
change from:
pip install git+https://github.com/cadquery/cadquery
to:
pip install --pre cadquery

hopefully will resolve cadquery-ocp
2022-10-27 16:26:56 -05:00
jdegenstein
14535ec7a4 Create pyinstaller-builds-actions-PIP-TAR.yml
trying to use as few conda/mamba steps as possible
2022-10-27 16:14:02 -05:00
jdegenstein
ee09398909 Update viewer.py to bring up to date with Mainline a2df6ff 2022-10-27 14:52:00 -05:00
jdegenstein
10a81d4e12 Update cq_utils.py to bring up to date with Mainline a2df6ff 2022-10-27 14:50:32 -05:00
jdegenstein
ca706045ba Update test_app.py to bring up to date with Mainline a2df6ff 2022-10-27 14:46:05 -05:00
jdegenstein
ce68e6e120 Update setup.py
add "pyqtgraph==0.12.4"
2022-10-27 13:30:47 -05:00
jdegenstein
839d514f66 add missing libraries to spec file
casadi_nlpsol_ipopt.dll
ipopt-3.dll
libblas.dll
liblapack.dll
dmumps.dll
flang.dll
flangrti.dll
libomp.dll
mkl*.dll (a few dozen DLLs)
2022-10-27 10:12:10 -05:00
jdegenstein
36f2896652 Fix casadi*.dll path 2022-10-26 13:10:40 -05:00
jdegenstein
36f23a4d7e change casadi...dll path in mmamba-TAR.yml 2022-10-26 12:55:46 -05:00
jdegenstein
c85c8358f5 Update mmamba-TAR.yml to include casadi_nlpsol_ipopt.dll
using Copy-Item after PyInstaller has run
2022-10-26 12:33:24 -05:00
jdegenstein
de73e88033 Remove casadi DLLs from pyinstaller.spec
remove cas_DLLs from binaries and datas
2022-10-26 12:29:24 -05:00
jdegenstein
be62509ba7 Update pyinstaller-builds-actions-mmamba-TAR.yml
install casadi and ipopt
2022-10-26 10:49:29 -05:00
jdegenstein
b0284ac80c Add ipopt and casadi to hidden imports in spec 2022-10-26 10:27:15 -05:00
jdegenstein
2d7cb64247 Update pyinstaller.spec
attempt to add cas_DLLs to datas (in addition to binaries earlier)
2022-10-25 21:41:28 -05:00
jdegenstein
5b474bb493 Update pyinstaller.spec
try to only add casadi DLLs
2022-10-25 21:17:11 -05:00
jdegenstein
0818537f34 Update pyinstaller-builds-actions-mmamba-TAR.yml
re-enable tar(tifact)
2022-10-25 21:07:35 -05:00
jdegenstein
cf7253ebe7 Update pyinstaller.spec
add casadi DLL directory
2022-10-25 20:57:27 -05:00
jdegenstein
4a6e9a1cc3 Update pyinstaller.spec
add casadi to datas
2022-10-25 19:45:18 -05:00
jdegenstein
22c3d3a37b Update pyinstaller-builds-actions-mmamba-TAR.yml 2022-10-25 16:42:33 -05:00
jdegenstein
dfb79aa306 Update pyinstaller.spec
add a comma
2022-10-25 16:41:51 -05:00
jdegenstein
3ac6e93a5e Update pyinstaller.spec to include casadi 2022-10-25 16:13:25 -05:00
jdegenstein
621e0dc86b Update pyinstaller.spec
str to list
2022-10-25 15:56:38 -05:00
jdegenstein
3764233ed7 Update pyinstaller-builds-actions-mmamba-TAR.yml
use windows only, disabled TAR
2022-10-25 15:33:08 -05:00
jdegenstein
6e26bdb7fc Testing add casadi DLLs to pyinstaller.spec 2022-10-25 15:31:03 -05:00
jdegenstein
5fd737ab50 pin jedi==0.17.2 in setup.py 2022-10-20 22:41:26 -05:00
jdegenstein
cfb952047f fix pin 2022-10-20 22:18:58 -05:00
jdegenstein
974fd51f88 Merge pull request #2 from jdegenstein/dev
Dev to Main
2022-10-21 03:13:34 +00:00
jdegenstein
6bd2148257 Pin ipython=8.4.0 version to setup.py 2022-10-20 22:11:29 -05:00
jdegenstein
39bb40289f Update setup.py for pip install per u/sethfischer
from a PR on mainline
2022-10-20 22:00:48 -05:00
jdegenstein
02453f7823 Link to projects, remove wrong install, link to build actions 2022-10-17 15:23:48 -05:00
21 changed files with 1363 additions and 1353 deletions

View File

@@ -0,0 +1,173 @@
name: build-PIP-TAR
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
type:
description: 'Whether to build a single file (onefile) or directory (dir) dist'
required: true
default: 'dir'
jobs:
build-linux-x86_64:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- uses: mamba-org/setup-micromamba@v1
with:
environment-name: test
environment-file: environment.yml
init-shell: >-
bash
- name: pip install cadquery CQ-editor ... etc
shell: bash --login {0}
run: |
sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
sudo apt install -y libxkbcommon0 libxkbcommon-x11-0 libxcb-xinerama0
sudo apt install -y qtbase5-dev qt5-qmake
micromamba info
pip install pyopengl
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install -vvv --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6
pip install path
pip install jupyter-rfb
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/gumyr/bd_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
shell: bash --login {0}
run: |
micromamba activate test
micromamba info
pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- uses: actions/upload-artifact@v4
with:
name: CQ-editor-Linux-x86_64
path: dist
build-macos-x86_64:
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- uses: mamba-org/setup-micromamba@v1
with:
environment-name: test
environment-file: environment.yml
init-shell: >-
bash
- name: pip install cadquery CQ-editor ... etc
shell: bash --login {0}
run: |
micromamba info
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6
pip install path
pip uninstall -y PyQt5
pip install PyQt5==5.15.7
pip install PyQtWebEngine==5.15.6
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/gumyr/bd_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
shell: bash --login {0}
run: |
micromamba activate test
micromamba info
pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- uses: actions/upload-artifact@v4
with:
name: CQ-editor-MacOS-x86_64
path: dist
build-macos-arm64:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: mamba-org/setup-micromamba@v1
with:
environment-name: test
environment-file: environment.yml
init-shell: >-
bash
- name: pip install cadquery CQ-editor ... etc
shell: bash --login {0}
run: |
micromamba info
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install https://github.com/CadQuery/ocp-build-system/releases/download/7.7.2.0/cadquery_ocp-7.7.2-cp311-cp311-macosx_11_0_arm64.whl
pip install https://github.com/jdegenstein/nlopt-python/releases/download/2.7.1.3/nlopt-2.7.1-cp311-cp311-macosx_11_0_arm64.whl
pip install --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6
pip install path
pip uninstall -y PyQt5
pip install PyQt5==5.15.10
pip install PyQtWebEngine==5.15.6
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/gumyr/bd_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
shell: bash --login {0}
run: |
micromamba activate test
micromamba info
pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- uses: actions/upload-artifact@v4
with:
name: CQ-editor-MacOS-arm64
path: dist
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: mamba-org/setup-micromamba@v1
with:
environment-name: test
environment-file: environment.yml
init-shell: >-
bash
- name: pip install cadquery CQ-editor ... etc
shell: bash --login {0}
run: |
micromamba info
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6
pip install path
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/gumyr/bd_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
shell: bash --login {0}
run: |
micromamba activate test
micromamba info
pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
cp D:/a/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.cmd D:/a/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- name: 7zip artifact (workaround for too many files during artifact upload)
shell: bash --login {0}
run: 7z a release.zip ./dist/*
- uses: actions/upload-artifact@v4
with:
name: CQ-editor-Windows
path: release.zip

View File

@@ -1,125 +0,0 @@
name: build-micromamba-TAR
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
type:
description: 'Whether to build a single file (onefile) or directory (dir) dist'
required: true
default: 'dir'
jobs:
# build-linux:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: mamba-org/provision-with-micromamba@main
# with:
# #miniconda-version: "latest"
# #auto-update-conda: true
# environment-name: test
# environment-file: environment.yml
# extra-specs:
# python=3.9
# - name: Mamba install CadQuery and pyinstaller
# shell: bash --login {0}
# run: |
# sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
# sudo apt install -y libxkbcommon0
# sudo apt install -y libxkbcommon-x11-0
# sudo apt install -y libxcb-xinerama0
# micromamba info
# micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
# micromamba install -c conda-forge pyinstaller=4.10
# micromamba uninstall --force -y importlib_resources
# pip install path
# pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
# pip install git+https://github.com/meadiode/cq_gears.git@main
# pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
# pip install git+https://github.com/gumyr/build123d.git#egg=build123d
# - name: Run build
# shell: bash --login {0}
# run: |
# micromamba info
# pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
# cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
# rm /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/CQ-editor/libstdc++.so.6
# - uses: actions/upload-tartifact@main
# with:
# name: CQ-editor-Linux-x86_64
# path: dist
# build-macos:
# runs-on: macos-latest
# steps:
# - uses: actions/checkout@v2
# - uses: mamba-org/provision-with-micromamba@main
# with:
# #miniconda-version: "latest"
# #auto-update-conda: true
# environment-name: test
# environment-file: environment.yml
# extra-specs:
# python=3.9
# - name: Mamba install CadQuery and pyinstaller
# shell: bash --login {0}
# run: |
# micromamba info
# micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
# micromamba install -c conda-forge pyinstaller
# micromamba uninstall --force -y importlib_resources
# pip install path
# pip uninstall -y PyQt5
# pip install PyQt5==5.15.7
# pip install PyQtWebEngine==5.15.6
# pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
# pip install git+https://github.com/meadiode/cq_gears.git@main
# pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
# pip install git+https://github.com/gumyr/build123d.git#egg=build123d
# - name: Run build
# shell: bash --login {0}
# run: |
# micromamba info
# pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
# cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
# - uses: actions/upload-tartifact@main
# with:
# name: CQ-editor-MacOS
# path: dist
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
with:
#miniconda-version: "latest"
#auto-update-conda: true
environment-name: test
environment-file: environment.yml
extra-specs:
python=3.9
- name: Mamba install CadQuery and pyinstaller
shell: powershell
run: |
micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
micromamba install -c conda-forge pyinstaller=4.10
pip install path
pip install pipwin
pipwin install numpy
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d.git#egg=build123d
- name: Run build
shell: powershell
run: |
micromamba info
pyinstaller --debug all pyinstaller.spec ${{ github.event.inputs.type }}
Copy-Item D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\pyinstaller\CQ-editor.cmd D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\dist\
- uses: alehechka/upload-tartifact@v1
with:
name: CQ-editor-Windows
path: dist
#- uses: alehechka/download-tartifact@v1
# with:
# name: CQ-editor-Windows
# path: dist

View File

@@ -1,121 +0,0 @@
name: build-micromamba
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
type:
description: 'Whether to build a single file (onefile) or directory (dir) dist'
required: true
default: 'dir'
jobs:
build-linux:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
with:
#miniconda-version: "latest"
#auto-update-conda: true
environment-name: test
environment-file: environment.yml
extra-specs:
python=3.9
- name: Mamba install CadQuery and pyinstaller
shell: bash --login {0}
run: |
sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
sudo apt install -y libxkbcommon0
sudo apt install -y libxkbcommon-x11-0
sudo apt install -y libxcb-xinerama0
micromamba info
micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
micromamba install -c conda-forge pyinstaller=4.10
pip install path
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d.git#egg=build123d
pip install git+https://github.com/JustinSDK/cqMore
- name: Run build
shell: bash --login {0}
run: |
micromamba info
pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- uses: actions/upload-artifact@v2
with:
name: CQ-editor-Linux-x86_64
path: dist
build-macos:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
with:
#miniconda-version: "latest"
#auto-update-conda: true
environment-name: test
environment-file: environment.yml
extra-specs:
python=3.9
- name: Mamba install CadQuery and pyinstaller
shell: bash --login {0}
run: |
micromamba info
micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
micromamba install -c conda-forge pyinstaller
pip install path
pip uninstall -y PyQt5
pip install PyQt5==5.15.7
pip install PyQtWebEngine==5.15.6
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d.git#egg=build123d
pip install git+https://github.com/JustinSDK/cqMore
- name: Run build
shell: bash --login {0}
run: |
micromamba info
pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
- uses: actions/upload-artifact@v2
with:
name: CQ-editor-MacOS
path: dist
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
with:
#miniconda-version: "latest"
#auto-update-conda: true
environment-name: test
environment-file: environment.yml
extra-specs:
python=3.9
- name: Mamba install CadQuery and pyinstaller
shell: powershell
run: |
micromamba install -c cadquery -c conda-forge cq-editor=master cadquery=master debugpy ipython=8.4.0 jedi=0.17.2 python=3.9
micromamba install -c conda-forge pyinstaller=4.10
pip install path
pip install pipwin
pipwin install numpy
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d.git#egg=build123d
pip install git+https://github.com/JustinSDK/cqMore
- name: Run build
shell: powershell
run: |
micromamba info
pyinstaller --debug all pyinstaller.spec ${{ github.event.inputs.type }}
Copy-Item D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\pyinstaller\CQ-editor.cmd D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\dist\
- uses: actions/upload-artifact@v2
with:
name: CQ-editor-Windows
path: dist

View File

@@ -1,120 +0,0 @@
name: build
on:
schedule:
- cron: '0 0 * * 1'
workflow_dispatch:
inputs:
type:
description: 'Whether to build a single file (onefile) or directory (dir) dist'
required: true
default: 'dir'
jobs:
# build-linux:
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v2
# - uses: conda-incubator/setup-miniconda@v2
# with:
# mamba-version: "*"
# channels: conda-forge,defaults
# channel-priority: true
# # auto-update-conda: true
# python-version: 3.9
# activate-environment: test
# - name: Install CadQuery, CQ-editor and pyinstaller
# shell: bash --login {0}
# run: |
# sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
# sudo apt install -y libxkbcommon0
# sudo apt install -y libxkbcommon-x11-0
# sudo apt install -y libxcb-xinerama0
# conda info
# conda install -c cadquery -c conda-forge cq-editor=master cadquery=master python=3.9
# conda install -c conda-forge pyinstaller=4.10
# conda uninstall --force -y importlib_resources
# pip install path
# pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
# pip install git+https://github.com/meadiode/cq_gears.git@main
# pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
# pip install git+https://github.com/gumyr/build123d.git#egg=build123d
# - name: Run build
# shell: bash --login {0}
# run: |
# conda info
# pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
# cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
# rm /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/CQ-editor/libstdc++.so.6
# - uses: actions/upload-artifact@v2
# with:
# name: CQ-editor-Linux-x86_64
# path: dist
# build-macos:
# runs-on: macos-latest
# steps:
# - uses: actions/checkout@v2
# - uses: conda-incubator/setup-miniconda@v2
# with:
# mamba-version: "*"
# channels: conda-forge,defaults
# # auto-update-conda: true
# python-version: 3.9
# activate-environment: test
# - name: Install CadQuery, CQ-editor and pyinstaller=4.10
# shell: bash --login {0}
# run: |
# conda info
# conda install -c cadquery -c conda-forge cq-editor=master cadquery=master python=3.9
# conda install -c conda-forge pyinstaller
# conda uninstall --force -y importlib_resources
# pip install path
# pip uninstall -y PyQt5
# pip install PyQt5==5.15.7
# pip install PyQtWebEngine==5.15.6
# pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
# pip install git+https://github.com/meadiode/cq_gears.git@main
# pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
# pip install git+https://github.com/gumyr/build123d.git#egg=build123d
# - name: Run build
# shell: bash --login {0}
# run: |
# conda info
# pyinstaller pyinstaller.spec ${{ github.event.inputs.type }}
# cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
# - uses: actions/upload-artifact@v2
# with:
# name: CQ-editor-MacOS
# path: dist
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: conda-incubator/setup-miniconda@v2
with:
miniconda-version: "latest"
auto-update-conda: true
python-version: 3.9
activate-environment: test
- name: Install CadQuery and pyinstaller
shell: powershell
run: |
conda install -c cadquery -c conda-forge cq-editor=master cadquery=master ipython=7.20 python=3.9
conda install -c conda-forge pyinstaller=4.10
pip install pipwin
pipwin install numpy
pip install path
pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
pip install git+https://github.com/meadiode/cq_gears.git@main
pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
pip install git+https://github.com/gumyr/build123d.git#egg=build123d
- name: Run build
shell: powershell
run: |
conda info
pyinstaller --debug all pyinstaller.spec ${{ github.event.inputs.type }}
Copy-Item C:\Miniconda3\Library\bin\libssl-1_1-x64.dll D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\dist\CQ-editor\
Copy-Item C:\Miniconda3\Library\bin\libcrypto-1_1-x64.dll D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\dist\CQ-editor\
Copy-Item D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\pyinstaller\CQ-editor.cmd D:\a\jmwright-CQ-Editor\jmwright-CQ-Editor\dist\
- uses: actions/upload-artifact@v2
with:
name: CQ-editor-Windows
path: dist

View File

@@ -1,10 +1,13 @@
# CadQuery editor
This is a fork of jmwright's fork of CadQuery/CQ-editor. This fork includes changes that enable dark mode for CQ-editor (see screenshot below). Under the GitHub Actions menu this fork also contains static builds of CQ-editor for Linux/MacOS/Windows that include the cq_gears, cq_cache, cq_warehouse, and build123d libraries. Note you need to change color preferences to enable dark mode for all panes (see Edit -> Preferences).
This is a fork of [jmwright's fork](https://github.com/jmwright/CQ-editor) of [CadQuery/CQ-editor](https://github.com/CadQuery/CQ-editor). This fork includes changes that enable dark mode for CQ-editor (see screenshot below). Under the GitHub Actions menu this fork also contains static builds of CQ-editor for Linux/MacOS/Windows that include the [cq_gears](https://github.com/meadiode/cq_gears), [cq_cache](https://github.com/CadQuery/cadquery-plugins/tree/main/plugins/cq_cache), [cq_more](https://github.com/JustinSDK/cqMore), [cq_warehouse](https://github.com/gumyr/cq_warehouse), [bd_warehouse](https://github.com/gumyr/bd_warehouse), and [build123d](https://github.com/gumyr/build123d) libraries. Note you need to change color preferences to enable dark mode for all panes (see Edit -> Preferences).
This fork also contains additional changes to the `show_object` function in CQ-editor that make it easier to display and export build123d objects and object lists.
Running into issues? Please click here to join the [***CadQuery, CQ-Editor, and build123d Discord***](https://discord.com/invite/Bj9AQPsCfx)
![image](https://user-images.githubusercontent.com/16868537/191054760-a2cac297-3488-48d4-b9f6-52747dffcce3.png)
[![Build status](https://ci.appveyor.com/api/projects/status/g98rs7la393mgy91/branch/master?svg=true)](https://ci.appveyor.com/project/adam-urbanczyk/cq-editor/branch/master)
[![codecov](https://codecov.io/gh/CadQuery/CQ-editor/branch/master/graph/badge.svg)](https://codecov.io/gh/CadQuery/CQ-editor)
[![Build Status](https://dev.azure.com/cadquery/CQ-editor/_apis/build/status/CadQuery.CQ-editor?branchName=master)](https://dev.azure.com/cadquery/CQ-editor/_build/latest?definitionId=3&branchName=master)
@@ -32,44 +35,35 @@ CadQuery GUI editor based on PyQT supports Linux, Windows and Mac.
### Release Packages
Stable release builds which do not require Anaconda are attached to the [latest release](https://github.com/CadQuery/CQ-editor/releases). Download the zip file for your operating system, extract it, and run the CQ-editor script for your OS (CQ-editor.cmd for Windows, CQ-editor.sh for Linux and MacOS). On Windows you should be able to simply double-click on CQ-editor.cmd. On Linux and MacOS you may need to make the script executable with `chmod +x CQ-editor.sh` and run the script from the command line. The script contains an environment variable export that may be required to get CQ-editor to launch correctly on MacOS Big Sur, so it is better to use the script than to launch CQ-editor directly.
Stable release builds which do not require Anaconda are attached to the [latest release](https://github.com/jdegenstein/jmwright-CQ-editor/releases). Download the zip file for your operating system, extract it, and run the CQ-editor script for your OS (CQ-editor.cmd for Windows, CQ-editor.sh for Linux and MacOS). On Windows you should be able to simply double-click on CQ-editor.cmd. On Linux and MacOS you may need to make the script executable with `chmod +x CQ-editor.sh` and run the script from the command line. On later MacOS versions you may also need `xattr -r -d com.apple.quarantine path/to/CQ-editor-MacOS`. The script contains an environment variable export that may be required to get CQ-editor to launch correctly on MacOS Big Sur, so it is better to use the script than to launch CQ-editor directly.
### Development Packages
Development builds are also available, but can be unstable and should be used at your own risk. Click on the newest build with a green checkmark [here](https://github.com/jmwright/CQ-editor/actions?query=workflow%3Abuild), wait for the _Artifacts_ section at the bottom of the page to load, and then click on the appropriate download for your operating system. Extract the archive file and run the shell (Linux/MacOS) or cmd (Windows) script in the root CQ-editor directory. The CQ-editor window should launch.
Development builds are also available, but can be unstable and should be used at your own risk. Click on the newest build with a green checkmark [here](https://github.com/jdegenstein/jmwright-CQ-editor/actions), wait for the _Artifacts_ section at the bottom of the page to load, and then click on the appropriate download for your operating system. Extract the archive file and run the shell (Linux/MacOS) or cmd (Windows) script in the root CQ-editor directory. The CQ-editor window should launch.
## Installation (Anaconda)
## Installation (pip)
Use conda to install:
Additional packages for Linux (known as needed on Ubuntu 22.04):
```
conda install -c cadquery -c conda-forge cq-editor=master
sudo apt install qtbase5-dev qt5-qmake
```
and then simply type `cq-editor` to run it. This installs the latest version built directly from the HEAD of this repository.
Alternatively clone this git repository and set up the following conda environment:
All platforms (Windows/Mac/Linux):
```
conda env create -f cqgui_env.yml -n cqgui
conda activate cqgui
python run.py
```
On some linux distributions (e.g. `Ubuntu 18.04`) it might be necessary to install additonal packages:
```
sudo apt install libglu1-mesa libgl1-mesa-dri mesa-common-dev libglu1-mesa-dev
```
On Fedora 29 the packages can be installed as follows:
```
dnf install -y mesa-libGLU mesa-libGL mesa-libGLU-devel
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre "cadquery>=2.2"
pip install git+https://github.com/gumyr/build123d
```
## Usage
### Showing Objects
By default, CQ-editor will display a 3D representation of all `Workplane` objects in a script with a default color and alpha (transparency). To have more control over what is shown, and what the color and alpha settings are, the `show_object` method can be used. `show_object` tells CQ-editor to explicity display an object, and accepts the `options` parameter. The `options` parameter is a dictionary of rendering options named `alpha` and `color`. `alpha` is scaled between 0.0 and 1.0, with 0.0 being completely opaque and 1.0 being completely transparent. The color is set using R (red), G (green) and B (blue) values, and each one is scaled from 0 to 255. Either option or both can be omitted.
By default, CQ-editor will display a 3D representation of all `Workplane` objects in a script with a default color and alpha (transparency). To have more control over what is shown, and what the color and alpha settings are, the `show_object` method can be used. `show_object` tells CQ-editor to explicity display an object, and accepts the `options` parameter. The `options` parameter is a dictionary of rendering options named `alpha` and `color`. `alpha` is scaled between 0.0 and 1.0, with 0.0 being completely opaque and 1.0 being completely transparent. The color is set using R (red), G (green) and B (blue) values, and each one is scaled from 0 to 255. Either option or both can be omitted. The `name` parameter can assign a custom name which will appear in the objects pane of CQ-editor.
```python
show_object(result, options={"alpha":0.5, "color": (64, 164, 223)})
show_object(result, name="somename", options={"alpha":0.5, "color": (64, 164, 223)})
# or using rand_color:
show_object(result, name="somename", options=rand_color(alpha=.5))
```
Note that `show_object` works for `Shape` and `TopoDS_Shape` objects too. In order to display objects from the embedded Python console use `show`.

View File

@@ -1,162 +1,233 @@
import cadquery as cq
from cadquery.occ_impl.assembly import toCAF
from typing import List, Union
from imp import reload
from types import SimpleNamespace
from OCP.XCAFPrs import XCAFPrs_AISObject
from OCP.TopoDS import TopoDS_Shape
from OCP.AIS import AIS_InteractiveObject, AIS_Shape
from OCP.Quantity import \
Quantity_TOC_RGB as TOC_RGB, Quantity_Color
from PyQt5.QtGui import QColor
def find_cq_objects(results : dict):
return {k:SimpleNamespace(shape=v,options={}) for k,v in results.items() if isinstance(v,cq.Workplane)}
def to_compound(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch]):
vals = []
if isinstance(obj,cq.Workplane):
vals.extend(obj.vals())
elif isinstance(obj,cq.Shape):
vals.append(obj)
elif isinstance(obj,list) and isinstance(obj[0],cq.Workplane):
for o in obj: vals.extend(o.vals())
elif isinstance(obj,list) and isinstance(obj[0],cq.Shape):
vals.extend(obj)
elif isinstance(obj, TopoDS_Shape):
vals.append(cq.Shape.cast(obj))
elif isinstance(obj,list) and isinstance(obj[0],TopoDS_Shape):
vals.extend(cq.Shape.cast(o) for o in obj)
elif hasattr(obj, "wrapped") and isinstance(obj.wrapped, TopoDS_Shape):
vals.append(cq.Shape.cast(obj.wrapped))
elif hasattr(obj, "_obj") and hasattr(obj._obj, "wrapped") and isinstance(obj._obj.wrapped, TopoDS_Shape):
vals.append(cq.Shape.cast(obj._obj.wrapped))
elif isinstance(obj, cq.Sketch):
if obj._faces:
vals.append(obj._faces)
else:
vals.extend(obj._edges)
else:
raise ValueError(f'Invalid type {type(obj)}')
return cq.Compound.makeCompound(vals)
def to_workplane(obj : cq.Shape):
rv = cq.Workplane('XY')
rv.objects = [obj,]
return rv
def make_AIS(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Assembly, AIS_InteractiveObject],
options={}):
shape = None
if isinstance(obj, cq.Assembly):
label, shape = toCAF(obj)
ais = XCAFPrs_AISObject(label)
elif isinstance(obj, AIS_InteractiveObject):
ais = obj
else:
shape = to_compound(obj)
ais = AIS_Shape(shape.wrapped)
if 'alpha' in options:
ais.SetTransparency(options['alpha'])
if 'color' in options:
ais.SetColor(to_occ_color(options['color']))
if 'rgba' in options:
r,g,b,a = options['rgba']
ais.SetColor(to_occ_color((r,g,b)))
ais.SetTransparency(a)
return ais,shape
def export(obj : Union[cq.Workplane, List[cq.Workplane]], type : str,
file, precision=1e-1):
comp = to_compound(obj)
if type == 'stl':
comp.exportStl(file, tolerance=precision)
elif type == 'step':
comp.exportStep(file)
elif type == 'brep':
comp.exportBrep(file)
def to_occ_color(color) -> Quantity_Color:
if not isinstance(color, QColor):
if isinstance(color, tuple):
if isinstance(color[0], int):
color = QColor(*color)
elif isinstance(color[0], float):
color = QColor.fromRgbF(*color)
else:
raise ValueError('Unknown color format')
else:
color = QColor(color)
return Quantity_Color(color.redF(),
color.greenF(),
color.blueF(),
TOC_RGB)
def get_occ_color(obj : Union[AIS_InteractiveObject, Quantity_Color]) -> QColor:
if isinstance(obj, AIS_InteractiveObject):
color = Quantity_Color()
obj.Color(color)
else:
color = obj
return QColor.fromRgbF(color.Red(), color.Green(), color.Blue())
def set_color(ais : AIS_Shape, color : Quantity_Color) -> AIS_Shape:
drawer = ais.Attributes()
drawer.ShadingAspect().SetColor(color)
return ais
def reload_cq():
# NB: order of reloads is important
reload(cq.types)
reload(cq.occ_impl.geom)
reload(cq.occ_impl.shapes)
reload(cq.occ_impl.importers.dxf)
reload(cq.occ_impl.importers)
reload(cq.occ_impl.solver)
reload(cq.occ_impl.assembly)
reload(cq.occ_impl.sketch_solver)
reload(cq.hull)
reload(cq.selectors)
reload(cq.sketch)
reload(cq.occ_impl.exporters.svg)
reload(cq.cq)
reload(cq.occ_impl.exporters.utils)
reload(cq.occ_impl.exporters.dxf)
reload(cq.occ_impl.exporters.amf)
reload(cq.occ_impl.exporters.json)
#reload(cq.occ_impl.exporters.assembly)
reload(cq.occ_impl.exporters)
reload(cq.assembly)
reload(cq)
def is_obj_empty(obj : Union[cq.Workplane,cq.Shape]) -> bool:
rv = False
if isinstance(obj, cq.Workplane):
rv = True if isinstance(obj.val(), cq.Vector) else False
return rv
import cadquery as cq
from cadquery.occ_impl.assembly import toCAF
from typing import List, Union
from imp import reload
from types import SimpleNamespace
from OCP.XCAFPrs import XCAFPrs_AISObject
from OCP.TopoDS import TopoDS_Shape
from OCP.AIS import AIS_InteractiveObject, AIS_Shape
from OCP.Quantity import (
Quantity_TOC_RGB as TOC_RGB,
Quantity_Color,
Quantity_NOC_GOLD as GOLD,
)
from OCP.Graphic3d import Graphic3d_NOM_JADE, Graphic3d_MaterialAspect
from PyQt5.QtGui import QColor
DEFAULT_FACE_COLOR = Quantity_Color(GOLD)
DEFAULT_MATERIAL = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE)
def find_cq_objects(results: dict):
return {
k: SimpleNamespace(shape=v, options={})
for k, v in results.items()
if isinstance(v, cq.Workplane)
}
def to_compound(
obj: Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch]
):
vals = []
if isinstance(obj, cq.Workplane):
vals.extend(obj.vals())
elif isinstance(obj, cq.Shape):
vals.append(obj)
elif isinstance(obj, list) and isinstance(obj[0], cq.Workplane):
for o in obj:
vals.extend(o.vals())
elif isinstance(obj, list) and isinstance(obj[0], cq.Shape):
vals.extend(obj)
elif isinstance(obj, TopoDS_Shape):
vals.append(cq.Shape.cast(obj))
elif isinstance(obj, list) and isinstance(obj[0], TopoDS_Shape):
vals.extend(cq.Shape.cast(o) for o in obj)
elif hasattr(obj, "wrapped") and isinstance(obj.wrapped, TopoDS_Shape):
vals.append(cq.Shape.cast(obj.wrapped))
elif (
isinstance(obj, list)
and hasattr(obj[0], "wrapped")
and isinstance(obj[0].wrapped, TopoDS_Shape)
):
vals.extend(o for o in obj)
elif (
hasattr(obj, "_obj")
and hasattr(obj._obj, "wrapped")
and isinstance(obj._obj.wrapped, TopoDS_Shape)
):
vals.append(cq.Shape.cast(obj._obj.wrapped))
elif (
isinstance(obj, list)
and hasattr(obj[0], "_obj")
and hasattr(obj[0]._obj, "wrapped")
and isinstance(obj[0]._obj.wrapped, TopoDS_Shape)
):
vals.append(o for o in obj)
elif isinstance(obj, cq.Sketch):
if obj._faces:
vals.append(obj._faces)
else:
vals.extend(obj._edges)
else:
raise ValueError(f"Invalid type {type(obj)}")
return cq.Compound.makeCompound(vals)
def to_workplane(obj: cq.Shape):
rv = cq.Workplane("XY")
rv.objects = [
obj,
]
return rv
def make_AIS(
obj: Union[
cq.Workplane,
List[cq.Workplane],
cq.Shape,
List[cq.Shape],
cq.Assembly,
AIS_InteractiveObject,
],
options={},
):
shape = None
if isinstance(obj, cq.Assembly):
label, shape = toCAF(obj)
ais = XCAFPrs_AISObject(label)
elif isinstance(obj, AIS_InteractiveObject):
ais = obj
else:
shape = to_compound(obj)
ais = AIS_Shape(shape.wrapped)
set_material(ais, DEFAULT_MATERIAL)
set_color(ais, DEFAULT_FACE_COLOR)
if "alpha" in options:
set_transparency(ais, options["alpha"])
if "color" in options:
set_color(ais, to_occ_color(options["color"]))
if "rgba" in options:
r, g, b, a = options["rgba"]
set_color(ais, to_occ_color((r, g, b)))
set_transparency(ais, a)
return ais, shape
def export(
obj: Union[cq.Workplane, List[cq.Workplane]], type: str, file, precision=1e-1
):
comp = to_compound(obj)
if type == "stl":
comp.exportStl(file, tolerance=precision)
elif type == "step":
comp.exportStep(file)
elif type == "brep":
comp.exportBrep(file)
def to_occ_color(color) -> Quantity_Color:
if not isinstance(color, QColor):
if isinstance(color, tuple):
if isinstance(color[0], int):
color = QColor(*color)
elif isinstance(color[0], float):
color = QColor.fromRgbF(*color)
else:
raise ValueError("Unknown color format")
else:
color = QColor(color)
return Quantity_Color(color.redF(), color.greenF(), color.blueF(), TOC_RGB)
def get_occ_color(obj: Union[AIS_InteractiveObject, Quantity_Color]) -> QColor:
if isinstance(obj, AIS_InteractiveObject):
color = Quantity_Color()
obj.Color(color)
else:
color = obj
return QColor.fromRgbF(color.Red(), color.Green(), color.Blue())
def set_color(ais: AIS_Shape, color: Quantity_Color) -> AIS_Shape:
drawer = ais.Attributes()
drawer.SetupOwnShadingAspect()
drawer.ShadingAspect().SetColor(color)
return ais
def set_material(ais: AIS_Shape, material: Graphic3d_MaterialAspect) -> AIS_Shape:
drawer = ais.Attributes()
drawer.SetupOwnShadingAspect()
drawer.ShadingAspect().SetMaterial(material)
return ais
def set_transparency(ais: AIS_Shape, alpha: float) -> AIS_Shape:
drawer = ais.Attributes()
drawer.SetupOwnShadingAspect()
drawer.ShadingAspect().SetTransparency(alpha)
return ais
def reload_cq():
# NB: order of reloads is important
reload(cq.types)
reload(cq.occ_impl.geom)
reload(cq.occ_impl.shapes)
reload(cq.occ_impl.importers.dxf)
reload(cq.occ_impl.importers)
reload(cq.occ_impl.solver)
reload(cq.occ_impl.assembly)
reload(cq.occ_impl.sketch_solver)
reload(cq.hull)
reload(cq.selectors)
reload(cq.sketch)
reload(cq.occ_impl.exporters.svg)
reload(cq.cq)
reload(cq.occ_impl.exporters.utils)
reload(cq.occ_impl.exporters.dxf)
reload(cq.occ_impl.exporters.amf)
reload(cq.occ_impl.exporters.json)
# reload(cq.occ_impl.exporters.assembly)
reload(cq.occ_impl.exporters)
reload(cq.assembly)
reload(cq)
def is_obj_empty(obj: Union[cq.Workplane, cq.Shape]) -> bool:
rv = False
if isinstance(obj, cq.Workplane):
rv = True if isinstance(obj.val(), cq.Vector) else False
return rv

View File

@@ -148,7 +148,7 @@ class Editor(CodeEditor,ComponentMixin):
if not self.confirm_discard(): return
curr_dir = Path(self.filename).abspath().dirname()
curr_dir = Path(self.filename).absolute().dirname()
fname = get_open_filename(self.EXTENSIONS, curr_dir)
if fname != '':
self.load_from_file(fname)

View File

@@ -299,6 +299,7 @@ class MainWindow(QMainWindow,MainMixin):
#CQ related items
console.push_vars({'show' : obj_tree.addObject,
'show_object' : obj_tree.addObject,
'rand_color' : self.components['debugger']._rand_color,
'cq' : cq,
'log' : Logger(self.name).info})

View File

@@ -1,365 +1,413 @@
import sys
from contextlib import ExitStack, contextmanager
from enum import Enum, auto
from types import SimpleNamespace, FrameType, ModuleType
from typing import List
import cadquery as cq
from PyQt5 import QtCore
from PyQt5.QtCore import Qt, QObject, pyqtSlot, pyqtSignal, QEventLoop, QAbstractTableModel
from PyQt5.QtWidgets import (QAction,
QTableView)
from logbook import info
from path import Path
from pyqtgraph.parametertree import Parameter
from spyder.utils.icon_manager import icon
from ..cq_utils import find_cq_objects, reload_cq
from ..mixins import ComponentMixin
DUMMY_FILE = '<string>'
class DbgState(Enum):
STEP = auto()
CONT = auto()
STEP_IN = auto()
RETURN = auto()
class DbgEevent(object):
LINE = 'line'
CALL = 'call'
RETURN = 'return'
class LocalsModel(QAbstractTableModel):
HEADER = ('Name','Type', 'Value')
def __init__(self,parent):
super(LocalsModel,self).__init__(parent)
self.frame = None
def update_frame(self,frame):
self.frame = \
[(k,type(v).__name__, str(v)) for k,v in frame.items() if not k.startswith('_')]
def rowCount(self,parent=QtCore.QModelIndex()):
if self.frame:
return len(self.frame)
else:
return 0
def columnCount(self,parent=QtCore.QModelIndex()):
return 3
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.HEADER[section]
return QAbstractTableModel.headerData(self, section, orientation, role)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return self.frame[i][j]
else:
return QtCore.QVariant()
class LocalsView(QTableView,ComponentMixin):
name = 'Variables'
def __init__(self,parent):
super(LocalsView,self).__init__(parent)
ComponentMixin.__init__(self)
header = self.horizontalHeader()
header.setStretchLastSection(True)
vheader = self.verticalHeader()
vheader.setVisible(False)
@pyqtSlot(dict)
def update_frame(self,frame):
model = LocalsModel(self)
model.update_frame(frame)
self.setModel(model)
class Debugger(QObject,ComponentMixin):
name = 'Debugger'
preferences = Parameter.create(name='Preferences',children=[
{'name': 'Reload CQ', 'type': 'bool', 'value': False},
{'name': 'Add script dir to path','type': 'bool', 'value': True},
{'name': 'Change working dir to script dir','type': 'bool', 'value': True},
{'name': 'Reload imported modules', 'type': 'bool', 'value': True},
])
sigRendered = pyqtSignal(dict)
sigLocals = pyqtSignal(dict)
sigTraceback = pyqtSignal(object,str)
sigFrameChanged = pyqtSignal(object)
sigLineChanged = pyqtSignal(int)
sigLocalsChanged = pyqtSignal(dict)
sigCQChanged = pyqtSignal(dict,bool)
sigDebugging = pyqtSignal(bool)
_frames : List[FrameType]
def __init__(self,parent):
super(Debugger,self).__init__(parent)
ComponentMixin.__init__(self)
self.inner_event_loop = QEventLoop(self)
self._actions = \
{'Run' : [QAction(icon('run'),
'Render',
self,
shortcut='F5',
triggered=self.render),
QAction(icon('debug'),
'Debug',
self,
checkable=True,
shortcut='ctrl+F5',
triggered=self.debug),
QAction(icon('arrow-step-over'),
'Step',
self,
shortcut='ctrl+F10',
triggered=lambda: self.debug_cmd(DbgState.STEP)),
QAction(icon('arrow-step-in'),
'Step in',
self,
shortcut='ctrl+F11',
triggered=lambda: self.debug_cmd(DbgState.STEP_IN)),
QAction(icon('arrow-continue'),
'Continue',
self,
shortcut='ctrl+F12',
triggered=lambda: self.debug_cmd(DbgState.CONT))
]}
self._frames = []
def get_current_script(self):
return self.parent().components['editor'].get_text_with_eol()
def get_breakpoints(self):
return self.parent().components['editor'].debugger.get_breakpoints()
def compile_code(self, cq_script):
try:
module = ModuleType('temp')
cq_code = compile(cq_script, '<string>', 'exec')
return cq_code, module
except Exception:
self.sigTraceback.emit(sys.exc_info(), cq_script)
return None, None
def _exec(self, code, locals_dict, globals_dict):
with ExitStack() as stack:
fname = self.parent().components['editor'].filename
p = Path(fname if fname else '').abspath().dirname()
if self.preferences['Add script dir to path'] and p.exists():
sys.path.insert(0,p)
stack.callback(sys.path.remove, p)
if self.preferences['Change working dir to script dir'] and p.exists():
stack.enter_context(p)
if self.preferences['Reload imported modules']:
stack.enter_context(module_manager())
exec(code, locals_dict, globals_dict)
def _inject_locals(self,module):
cq_objects = {}
def _show_object(obj,name=None, options={}):
if name:
cq_objects.update({name : SimpleNamespace(shape=obj,options=options)})
else:
cq_objects.update({str(id(obj)) : SimpleNamespace(shape=obj,options=options)})
def _debug(obj,name=None):
_show_object(obj,name,options=dict(color='red',alpha=0.2))
module.__dict__['show_object'] = _show_object
module.__dict__['debug'] = _debug
module.__dict__['log'] = lambda x: info(str(x))
module.__dict__['cq'] = cq
return cq_objects, set(module.__dict__)-{'cq'}
def _cleanup_locals(self,module,injected_names):
for name in injected_names: module.__dict__.pop(name)
@pyqtSlot(bool)
def render(self):
if self.preferences['Reload CQ']:
reload_cq()
cq_script = self.get_current_script()
cq_code,module = self.compile_code(cq_script)
if cq_code is None: return
cq_objects,injected_names = self._inject_locals(module)
try:
self._exec(cq_code, module.__dict__, module.__dict__)
#remove the special methods
self._cleanup_locals(module,injected_names)
#collect all CQ objects if no explicit show_object was called
if len(cq_objects) == 0:
cq_objects = find_cq_objects(module.__dict__)
self.sigRendered.emit(cq_objects)
self.sigTraceback.emit(None,
cq_script)
self.sigLocals.emit(module.__dict__)
except Exception:
exc_info = sys.exc_info()
sys.last_traceback = exc_info[-1]
self.sigTraceback.emit(exc_info, cq_script)
@property
def breakpoints(self):
return [ el[0] for el in self.get_breakpoints()]
@pyqtSlot(bool)
def debug(self,value):
previous_trace = sys.gettrace()
if value:
self.sigDebugging.emit(True)
self.state = DbgState.STEP
self.script = self.get_current_script()
code,module = self.compile_code(self.script)
if code is None:
self.sigDebugging.emit(False)
self._actions['Run'][1].setChecked(False)
return
cq_objects,injected_names = self._inject_locals(module)
#clear possible traceback
self.sigTraceback.emit(None,
self.script)
try:
sys.settrace(self.trace_callback)
exec(code,module.__dict__,module.__dict__)
except Exception:
exc_info = sys.exc_info()
sys.last_traceback = exc_info[-1]
self.sigTraceback.emit(exc_info,
self.script)
finally:
sys.settrace(previous_trace)
self.sigDebugging.emit(False)
self._actions['Run'][1].setChecked(False)
if len(cq_objects) == 0:
cq_objects = find_cq_objects(module.__dict__)
self.sigRendered.emit(cq_objects)
self._cleanup_locals(module,injected_names)
self.sigLocals.emit(module.__dict__)
self._frames = []
else:
sys.settrace(previous_trace)
self.inner_event_loop.exit(0)
def debug_cmd(self,state=DbgState.STEP):
self.state = state
self.inner_event_loop.exit(0)
def trace_callback(self,frame,event,arg):
filename = frame.f_code.co_filename
if filename==DUMMY_FILE:
if not self._frames:
self._frames.append(frame)
self.trace_local(frame,event,arg)
return self.trace_callback
else:
return None
def trace_local(self,frame,event,arg):
lineno = frame.f_lineno
if event in (DbgEevent.LINE,):
if (self.state in (DbgState.STEP, DbgState.STEP_IN) and frame is self._frames[-1]) \
or (lineno in self.breakpoints):
if lineno in self.breakpoints:
self._frames.append(frame)
self.sigLineChanged.emit(lineno)
self.sigFrameChanged.emit(frame)
self.sigLocalsChanged.emit(frame.f_locals)
self.sigCQChanged.emit(find_cq_objects(frame.f_locals),True)
self.inner_event_loop.exec_()
elif event in (DbgEevent.RETURN):
self.sigLocalsChanged.emit(frame.f_locals)
self._frames.pop()
elif event == DbgEevent.CALL:
func_filename = frame.f_code.co_filename
if self.state == DbgState.STEP_IN and func_filename == DUMMY_FILE:
self.sigLineChanged.emit(lineno)
self.sigFrameChanged.emit(frame)
self.state = DbgState.STEP
self._frames.append(frame)
@contextmanager
def module_manager():
""" unloads any modules loaded while the context manager is active """
loaded_modules = set(sys.modules.keys())
try:
yield
finally:
new_modules = set(sys.modules.keys()) - loaded_modules
for module_name in new_modules:
del sys.modules[module_name]
import sys
from contextlib import ExitStack, contextmanager
from enum import Enum, auto
from types import SimpleNamespace, FrameType, ModuleType
from typing import List
import cadquery as cq
from PyQt5 import QtCore
from PyQt5.QtCore import (
Qt,
QObject,
pyqtSlot,
pyqtSignal,
QEventLoop,
QAbstractTableModel,
)
from PyQt5.QtWidgets import QAction, QTableView
from logbook import info
from path import Path
from pyqtgraph.parametertree import Parameter
from spyder.utils.icon_manager import icon
from random import randrange as rrr, seed
from ..cq_utils import find_cq_objects, reload_cq
from ..mixins import ComponentMixin
DUMMY_FILE = "<string>"
class DbgState(Enum):
STEP = auto()
CONT = auto()
STEP_IN = auto()
RETURN = auto()
class DbgEevent(object):
LINE = "line"
CALL = "call"
RETURN = "return"
class LocalsModel(QAbstractTableModel):
HEADER = ("Name", "Type", "Value")
def __init__(self, parent):
super(LocalsModel, self).__init__(parent)
self.frame = None
def update_frame(self, frame):
self.frame = [
(k, type(v).__name__, str(v))
for k, v in frame.items()
if not k.startswith("_")
]
def rowCount(self, parent=QtCore.QModelIndex()):
if self.frame:
return len(self.frame)
else:
return 0
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def headerData(self, section, orientation, role=Qt.DisplayRole):
if role == Qt.DisplayRole and orientation == Qt.Horizontal:
return self.HEADER[section]
return QAbstractTableModel.headerData(self, section, orientation, role)
def data(self, index, role):
if role == QtCore.Qt.DisplayRole:
i = index.row()
j = index.column()
return self.frame[i][j]
else:
return QtCore.QVariant()
class LocalsView(QTableView, ComponentMixin):
name = "Variables"
def __init__(self, parent):
super(LocalsView, self).__init__(parent)
ComponentMixin.__init__(self)
header = self.horizontalHeader()
header.setStretchLastSection(True)
vheader = self.verticalHeader()
vheader.setVisible(False)
@pyqtSlot(dict)
def update_frame(self, frame):
model = LocalsModel(self)
model.update_frame(frame)
self.setModel(model)
class Debugger(QObject, ComponentMixin):
name = "Debugger"
preferences = Parameter.create(
name="Preferences",
children=[
{"name": "Reload CQ", "type": "bool", "value": False},
{"name": "Add script dir to path", "type": "bool", "value": True},
{"name": "Change working dir to script dir", "type": "bool", "value": True},
{"name": "Reload imported modules", "type": "bool", "value": True},
],
)
sigRendered = pyqtSignal(dict)
sigLocals = pyqtSignal(dict)
sigTraceback = pyqtSignal(object, str)
sigFrameChanged = pyqtSignal(object)
sigLineChanged = pyqtSignal(int)
sigLocalsChanged = pyqtSignal(dict)
sigCQChanged = pyqtSignal(dict, bool)
sigDebugging = pyqtSignal(bool)
_frames: List[FrameType]
def __init__(self, parent):
super(Debugger, self).__init__(parent)
ComponentMixin.__init__(self)
self.inner_event_loop = QEventLoop(self)
self._actions = {
"Run": [
QAction(
icon("run"), "Render", self, shortcut="F5", triggered=self.render
),
QAction(
icon("debug"),
"Debug",
self,
checkable=True,
shortcut="ctrl+F5",
triggered=self.debug,
),
QAction(
icon("arrow-step-over"),
"Step",
self,
shortcut="ctrl+F10",
triggered=lambda: self.debug_cmd(DbgState.STEP),
),
QAction(
icon("arrow-step-in"),
"Step in",
self,
shortcut="ctrl+F11",
triggered=lambda: self.debug_cmd(DbgState.STEP_IN),
),
QAction(
icon("arrow-continue"),
"Continue",
self,
shortcut="ctrl+F12",
triggered=lambda: self.debug_cmd(DbgState.CONT),
),
]
}
self._frames = []
def get_current_script(self):
return self.parent().components["editor"].get_text_with_eol()
def get_breakpoints(self):
return self.parent().components["editor"].debugger.get_breakpoints()
def compile_code(self, cq_script):
try:
module = ModuleType("temp")
cq_code = compile(cq_script, "<string>", "exec")
return cq_code, module
except Exception:
self.sigTraceback.emit(sys.exc_info(), cq_script)
return None, None
def _exec(self, code, locals_dict, globals_dict):
with ExitStack() as stack:
fname = self.parent().components["editor"].filename
p = Path(fname if fname else "").absolute().dirname()
if self.preferences["Add script dir to path"] and p.exists():
sys.path.insert(0, p)
stack.callback(sys.path.remove, p)
if self.preferences["Change working dir to script dir"] and p.exists():
stack.enter_context(p)
if self.preferences["Reload imported modules"]:
stack.enter_context(module_manager())
exec(code, locals_dict, globals_dict)
def _rand_color(self, alpha=0.0, cfloat=False):
# helper function to generate a random color dict
# for CQ-editor's show_object function
lower = 10
upper = 100 # not too high to keep color brightness in check
if cfloat: # for two output types depending on need
return (
(rrr(lower, upper) / 255),
(rrr(lower, upper) / 255),
(rrr(lower, upper) / 255),
alpha,
)
return {
"alpha": alpha,
"color": (
rrr(lower, upper),
rrr(lower, upper),
rrr(lower, upper),
),
}
def _inject_locals(self, module):
cq_objects = {}
def _show_object(
obj,
name=None,
options={}, # all following inputs are ignored by cq-editor
parent=1,
clear=True,
port=3939,
axes=False,
axes0=False,
grid=False,
ticks=10,
ortho=True,
transparent=False,
default_color=(232, 176, 36),
reset_camera=True,
zoom=1.0,
default_edgecolor=(128, 128, 128),
render_edges=True,
render_normals=False,
render_mates=False,
mate_scale=1.0,
deviation=0.1,
angular_tolerance=0.2,
edge_accuracy=5.0,
ambient_intensity=1.0,
direct_intensity=0.12,
):
if name:
cq_objects.update({name: SimpleNamespace(shape=obj, options=options)})
else:
cq_objects.update(
{str(id(obj)): SimpleNamespace(shape=obj, options=options)}
)
def _debug(obj, name=None):
_show_object(obj, name, options=dict(color="red", alpha=0.2))
module.__dict__["show_object"] = _show_object
module.__dict__["debug"] = _debug
module.__dict__["rand_color"] = self._rand_color
module.__dict__["log"] = lambda x: info(str(x))
module.__dict__["cq"] = cq
return cq_objects, set(module.__dict__) - {"cq"}
def _cleanup_locals(self, module, injected_names):
for name in injected_names:
module.__dict__.pop(name)
@pyqtSlot(bool)
def render(self):
seed(
59798267586177
) # reset the seed every time render is called (preserves colors run to run)
if self.preferences["Reload CQ"]:
reload_cq()
cq_script = self.get_current_script()
cq_code, module = self.compile_code(cq_script)
if cq_code is None:
return
cq_objects, injected_names = self._inject_locals(module)
try:
self._exec(cq_code, module.__dict__, module.__dict__)
# remove the special methods
self._cleanup_locals(module, injected_names)
# collect all CQ objects if no explicit show_object was called
if len(cq_objects) == 0:
cq_objects = find_cq_objects(module.__dict__)
self.sigRendered.emit(cq_objects)
self.sigTraceback.emit(None, cq_script)
self.sigLocals.emit(module.__dict__)
except Exception:
exc_info = sys.exc_info()
sys.last_traceback = exc_info[-1]
self.sigTraceback.emit(exc_info, cq_script)
@property
def breakpoints(self):
return [el[0] for el in self.get_breakpoints()]
@pyqtSlot(bool)
def debug(self, value):
previous_trace = sys.gettrace()
if value:
self.sigDebugging.emit(True)
self.state = DbgState.STEP
self.script = self.get_current_script()
code, module = self.compile_code(self.script)
if code is None:
self.sigDebugging.emit(False)
self._actions["Run"][1].setChecked(False)
return
cq_objects, injected_names = self._inject_locals(module)
# clear possible traceback
self.sigTraceback.emit(None, self.script)
try:
sys.settrace(self.trace_callback)
exec(code, module.__dict__, module.__dict__)
except Exception:
exc_info = sys.exc_info()
sys.last_traceback = exc_info[-1]
self.sigTraceback.emit(exc_info, self.script)
finally:
sys.settrace(previous_trace)
self.sigDebugging.emit(False)
self._actions["Run"][1].setChecked(False)
if len(cq_objects) == 0:
cq_objects = find_cq_objects(module.__dict__)
self.sigRendered.emit(cq_objects)
self._cleanup_locals(module, injected_names)
self.sigLocals.emit(module.__dict__)
self._frames = []
else:
sys.settrace(previous_trace)
self.inner_event_loop.exit(0)
def debug_cmd(self, state=DbgState.STEP):
self.state = state
self.inner_event_loop.exit(0)
def trace_callback(self, frame, event, arg):
filename = frame.f_code.co_filename
if filename == DUMMY_FILE:
if not self._frames:
self._frames.append(frame)
self.trace_local(frame, event, arg)
return self.trace_callback
else:
return None
def trace_local(self, frame, event, arg):
lineno = frame.f_lineno
if event in (DbgEevent.LINE,):
if (
self.state in (DbgState.STEP, DbgState.STEP_IN)
and frame is self._frames[-1]
) or (lineno in self.breakpoints):
if lineno in self.breakpoints:
self._frames.append(frame)
self.sigLineChanged.emit(lineno)
self.sigFrameChanged.emit(frame)
self.sigLocalsChanged.emit(frame.f_locals)
self.sigCQChanged.emit(find_cq_objects(frame.f_locals), True)
self.inner_event_loop.exec_()
elif event in (DbgEevent.RETURN):
self.sigLocalsChanged.emit(frame.f_locals)
self._frames.pop()
elif event == DbgEevent.CALL:
func_filename = frame.f_code.co_filename
if self.state == DbgState.STEP_IN and func_filename == DUMMY_FILE:
self.sigLineChanged.emit(lineno)
self.sigFrameChanged.emit(frame)
self.state = DbgState.STEP
self._frames.append(frame)
@contextmanager
def module_manager():
"""unloads any modules loaded while the context manager is active"""
loaded_modules = set(sys.modules.keys())
try:
yield
finally:
new_modules = set(sys.modules.keys()) - loaded_modules
for module_name in new_modules:
del sys.modules[module_name]

View File

@@ -147,7 +147,7 @@ class Editor(CodeEditor,ComponentMixin):
if not self.confirm_discard(): return
curr_dir = Path(self.filename).abspath().dirname()
curr_dir = Path(self.filename).absolute().dirname()
fname = get_open_filename(self.EXTENSIONS, curr_dir)
if fname != '':
self.load_from_file(fname)

View File

@@ -1,395 +1,424 @@
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAction, QMenu, QWidget, QAbstractItemView
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from pyqtgraph.parametertree import Parameter, ParameterTree
from OCP.AIS import AIS_Line
from OCP.Geom import Geom_Line
from OCP.gp import gp_Dir, gp_Pnt, gp_Ax1
from ..mixins import ComponentMixin
from ..icons import icon
from ..cq_utils import make_AIS, export, to_occ_color, is_obj_empty, get_occ_color, set_color
from .viewer import DEFAULT_FACE_COLOR
from ..utils import splitter, layout, get_save_filename
class TopTreeItem(QTreeWidgetItem):
def __init__(self,*args,**kwargs):
super(TopTreeItem,self).__init__(*args,**kwargs)
class ObjectTreeItem(QTreeWidgetItem):
props = [{'name': 'Name', 'type': 'str', 'value': ''},
{'name': 'Color', 'type': 'color', 'value': "#f4a824"},
{'name': 'Alpha', 'type': 'float', 'value': 0, 'limits': (0,1), 'step': 1e-1},
{'name': 'Visible', 'type': 'bool','value': True}]
def __init__(self,
name,
ais=None,
shape=None,
shape_display=None,
sig=None,
alpha=0.,
color='#f4a824',
**kwargs):
super(ObjectTreeItem,self).__init__([name],**kwargs)
self.setFlags( self.flags() | Qt.ItemIsUserCheckable)
self.setCheckState(0,Qt.Checked)
self.ais = ais
self.shape = shape
self.shape_display = shape_display
self.sig = sig
self.properties = Parameter.create(name='Properties',
children=self.props)
self.properties['Name'] = name
self.properties['Alpha'] = ais.Transparency()
self.properties['Color'] = get_occ_color(ais) if ais and ais.HasColor() else get_occ_color(DEFAULT_FACE_COLOR)
self.properties.sigTreeStateChanged.connect(self.propertiesChanged)
def propertiesChanged(self, properties, changed):
changed_prop = changed[0][0]
self.setData(0,0,self.properties['Name'])
self.ais.SetTransparency(self.properties['Alpha'])
if changed_prop.name() == 'Color':
set_color(self.ais, to_occ_color(self.properties['Color']))
self.ais.Redisplay()
if self.properties['Visible']:
self.setCheckState(0,Qt.Checked)
else:
self.setCheckState(0,Qt.Unchecked)
if self.sig:
self.sig.emit()
class CQRootItem(TopTreeItem):
def __init__(self,*args,**kwargs):
super(CQRootItem,self).__init__(['CQ models'],*args,**kwargs)
class HelpersRootItem(TopTreeItem):
def __init__(self,*args,**kwargs):
super(HelpersRootItem,self).__init__(['Helpers'],*args,**kwargs)
class ObjectTree(QWidget,ComponentMixin):
name = 'Object Tree'
_stash = []
preferences = Parameter.create(name='Preferences',children=[
{'name': 'Preserve properties on reload', 'type': 'bool', 'value': False},
{'name': 'Clear all before each run', 'type': 'bool', 'value': True},
{'name': 'STL precision','type': 'float', 'value': .1}])
sigObjectsAdded = pyqtSignal([list],[list,bool])
sigObjectsRemoved = pyqtSignal(list)
sigCQObjectSelected = pyqtSignal(object)
sigAISObjectsSelected = pyqtSignal(list)
sigItemChanged = pyqtSignal(QTreeWidgetItem,int)
sigObjectPropertiesChanged = pyqtSignal()
def __init__(self,parent):
super(ObjectTree,self).__init__(parent)
self.tree = tree = QTreeWidget(self,
selectionMode=QAbstractItemView.ExtendedSelection)
self.properties_editor = ParameterTree(self)
tree.setHeaderHidden(True)
tree.setItemsExpandable(False)
tree.setRootIsDecorated(False)
tree.setContextMenuPolicy(Qt.ActionsContextMenu)
#forward itemChanged singal
tree.itemChanged.connect(\
lambda item,col: self.sigItemChanged.emit(item,col))
#handle visibility changes form tree
tree.itemChanged.connect(self.handleChecked)
self.CQ = CQRootItem()
self.Helpers = HelpersRootItem()
root = tree.invisibleRootItem()
root.addChild(self.CQ)
root.addChild(self.Helpers)
tree.expandToDepth(1)
self._export_STL_action = \
QAction('Export as STL',
self,
enabled=False,
triggered=lambda: \
self.export('stl',
self.preferences['STL precision']))
self._export_STEP_action = \
QAction('Export as STEP',
self,
enabled=False,
triggered=lambda: \
self.export('step'))
self._clear_current_action = QAction(icon('delete'),
'Clear current',
self,
enabled=False,
triggered=self.removeSelected)
self._toolbar_actions = \
[QAction(icon('delete-many'),'Clear all',self,triggered=self.removeObjects),
self._clear_current_action,]
self.prepareMenu()
tree.itemSelectionChanged.connect(self.handleSelection)
tree.customContextMenuRequested.connect(self.showMenu)
self.prepareLayout()
def prepareMenu(self):
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self._context_menu = QMenu(self)
self._context_menu.addActions(self._toolbar_actions)
self._context_menu.addActions((self._export_STL_action,
self._export_STEP_action))
def prepareLayout(self):
self._splitter = splitter((self.tree,self.properties_editor),
stretch_factors = (2,1),
orientation=Qt.Vertical)
layout(self,(self._splitter,),top_widget=self)
self._splitter.show()
def showMenu(self,position):
self._context_menu.exec_(self.tree.viewport().mapToGlobal(position))
def menuActions(self):
return {'Tools' : [self._export_STL_action,
self._export_STEP_action]}
def toolbarActions(self):
return self._toolbar_actions
def addLines(self):
origin = (0,0,0)
ais_list = []
for name,color,direction in zip(('X','Y','Z'),
('red','lawngreen','blue'),
((1,0,0),(0,1,0),(0,0,1))):
line_placement = Geom_Line(gp_Ax1(gp_Pnt(*origin),
gp_Dir(*direction)))
line = AIS_Line(line_placement)
line.SetColor(to_occ_color(color))
self.Helpers.addChild(ObjectTreeItem(name,
ais=line))
ais_list.append(line)
self.sigObjectsAdded.emit(ais_list)
def _current_properties(self):
current_params = {}
for i in range(self.CQ.childCount()):
child = self.CQ.child(i)
current_params[child.properties['Name']] = child.properties
return current_params
def _restore_properties(self,obj,properties):
for p in properties[obj.properties['Name']]:
obj.properties[p.name()] = p.value()
@pyqtSlot(dict,bool)
@pyqtSlot(dict)
def addObjects(self,objects,clean=False,root=None):
if root is None:
root = self.CQ
request_fit_view = True if root.childCount() == 0 else False
preserve_props = self.preferences['Preserve properties on reload']
if preserve_props:
current_props = self._current_properties()
if clean or self.preferences['Clear all before each run']:
self.removeObjects()
ais_list = []
#remove empty objects
objects_f = {k:v for k,v in objects.items() if not is_obj_empty(v.shape)}
for name,obj in objects_f.items():
ais,shape_display = make_AIS(obj.shape,obj.options)
child = ObjectTreeItem(name,
shape=obj.shape,
shape_display=shape_display,
ais=ais,
sig=self.sigObjectPropertiesChanged)
if preserve_props and name in current_props:
self._restore_properties(child,current_props)
if child.properties['Visible']:
ais_list.append(ais)
root.addChild(child)
if request_fit_view:
self.sigObjectsAdded[list,bool].emit(ais_list,True)
else:
self.sigObjectsAdded[list].emit(ais_list)
@pyqtSlot(object,str,object)
def addObject(self,obj,name='',options={}):
root = self.CQ
ais,shape_display = make_AIS(obj, options)
root.addChild(ObjectTreeItem(name,
shape=obj,
shape_display=shape_display,
ais=ais,
sig=self.sigObjectPropertiesChanged))
self.sigObjectsAdded.emit([ais])
@pyqtSlot(list)
@pyqtSlot()
def removeObjects(self,objects=None):
if objects:
removed_items_ais = [self.CQ.takeChild(i).ais for i in objects]
else:
removed_items_ais = [ch.ais for ch in self.CQ.takeChildren()]
self.sigObjectsRemoved.emit(removed_items_ais)
@pyqtSlot(bool)
def stashObjects(self,action : bool):
if action:
self._stash = self.CQ.takeChildren()
removed_items_ais = [ch.ais for ch in self._stash]
self.sigObjectsRemoved.emit(removed_items_ais)
else:
self.removeObjects()
self.CQ.addChildren(self._stash)
ais_list = [el.ais for el in self._stash]
self.sigObjectsAdded.emit(ais_list)
@pyqtSlot()
def removeSelected(self):
ixs = self.tree.selectedIndexes()
rows = [ix.row() for ix in ixs]
self.removeObjects(rows)
def export(self,export_type,precision=None):
items = self.tree.selectedItems()
# if CQ models is selected get all children
if [item for item in items if item is self.CQ]:
CQ = self.CQ
shapes = [CQ.child(i).shape for i in range(CQ.childCount())]
# otherwise collect all selected children of CQ
else:
shapes = [item.shape for item in items if item.parent() is self.CQ]
fname = get_save_filename(export_type)
if fname != '':
export(shapes,export_type,fname,precision)
@pyqtSlot()
def handleSelection(self):
items =self.tree.selectedItems()
if len(items) == 0:
self._export_STL_action.setEnabled(False)
self._export_STEP_action.setEnabled(False)
return
# emit list of all selected ais objects (might be empty)
ais_objects = [item.ais for item in items if item.parent() is self.CQ]
self.sigAISObjectsSelected.emit(ais_objects)
# handle context menu and emit last selected CQ object (if present)
item = items[-1]
if item.parent() is self.CQ:
self._export_STL_action.setEnabled(True)
self._export_STEP_action.setEnabled(True)
self._clear_current_action.setEnabled(True)
self.sigCQObjectSelected.emit(item.shape)
self.properties_editor.setParameters(item.properties,
showTop=False)
self.properties_editor.setEnabled(True)
elif item is self.CQ and item.childCount()>0:
self._export_STL_action.setEnabled(True)
self._export_STEP_action.setEnabled(True)
else:
self._export_STL_action.setEnabled(False)
self._export_STEP_action.setEnabled(False)
self._clear_current_action.setEnabled(False)
self.properties_editor.setEnabled(False)
self.properties_editor.clear()
@pyqtSlot(list)
def handleGraphicalSelection(self,shapes):
self.tree.clearSelection()
CQ = self.CQ
for i in range(CQ.childCount()):
item = CQ.child(i)
for shape in shapes:
if item.ais.Shape().IsEqual(shape):
item.setSelected(True)
@pyqtSlot(QTreeWidgetItem,int)
def handleChecked(self,item,col):
if type(item) is ObjectTreeItem:
if item.checkState(0):
item.properties['Visible'] = True
else:
item.properties['Visible'] = False
from PyQt5.QtWidgets import (
QTreeWidget,
QTreeWidgetItem,
QAction,
QMenu,
QWidget,
QAbstractItemView,
)
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from pyqtgraph.parametertree import Parameter, ParameterTree
from OCP.AIS import AIS_Line
from OCP.Geom import Geom_Line
from OCP.gp import gp_Dir, gp_Pnt, gp_Ax1
from ..mixins import ComponentMixin
from ..icons import icon
from ..cq_utils import (
make_AIS,
export,
to_occ_color,
is_obj_empty,
get_occ_color,
set_color,
)
from .viewer import DEFAULT_FACE_COLOR
from ..utils import splitter, layout, get_save_filename
class TopTreeItem(QTreeWidgetItem):
def __init__(self, *args, **kwargs):
super(TopTreeItem, self).__init__(*args, **kwargs)
class ObjectTreeItem(QTreeWidgetItem):
props = [
{"name": "Name", "type": "str", "value": ""},
{"name": "Color", "type": "color", "value": "#f4a824"},
{"name": "Alpha", "type": "float", "value": 0, "limits": (0, 1), "step": 1e-1},
{"name": "Visible", "type": "bool", "value": True},
]
def __init__(
self,
name,
ais=None,
shape=None,
shape_display=None,
sig=None,
alpha=0.0,
color="#f4a824",
**kwargs
):
super(ObjectTreeItem, self).__init__([name], **kwargs)
self.setFlags(self.flags() | Qt.ItemIsUserCheckable)
self.setCheckState(0, Qt.Checked)
self.ais = ais
self.shape = shape
self.shape_display = shape_display
self.sig = sig
self.properties = Parameter.create(name="Properties", children=self.props)
self.properties["Name"] = name
self.properties["Alpha"] = ais.Transparency()
self.properties["Color"] = (
get_occ_color(ais)
if ais and ais.HasColor()
else get_occ_color(DEFAULT_FACE_COLOR)
)
self.properties.sigTreeStateChanged.connect(self.propertiesChanged)
def propertiesChanged(self, properties, changed):
changed_prop = changed[0][0]
self.setData(0, 0, self.properties["Name"])
self.ais.SetTransparency(self.properties["Alpha"])
if changed_prop.name() == "Color":
set_color(self.ais, to_occ_color(self.properties["Color"]))
self.ais.Redisplay()
if self.properties["Visible"]:
self.setCheckState(0, Qt.Checked)
else:
self.setCheckState(0, Qt.Unchecked)
if self.sig:
self.sig.emit()
class CQRootItem(TopTreeItem):
def __init__(self, *args, **kwargs):
super(CQRootItem, self).__init__(["CQ models"], *args, **kwargs)
class HelpersRootItem(TopTreeItem):
def __init__(self, *args, **kwargs):
super(HelpersRootItem, self).__init__(["Helpers"], *args, **kwargs)
class ObjectTree(QWidget, ComponentMixin):
name = "Object Tree"
_stash = []
preferences = Parameter.create(
name="Preferences",
children=[
{"name": "Preserve properties on reload", "type": "bool", "value": False},
{"name": "Clear all before each run", "type": "bool", "value": True},
{"name": "STL precision", "type": "float", "value": 0.1},
],
)
sigObjectsAdded = pyqtSignal([list], [list, bool])
sigObjectsRemoved = pyqtSignal(list)
sigCQObjectSelected = pyqtSignal(object)
sigAISObjectsSelected = pyqtSignal(list)
sigItemChanged = pyqtSignal(QTreeWidgetItem, int)
sigObjectPropertiesChanged = pyqtSignal()
def __init__(self, parent):
super(ObjectTree, self).__init__(parent)
self.tree = tree = QTreeWidget(
self, selectionMode=QAbstractItemView.ExtendedSelection
)
self.properties_editor = ParameterTree(self)
tree.setHeaderHidden(True)
tree.setItemsExpandable(False)
tree.setRootIsDecorated(False)
tree.setContextMenuPolicy(Qt.ActionsContextMenu)
# forward itemChanged singal
tree.itemChanged.connect(lambda item, col: self.sigItemChanged.emit(item, col))
# handle visibility changes form tree
tree.itemChanged.connect(self.handleChecked)
self.CQ = CQRootItem()
self.Helpers = HelpersRootItem()
root = tree.invisibleRootItem()
root.addChild(self.CQ)
root.addChild(self.Helpers)
tree.expandToDepth(1)
self._export_STL_action = QAction(
"Export as STL",
self,
enabled=False,
triggered=lambda: self.export("stl", self.preferences["STL precision"]),
)
self._export_STEP_action = QAction(
"Export as STEP", self, enabled=False, triggered=lambda: self.export("step")
)
self._clear_current_action = QAction(
icon("delete"),
"Clear current",
self,
enabled=False,
triggered=self.removeSelected,
)
self._toolbar_actions = [
QAction(
icon("delete-many"), "Clear all", self, triggered=self.removeObjects
),
self._clear_current_action,
]
self.prepareMenu()
tree.itemSelectionChanged.connect(self.handleSelection)
tree.customContextMenuRequested.connect(self.showMenu)
self.prepareLayout()
def prepareMenu(self):
self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
self._context_menu = QMenu(self)
self._context_menu.addActions(self._toolbar_actions)
self._context_menu.addActions(
(self._export_STL_action, self._export_STEP_action)
)
def prepareLayout(self):
self._splitter = splitter(
(self.tree, self.properties_editor),
stretch_factors=(2, 1),
orientation=Qt.Vertical,
)
layout(self, (self._splitter,), top_widget=self)
self._splitter.show()
def showMenu(self, position):
self._context_menu.exec_(self.tree.viewport().mapToGlobal(position))
def menuActions(self):
return {"Tools": [self._export_STL_action, self._export_STEP_action]}
def toolbarActions(self):
return self._toolbar_actions
def addLines(self):
origin = (0, 0, 0)
ais_list = []
for name, color, direction in zip(
("X", "Y", "Z"),
((0.2, 0, 0), "lawngreen", "blue"),
((1, 0, 0), (0, 1, 0), (0, 0, 1)),
):
line_placement = Geom_Line(gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction)))
line = AIS_Line(line_placement)
line.SetColor(to_occ_color(color))
self.Helpers.addChild(ObjectTreeItem(name, ais=line))
ais_list.append(line)
self.sigObjectsAdded.emit(ais_list)
def _current_properties(self):
current_params = {}
for i in range(self.CQ.childCount()):
child = self.CQ.child(i)
current_params[child.properties["Name"]] = child.properties
return current_params
def _restore_properties(self, obj, properties):
for p in properties[obj.properties["Name"]]:
obj.properties[p.name()] = p.value()
@pyqtSlot(dict, bool)
@pyqtSlot(dict)
def addObjects(self, objects, clean=False, root=None):
if root is None:
root = self.CQ
request_fit_view = True if root.childCount() == 0 else False
preserve_props = self.preferences["Preserve properties on reload"]
if preserve_props:
current_props = self._current_properties()
if clean or self.preferences["Clear all before each run"]:
self.removeObjects()
ais_list = []
# remove empty objects
objects_f = {k: v for k, v in objects.items() if not is_obj_empty(v.shape)}
for name, obj in objects_f.items():
ais, shape_display = make_AIS(obj.shape, obj.options)
child = ObjectTreeItem(
name,
shape=obj.shape,
shape_display=shape_display,
ais=ais,
sig=self.sigObjectPropertiesChanged,
)
if preserve_props and name in current_props:
self._restore_properties(child, current_props)
if child.properties["Visible"]:
ais_list.append(ais)
root.addChild(child)
if request_fit_view:
self.sigObjectsAdded[list, bool].emit(ais_list, True)
else:
self.sigObjectsAdded[list].emit(ais_list)
@pyqtSlot(object, str, object)
def addObject(
self,
obj,
name="",
options={}, # all following inputs are ignored by cq-editor
parent=1,
clear=True,
port=3939,
axes=False,
axes0=False,
grid=False,
ticks=10,
ortho=True,
transparent=False,
default_color=(232, 176, 36),
reset_camera=True,
zoom=1.0,
default_edgecolor=(128, 128, 128),
render_edges=True,
render_normals=False,
render_mates=False,
mate_scale=1.0,
deviation=0.1,
angular_tolerance=0.2,
edge_accuracy=5.0,
ambient_intensity=1.0,
direct_intensity=0.12,
):
root = self.CQ
ais, shape_display = make_AIS(obj, options)
root.addChild(
ObjectTreeItem(
name,
shape=obj,
shape_display=shape_display,
ais=ais,
sig=self.sigObjectPropertiesChanged,
)
)
self.sigObjectsAdded.emit([ais])
@pyqtSlot(list)
@pyqtSlot()
def removeObjects(self, objects=None):
if objects:
removed_items_ais = [self.CQ.takeChild(i).ais for i in objects]
else:
removed_items_ais = [ch.ais for ch in self.CQ.takeChildren()]
self.sigObjectsRemoved.emit(removed_items_ais)
@pyqtSlot(bool)
def stashObjects(self, action: bool):
if action:
self._stash = self.CQ.takeChildren()
removed_items_ais = [ch.ais for ch in self._stash]
self.sigObjectsRemoved.emit(removed_items_ais)
else:
self.removeObjects()
self.CQ.addChildren(self._stash)
ais_list = [el.ais for el in self._stash]
self.sigObjectsAdded.emit(ais_list)
@pyqtSlot()
def removeSelected(self):
ixs = self.tree.selectedIndexes()
rows = [ix.row() for ix in ixs]
self.removeObjects(rows)
def export(self, export_type, precision=None):
items = self.tree.selectedItems()
# if CQ models is selected get all children
if [item for item in items if item is self.CQ]:
CQ = self.CQ
shapes = [CQ.child(i).shape for i in range(CQ.childCount())]
# otherwise collect all selected children of CQ
else:
shapes = [item.shape for item in items if item.parent() is self.CQ]
fname = get_save_filename(export_type)
if fname != "":
export(shapes, export_type, fname, precision)
@pyqtSlot()
def handleSelection(self):
items = self.tree.selectedItems()
if len(items) == 0:
self._export_STL_action.setEnabled(False)
self._export_STEP_action.setEnabled(False)
return
# emit list of all selected ais objects (might be empty)
ais_objects = [item.ais for item in items if item.parent() is self.CQ]
self.sigAISObjectsSelected.emit(ais_objects)
# handle context menu and emit last selected CQ object (if present)
item = items[-1]
if item.parent() is self.CQ:
self._export_STL_action.setEnabled(True)
self._export_STEP_action.setEnabled(True)
self._clear_current_action.setEnabled(True)
self.sigCQObjectSelected.emit(item.shape)
self.properties_editor.setParameters(item.properties, showTop=False)
self.properties_editor.setEnabled(True)
elif item is self.CQ and item.childCount() > 0:
self._export_STL_action.setEnabled(True)
self._export_STEP_action.setEnabled(True)
else:
self._export_STL_action.setEnabled(False)
self._export_STEP_action.setEnabled(False)
self._clear_current_action.setEnabled(False)
self.properties_editor.setEnabled(False)
self.properties_editor.clear()
@pyqtSlot(list)
def handleGraphicalSelection(self, shapes):
self.tree.clearSelection()
CQ = self.CQ
for i in range(CQ.childCount()):
item = CQ.child(i)
for shape in shapes:
if item.ais.Shape().IsEqual(shape):
item.setSelected(True)
@pyqtSlot(QTreeWidgetItem, int)
def handleChecked(self, item, col):
if type(item) is ObjectTreeItem:
if item.checkState(0):
item.properties["Visible"] = True
else:
item.properties["Visible"] = False

View File

@@ -10,7 +10,7 @@ from OCP.Aspect import Aspect_DisplayConnection, Aspect_TypeOfTriedronPosition
from OCP.OpenGl import OpenGl_GraphicDriver
from OCP.V3d import V3d_Viewer
from OCP.AIS import AIS_InteractiveContext, AIS_DisplayMode
from OCP.Quantity import Quantity_Color
from OCP.Quantity import Quantity_Color, Quantity_TOC_RGB as TOC_RGB
ZOOM_STEP = 0.9
@@ -54,6 +54,8 @@ class OCCTWidget(QWidget):
Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER,
Quantity_Color(), 0.1)
view.ZBufferTriedronSetup(Quantity_Color(*(0.2, 0.0, 0.0), TOC_RGB))
viewer = self.viewer
viewer.SetDefaultLights()
@@ -95,8 +97,8 @@ class OCCTWidget(QWidget):
self.old_pos.y() - y, theToStart=True)
elif event.buttons() == Qt.RightButton:
self.view.ZoomAtPoint(self.old_pos.x(), y,
x, self.old_pos.y())
self.view.Pan(x - self.old_pos.x(),
self.old_pos.y() - y, theToStart=True)
self.old_pos = pos

View File

@@ -7,21 +7,21 @@ from OCP.Graphic3d import Graphic3d_Camera, Graphic3d_StereoMode, Graphic3d_NOM_
Graphic3d_MaterialAspect
from OCP.AIS import AIS_Shaded,AIS_WireFrame, AIS_ColoredShape, AIS_Axis
from OCP.Aspect import Aspect_GDM_Lines, Aspect_GT_Rectangular
from OCP.Quantity import Quantity_NOC_BLACK as BLACK, Quantity_NOC_GOLD as GOLD,\
Quantity_TOC_RGB as TOC_RGB, Quantity_Color
from OCP.Quantity import Quantity_NOC_BLACK as BLACK, Quantity_TOC_RGB as TOC_RGB,\
Quantity_Color
from OCP.Geom import Geom_Axis1Placement
from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1
from ..utils import layout, get_save_filename
from ..mixins import ComponentMixin
from ..icons import icon
from ..cq_utils import to_occ_color, make_AIS
from ..cq_utils import to_occ_color, make_AIS, DEFAULT_FACE_COLOR
from .occt_widget import OCCTWidget
from pyqtgraph.parametertree import Parameter
import qtawesome as qta
DEFAULT_FACE_COLOR = Quantity_Color(GOLD)
DEFAULT_EDGE_COLOR = Quantity_Color(BLACK)
DEFAULT_EDGE_WIDTH = 2
@@ -252,13 +252,13 @@ class OCCViewer(QWidget,ComponentMixin):
def front_view(self):
v = self._get_view()
v.SetProj(0,1,0)
v.SetProj(0,-1,0)
v.SetTwist(0)
def back_view(self):
v = self._get_view()
v.SetProj(0,-1,0)
v.SetProj(0,1,0)
v.SetTwist(0)
def left_view(self):

View File

@@ -5,7 +5,7 @@ channels:
dependencies:
- pyqt=5
- pyqtgraph
- python=3.10
- python=3.11
- spyder=5
- path
- logbook

View File

@@ -3,4 +3,4 @@ channels:
- conda-forge
- defaults
dependencies:
- python=3.9
- python=3.11

View File

@@ -1,4 +1,4 @@
#!/bin/sh
export QT_MAC_WANTS_LAYER=1
chmod u+x ./CQ-editor/CQ-editor
./CQ-editor/CQ-editor
QT_QPA_PLATFORM=xcb PYOPENGL_PLATFORM=x11 ./CQ-editor/CQ-editor

View File

@@ -0,0 +1,9 @@
# hook-casadi.py
from PyInstaller.utils.hooks import collect_dynamic_libs
binaries = collect_dynamic_libs('casadi')
# Something about legacy import codepaths in casadi.casadi causes PyInstaller's analysis to pick up
# casadi._casadi as a top-level _casadi module, which is wrong.
hiddenimports = ['casadi._casadi']
excludedimports = ['_casadi']

View File

@@ -0,0 +1,4 @@
# hook-py_lib3mf.py
from PyInstaller.utils.hooks import collect_dynamic_libs
binaries = collect_dynamic_libs('py_lib3mf')

View File

@@ -9,27 +9,28 @@ block_cipher = None
spyder_data = Path(site.getsitepackages()[-1]) / 'spyder'
parso_grammar = (Path(site.getsitepackages()[-1]) / 'parso/python').glob('grammar*')
cqw_path = Path(site.getsitepackages()[-1]) / 'cq_warehouse'
bdw_path = Path(site.getsitepackages()[-1]) / 'bd_warehouse'
cq_path = Path(site.getsitepackages()[-1]) / 'cadquery'
if sys.platform == 'linux':
occt_dir = os.path.join(Path(sys.prefix), 'share', 'opencascade')
ocp_path = (os.path.join(HOMEPATH, 'OCP.cpython-39-x86_64-linux-gnu.so'), '.')
ocp_path = [(os.path.join(HOMEPATH, 'OCP.cpython-311-x86_64-linux-gnu.so'), '.')]
elif sys.platform == 'darwin':
occt_dir = os.path.join(Path(sys.prefix), 'share', 'opencascade')
ocp_path = (os.path.join(HOMEPATH, 'OCP.cpython-39-darwin.so'), '.')
ocp_path = [(os.path.join(HOMEPATH, 'OCP.cpython-311-darwin.so'), '.')]
elif sys.platform == 'win32':
occt_dir = os.path.join(Path(sys.prefix), 'Library', 'share', 'opencascade')
ocp_path = (os.path.join(HOMEPATH, 'OCP.cp39-win_amd64.pyd'), '.')
ocp_path = [(os.path.join(HOMEPATH, 'OCP.cp311-win_amd64.pyd'), '.')]
datas1, binaries1, hiddenimports1 = collect_all('debugpy')
hiddenimports2 = collect_submodules('xmlrpc')
a = Analysis(['run.py'],
pathex=['.'],
binaries=[ocp_path] + binaries1,
binaries=ocp_path + binaries1,
datas=[(spyder_data, 'spyder'),
(occt_dir, 'opencascade'),
(cqw_path, 'cq_warehouse'),
(bdw_path, 'bd_warehouse'),
(cq_path, 'cadquery')] +
[(p, 'parso/python') for p in parso_grammar] + datas1,
hiddenimports=['ipykernel.datapub', 'debugpy', 'vtkmodules', 'vtkmodules.all',
@@ -38,8 +39,10 @@ a = Analysis(['run.py'],
'pyqtgraph.imageview.ImageViewTemplate_pyqt5', 'xmlrpc',
'zmq.backend', 'cq_warehouse', 'cq_warehouse.bearing', 'cq_warehouse.chain',
'cq_warehouse.drafting', 'cq_warehouse.extensions', 'cq_warehouse.fastener',
'cq_warehouse.sprocket', 'cq_warehouse.thread', 'cq_gears', 'cq_cache', 'build123d', 'cqmore'] + hiddenimports1 + hiddenimports2,
hookspath=[],
'cq_warehouse.sprocket', 'cq_warehouse.thread', 'cq_gears', 'cq_cache',
'build123d', 'cqmore', 'bd_warehouse', 'bd_warehouse.pipe', 'bd_warehouse.flange',
'bd_warehouse.thread', 'bd_warehouse.gears'] + hiddenimports1 + hiddenimports2,
hookspath=['pyinstaller/extrahooks/'],
runtime_hooks=['pyinstaller/pyi_rth_occ.py',
'pyinstaller/pyi_rth_fontconfig.py'],
excludes=['_tkinter'],
@@ -50,13 +53,13 @@ a = Analysis(['run.py'],
# There is an issue that keeps the OpenSSL libraries from being copied to the output directory.
# This should work if nothing else, but does not with GitHub Actions
if sys.platform == 'win32':
from PyInstaller.depend.bindepend import getfullnameof
rel_data_path = ['PyQt5', 'Qt', 'bin']
a.datas += [
(getfullnameof('libssl-1_1-x64.dll'), os.path.join(*rel_data_path), 'DATA'),
(getfullnameof('libcrypto-1_1-x64.dll'), os.path.join(*rel_data_path), 'DATA'),
]
# if sys.platform == 'win32':
# from PyInstaller.depend.bindepend import getfullnameof
# rel_data_path = ['PyQt5', 'Qt', 'bin']
# a.datas += [
# (getfullnameof('libssl-1_1-x64.dll'), os.path.join(*rel_data_path), 'DATA'),
# (getfullnameof('libcrypto-1_1-x64.dll'), os.path.join(*rel_data_path), 'DATA'),
# ]
pyz = PYZ(a.pure, a.zipped_data,

View File

@@ -16,12 +16,25 @@ def get_version(rel_path):
else:
raise RuntimeError("Unable to find version string.")
setup(name='CQ-editor',
version=get_version('cq_editor/_version.py'),
packages=find_packages(),
entry_points={
'gui_scripts': [
'cq-editor = cq_editor.__main__:main',
'CQ-editor = cq_editor.__main__:main'
]}
)
setup(
name="CQ-editor",
version=get_version("cq_editor/_version.py"),
packages=find_packages(),
entry_points={
"gui_scripts": [
"cq-editor = cq_editor.__main__:main",
"CQ-editor = cq_editor.__main__:main",
]
},
python_requires=">=3.8,<3.12",
install_requires=[
"logbook>=1",
"ipython",
"path>=16",
"PyQt5>=5",
"requests>=2,<3",
"spyder>=5,<6",
"pyqtgraph",
"numpy >= 1.24.1, <2",
],
)

View File

@@ -1018,13 +1018,13 @@ def test_render_colors(main_clean):
# object 1 (defualt color)
assert not CQ.child(0).ais.HasColor()
# object 2
r,g,b,a = get_rgba(CQ.child(1).ais)
assert( a == 0.5 )
assert( r == 1.0 )
assert( g == 0.0 )
# object 3
r,g,b,a = get_rgba(CQ.child(2).ais)
assert( a == 0.5)
@@ -1058,7 +1058,7 @@ def test_render_colors_console(main_clean):
console = win.components['console']
console.execute_command(code_color)
CQ = obj_tree.CQ
# object 1 (defualt color)
@@ -1092,7 +1092,37 @@ def test_render_colors_console(main_clean):
# check if error occured
qtbot.wait(100)
assert('Unknown color format' in log.toPlainText().splitlines()[-1])
code_shading = \
'''
import cadquery as cq
res1 = cq.Workplane('XY').box(5, 7, 5)
res2 = cq.Workplane('XY').box(8, 5, 4)
show_object(res1)
show_object(res2,options={"alpha":0})
'''
def test_shading_aspect(main_clean):
qtbot, win = main_clean
obj_tree = win.components['object_tree']
editor = win.components['editor']
debugger = win.components['debugger']
editor.set_text(code_shading)
debugger._actions['Run'][0].triggered.emit()
CQ = obj_tree.CQ
# get material aspects
ma1 = CQ.child(0).ais.Attributes().ShadingAspect().Material()
ma2 = CQ.child(1).ais.Attributes().ShadingAspect().Material()
# verify that they are the same
assert ma1.Shininess() == ma2.Shininess()
def test_confirm_new(monkeypatch,editor):
qtbot, editor = editor
@@ -1433,4 +1463,3 @@ def test_modulefinder(tmp_path, main):
qtbot.wait(100)
assert("Cannot determine imported modules" in log.toPlainText().splitlines()[-1])