59 Commits

Author SHA1 Message Date
jdegenstein
960c9fe5ca more testing of pyside6 2023-10-11 12:11:37 -05:00
jdegenstein
8fca86e2c9 Update viewer.py PySide6 2023-10-10 14:00:54 -05:00
jdegenstein
73199b6ec8 Update traceback_viewer.py PySide6 2023-10-10 14:00:31 -05:00
jdegenstein
39e2f60b2e Update occt_widget.py PySide6 2023-10-10 14:00:14 -05:00
jdegenstein
16d9609bb8 Update object_tree.py PySide6 2023-10-10 13:59:57 -05:00
jdegenstein
f3f6c9eb6b Update log.py PySide6 2023-10-10 13:59:37 -05:00
jdegenstein
750ed4f4e8 Update editor.py PySide6 2023-10-10 13:59:21 -05:00
jdegenstein
127f7133d2 Update debugger.py PySide6 2023-10-10 13:59:02 -05:00
jdegenstein
5ed12949d3 Update cq_object_inspector.py PySide6 2023-10-10 13:58:42 -05:00
jdegenstein
8f559821e7 Update console.py PySide6 2023-10-10 13:58:20 -05:00
jdegenstein
4deb489cbd Update setup.py PySide6 2023-10-10 13:57:30 -05:00
jdegenstein
839b7f02ee Update utils.py PySide6 2023-10-10 13:56:35 -05:00
jdegenstein
aeea6cb0cb Update preferences.py PySide6 2023-10-10 13:56:13 -05:00
jdegenstein
4e2d22108d Update mixins.py PySide6 2023-10-10 13:55:59 -05:00
jdegenstein
71cf617f16 Update main_window.py PySide6 2023-10-10 13:55:42 -05:00
jdegenstein
cfc03f94e7 Update icons_res.py PySide6 2023-10-10 13:55:26 -05:00
jdegenstein
81d84f0d0d Update icons.py 2023-10-10 13:54:55 -05:00
jdegenstein
1df1853a3f Update editor.py PySide6 2023-10-10 13:54:36 -05:00
jdegenstein
f47a9e1701 Update cq_utils.py PySide6 2023-10-10 13:54:16 -05:00
jdegenstein
cfd34a8339 Update __main__.py PySide6 2023-10-10 13:53:57 -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
24 changed files with 1911 additions and 1654 deletions

View File

@@ -12,31 +12,34 @@ jobs:
build-linux:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
- uses: actions/checkout@v3
- uses: mamba-org/setup-micromamba@v1
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
create-args: >-
python=3.10
- 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
sudo apt install -y libxkbcommon-x11-0
sudo apt install -y 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==3.5.5
pip install pyinstaller==4.10
pip install -vvv --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6 git+https://github.com/jdegenstein/pyinstaller-hooks-contrib
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/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/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
@@ -45,29 +48,29 @@ jobs:
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: alehechka/upload-tartifact@v1
- uses: alehechka/upload-tartifact@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
- uses: actions/checkout@v3
- uses: mamba-org/setup-micromamba@v1
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
create-args: >-
python=3.10
- 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==3.5.5
pip install pyinstaller==4.10
pip install --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6 git+https://github.com/jdegenstein/pyinstaller-hooks-contrib
pip install path
pip uninstall -y PyQt5
pip install PyQt5==5.15.7
@@ -75,7 +78,7 @@ jobs:
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/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
@@ -91,36 +94,38 @@ jobs:
build-windows:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- uses: mamba-org/provision-with-micromamba@main
- uses: actions/checkout@v3
- uses: mamba-org/setup-micromamba@v1
with:
#miniconda-version: "latest"
#auto-update-conda: true
environment-name: test
environment-file: environment.yml
extra-specs:
python=3.9
init-shell: >-
powershell
create-args: >-
python=3.10
- name: pip install cadquery CQ-editor ... etc
shell: powershell
run: |
micromamba info
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre git+https://github.com/cadquery/cadquery casadi==3.5.5
pip install pyinstaller==4.10
pip install --pre git+https://github.com/cadquery/cadquery casadi
pip install pyinstaller>=5.6 git+https://github.com/jdegenstein/pyinstaller-hooks-contrib
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/gumyr/build123d
pip install git+https://github.com/JustinSDK/cqMore
pip list
- name: Run build
shell: powershell
run: |
micromamba info
pyinstaller --debug all pyinstaller_pip.spec ${{ github.event.inputs.type }}
pyinstaller pyinstaller_pip.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
- uses: alehechka/upload-tartifact@v2
with:
name: CQ-editor-Windows
path: dist

View File

@@ -0,0 +1,126 @@
name: build-PIP-TAR-pyinst5-6p
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-20.04
# 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
# pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
# pip install -vvv --pre git+https://github.com/cadquery/cadquery casadi==3.5.5
# pip install 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
# pip list
# - name: Run build
# shell: bash --login {0}
# run: |
# 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: alehechka/upload-tartifact@v1
# 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
# pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
# pip install --pre git+https://github.com/cadquery/cadquery casadi==3.5.5
# pip install pyinstaller==4.10
# 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
# pip list
# - name: Run build
# shell: bash --login {0}
# run: |
# 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: alehechka/upload-tartifact@v1
# 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: pip install cadquery CQ-editor ... etc
shell: powershell
run: |
micromamba info
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre git+https://github.com/cadquery/cadquery casadi==3.5.5
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/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: powershell
run: |
micromamba info
pyinstaller pyinstaller_pip.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

View File

@@ -4,6 +4,8 @@ This is a fork of [jmwright's fork](https://github.com/jmwright/CQ-editor) of [C
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)
@@ -33,7 +35,7 @@ CadQuery GUI editor based on PyQT supports Linux, Windows and Mac.
### Release Packages
TBD
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
@@ -41,19 +43,27 @@ Development builds are also available, but can be unstable and should be used at
## Installation (pip)
Additional packages for Linux (known as needed on Ubuntu 22.04):
```
sudo apt install qtbase5-dev qt5-qmake
```
All platforms (Windows/Mac/Linux):
```
pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
pip install --pre git+https://github.com/cadquery/cadquery
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,7 +1,7 @@
import sys
import argparse
from PyQt5.QtWidgets import QApplication
from PySide6.QtWidgets import QApplication
NAME = 'CQ-editor'

View File

@@ -15,7 +15,7 @@ from OCP.Quantity import (
)
from OCP.Graphic3d import Graphic3d_NOM_JADE, Graphic3d_MaterialAspect
from PyQt5.QtGui import QColor
from PySide6.QtGui import QColor
DEFAULT_FACE_COLOR = Quantity_Color(GOLD)
DEFAULT_MATERIAL = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE)

View File

@@ -3,9 +3,9 @@ import spyder.utils.encoding
from modulefinder import ModuleFinder
from spyder.plugins.editor.widgets.codeeditor import CodeEditor
from PyQt5.QtCore import pyqtSignal, QFileSystemWatcher, QTimer
from PyQt5.QtWidgets import QAction, QFileDialog
from PyQt5.QtGui import QFontDatabase
from PySide6.QtCore import Signal as pyqtSignal, QFileSystemWatcher, QTimer
from PySide6.QtWidgets import QAction, QFileDialog
from PySide6.QtGui import QFontDatabase
from path import Path
import sys
@@ -17,91 +17,104 @@ from ..utils import get_save_filename, get_open_filename, confirm
from ..icons import icon
class Editor(CodeEditor,ComponentMixin):
name = 'Code Editor'
class Editor(CodeEditor, ComponentMixin):
name = "Code Editor"
# This signal is emitted whenever the currently-open file changes and
# autoreload is enabled.
triggerRerender = pyqtSignal(bool)
sigFilenameChanged = pyqtSignal(str)
preferences = Parameter.create(name='Preferences',children=[
{'name': 'Font size', 'type': 'int', 'value': 12},
{'name': 'Autoreload', 'type': 'bool', 'value': False},
{'name': 'Autoreload delay', 'type': 'int', 'value': 50},
{'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False},
{'name': 'Line wrap', 'type': 'bool', 'value': False},
{'name': 'Color scheme', 'type': 'list',
'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}])
preferences = Parameter.create(
name="Preferences",
children=[
{"name": "Font size", "type": "int", "value": 12},
{"name": "Autoreload", "type": "bool", "value": False},
{"name": "Autoreload delay", "type": "int", "value": 50},
{
"name": "Autoreload: watch imported modules",
"type": "bool",
"value": False,
},
{"name": "Line wrap", "type": "bool", "value": False},
{
"name": "Color scheme",
"type": "list",
"values": ["Spyder", "Monokai", "Zenburn"],
"value": "Spyder",
},
],
)
EXTENSIONS = 'py'
def __init__(self,parent=None):
EXTENSIONS = "py"
def __init__(self, parent=None):
self._watched_file = None
super(Editor,self).__init__(parent)
super(Editor, self).__init__(parent)
ComponentMixin.__init__(self)
self.setup_editor(linenumbers=True,
markers=True,
edge_line=False,
tab_mode=False,
show_blanks=True,
font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
language='Python',
filename='')
self.setup_editor(
linenumbers=True,
markers=True,
edge_line=False,
tab_mode=False,
show_blanks=True,
font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
language="Python",
filename="",
)
self._actions = \
{'File' : [QAction(icon('new'),
'New',
self,
shortcut='ctrl+N',
triggered=self.new),
QAction(icon('open'),
'Open',
self,
shortcut='ctrl+O',
triggered=self.open),
QAction(icon('save'),
'Save',
self,
shortcut='ctrl+S',
triggered=self.save),
QAction(icon('save_as'),
'Save as',
self,
shortcut='ctrl+shift+S',
triggered=self.save_as),
QAction(icon('autoreload'),
'Automatic reload and preview',
self,triggered=self.autoreload,
checkable=True,
checked=False,
objectName='autoreload'),
]}
self._actions = {
"File": [
QAction(
icon("new"), "New", self, shortcut="ctrl+N", triggered=self.new
),
QAction(
icon("open"), "Open", self, shortcut="ctrl+O", triggered=self.open
),
QAction(
icon("save"), "Save", self, shortcut="ctrl+S", triggered=self.save
),
QAction(
icon("save_as"),
"Save as",
self,
shortcut="ctrl+shift+S",
triggered=self.save_as,
),
QAction(
icon("autoreload"),
"Automatic reload and preview",
self,
triggered=self.autoreload,
checkable=True,
checked=False,
objectName="autoreload",
),
]
}
for a in self._actions.values():
self.addActions(a)
self._fixContextMenu()
# autoreload support
self._file_watcher = QFileSystemWatcher(self)
# we wait for 50ms after a file change for the file to be written completely
self._file_watch_timer = QTimer(self)
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self._file_watch_timer.setInterval(self.preferences["Autoreload delay"])
self._file_watch_timer.setSingleShot(True)
self._file_watcher.fileChanged.connect(
lambda val: self._file_watch_timer.start())
lambda val: self._file_watch_timer.start()
)
self._file_watch_timer.timeout.connect(self._file_changed)
self.updatePreferences()
def _fixContextMenu(self):
menu = self.menu
menu.removeAction(self.run_cell_action)
@@ -109,52 +122,52 @@ class Editor(CodeEditor,ComponentMixin):
menu.removeAction(self.run_selection_action)
menu.removeAction(self.re_run_last_cell_action)
def updatePreferences(self,*args):
self.set_color_scheme(self.preferences['Color scheme'])
def updatePreferences(self, *args):
self.set_color_scheme(self.preferences["Color scheme"])
font = self.font()
font.setPointSize(self.preferences['Font size'])
font.setPointSize(self.preferences["Font size"])
self.set_font(font)
self.findChild(QAction, 'autoreload') \
.setChecked(self.preferences['Autoreload'])
self.findChild(QAction, "autoreload").setChecked(self.preferences["Autoreload"])
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self._file_watch_timer.setInterval(self.preferences["Autoreload delay"])
self.toggle_wrap_mode(self.preferences['Line wrap'])
self.toggle_wrap_mode(self.preferences["Line wrap"])
self._clear_watched_paths()
self._watch_paths()
def confirm_discard(self):
if self.modified:
rv = confirm(self,'Please confirm','Current document is not saved - do you want to continue?')
rv = confirm(
self,
"Please confirm",
"Current document is not saved - do you want to continue?",
)
else:
rv = True
return rv
def new(self):
if not self.confirm_discard():
return
if not self.confirm_discard(): return
self.set_text('')
self.filename = ''
self.set_text("")
self.filename = ""
self.reset_modified()
def open(self):
if not self.confirm_discard(): return
if not self.confirm_discard():
return
curr_dir = Path(self.filename).abspath().dirname()
fname = get_open_filename(self.EXTENSIONS, curr_dir)
if fname != '':
if fname != "":
self.load_from_file(fname)
def load_from_file(self,fname):
def load_from_file(self, fname):
self.set_text_from_file(fname)
self.filename = fname
self.reset_modified()
@@ -164,24 +177,22 @@ class Editor(CodeEditor,ComponentMixin):
# this function returns the encoding spyder used to read the file
_, encoding = spyder.utils.encoding.read(fname)
# spyder returns a -guessed suffix in some cases
return encoding.replace('-guessed', '')
return encoding.replace("-guessed", "")
else:
return 'utf-8'
return "utf-8"
def save(self):
if self._filename != '':
if self.preferences['Autoreload']:
if self._filename != "":
if self.preferences["Autoreload"]:
self._file_watcher.blockSignals(True)
self._file_watch_timer.stop()
encoding = self.determine_encoding(self._filename)
encoded = self.toPlainText().encode(encoding)
with open(self._filename, 'wb') as f:
with open(self._filename, "wb") as f:
f.write(encoded)
if self.preferences['Autoreload']:
if self.preferences["Autoreload"]:
self._file_watcher.blockSignals(False)
self.triggerRerender.emit(True)
@@ -191,21 +202,26 @@ class Editor(CodeEditor,ComponentMixin):
self.save_as()
def save_as(self):
fname = get_save_filename(self.EXTENSIONS)
if fname != '':
encoded = self.toPlainText().encode('utf-8')
with open(fname, 'wb') as f:
if fname != "":
encoded = self.toPlainText().encode("utf-8")
with open(fname, "wb") as f:
f.write(encoded)
self.filename = fname
self.reset_modified()
def _update_filewatcher(self):
if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']):
if self._watched_file and (
self._watched_file != self.filename or not self.preferences["Autoreload"]
):
self._clear_watched_paths()
self._watched_file = None
if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
if (
self.preferences["Autoreload"]
and self.filename
and self.filename != self._watched_file
):
self._watched_file = self._filename
self._watch_paths()
@@ -227,8 +243,8 @@ class Editor(CodeEditor,ComponentMixin):
def _watch_paths(self):
if Path(self._filename).exists():
self._file_watcher.addPath(self._filename)
if self.preferences['Autoreload: watch imported modules']:
module_paths = self.get_imported_module_paths(self._filename)
if self.preferences["Autoreload: watch imported modules"]:
module_paths = self.get_imported_module_paths(self._filename)
if module_paths:
self._file_watcher.addPaths(module_paths)
@@ -241,51 +257,45 @@ class Editor(CodeEditor,ComponentMixin):
# Turn autoreload on/off.
def autoreload(self, enabled):
self.preferences['Autoreload'] = enabled
self.preferences["Autoreload"] = enabled
self._update_filewatcher()
def reset_modified(self):
self.document().setModified(False)
@property
def modified(self):
return self.document().isModified()
def saveComponentState(self,store):
def saveComponentState(self, store):
if self.filename != "":
store.setValue(self.name + "/state", self.filename)
if self.filename != '':
store.setValue(self.name+'/state',self.filename)
def restoreComponentState(self, store):
filename = store.value(self.name + "/state")
def restoreComponentState(self,store):
filename = store.value(self.name+'/state')
if filename and self.filename == '':
if filename and self.filename == "":
try:
self.load_from_file(filename)
except IOError:
self._logger.warning(f'could not open {filename}')
self._logger.warning(f"could not open {filename}")
def get_imported_module_paths(self, module_path):
finder = ModuleFinder([os.path.dirname(module_path)])
imported_modules = []
try:
finder.run_script(module_path)
except SyntaxError as err:
self._logger.warning(f'Syntax error in {module_path}: {err}')
self._logger.warning(f"Syntax error in {module_path}: {err}")
except Exception as err:
self._logger.warning(
f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}'
f"Cannot determine imported modules in {module_path}: {type(err).__name__} {err}"
)
else:
for module_name, module in finder.modules.items():
if module_name != '__main__':
path = getattr(module, '__file__', None)
if module_name != "__main__":
path = getattr(module, "__file__", None)
if path is not None and os.path.isfile(path):
imported_modules.append(path)
@@ -293,8 +303,7 @@ class Editor(CodeEditor,ComponentMixin):
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
editor = Editor()

View File

@@ -6,7 +6,7 @@ Created on Fri May 25 14:47:10 2018
@author: adam
"""
from PyQt5.QtGui import QIcon
from PySide6.QtGui import QIcon
from . import icons_res
_icons = {
@@ -56,4 +56,4 @@ def icon(name):
args,kwargs = _icons_specs[name]
return qta.icon(*args,**kwargs)
return qta.icon(*args,**kwargs)

View File

@@ -6,7 +6,7 @@
#
# WARNING! All changes made in this file will be lost!
from PyQt5 import QtCore
from PySide6 import QtCore
qt_resource_data = b"\
\x00\x00\x4e\x4c\

View File

@@ -1,6 +1,8 @@
import sys
from PyQt5.QtWidgets import (QLabel, QMainWindow, QToolBar, QDockWidget, QAction)
from PySide6.QtWidgets import QLabel, QMainWindow, QToolBar, QDockWidget
from PySide6.QtGui import QAction
from PySide6.QtCore import Signal as pyqtSignal
from logbook import Logger
import cadquery as cq
@@ -14,16 +16,26 @@ from .widgets.cq_object_inspector import CQObjectInspector
from .widgets.log import LogViewer
from . import __version__
from .utils import dock, add_actions, open_url, about_dialog, check_gtihub_for_updates, confirm
from .utils import (
dock,
add_actions,
open_url,
about_dialog,
check_gtihub_for_updates,
confirm,
)
from .mixins import MainMixin
from .icons import icon
from .preferences import PreferencesWidget
#DARKMODE edits: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QPalette, QColor
app = QApplication([])
# DARKMODE edits: https://stackoverflow.com/questions/48256772/dark-theme-for-qt-widgets
from PySide6.QtCore import Qt
from PySide6.QtWidgets import QApplication
from PySide6.QtGui import QPalette, QColor
app = QApplication.instance()
if app == None:
app = QApplication([])
# Force the style to be the same on all OSs:
app.setStyle("Fusion")
# Now use a palette to switch to dark colors:
@@ -43,141 +55,131 @@ palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
palette.setColor(QPalette.HighlightedText, Qt.black)
app.setPalette(palette)
class MainWindow(QMainWindow,MainMixin):
name = 'CQ-Editor'
org = 'CadQuery'
class MainWindow(QMainWindow, MainMixin):
name = "CQ-Editor"
org = "CadQuery"
def __init__(self,parent=None, filename=None):
super(MainWindow,self).__init__(parent)
def __init__(self, parent=None, filename=None):
super(MainWindow, self).__init__(parent)
MainMixin.__init__(self)
self.setWindowIcon(icon('app'))
self.setWindowIcon(icon("app"))
self.viewer = OCCViewer(self)
self.setCentralWidget(self.viewer.canvas)
# self.setCentralWidget(self.viewer.canvas)
self.prepare_panes()
self.registerComponent('viewer',self.viewer)
self.prepare_toolbar()
self.prepare_menubar()
# self.prepare_panes()
# self.registerComponent("viewer", self.viewer)
# self.prepare_toolbar()
# self.prepare_menubar()
self.prepare_statusbar()
self.prepare_actions()
self.components['object_tree'].addLines()
# self.prepare_statusbar()
# self.prepare_actions()
self.prepare_console()
# self.components["object_tree"].addLines()
self.fill_dummy()
# self.prepare_console()
self.setup_logging()
# self.fill_dummy()
self.restorePreferences()
self.restoreWindow()
# self.setup_logging()
if filename:
self.components['editor'].load_from_file(filename)
# self.restorePreferences()
# self.restoreWindow()
self.restoreComponentState()
# if filename:
# self.components["editor"].load_from_file(filename)
def closeEvent(self,event):
# self.restoreComponentState()
def closeEvent(self, event):
self.saveWindow()
self.savePreferences()
self.saveComponentState()
if self.components['editor'].document().isModified():
if self.components["editor"].document().isModified():
rv = confirm(self, "Confirm close", "Close without saving?")
rv = confirm(self, 'Confirm close', 'Close without saving?')
if rv:
event.accept()
super(MainWindow,self).closeEvent(event)
super(MainWindow, self).closeEvent(event)
else:
event.ignore()
else:
super(MainWindow,self).closeEvent(event)
super(MainWindow, self).closeEvent(event)
def prepare_panes(self):
self.registerComponent(
"editor",
Editor(self),
lambda c: dock(c, "Editor", self, defaultArea="left"),
)
self.registerComponent('editor',
Editor(self),
lambda c : dock(c,
'Editor',
self,
defaultArea='left'))
self.registerComponent(
"object_tree",
ObjectTree(self),
lambda c: dock(c, "Objects", self, defaultArea="right"),
)
self.registerComponent('object_tree',
ObjectTree(self),
lambda c: dock(c,
'Objects',
self,
defaultArea='right'))
self.registerComponent(
"console",
ConsoleWidget(self),
lambda c: dock(c, "Console", self, defaultArea="bottom"),
)
self.registerComponent('console',
ConsoleWidget(self),
lambda c: dock(c,
'Console',
self,
defaultArea='bottom'))
self.registerComponent(
"traceback_viewer",
TracebackPane(self),
lambda c: dock(c, "Current traceback", self, defaultArea="bottom"),
)
self.registerComponent('traceback_viewer',
TracebackPane(self),
lambda c: dock(c,
'Current traceback',
self,
defaultArea='bottom'))
self.registerComponent("debugger", Debugger(self))
self.registerComponent('debugger',Debugger(self))
self.registerComponent(
"variables_viewer",
LocalsView(self),
lambda c: dock(c, "Variables", self, defaultArea="right"),
)
self.registerComponent('variables_viewer',LocalsView(self),
lambda c: dock(c,
'Variables',
self,
defaultArea='right'))
self.registerComponent('cq_object_inspector',
CQObjectInspector(self),
lambda c: dock(c,
'CQ object inspector',
self,
defaultArea='right'))
self.registerComponent('log',
LogViewer(self),
lambda c: dock(c,
'Log viewer',
self,
defaultArea='bottom'))
self.registerComponent(
"cq_object_inspector",
CQObjectInspector(self),
lambda c: dock(c, "CQ object inspector", self, defaultArea="right"),
)
self.registerComponent(
"log",
LogViewer(self),
lambda c: dock(c, "Log viewer", self, defaultArea="bottom"),
)
for d in self.docks.values():
d.show()
def prepare_menubar(self):
menu = self.menuBar()
menu_file = menu.addMenu('&File')
menu_edit = menu.addMenu('&Edit')
menu_tools = menu.addMenu('&Tools')
menu_run = menu.addMenu('&Run')
menu_view = menu.addMenu('&View')
menu_help = menu.addMenu('&Help')
menu_file = menu.addMenu("&File")
menu_edit = menu.addMenu("&Edit")
menu_tools = menu.addMenu("&Tools")
menu_run = menu.addMenu("&Run")
menu_view = menu.addMenu("&View")
menu_help = menu.addMenu("&Help")
#per component menu elements
menus = {'File' : menu_file,
'Edit' : menu_edit,
'Run' : menu_run,
'Tools': menu_tools,
'View' : menu_view,
'Help' : menu_help}
# per component menu elements
menus = {
"File": menu_file,
"Edit": menu_edit,
"Run": menu_run,
"Tools": menu_tools,
"View": menu_view,
"Help": menu_help,
}
for comp in self.components.values():
self.prepare_menubar_component(menus,
comp.menuActions())
self.prepare_menubar_component(menus, comp.menuActions())
#global menu elements
# global menu elements
menu_view.addSeparator()
for d in self.findChildren(QDockWidget):
menu_view.addAction(d.toggleViewAction())
@@ -186,181 +188,200 @@ class MainWindow(QMainWindow,MainMixin):
for t in self.findChildren(QToolBar):
menu_view.addAction(t.toggleViewAction())
menu_edit.addAction( \
QAction(icon('preferences'),
'Preferences',
self,triggered=self.edit_preferences))
menu_edit.addAction(
QAction(
icon("preferences"),
"Preferences",
self,
triggered=self.edit_preferences,
)
)
menu_help.addAction( \
QAction(icon('help'),
'Documentation',
self,triggered=self.documentation))
menu_help.addAction(
QAction(icon("help"), "Documentation", self, triggered=self.documentation)
)
menu_help.addAction( \
QAction('CQ documentation',
self,triggered=self.cq_documentation))
menu_help.addAction(
QAction("CQ documentation", self, triggered=self.cq_documentation)
)
menu_help.addAction( \
QAction(icon('about'),
'About',
self,triggered=self.about))
menu_help.addAction( \
QAction('Check for CadQuery updates',
self,triggered=self.check_for_cq_updates))
menu_help.addAction(QAction(icon("about"), "About", self, triggered=self.about))
def prepare_menubar_component(self,menus,comp_menu_dict):
menu_help.addAction(
QAction(
"Check for CadQuery updates", self, triggered=self.check_for_cq_updates
)
)
for name,action in comp_menu_dict.items():
def prepare_menubar_component(self, menus, comp_menu_dict):
for name, action in comp_menu_dict.items():
menus[name].addActions(action)
def prepare_toolbar(self):
self.toolbar = QToolBar('Main toolbar',self,objectName='Main toolbar')
self.toolbar = QToolBar("Main toolbar", self, objectName="Main toolbar")
for c in self.components.values():
add_actions(self.toolbar,c.toolbarActions())
add_actions(self.toolbar, c.toolbarActions())
self.addToolBar(self.toolbar)
def prepare_statusbar(self):
self.status_label = QLabel('',parent=self)
self.status_label = QLabel("", parent=self)
self.statusBar().insertPermanentWidget(0, self.status_label)
def prepare_actions(self):
self.components["debugger"].sigRendered.connect(
self.components["object_tree"].addObjects
)
self.components["debugger"].sigTraceback.connect(
self.components["traceback_viewer"].addTraceback
)
self.components["debugger"].sigLocals.connect(
self.components["variables_viewer"].update_frame
)
self.components["debugger"].sigLocals.connect(
self.components["console"].push_vars
)
self.components['debugger'].sigRendered\
.connect(self.components['object_tree'].addObjects)
self.components['debugger'].sigTraceback\
.connect(self.components['traceback_viewer'].addTraceback)
self.components['debugger'].sigLocals\
.connect(self.components['variables_viewer'].update_frame)
self.components['debugger'].sigLocals\
.connect(self.components['console'].push_vars)
self.components["object_tree"].sigObjectsAdded[list].connect(
self.components["viewer"].display_many
)
self.components["object_tree"].sigObjectsAdded2[list, bool].connect(
self.components["viewer"].display_many
)
self.components["object_tree"].sigItemChanged.connect(
self.components["viewer"].update_item
)
self.components["object_tree"].sigObjectsRemoved.connect(
self.components["viewer"].remove_items
)
self.components["object_tree"].sigCQObjectSelected.connect(
self.components["cq_object_inspector"].setObject
)
self.components["object_tree"].sigObjectPropertiesChanged.connect(
self.components["viewer"].redraw
)
self.components["object_tree"].sigAISObjectsSelected.connect(
self.components["viewer"].set_selected
)
self.components['object_tree'].sigObjectsAdded[list]\
.connect(self.components['viewer'].display_many)
self.components['object_tree'].sigObjectsAdded[list,bool]\
.connect(self.components['viewer'].display_many)
self.components['object_tree'].sigItemChanged.\
connect(self.components['viewer'].update_item)
self.components['object_tree'].sigObjectsRemoved\
.connect(self.components['viewer'].remove_items)
self.components['object_tree'].sigCQObjectSelected\
.connect(self.components['cq_object_inspector'].setObject)
self.components['object_tree'].sigObjectPropertiesChanged\
.connect(self.components['viewer'].redraw)
self.components['object_tree'].sigAISObjectsSelected\
.connect(self.components['viewer'].set_selected)
self.components["viewer"].sigObjectSelected.connect(
self.components["object_tree"].handleGraphicalSelection
)
self.components['viewer'].sigObjectSelected\
.connect(self.components['object_tree'].handleGraphicalSelection)
self.components["traceback_viewer"].sigHighlightLine.connect(
self.components["editor"].go_to_line
)
self.components['traceback_viewer'].sigHighlightLine\
.connect(self.components['editor'].go_to_line)
self.components["cq_object_inspector"].sigDisplayObjects.connect(
self.components["viewer"].display_many
)
self.components["cq_object_inspector"].sigRemoveObjects.connect(
self.components["viewer"].remove_items
)
self.components["cq_object_inspector"].sigShowPlane.connect(
self.components["viewer"].toggle_grid
)
self.components["cq_object_inspector"].sigShowPlane2[bool, float].connect(
self.components["viewer"].toggle_grid
)
self.components["cq_object_inspector"].sigChangePlane.connect(
self.components["viewer"].set_grid_orientation
)
self.components['cq_object_inspector'].sigDisplayObjects\
.connect(self.components['viewer'].display_many)
self.components['cq_object_inspector'].sigRemoveObjects\
.connect(self.components['viewer'].remove_items)
self.components['cq_object_inspector'].sigShowPlane\
.connect(self.components['viewer'].toggle_grid)
self.components['cq_object_inspector'].sigShowPlane[bool,float]\
.connect(self.components['viewer'].toggle_grid)
self.components['cq_object_inspector'].sigChangePlane\
.connect(self.components['viewer'].set_grid_orientation)
self.components['debugger'].sigLocalsChanged\
.connect(self.components['variables_viewer'].update_frame)
self.components['debugger'].sigLineChanged\
.connect(self.components['editor'].go_to_line)
self.components['debugger'].sigDebugging\
.connect(self.components['object_tree'].stashObjects)
self.components['debugger'].sigCQChanged\
.connect(self.components['object_tree'].addObjects)
self.components['debugger'].sigTraceback\
.connect(self.components['traceback_viewer'].addTraceback)
self.components["debugger"].sigLocalsChanged.connect(
self.components["variables_viewer"].update_frame
)
self.components["debugger"].sigLineChanged.connect(
self.components["editor"].go_to_line
)
self.components["debugger"].sigDebugging.connect(
self.components["object_tree"].stashObjects
)
self.components["debugger"].sigCQChanged.connect(
self.components["object_tree"].addObjects
)
self.components["debugger"].sigTraceback.connect(
self.components["traceback_viewer"].addTraceback
)
# trigger re-render when file is modified externally or saved
self.components['editor'].triggerRerender \
.connect(self.components['debugger'].render)
self.components['editor'].sigFilenameChanged\
.connect(self.handle_filename_change)
self.components["editor"].triggerRerender.connect(
self.components["debugger"].render
)
self.components["editor"].sigFilenameChanged.connect(
self.handle_filename_change
)
def prepare_console(self):
console = self.components["console"]
obj_tree = self.components["object_tree"]
console = self.components['console']
obj_tree = self.components['object_tree']
#application related items
console.push_vars({'self' : self})
# application related items
console.push_vars({"self": self})
# 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,
}
)
#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})
def fill_dummy(self):
self.components['editor']\
.set_text('import cadquery as cq\nresult = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)')
self.components["editor"].set_text(
'import cadquery as cq\nresult = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)'
)
def setup_logging(self):
from logbook.compat import redirect_logging
from logbook import INFO, Logger
redirect_logging()
self.components['log'].handler.level = INFO
self.components['log'].handler.push_application()
self.components["log"].handler.level = INFO
self.components["log"].handler.push_application()
self._logger = Logger(self.name)
def handle_exception(exc_type, exc_value, exc_traceback):
if issubclass(exc_type, KeyboardInterrupt):
sys.__excepthook__(exc_type, exc_value, exc_traceback)
return
self._logger.error("Uncaught exception occurred",
exc_info=(exc_type, exc_value, exc_traceback))
self._logger.error(
"Uncaught exception occurred",
exc_info=(exc_type, exc_value, exc_traceback),
)
sys.excepthook = handle_exception
def edit_preferences(self):
prefs = PreferencesWidget(self,self.components)
prefs = PreferencesWidget(self, self.components)
prefs.exec_()
def about(self):
about_dialog(
self,
f'About CQ-editor',
f'PyQt GUI for CadQuery.\nVersion: {__version__}.\nSource Code: https://github.com/CadQuery/CQ-editor',
f"About CQ-editor",
f"PyQt GUI for CadQuery.\nVersion: {__version__}.\nSource Code: https://github.com/CadQuery/CQ-editor",
)
def check_for_cq_updates(self):
check_gtihub_for_updates(self,cq)
check_gtihub_for_updates(self, cq)
def documentation(self):
open_url('https://github.com/CadQuery')
open_url("https://github.com/CadQuery")
def cq_documentation(self):
open_url('https://cadquery.readthedocs.io/en/latest/')
open_url("https://cadquery.readthedocs.io/en/latest/")
def handle_filename_change(self, fname):
new_title = fname if fname else "*"
self.setWindowTitle(f"{self.name}: {new_title}")
if __name__ == "__main__":
if __name__ == "__main__":
pass

View File

@@ -10,72 +10,67 @@ from functools import reduce
from operator import add
from logbook import Logger
from PyQt5.QtCore import pyqtSlot, QSettings
from PySide6.QtCore import QSettings
from PySide6.QtCore import Slot as pyqtSlot
class MainMixin(object):
name = 'Main'
org = 'Unknown'
name = "Main"
org = "Unknown"
components = {}
docks = {}
preferences = None
def __init__(self):
self.settings = QSettings(self.org, self.name)
self.settings = QSettings(self.org,self.name)
def registerComponent(self,name,component,dock=None):
def registerComponent(self, name, component, dock=None):
self.components[name] = component
if dock:
self.docks[name] = dock(component)
def saveWindow(self):
self.settings.setValue('geometry',self.saveGeometry())
self.settings.setValue('windowState',self.saveState())
self.settings.setValue("geometry", self.saveGeometry())
self.settings.setValue("windowState", self.saveState())
def restoreWindow(self):
if self.settings.value('geometry'):
self.restoreGeometry(self.settings.value('geometry'))
if self.settings.value('windowState'):
self.restoreState(self.settings.value('windowState'))
if self.settings.value("geometry"):
self.restoreGeometry(self.settings.value("geometry"))
if self.settings.value("windowState"):
self.restoreState(self.settings.value("windowState"))
def savePreferences(self):
settings = self.settings
if self.preferences:
settings.setValue('General',self.preferences.saveState())
settings.setValue("General", self.preferences.saveState())
for comp in (c for c in self.components.values() if c.preferences):
settings.setValue(comp.name,comp.preferences.saveState())
settings.setValue(comp.name, comp.preferences.saveState())
def restorePreferences(self):
settings = self.settings
if self.preferences and settings.value('General'):
self.preferences.restoreState(settings.value('General'),
removeChildren=False)
if self.preferences and settings.value("General"):
self.preferences.restoreState(
settings.value("General"), removeChildren=False
)
for comp in (c for c in self.components.values() if c.preferences):
if settings.value(comp.name):
comp.preferences.restoreState(settings.value(comp.name),
removeChildren=False)
comp.preferences.restoreState(
settings.value(comp.name), removeChildren=False
)
def saveComponentState(self):
settings = self.settings
for comp in self.components.values():
comp.saveComponentState(settings)
def restoreComponentState(self):
settings = self.settings
for comp in self.components.values():
@@ -83,42 +78,32 @@ class MainMixin(object):
class ComponentMixin(object):
name = 'Component'
name = "Component"
preferences = None
_actions = {}
def __init__(self):
if self.preferences:
self.preferences.sigTreeStateChanged.\
connect(self.updatePreferences)
self.preferences.sigTreeStateChanged.connect(self.updatePreferences)
self._logger = Logger(self.name)
def menuActions(self):
return self._actions
def toolbarActions(self):
if len(self._actions) > 0:
return reduce(add,[a for a in self._actions.values()])
return reduce(add, [a for a in self._actions.values()])
else:
return []
@pyqtSlot(object,object)
def updatePreferences(self,*args):
@pyqtSlot(object, object)
def updatePreferences(self, *args):
pass
def saveComponentState(self,store):
def saveComponentState(self, store):
pass
def restoreComponentState(self,store):
pass
def restoreComponentState(self, store):
pass

View File

@@ -1,6 +1,5 @@
from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem,
QStackedWidget, QDialog)
from PyQt5.QtCore import pyqtSlot, Qt
from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem, QStackedWidget, QDialog
from PySide6.QtCore import Slot as pyqtSlot, Qt
from pyqtgraph.parametertree import ParameterTree
@@ -8,55 +7,54 @@ from .utils import splitter, layout
class PreferencesTreeItem(QTreeWidgetItem):
def __init__(self,name,widget,):
super(PreferencesTreeItem,self).__init__(name)
def __init__(
self,
name,
widget,
):
super(PreferencesTreeItem, self).__init__(name)
self.widget = widget
class PreferencesWidget(QDialog):
def __init__(self,parent,components):
super(PreferencesWidget,self).__init__(
parent,
Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint,
windowTitle='Preferences')
def __init__(self, parent, components):
super(PreferencesWidget, self).__init__(
parent,
Qt.Window | Qt.CustomizeWindowHint | Qt.WindowCloseButtonHint,
windowTitle="Preferences",
)
self.stacked = QStackedWidget(self)
self.preferences_tree = QTreeWidget(self,
headerHidden=True,
itemsExpandable=False,
rootIsDecorated=False,
columnCount=1)
self.preferences_tree = QTreeWidget(
self,
headerHidden=True,
itemsExpandable=False,
rootIsDecorated=False,
columnCount=1,
)
self.root = self.preferences_tree.invisibleRootItem()
self.add('General',
parent)
self.add("General", parent)
for v in parent.components.values():
self.add(v.name,v)
self.splitter = splitter((self.preferences_tree,self.stacked),(2,5))
layout(self,(self.splitter,),self)
self.add(v.name, v)
self.splitter = splitter((self.preferences_tree, self.stacked), (2, 5))
layout(self, (self.splitter,), self)
self.preferences_tree.currentItemChanged.connect(self.handleSelection)
def add(self,name,component):
def add(self, name, component):
if component.preferences:
widget = ParameterTree()
widget.setHeaderHidden(True)
widget.setParameters(component.preferences,showTop=False)
self.root.addChild(PreferencesTreeItem((name,),
widget))
widget.setParameters(component.preferences, showTop=False)
self.root.addChild(PreferencesTreeItem((name,), widget))
self.stacked.addWidget(widget)
@pyqtSlot(QTreeWidgetItem,QTreeWidgetItem)
def handleSelection(self,item,*args):
@pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
def handleSelection(self, item, *args):
if item:
self.stacked.setCurrentWidget(item.widget)

View File

@@ -2,22 +2,27 @@ import requests
from pkg_resources import parse_version
from PyQt5 import QtCore, QtWidgets
from PyQt5.QtGui import QDesktopServices
from PyQt5.QtCore import QUrl
from PyQt5.QtWidgets import QFileDialog, QMessageBox
from PySide6 import QtCore, QtWidgets
from PySide6.QtGui import QDesktopServices
from PySide6.QtCore import QUrl
from PySide6.QtWidgets import QFileDialog, QMessageBox
DOCK_POSITIONS = {'right' : QtCore.Qt.RightDockWidgetArea,
'left' : QtCore.Qt.LeftDockWidgetArea,
'top' : QtCore.Qt.TopDockWidgetArea,
'bottom' : QtCore.Qt.BottomDockWidgetArea}
DOCK_POSITIONS = {
"right": QtCore.Qt.RightDockWidgetArea,
"left": QtCore.Qt.LeftDockWidgetArea,
"top": QtCore.Qt.TopDockWidgetArea,
"bottom": QtCore.Qt.BottomDockWidgetArea,
}
def layout(parent,items,
top_widget = None,
layout_type = QtWidgets.QVBoxLayout,
margin = 2,
spacing = 0):
def layout(
parent,
items,
top_widget=None,
layout_type=QtWidgets.QVBoxLayout,
margin=2,
spacing=0,
):
if not top_widget:
top_widget = QtWidgets.QWidget(parent)
top_widget_was_none = True
@@ -25,110 +30,124 @@ def layout(parent,items,
top_widget_was_none = False
layout = layout_type(top_widget)
top_widget.setLayout(layout)
for item in items: layout.addWidget(item)
for item in items:
layout.addWidget(item)
layout.setSpacing(spacing)
layout.setContentsMargins(margin,margin,margin,margin)
layout.setContentsMargins(margin, margin, margin, margin)
if top_widget_was_none:
return top_widget
else:
return layout
def splitter(items,
stretch_factors = None,
orientation=QtCore.Qt.Horizontal):
def splitter(items, stretch_factors=None, orientation=QtCore.Qt.Horizontal):
sp = QtWidgets.QSplitter(orientation)
for item in items: sp.addWidget(item)
for item in items:
sp.addWidget(item)
if stretch_factors:
for i,s in enumerate(stretch_factors):
sp.setStretchFactor(i,s)
for i, s in enumerate(stretch_factors):
sp.setStretchFactor(i, s)
return sp
def dock(widget,
title,
parent,
allowedAreas = QtCore.Qt.AllDockWidgetAreas,
defaultArea = 'right',
name=None,
icon = None):
dock = QtWidgets.QDockWidget(title,parent,objectName=title)
if name: dock.setObjectName(name)
if icon: dock.toggleViewAction().setIcon(icon)
def dock(
widget,
title,
parent,
allowedAreas=QtCore.Qt.AllDockWidgetAreas,
defaultArea="right",
name=None,
icon=None,
):
dock = QtWidgets.QDockWidget(title, parent, objectName=title)
if name:
dock.setObjectName(name)
if icon:
dock.toggleViewAction().setIcon(icon)
dock.setAllowedAreas(allowedAreas)
dock.setWidget(widget)
action = dock.toggleViewAction()
action.setText(title)
dock.setFeatures(QtWidgets.QDockWidget.DockWidgetFeatures(\
QtWidgets.QDockWidget.AllDockWidgetFeatures))
parent.addDockWidget(DOCK_POSITIONS[defaultArea],
dock)
dock.setFeatures(
QtWidgets.QDockWidget.DockWidgetFeatures(
QtWidgets.QDockWidget.DockWidgetFloatable
)
)
parent.addDockWidget(DOCK_POSITIONS[defaultArea], dock)
return dock
def add_actions(menu,actions):
def add_actions(menu, actions):
if len(actions) > 0:
menu.addActions(actions)
menu.addSeparator()
def open_url(url):
QDesktopServices.openUrl(QUrl(url))
def about_dialog(parent,title,text):
QtWidgets.QMessageBox.about(parent,title,text)
QDesktopServices.openUrl(QUrl(url))
def about_dialog(parent, title, text):
QtWidgets.QMessageBox.about(parent, title, text)
def get_save_filename(suffix):
rv,_ = QFileDialog.getSaveFileName(filter='*.{}'.format(suffix))
if rv != '' and not rv.endswith(suffix): rv += '.'+suffix
rv, _ = QFileDialog.getSaveFileName(filter="*.{}".format(suffix))
if rv != "" and not rv.endswith(suffix):
rv += "." + suffix
return rv
def get_open_filename(suffix, curr_dir):
rv,_ = QFileDialog.getOpenFileName(directory=curr_dir, filter='*.{}'.format(suffix))
if rv != '' and not rv.endswith(suffix): rv += '.'+suffix
rv, _ = QFileDialog.getOpenFileName(
directory=curr_dir, filter="*.{}".format(suffix)
)
if rv != "" and not rv.endswith(suffix):
rv += "." + suffix
return rv
def check_gtihub_for_updates(parent,
mod,
github_org='cadquery',
github_proj='cadquery'):
url = f'https://api.github.com/repos/{github_org}/{github_proj}/releases'
def check_gtihub_for_updates(
parent, mod, github_org="cadquery", github_proj="cadquery"
):
url = f"https://api.github.com/repos/{github_org}/{github_proj}/releases"
resp = requests.get(url).json()
newer = [el['tag_name'] for el in resp if not el['draft'] and \
parse_version(el['tag_name']) > parse_version(mod.__version__)]
newer = [
el["tag_name"]
for el in resp
if not el["draft"]
and parse_version(el["tag_name"]) > parse_version(mod.__version__)
]
if newer:
title='Updates available'
text=f'There are newer versions of {github_proj} ' \
f'available on github:\n' + '\n'.join(newer)
title = "Updates available"
text = (
f"There are newer versions of {github_proj} "
f"available on github:\n" + "\n".join(newer)
)
else:
title='No updates available'
text=f'You are already using the latest version of {github_proj}'
QtWidgets.QMessageBox.about(parent,title,text)
def confirm(parent,title,msg):
title = "No updates available"
text = f"You are already using the latest version of {github_proj}"
QtWidgets.QMessageBox.about(parent, title, text)
def confirm(parent, title, msg):
rv = QMessageBox.question(parent, title, msg, QMessageBox.Yes, QMessageBox.No)
return True if rv == QMessageBox.Yes else False

View File

@@ -1,23 +1,23 @@
from PyQt5.QtWidgets import QApplication
from PyQt5.QtCore import pyqtSlot
from PySide6.QtWidgets import QApplication
from PySide6.QtCore import Slot as pyqtSlot
from qtconsole.rich_jupyter_widget import RichJupyterWidget
from qtconsole.inprocess import QtInProcessKernelManager
from ..mixins import ComponentMixin
class ConsoleWidget(RichJupyterWidget,ComponentMixin):
name = 'Console'
class ConsoleWidget(RichJupyterWidget, ComponentMixin):
name = "Console"
def __init__(self, customBanner=None, namespace=dict(), *args, **kwargs):
super(ConsoleWidget, self).__init__(*args, **kwargs)
# if not customBanner is None:
# self.banner = customBanner
# if not customBanner is None:
# self.banner = customBanner
self.font_size = 6
self.style_sheet = '''<style>
self.style_sheet = """<style>
QPlainTextEdit, QTextEdit {
background-color: #3f3f3f;
background-clip: padding;
@@ -34,14 +34,14 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin):
.in-prompt { color: navy; }
.out-prompt { color: darkred; }
</style>
'''
self.syntax_style = 'zenburn' #CHANGES FOR DARKMODE
"""
self.syntax_style = "zenburn" # CHANGES FOR DARKMODE
self.kernel_manager = kernel_manager = QtInProcessKernelManager()
kernel_manager.start_kernel(show_banner=False)
kernel_manager.kernel.gui = 'qt'
kernel_manager.kernel.gui = "qt"
kernel_manager.kernel.shell.banner1 = ""
self.kernel_client = kernel_client = self._kernel_manager.client()
kernel_client.start_channels()
@@ -51,9 +51,9 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin):
QApplication.instance().exit()
self.exit_requested.connect(stop)
self.clear()
self.push_vars(namespace)
@pyqtSlot(dict)
@@ -70,7 +70,6 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin):
"""
self._control.clear()
def print_text(self, text):
"""
Prints some plain text to the console
@@ -82,20 +81,17 @@ class ConsoleWidget(RichJupyterWidget,ComponentMixin):
Execute a command in the frame of the console widget
"""
self._execute(command, False)
def _banner_default(self):
return ''
def _banner_default(self):
return ""
if __name__ == "__main__":
import sys
app = QApplication(sys.argv)
console = ConsoleWidget(customBanner='IPython console test')
console = ConsoleWidget(customBanner="IPython console test")
console.show()
sys.exit(app.exec_())

View File

@@ -1,6 +1,6 @@
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAction
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PySide6.QtWidgets import QTreeWidget, QTreeWidgetItem
from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as pyqtSignal
from PySide6.QtGui import QAction
from OCP.AIS import AIS_ColoredShape
from OCP.gp import gp_Ax3
@@ -10,63 +10,62 @@ from ..mixins import ComponentMixin
from ..icons import icon
class CQChildItem(QTreeWidgetItem):
def __init__(self,cq_item,**kwargs):
super(CQChildItem,self).\
__init__([type(cq_item).__name__,str(cq_item)],**kwargs)
def __init__(self, cq_item, **kwargs):
super(CQChildItem, self).__init__(
[type(cq_item).__name__, str(cq_item)], **kwargs
)
self.cq_item = cq_item
class CQStackItem(QTreeWidgetItem):
def __init__(self,name,workplane=None,**kwargs):
super(CQStackItem,self).__init__([name,''],**kwargs)
def __init__(self, name, workplane=None, **kwargs):
super(CQStackItem, self).__init__([name, ""], **kwargs)
self.workplane = workplane
class CQObjectInspector(QTreeWidget,ComponentMixin):
name = 'CQ Object Inspector'
class CQObjectInspector(QTreeWidget, ComponentMixin):
name = "CQ Object Inspector"
sigRemoveObjects = pyqtSignal(list)
sigDisplayObjects = pyqtSignal(list,bool)
sigShowPlane = pyqtSignal([bool],[bool,float])
sigDisplayObjects = pyqtSignal(list, bool)
sigShowPlane = pyqtSignal(bool)
sigShowPlane2 = pyqtSignal(bool, float)
sigChangePlane = pyqtSignal(gp_Ax3)
def __init__(self,parent):
super(CQObjectInspector,self).__init__(parent)
def __init__(self, parent):
super(CQObjectInspector, self).__init__(parent)
self.setHeaderHidden(False)
self.setRootIsDecorated(True)
self.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setColumnCount(2)
self.setHeaderLabels(['Type','Value'])
self.setHeaderLabels(["Type", "Value"])
self.root = self.invisibleRootItem()
self.inspected_items = []
self._toolbar_actions = \
[QAction(icon('inspect'),'Inspect CQ object',self,\
toggled=self.inspect,checkable=True)]
self._toolbar_actions = [
QAction(
icon("inspect"),
"Inspect CQ object",
self,
toggled=self.inspect,
checkable=True,
)
]
self.addActions(self._toolbar_actions)
def menuActions(self):
return {'Tools' : self._toolbar_actions}
return {"Tools": self._toolbar_actions}
def toolbarActions(self):
return self._toolbar_actions
@pyqtSlot(bool)
def inspect(self,value):
def inspect(self, value):
if value:
self.itemSelectionChanged.connect(self.handleSelection)
self.itemSelectionChanged.emit()
@@ -74,56 +73,52 @@ class CQObjectInspector(QTreeWidget,ComponentMixin):
self.itemSelectionChanged.disconnect(self.handleSelection)
self.sigRemoveObjects.emit(self.inspected_items)
self.sigShowPlane.emit(False)
@pyqtSlot()
@pyqtSlot()
def handleSelection(self):
inspected_items = self.inspected_items
self.sigRemoveObjects.emit(inspected_items)
inspected_items.clear()
items = self.selectedItems()
if len(items) == 0:
return
item = items[-1]
if type(item) is CQStackItem:
cq_plane = item.workplane.plane
dim = item.workplane.largestDimension()
plane = gp_Ax3(cq_plane.origin.toPnt(),
cq_plane.zDir.toDir(),
cq_plane.xDir.toDir())
plane = gp_Ax3(
cq_plane.origin.toPnt(), cq_plane.zDir.toDir(), cq_plane.xDir.toDir()
)
self.sigChangePlane.emit(plane)
self.sigShowPlane[bool,float].emit(True,dim)
self.sigShowPlane[bool, float].emit(True, dim)
for child in (item.child(i) for i in range(item.childCount())):
obj = child.cq_item
if hasattr(obj,'wrapped') and type(obj) != Vector:
if hasattr(obj, "wrapped") and type(obj) != Vector:
ais = AIS_ColoredShape(obj.wrapped)
inspected_items.append(ais)
else:
self.sigShowPlane.emit(False)
obj = item.cq_item
if hasattr(obj,'wrapped') and type(obj) != Vector:
if hasattr(obj, "wrapped") and type(obj) != Vector:
ais = AIS_ColoredShape(obj.wrapped)
inspected_items.append(ais)
self.sigDisplayObjects.emit(inspected_items,False)
self.sigDisplayObjects.emit(inspected_items, False)
@pyqtSlot(object)
def setObject(self,cq_obj):
def setObject(self, cq_obj):
self.root.takeChildren()
# iterate through parent objects if they exist
while getattr(cq_obj, 'parent', None):
current_frame = CQStackItem(str(cq_obj.plane.origin),workplane=cq_obj)
while getattr(cq_obj, "parent", None):
current_frame = CQStackItem(str(cq_obj.plane.origin), workplane=cq_obj)
self.root.addChild(current_frame)
for obj in cq_obj.objects:
current_frame.addChild(CQChildItem(obj))
cq_obj = cq_obj.parent

View File

@@ -5,10 +5,17 @@ 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 PySide6 import QtCore
from PySide6.QtCore import (
Qt,
QObject,
Slot as pyqtSlot,
Signal as pyqtSignal,
QEventLoop,
QAbstractTableModel,
)
from PySide6.QtWidgets import QTableView
from PySide6.QtGui import QAction
from logbook import info
from path import Path
from pyqtgraph.parametertree import Parameter
@@ -18,46 +25,43 @@ from random import randrange as rrr, seed
from ..cq_utils import find_cq_objects, reload_cq
from ..mixins import ComponentMixin
DUMMY_FILE = '<string>'
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 DbgEevent(object):
LINE = "line"
CALL = "call"
RETURN = "return"
class LocalsModel(QAbstractTableModel):
HEADER = ("Name", "Type", "Value")
HEADER = ('Name','Type', 'Value')
def __init__(self,parent):
super(LocalsModel,self).__init__(parent)
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()):
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()):
def columnCount(self, parent=QtCore.QModelIndex()):
return 3
def headerData(self, section, orientation, role=Qt.DisplayRole):
@@ -74,13 +78,11 @@ class LocalsModel(QAbstractTableModel):
return QtCore.QVariant()
class LocalsView(QTableView,ComponentMixin):
class LocalsView(QTableView, ComponentMixin):
name = "Variables"
name = 'Variables'
def __init__(self,parent):
super(LocalsView,self).__init__(parent)
def __init__(self, parent):
super(LocalsView, self).__init__(parent)
ComponentMixin.__init__(self)
header = self.horizontalHeader()
@@ -90,193 +92,225 @@ class LocalsView(QTableView,ComponentMixin):
vheader.setVisible(False)
@pyqtSlot(dict)
def update_frame(self,frame):
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},
])
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)
sigTraceback = pyqtSignal(object, str)
sigFrameChanged = pyqtSignal(object)
sigLineChanged = pyqtSignal(int)
sigLocalsChanged = pyqtSignal(dict)
sigCQChanged = pyqtSignal(dict,bool)
sigCQChanged = pyqtSignal(dict, bool)
sigDebugging = pyqtSignal(bool)
_frames : List[FrameType]
_frames: List[FrameType]
def __init__(self,parent):
super(Debugger,self).__init__(parent)
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._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()
return self.parent().components["editor"].get_text_with_eol()
def get_breakpoints(self):
return self.parent().components['editor'].debugger.get_breakpoints()
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')
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()
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)
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():
if self.preferences["Change working dir to script dir"] and p.exists():
stack.enter_context(p)
if self.preferences['Reload imported modules']:
if self.preferences["Reload imported modules"]:
stack.enter_context(module_manager())
exec(code, locals_dict, globals_dict)
exec(code, locals_dict, globals_dict)
def _rand_color(self, alpha = 0., cfloat=False):
#helper function to generate a random color dict
#for CQ-editor's show_object function
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
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):
(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={}):
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)})
cq_objects.update({name: SimpleNamespace(shape=obj, options=options)})
else:
cq_objects.update({str(id(obj)) : SimpleNamespace(shape=obj,options=options)})
cq_objects.update(
{str(id(obj)): SimpleNamespace(shape=obj, options=options)}
)
def _debug(obj,name=None):
def _debug(obj, name=None):
_show_object(obj, name, options=dict(color="red", alpha=0.2))
_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
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"}
return cq_objects, set(module.__dict__)-{'cq'}
def _cleanup_locals(self,module,injected_names):
for name in injected_names: module.__dict__.pop(name)
def _cleanup_locals(self, module, injected_names):
for name in injected_names:
module.__dict__.pop(name)
@pyqtSlot(bool)
def render(self):
seed(371353) #reset the seed every time render is called (preserves colors run to run)
if self.preferences['Reload CQ']:
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)
cq_code, module = self.compile_code(cq_script)
if cq_code is None: return
if cq_code is None:
return
cq_objects,injected_names = self._inject_locals(module)
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)
# remove the special methods
self._cleanup_locals(module, injected_names)
#collect all CQ objects if no explicit show_object was called
# 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.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()]
return [el[0] for el in self.get_breakpoints()]
@pyqtSlot(bool)
def debug(self,value):
def debug(self, value):
previous_trace = sys.gettrace()
if value:
@@ -284,79 +318,73 @@ class Debugger(QObject,ComponentMixin):
self.state = DbgState.STEP
self.script = self.get_current_script()
code,module = self.compile_code(self.script)
code, module = self.compile_code(self.script)
if code is None:
self.sigDebugging.emit(False)
self._actions['Run'][1].setChecked(False)
self._actions["Run"][1].setChecked(False)
return
cq_objects,injected_names = self._inject_locals(module)
cq_objects, injected_names = self._inject_locals(module)
#clear possible traceback
self.sigTraceback.emit(None,
self.script)
# clear possible traceback
self.sigTraceback.emit(None, self.script)
try:
sys.settrace(self.trace_callback)
exec(code,module.__dict__,module.__dict__)
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)
self.sigTraceback.emit(exc_info, self.script)
finally:
sys.settrace(previous_trace)
self.sigDebugging.emit(False)
self._actions['Run'][1].setChecked(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._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):
def debug_cmd(self, state=DbgState.STEP):
self.state = state
self.inner_event_loop.exit(0)
def trace_callback(self,frame,event,arg):
def trace_callback(self, frame, event, arg):
filename = frame.f_code.co_filename
if filename==DUMMY_FILE:
if filename == DUMMY_FILE:
if not self._frames:
self._frames.append(frame)
self.trace_local(frame,event,arg)
self.trace_local(frame, event, arg)
return self.trace_callback
else:
return None
def trace_local(self,frame,event,arg):
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 (
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.sigCQChanged.emit(find_cq_objects(frame.f_locals), True)
self.inner_event_loop.exec_()
@@ -375,7 +403,7 @@ class Debugger(QObject,ComponentMixin):
@contextmanager
def module_manager():
""" unloads any modules loaded while the context manager is active """
"""unloads any modules loaded while the context manager is active"""
loaded_modules = set(sys.modules.keys())
try:

View File

@@ -2,9 +2,11 @@ import os
from modulefinder import ModuleFinder
from spyder.plugins.editor.widgets.codeeditor import CodeEditor
from PyQt5.QtCore import pyqtSignal, QFileSystemWatcher, QTimer
from PyQt5.QtWidgets import QAction, QFileDialog
from PyQt5.QtGui import QFontDatabase
from PySide6.QtCore import Signal as pyqtSignal
from PySide6.QtCore import QFileSystemWatcher, QTimerEvent, QTimer
from PySide6.QtWidgets import QFileDialog
from PySide6.QtGui import QFontDatabase, QAction
from path import Path
import sys
@@ -16,91 +18,104 @@ from ..utils import get_save_filename, get_open_filename, confirm
from ..icons import icon
class Editor(CodeEditor,ComponentMixin):
name = 'Code Editor'
class Editor(CodeEditor, ComponentMixin):
name = "Code Editor"
# This signal is emitted whenever the currently-open file changes and
# autoreload is enabled.
triggerRerender = pyqtSignal(bool)
sigFilenameChanged = pyqtSignal(str)
preferences = Parameter.create(name='Preferences',children=[
{'name': 'Font size', 'type': 'int', 'value': 12},
{'name': 'Autoreload', 'type': 'bool', 'value': False},
{'name': 'Autoreload delay', 'type': 'int', 'value': 50},
{'name': 'Autoreload: watch imported modules', 'type': 'bool', 'value': False},
{'name': 'Line wrap', 'type': 'bool', 'value': False},
{'name': 'Color scheme', 'type': 'list',
'values': ['Spyder','Monokai','Zenburn'], 'value': 'Spyder'}])
preferences = Parameter.create(
name="Preferences",
children=[
{"name": "Font size", "type": "int", "value": 12},
{"name": "Autoreload", "type": "bool", "value": False},
{"name": "Autoreload delay", "type": "int", "value": 50},
{
"name": "Autoreload: watch imported modules",
"type": "bool",
"value": False,
},
{"name": "Line wrap", "type": "bool", "value": False},
{
"name": "Color scheme",
"type": "list",
"values": ["Spyder", "Monokai", "Zenburn"],
"value": "Spyder",
},
],
)
EXTENSIONS = 'py'
def __init__(self,parent=None):
EXTENSIONS = "py"
def __init__(self, parent=None):
self._watched_file = None
super(Editor,self).__init__(parent)
super(Editor, self).__init__(parent)
ComponentMixin.__init__(self)
self.setup_editor(linenumbers=True,
markers=True,
edge_line=False,
tab_mode=False,
show_blanks=True,
font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
language='Python',
filename='')
self.setup_editor(
linenumbers=True,
markers=True,
edge_line=False,
tab_mode=False,
show_blanks=True,
font=QFontDatabase.systemFont(QFontDatabase.FixedFont),
language="Python",
filename="",
)
self._actions = \
{'File' : [QAction(icon('new'),
'New',
self,
shortcut='ctrl+N',
triggered=self.new),
QAction(icon('open'),
'Open',
self,
shortcut='ctrl+O',
triggered=self.open),
QAction(icon('save'),
'Save',
self,
shortcut='ctrl+S',
triggered=self.save),
QAction(icon('save_as'),
'Save as',
self,
shortcut='ctrl+shift+S',
triggered=self.save_as),
QAction(icon('autoreload'),
'Automatic reload and preview',
self,triggered=self.autoreload,
checkable=True,
checked=False,
objectName='autoreload'),
]}
self._actions = {
"File": [
QAction(
icon("new"), "New", self, shortcut="ctrl+N", triggered=self.new
),
QAction(
icon("open"), "Open", self, shortcut="ctrl+O", triggered=self.open
),
QAction(
icon("save"), "Save", self, shortcut="ctrl+S", triggered=self.save
),
QAction(
icon("save_as"),
"Save as",
self,
shortcut="ctrl+shift+S",
triggered=self.save_as,
),
QAction(
icon("autoreload"),
"Automatic reload and preview",
self,
triggered=self.autoreload,
checkable=True,
checked=False,
objectName="autoreload",
),
]
}
for a in self._actions.values():
self.addActions(a)
self._fixContextMenu()
# autoreload support
self._file_watcher = QFileSystemWatcher(self)
# we wait for 50ms after a file change for the file to be written completely
self._file_watch_timer = QTimer(self)
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self._file_watch_timer.setInterval(self.preferences["Autoreload delay"])
self._file_watch_timer.setSingleShot(True)
self._file_watcher.fileChanged.connect(
lambda val: self._file_watch_timer.start())
lambda val: self._file_watch_timer.start()
)
self._file_watch_timer.timeout.connect(self._file_changed)
self.updatePreferences()
def _fixContextMenu(self):
menu = self.menu
menu.removeAction(self.run_cell_action)
@@ -108,68 +123,66 @@ class Editor(CodeEditor,ComponentMixin):
menu.removeAction(self.run_selection_action)
menu.removeAction(self.re_run_last_cell_action)
def updatePreferences(self,*args):
self.set_color_scheme(self.preferences['Color scheme'])
def updatePreferences(self, *args):
self.set_color_scheme(self.preferences["Color scheme"])
font = self.font()
font.setPointSize(self.preferences['Font size'])
font.setPointSize(self.preferences["Font size"])
self.set_font(font)
self.findChild(QAction, 'autoreload') \
.setChecked(self.preferences['Autoreload'])
self.findChild(QAction, "autoreload").setChecked(self.preferences["Autoreload"])
self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
self._file_watch_timer.setInterval(self.preferences["Autoreload delay"])
self.toggle_wrap_mode(self.preferences['Line wrap'])
self.toggle_wrap_mode(self.preferences["Line wrap"])
self._clear_watched_paths()
self._watch_paths()
def confirm_discard(self):
if self.modified:
rv = confirm(self,'Please confirm','Current document is not saved - do you want to continue?')
rv = confirm(
self,
"Please confirm",
"Current document is not saved - do you want to continue?",
)
else:
rv = True
return rv
def new(self):
if not self.confirm_discard():
return
if not self.confirm_discard(): return
self.set_text('')
self.filename = ''
self.set_text("")
self.filename = ""
self.reset_modified()
def open(self):
if not self.confirm_discard(): return
if not self.confirm_discard():
return
curr_dir = Path(self.filename).abspath().dirname()
fname = get_open_filename(self.EXTENSIONS, curr_dir)
if fname != '':
if fname != "":
self.load_from_file(fname)
def load_from_file(self,fname):
def load_from_file(self, fname):
self.set_text_from_file(fname)
self.filename = fname
self.reset_modified()
def save(self):
if self._filename != '':
if self.preferences['Autoreload']:
if self._filename != "":
if self.preferences["Autoreload"]:
self._file_watcher.blockSignals(True)
self._file_watch_timer.stop()
with open(self._filename, 'w') as f:
with open(self._filename, "w") as f:
f.write(self.toPlainText())
if self.preferences['Autoreload']:
if self.preferences["Autoreload"]:
self._file_watcher.blockSignals(False)
self.triggerRerender.emit(True)
@@ -179,20 +192,25 @@ class Editor(CodeEditor,ComponentMixin):
self.save_as()
def save_as(self):
fname = get_save_filename(self.EXTENSIONS)
if fname != '':
with open(fname,'w') as f:
if fname != "":
with open(fname, "w") as f:
f.write(self.toPlainText())
self.filename = fname
self.reset_modified()
def _update_filewatcher(self):
if self._watched_file and (self._watched_file != self.filename or not self.preferences['Autoreload']):
if self._watched_file and (
self._watched_file != self.filename or not self.preferences["Autoreload"]
):
self._clear_watched_paths()
self._watched_file = None
if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
if (
self.preferences["Autoreload"]
and self.filename
and self.filename != self._watched_file
):
self._watched_file = self._filename
self._watch_paths()
@@ -214,8 +232,8 @@ class Editor(CodeEditor,ComponentMixin):
def _watch_paths(self):
if Path(self._filename).exists():
self._file_watcher.addPath(self._filename)
if self.preferences['Autoreload: watch imported modules']:
module_paths = self.get_imported_module_paths(self._filename)
if self.preferences["Autoreload: watch imported modules"]:
module_paths = self.get_imported_module_paths(self._filename)
if module_paths:
self._file_watcher.addPaths(module_paths)
@@ -228,51 +246,45 @@ class Editor(CodeEditor,ComponentMixin):
# Turn autoreload on/off.
def autoreload(self, enabled):
self.preferences['Autoreload'] = enabled
self.preferences["Autoreload"] = enabled
self._update_filewatcher()
def reset_modified(self):
self.document().setModified(False)
@property
def modified(self):
return self.document().isModified()
def saveComponentState(self,store):
def saveComponentState(self, store):
if self.filename != "":
store.setValue(self.name + "/state", self.filename)
if self.filename != '':
store.setValue(self.name+'/state',self.filename)
def restoreComponentState(self, store):
filename = store.value(self.name + "/state")
def restoreComponentState(self,store):
filename = store.value(self.name+'/state')
if filename and self.filename == '':
if filename and self.filename == "":
try:
self.load_from_file(filename)
except IOError:
self._logger.warning(f'could not open {filename}')
self._logger.warning(f"could not open {filename}")
def get_imported_module_paths(self, module_path):
finder = ModuleFinder([os.path.dirname(module_path)])
imported_modules = []
try:
finder.run_script(module_path)
except SyntaxError as err:
self._logger.warning(f'Syntax error in {module_path}: {err}')
self._logger.warning(f"Syntax error in {module_path}: {err}")
except Exception as err:
self._logger.warning(
f'Cannot determine imported modules in {module_path}: {type(err).__name__} {err}'
f"Cannot determine imported modules in {module_path}: {type(err).__name__} {err}"
)
else:
for module_name, module in finder.modules.items():
if module_name != '__main__':
path = getattr(module, '__file__', None)
if module_name != "__main__":
path = getattr(module, "__file__", None)
if path is not None and os.path.isfile(path):
imported_modules.append(path)
@@ -280,8 +292,7 @@ class Editor(CodeEditor,ComponentMixin):
if __name__ == "__main__":
from PyQt5.QtWidgets import QApplication
from PySide6.QtWidgets import QApplication
app = QApplication(sys.argv)
editor = Editor()

View File

@@ -1,7 +1,7 @@
import logbook as logging
from PyQt5.QtWidgets import QPlainTextEdit
from PyQt5 import QtCore
from PySide6.QtWidgets import QPlainTextEdit
from PySide6 import QtCore
from ..mixins import ComponentMixin

View File

@@ -1,391 +1,425 @@
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 PySide6.QtWidgets import (
QTreeWidget,
QTreeWidgetItem,
QMenu,
QWidget,
QAbstractItemView,
)
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as 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)
sigObjectsAdded2 = pyqtSignal(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

@@ -1,8 +1,8 @@
from sys import platform
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtCore import pyqtSlot, pyqtSignal, Qt, QEvent
from PySide6.QtWidgets import QWidget, QApplication
from PySide6.QtCore import Slot as pyqtSlot, Signal as pyqtSignal, Qt, QEvent
import OCP
@@ -10,164 +10,149 @@ 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
class OCCTWidget(QWidget):
sigObjectSelected = pyqtSignal(list)
def __init__(self,parent=None):
super(OCCTWidget,self).__init__(parent)
def __init__(self, parent=None):
super(OCCTWidget, self).__init__(parent)
self.setAttribute(Qt.WA_NativeWindow)
self.setAttribute(Qt.WA_PaintOnScreen)
self.setAttribute(Qt.WA_NoSystemBackground)
self._initialized = False
self._needs_update = False
#OCCT secific things
# OCCT secific things
self.display_connection = Aspect_DisplayConnection()
self.graphics_driver = OpenGl_GraphicDriver(self.display_connection)
self.viewer = V3d_Viewer(self.graphics_driver)
self.view = self.viewer.CreateView()
self.context = AIS_InteractiveContext(self.viewer)
#Trihedorn, lights, etc
# Trihedorn, lights, etc
self.prepare_display()
def prepare_display(self):
view = self.view
params = view.ChangeRenderingParams()
params.NbMsaaSamples = 8
params.IsAntialiasingEnabled = True
view.TriedronDisplay(
Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER,
Quantity_Color(), 0.1)
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()
viewer.SetLightOn()
ctx = self.context
ctx.SetDisplayMode(AIS_DisplayMode.AIS_Shaded, True)
ctx.DefaultDrawer().SetFaceBoundaryDraw(True)
def wheelEvent(self, event):
delta = event.angleDelta().y()
factor = ZOOM_STEP if delta<0 else 1/ZOOM_STEP
factor = ZOOM_STEP if delta < 0 else 1 / ZOOM_STEP
self.view.SetZoom(factor)
def mousePressEvent(self,event):
def mousePressEvent(self, event):
pos = event.pos()
if event.button() == Qt.LeftButton:
self.view.StartRotation(pos.x(), pos.y())
elif event.button() == Qt.RightButton:
self.view.StartZoomAtPoint(pos.x(), pos.y())
self.old_pos = pos
def mouseMoveEvent(self,event):
def mouseMoveEvent(self, event):
pos = event.pos()
x,y = pos.x(),pos.y()
x, y = pos.x(), pos.y()
if event.buttons() == Qt.LeftButton:
self.view.Rotation(x,y)
self.view.Rotation(x, y)
elif event.buttons() == Qt.MiddleButton:
self.view.Pan(x - self.old_pos.x(),
self.old_pos.y() - y, theToStart=True)
self.view.Pan(x - self.old_pos.x(), self.old_pos.y() - y, theToStart=True)
elif event.buttons() == Qt.RightButton:
self.view.Pan(x - self.old_pos.x(),
self.old_pos.y() - y, theToStart=True)
self.view.Pan(x - self.old_pos.x(), self.old_pos.y() - y, theToStart=True)
self.old_pos = pos
def mouseReleaseEvent(self,event):
def mouseReleaseEvent(self, event):
if event.button() == Qt.LeftButton:
pos = event.pos()
x,y = pos.x(),pos.y()
self.context.MoveTo(x,y,self.view,True)
x, y = pos.x(), pos.y()
self.context.MoveTo(x, y, self.view, True)
self._handle_selection()
def _handle_selection(self):
self.context.Select(True)
self.context.InitSelected()
selected = []
if self.context.HasSelectedShape():
selected.append(self.context.SelectedShape())
self.sigObjectSelected.emit(selected)
def paintEngine(self):
return None
def paintEvent(self, event):
if not self._initialized:
self._initialize()
else:
self.view.Redraw()
def showEvent(self, event):
super(OCCTWidget,self).showEvent(event)
def resizeEvent(self, event):
super(OCCTWidget,self).resizeEvent(event)
self.view.MustBeResized()
def _initialize(self):
super(OCCTWidget, self).showEvent(event)
def resizeEvent(self, event):
super(OCCTWidget, self).resizeEvent(event)
self.view.MustBeResized()
def _initialize(self):
wins = {
'darwin' : self._get_window_osx,
'linux' : self._get_window_linux,
'win32': self._get_window_win
"darwin": self._get_window_osx,
"linux": self._get_window_linux,
"win32": self._get_window_win,
}
self.view.SetWindow(wins.get(platform,self._get_window_linux)(self.winId()))
self.view.SetWindow(wins.get(platform, self._get_window_linux)(self.winId()))
self._initialized = True
def _get_window_win(self,wid):
def _get_window_win(self, wid):
from OCP.WNT import WNT_Window
print(wid)
return WNT_Window(wid.ascapsule())
def _get_window_linux(self,wid):
def _get_window_linux(self, wid):
from OCP.Xw import Xw_Window
return Xw_Window(self.display_connection,int(wid))
def _get_window_osx(self,wid):
return Xw_Window(self.display_connection, int(wid))
def _get_window_osx(self, wid):
from OCP.Cocoa import Cocoa_Window
return Cocoa_Window(wid.ascapsule())

View File

@@ -1,97 +1,89 @@
from traceback import extract_tb, format_exception_only
from PyQt5.QtWidgets import (QWidget, QTreeWidget, QTreeWidgetItem, QAction,
QLabel)
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
from PySide6.QtWidgets import QWidget, QTreeWidget, QTreeWidgetItem, QLabel
from PySide6.QtGui import QAction
from PySide6.QtCore import Qt, Slot as pyqtSlot, Signal as pyqtSignal
from ..mixins import ComponentMixin
from ..utils import layout
class TracebackTree(QTreeWidget):
name = 'Traceback Viewer'
def __init__(self,parent):
super(TracebackTree,self).__init__(parent)
class TracebackTree(QTreeWidget):
name = "Traceback Viewer"
def __init__(self, parent):
super(TracebackTree, self).__init__(parent)
self.setHeaderHidden(False)
self.setItemsExpandable(False)
self.setRootIsDecorated(False)
self.setContextMenuPolicy(Qt.ActionsContextMenu)
self.setColumnCount(3)
self.setHeaderLabels(['File','Line','Code'])
self.setHeaderLabels(["File", "Line", "Code"])
self.root = self.invisibleRootItem()
class TracebackPane(QWidget,ComponentMixin):
class TracebackPane(QWidget, ComponentMixin):
sigHighlightLine = pyqtSignal(int)
def __init__(self,parent):
super(TracebackPane,self).__init__(parent)
def __init__(self, parent):
super(TracebackPane, self).__init__(parent)
self.tree = TracebackTree(self)
self.current_exception = QLabel(self)
self.current_exception.setStyleSheet(\
"QLabel {color : red; }");
layout(self,
(self.current_exception,
self.tree),
self)
self.current_exception.setStyleSheet("QLabel {color : red; }")
layout(self, (self.current_exception, self.tree), self)
self.tree.currentItemChanged.connect(self.handleSelection)
@pyqtSlot(object,str)
def addTraceback(self,exc_info,code):
@pyqtSlot(object, str)
def addTraceback(self, exc_info, code):
self.tree.clear()
if exc_info:
t,exc,tb = exc_info
t, exc, tb = exc_info
root = self.tree.root
code = code.splitlines()
tb = [t for t in extract_tb(tb) if '<string>' in t.filename] #ignore highest frames (debug, exec)
tb = [
t for t in extract_tb(tb) if "<string>" in t.filename
] # ignore highest frames (debug, exec)
for el in tb:
#workaround of the traceback module
if el.line == '':
line = code[el.lineno-1].strip()
# workaround of the traceback module
if el.line == "":
line = code[el.lineno - 1].strip()
else:
line = el.line
root.addChild(QTreeWidgetItem([el.filename,
str(el.lineno),
line]))
root.addChild(QTreeWidgetItem([el.filename, str(el.lineno), line]))
exc_name = t.__name__
exc_msg = str(exc)
exc_msg = exc_msg.replace('<', '&lt;').replace('>', '&gt;') #replace <>
exc_msg = exc_msg.replace("<", "&lt;").replace(">", "&gt;") # replace <>
self.current_exception.setText("<b>{}</b>: {}".format(exc_name, exc_msg))
self.current_exception.\
setText('<b>{}</b>: {}'.format(exc_name,exc_msg))
# handle the special case of a SyntaxError
if t is SyntaxError:
root.addChild(QTreeWidgetItem(
[exc.filename,
str(exc.lineno),
exc.text.strip() if exc.text else '']
))
if t is SyntaxError:
root.addChild(
QTreeWidgetItem(
[
exc.filename,
str(exc.lineno),
exc.text.strip() if exc.text else "",
]
)
)
else:
self.current_exception.setText('')
self.current_exception.setText("")
@pyqtSlot(QTreeWidgetItem,QTreeWidgetItem)
def handleSelection(self,item,*args):
@pyqtSlot(QTreeWidgetItem, QTreeWidgetItem)
def handleSelection(self, item, *args):
if item:
f,line = item.data(0,0),int(item.data(1,0))
if '<string>' in f:
f, line = item.data(0, 0), int(item.data(1, 0))
if "<string>" in f:
self.sigHighlightLine.emit(line)

View File

@@ -1,14 +1,21 @@
from PyQt5.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication, QAction
from PySide6.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication
from PyQt5.QtCore import pyqtSlot, pyqtSignal
from PyQt5.QtGui import QIcon
from PySide6.QtCore import Slot as pyqtSlot, Signal as pyqtSignal
from PySide6.QtGui import QIcon, QAction
from OCP.Graphic3d import Graphic3d_Camera, Graphic3d_StereoMode, Graphic3d_NOM_JADE,\
Graphic3d_MaterialAspect
from OCP.AIS import AIS_Shaded,AIS_WireFrame, AIS_ColoredShape, AIS_Axis
from OCP.Graphic3d import (
Graphic3d_Camera,
Graphic3d_StereoMode,
Graphic3d_NOM_JADE,
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_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
@@ -25,30 +32,66 @@ import qtawesome as qta
DEFAULT_EDGE_COLOR = Quantity_Color(BLACK)
DEFAULT_EDGE_WIDTH = 2
class OCCViewer(QWidget,ComponentMixin):
name = '3D Viewer'
class OCCViewer(QWidget, ComponentMixin):
name = "3D Viewer"
preferences = Parameter.create(name='Pref',children=[
{'name': 'Fit automatically', 'type': 'bool', 'value': True},
{'name': 'Use gradient', 'type': 'bool', 'value': False},
{'name': 'Background color', 'type': 'color', 'value': (95,95,95)},
{'name': 'Background color (aux)', 'type': 'color', 'value': (30,30,30)},
{'name': 'Default object color', 'type': 'color', 'value': "#FF0"},
{'name': 'Deviation', 'type': 'float', 'value': 1e-5, 'dec': True, 'step': 1},
{'name': 'Angular deviation', 'type': 'float', 'value': 0.1, 'dec': True, 'step': 1},
{'name': 'Projection Type', 'type': 'list', 'value': 'Orthographic',
'values': ['Orthographic', 'Perspective', 'Stereo', 'MonoLeftEye', 'MonoRightEye']},
{'name': 'Stereo Mode', 'type': 'list', 'value': 'QuadBuffer',
'values': ['QuadBuffer', 'Anaglyph', 'RowInterlaced', 'ColumnInterlaced',
'ChessBoard', 'SideBySide', 'OverUnder']}])
IMAGE_EXTENSIONS = 'png'
preferences = Parameter.create(
name="Pref",
children=[
{"name": "Fit automatically", "type": "bool", "value": True},
{"name": "Use gradient", "type": "bool", "value": False},
{"name": "Background color", "type": "color", "value": (95, 95, 95)},
{"name": "Background color (aux)", "type": "color", "value": (30, 30, 30)},
{"name": "Default object color", "type": "color", "value": "#FF0"},
{
"name": "Deviation",
"type": "float",
"value": 1e-5,
"dec": True,
"step": 1,
},
{
"name": "Angular deviation",
"type": "float",
"value": 0.1,
"dec": True,
"step": 1,
},
{
"name": "Projection Type",
"type": "list",
"value": "Orthographic",
"values": [
"Orthographic",
"Perspective",
"Stereo",
"MonoLeftEye",
"MonoRightEye",
],
},
{
"name": "Stereo Mode",
"type": "list",
"value": "QuadBuffer",
"values": [
"QuadBuffer",
"Anaglyph",
"RowInterlaced",
"ColumnInterlaced",
"ChessBoard",
"SideBySide",
"OverUnder",
],
},
],
)
IMAGE_EXTENSIONS = "png"
sigObjectSelected = pyqtSignal(list)
def __init__(self,parent=None):
super(OCCViewer,self).__init__(parent)
def __init__(self, parent=None):
super(OCCViewer, self).__init__(parent)
ComponentMixin.__init__(self)
self.canvas = OCCTWidget()
@@ -56,15 +99,18 @@ class OCCViewer(QWidget,ComponentMixin):
self.create_actions(self)
self.layout_ = layout(self,
[self.canvas,],
top_widget=self,
margin=0)
self.setup_default_drawer() #misspelled in original
self.layout_ = layout(
self,
[
self.canvas,
],
top_widget=self,
margin=0,
)
self.setup_default_drawer() # misspelled in original
self.updatePreferences()
def setup_default_drawer(self):
def setup_default_drawer(self):
# set the default color and material
material = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE)
@@ -76,99 +122,129 @@ class OCCViewer(QWidget,ComponentMixin):
line_aspect = self.canvas.context.DefaultDrawer().FaceBoundaryAspect()
line_aspect.SetWidth(DEFAULT_EDGE_WIDTH)
line_aspect.SetColor(DEFAULT_EDGE_COLOR)
def updatePreferences(self,*args):
color1 = to_occ_color(self.preferences['Background color'])
color2 = to_occ_color(self.preferences['Background color (aux)'])
def updatePreferences(self, *args):
color1 = to_occ_color(self.preferences["Background color"])
color2 = to_occ_color(self.preferences["Background color (aux)"])
if not self.preferences['Use gradient']:
if not self.preferences["Use gradient"]:
color2 = color1
self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True)
self.canvas.view.SetBgGradientColors(color1, color2, theToUpdate=True)
self.canvas.update()
ctx = self.canvas.context
ctx.SetDeviationCoefficient(self.preferences['Deviation'])
ctx.SetDeviationAngle(self.preferences['Angular deviation'])
ctx.SetDeviationCoefficient(self.preferences["Deviation"])
ctx.SetDeviationAngle(self.preferences["Angular deviation"])
v = self._get_view()
camera = v.Camera()
projection_type = self.preferences['Projection Type']
camera.SetProjectionType(getattr(Graphic3d_Camera, f'Projection_{projection_type}',
Graphic3d_Camera.Projection_Orthographic))
projection_type = self.preferences["Projection Type"]
camera.SetProjectionType(
getattr(
Graphic3d_Camera,
f"Projection_{projection_type}",
Graphic3d_Camera.Projection_Orthographic,
)
)
# onle relevant for stereo projection
stereo_mode = self.preferences['Stereo Mode']
stereo_mode = self.preferences["Stereo Mode"]
params = v.ChangeRenderingParams()
params.StereoMode = getattr(Graphic3d_StereoMode, f'Graphic3d_StereoMode_{stereo_mode}',
Graphic3d_StereoMode.Graphic3d_StereoMode_QuadBuffer)
params.StereoMode = getattr(
Graphic3d_StereoMode,
f"Graphic3d_StereoMode_{stereo_mode}",
Graphic3d_StereoMode.Graphic3d_StereoMode_QuadBuffer,
)
def create_actions(self,parent):
self._actions = \
{'View' : [QAction(qta.icon('fa.arrows-alt'),
'Fit (Shift+F1)',
parent,
shortcut='shift+F1',
triggered=self.fit),
QAction(QIcon(':/images/icons/isometric_view.svg'),
'Iso (Shift+F2)',
parent,
shortcut='shift+F2',
triggered=self.iso_view),
QAction(QIcon(':/images/icons/top_view.svg'),
'Top (Shift+F3)',
parent,
shortcut='shift+F3',
triggered=self.top_view),
QAction(QIcon(':/images/icons/bottom_view.svg'),
'Bottom (Shift+F4)',
parent,
shortcut='shift+F4',
triggered=self.bottom_view),
QAction(QIcon(':/images/icons/front_view.svg'),
'Front (Shift+F5)',
parent,
shortcut='shift+F5',
triggered=self.front_view),
QAction(QIcon(':/images/icons/back_view.svg'),
'Back (Shift+F6)',
parent,
shortcut='shift+F6',
triggered=self.back_view),
QAction(QIcon(':/images/icons/left_side_view.svg'),
'Left (Shift+F7)',
parent,
shortcut='shift+F7',
triggered=self.left_view),
QAction(QIcon(':/images/icons/right_side_view.svg'),
'Right (Shift+F8)',
parent,
shortcut='shift+F8',
triggered=self.right_view),
QAction(qta.icon('fa.square-o'),
'Wireframe (Shift+F9)',
parent,
shortcut='shift+F9',
triggered=self.wireframe_view),
QAction(qta.icon('fa.square'),
'Shaded (Shift+F10)',
parent,
shortcut='shift+F10',
triggered=self.shaded_view)],
'Tools' : [QAction(icon('screenshot'),
'Screenshot',
parent,
triggered=self.save_screenshot)]}
def create_actions(self, parent):
self._actions = {
"View": [
QAction(
qta.icon("fa.arrows-alt"),
"Fit (Shift+F1)",
parent,
shortcut="shift+F1",
triggered=self.fit,
),
QAction(
QIcon(":/images/icons/isometric_view.svg"),
"Iso (Shift+F2)",
parent,
shortcut="shift+F2",
triggered=self.iso_view,
),
QAction(
QIcon(":/images/icons/top_view.svg"),
"Top (Shift+F3)",
parent,
shortcut="shift+F3",
triggered=self.top_view,
),
QAction(
QIcon(":/images/icons/bottom_view.svg"),
"Bottom (Shift+F4)",
parent,
shortcut="shift+F4",
triggered=self.bottom_view,
),
QAction(
QIcon(":/images/icons/front_view.svg"),
"Front (Shift+F5)",
parent,
shortcut="shift+F5",
triggered=self.front_view,
),
QAction(
QIcon(":/images/icons/back_view.svg"),
"Back (Shift+F6)",
parent,
shortcut="shift+F6",
triggered=self.back_view,
),
QAction(
QIcon(":/images/icons/left_side_view.svg"),
"Left (Shift+F7)",
parent,
shortcut="shift+F7",
triggered=self.left_view,
),
QAction(
QIcon(":/images/icons/right_side_view.svg"),
"Right (Shift+F8)",
parent,
shortcut="shift+F8",
triggered=self.right_view,
),
QAction(
qta.icon("fa.square-o"),
"Wireframe (Shift+F9)",
parent,
shortcut="shift+F9",
triggered=self.wireframe_view,
),
QAction(
qta.icon("fa.square"),
"Shaded (Shift+F10)",
parent,
shortcut="shift+F10",
triggered=self.shaded_view,
),
],
"Tools": [
QAction(
icon("screenshot"),
"Screenshot",
parent,
triggered=self.save_screenshot,
)
],
}
def toolbarActions(self):
return self._actions['View']
return self._actions["View"]
def clear(self):
self.displayed_shapes = []
self.displayed_ais = []
self.canvas.context.EraseAll(True)
@@ -176,199 +252,167 @@ class OCCViewer(QWidget,ComponentMixin):
context.PurgeDisplay()
context.RemoveAll(True)
def _display(self,shape):
def _display(self, shape):
ais = make_AIS(shape)
self.canvas.context.Display(shape,True)
self.canvas.context.Display(shape, True)
self.displayed_shapes.append(shape)
self.displayed_ais.append(ais)
#self.canvas._display.Repaint()
# self.canvas._display.Repaint()
@pyqtSlot(object)
def display(self,ais):
def display(self, ais):
context = self._get_context()
context.Display(ais,True)
context.Display(ais, True)
if self.preferences['Fit automatically']: self.fit()
if self.preferences["Fit automatically"]:
self.fit()
@pyqtSlot(list)
@pyqtSlot(list,bool)
def display_many(self,ais_list,fit=None):
@pyqtSlot(list, bool)
def display_many(self, ais_list, fit=None):
context = self._get_context()
for ais in ais_list:
context.Display(ais,True)
context.Display(ais, True)
if self.preferences['Fit automatically'] and fit is None:
if self.preferences["Fit automatically"] and fit is None:
self.fit()
elif fit:
self.fit()
@pyqtSlot(QTreeWidgetItem,int)
def update_item(self,item,col):
@pyqtSlot(QTreeWidgetItem, int)
def update_item(self, item, col):
ctx = self._get_context()
if item.checkState(0):
ctx.Display(item.ais,True)
ctx.Display(item.ais, True)
else:
ctx.Erase(item.ais,True)
ctx.Erase(item.ais, True)
@pyqtSlot(list)
def remove_items(self,ais_items):
def remove_items(self, ais_items):
ctx = self._get_context()
for ais in ais_items: ctx.Erase(ais,True)
for ais in ais_items:
ctx.Erase(ais, True)
@pyqtSlot()
def redraw(self):
self._get_viewer().Redraw()
def fit(self):
self.canvas.view.FitAll()
def iso_view(self):
v = self._get_view()
v.SetProj(1,-1,1)
v.SetProj(1, -1, 1)
v.SetTwist(0)
def bottom_view(self):
v = self._get_view()
v.SetProj(0,0,-1)
v.SetProj(0, 0, -1)
v.SetTwist(0)
def top_view(self):
v = self._get_view()
v.SetProj(0,0,1)
v.SetProj(0, 0, 1)
v.SetTwist(0)
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):
v = self._get_view()
v.SetProj(-1,0,0)
v.SetProj(-1, 0, 0)
v.SetTwist(0)
def right_view(self):
v = self._get_view()
v.SetProj(1,0,0)
v.SetProj(1, 0, 0)
v.SetTwist(0)
def shaded_view(self):
c = self._get_context()
c.SetDisplayMode(AIS_Shaded, True)
def wireframe_view(self):
c = self._get_context()
c.SetDisplayMode(AIS_WireFrame, True)
def show_grid(self,
step=1.,
size=10.+1e-6,
color1=(.7,.7,.7),
color2=(0,0,0)):
def show_grid(
self, step=1.0, size=10.0 + 1e-6, color1=(0.7, 0.7, 0.7), color2=(0, 0, 0)
):
viewer = self._get_viewer()
viewer.ActivateGrid(Aspect_GT_Rectangular,
Aspect_GDM_Lines)
viewer.ActivateGrid(Aspect_GT_Rectangular, Aspect_GDM_Lines)
viewer.SetRectangularGridGraphicValues(size, size, 0)
viewer.SetRectangularGridValues(0, 0, step, step, 0)
grid = viewer.Grid()
grid.SetColors(Quantity_Color(*color1,TOC_RGB),
Quantity_Color(*color2,TOC_RGB))
grid.SetColors(
Quantity_Color(*color1, TOC_RGB), Quantity_Color(*color2, TOC_RGB)
)
def hide_grid(self):
viewer = self._get_viewer()
viewer.DeactivateGrid()
@pyqtSlot(bool,float)
@pyqtSlot(bool, float)
@pyqtSlot(bool)
def toggle_grid(self,
value : bool,
dim : float = 10.):
def toggle_grid(self, value: bool, dim: float = 10.0):
if value:
self.show_grid(step=dim/20,size=dim+1e-9)
self.show_grid(step=dim / 20, size=dim + 1e-9)
else:
self.hide_grid()
@pyqtSlot(gp_Ax3)
def set_grid_orientation(self,orientation : gp_Ax3):
def set_grid_orientation(self, orientation: gp_Ax3):
viewer = self._get_viewer()
viewer.SetPrivilegedPlane(orientation)
def show_axis(self,origin = (0,0,0), direction=(0,0,1)):
ax_placement = Geom_Axis1Placement(gp_Ax1(gp_Pnt(*origin),
gp_Dir(*direction)))
def show_axis(self, origin=(0, 0, 0), direction=(0, 0, 1)):
ax_placement = Geom_Axis1Placement(gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction)))
ax = AIS_Axis(ax_placement)
self._display_ais(ax)
def save_screenshot(self):
fname = get_save_filename(self.IMAGE_EXTENSIONS)
if fname != '':
self._get_view().Dump(fname)
def _display_ais(self,ais):
if fname != "":
self._get_view().Dump(fname)
def _display_ais(self, ais):
self._get_context().Display(ais)
def _get_view(self):
return self.canvas.view
def _get_viewer(self):
return self.canvas.viewer
def _get_context(self):
return self.canvas.context
@pyqtSlot(list)
def handle_selection(self,obj):
def handle_selection(self, obj):
self.sigObjectSelected.emit(obj)
@pyqtSlot(list)
def set_selected(self,ais):
def set_selected(self, ais):
ctx = self._get_context()
ctx.ClearSelected(False)
for obj in ais:
ctx.AddOrRemoveSelected(obj,False)
ctx.AddOrRemoveSelected(obj, False)
self.redraw()
if __name__ == "__main__":
# pass
import sys
from OCP.BRepPrimAPI import BRepPrimAPI_MakeBox
@@ -379,10 +423,10 @@ if __name__ == "__main__":
dlg.setFixedHeight(400)
dlg.setFixedWidth(600)
layout(dlg,(viewer,),dlg)
layout(dlg, (viewer,), dlg)
dlg.show()
box = BRepPrimAPI_MakeBox(20,20,30)
box = BRepPrimAPI_MakeBox(20, 20, 30)
box_ais = AIS_ColoredShape(box.Shape())
viewer.display(box_ais)

View File

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

View File

@@ -13,13 +13,13 @@ 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-310-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-310-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.cp310-win_amd64.pyd'), '.')]
datas1, binaries1, hiddenimports1 = collect_all('debugpy')
hiddenimports2 = collect_submodules('xmlrpc')
@@ -50,13 +50,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

@@ -26,15 +26,14 @@ setup(
"CQ-editor = cq_editor.__main__:main",
]
},
python_requires=">=3.8,<3.11",
python_requires=">=3.8,<3.12",
install_requires=[
"logbook>=1",
"ipython==8.4.0",
"jedi==0.17.2",
"ipython",
"path>=16",
"PyQt5>=5",
"PySide6",
"requests>=2,<3",
"spyder>=5,<6",
"pyqtgraph==0.12.4",
"pyqtgraph",
],
)