diff --git a/.github/workflows/pyinstaller-builds-actions-PIP-TAR.yml b/.github/workflows/pyinstaller-builds-actions-PIP-TAR.yml
new file mode 100644
index 00000000..24957639
--- /dev/null
+++ b/.github/workflows/pyinstaller-builds-actions-PIP-TAR.yml
@@ -0,0 +1,177 @@
+name: build-PIP-TAR
+on:
+ schedule:
+ - cron: '0 0 * * 1'
+ workflow_dispatch:
+ inputs:
+ type:
+ description: 'Whether to build a single file (onefile) or directory (dir) dist'
+ required: true
+ default: 'dir'
+jobs:
+ build-linux-x86_64:
+ runs-on: ubuntu-22.04
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ environment-name: test
+ environment-file: environment.yml
+ init-shell: >-
+ bash
+ # create-args: >-
+ - name: pip install cadquery CQ-editor ... etc
+ shell: bash --login {0}
+ run: |
+ sudo apt install -y libblas-dev libblas3 libblas64-3 libblas64-dev
+ sudo apt install -y libxkbcommon0 libxkbcommon-x11-0 libxcb-xinerama0
+ sudo apt install -y qtbase5-dev qt5-qmake
+ micromamba info
+ pip install pyopengl
+ pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
+ pip install -vvv --pre git+https://github.com/cadquery/cadquery casadi
+ pip install pyinstaller>=5.6
+ pip install path
+ pip install jupyter-rfb
+ pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
+ pip install git+https://github.com/gumyr/bd_warehouse
+ pip install git+https://github.com/meadiode/cq_gears.git@main
+ pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
+ pip install git+https://github.com/gumyr/build123d
+ pip install git+https://github.com/JustinSDK/cqMore
+ pip list
+ - name: Run build
+ shell: bash --login {0}
+ run: |
+ micromamba activate test
+ micromamba info
+ echo $LD_LIBRARY_PATH
+ export LD_LIBRARY_PATH=/home/runner/micromamba/envs/test/lib
+ echo $LD_LIBRARY_PATH
+ pyinstaller --log-level=DEBUG pyinstaller_pip.spec ${{ github.event.inputs.type }}
+ cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
+ - uses: actions/upload-artifact@v4
+ with:
+ name: CQ-editor-Linux-x86_64
+ path: dist
+ build-macos-x86_64:
+ runs-on: macos-13
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ environment-name: test
+ environment-file: environment.yml
+ init-shell: >-
+ bash
+ - name: pip install cadquery CQ-editor ... etc
+ shell: bash --login {0}
+ run: |
+ micromamba info
+ pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
+ pip install --pre git+https://github.com/cadquery/cadquery casadi
+ pip install pyinstaller>=5.6
+ pip install path
+ pip uninstall -y PyQt5
+ pip install PyQt5==5.15.7
+ pip install PyQtWebEngine==5.15.6
+ pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
+ pip install git+https://github.com/gumyr/bd_warehouse
+ pip install git+https://github.com/meadiode/cq_gears.git@main
+ pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
+ pip install git+https://github.com/gumyr/build123d
+ pip install git+https://github.com/JustinSDK/cqMore
+ pip list
+ - name: Run build
+ shell: bash --login {0}
+ run: |
+ micromamba activate test
+ micromamba info
+ pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
+ cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor-mac.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/CQ-editor.sh
+ - uses: actions/upload-artifact@v4
+ with:
+ name: CQ-editor-MacOS-x86_64
+ path: dist
+ build-macos-arm64:
+ runs-on: macos-14
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ environment-name: test
+ environment-file: environment.yml
+ init-shell: >-
+ bash
+ - name: pip install cadquery CQ-editor ... etc
+ shell: bash --login {0}
+ run: |
+ micromamba info
+ pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
+ pip install https://github.com/CadQuery/ocp-build-system/releases/download/7.7.2.0/cadquery_ocp-7.7.2-cp311-cp311-macosx_11_0_arm64.whl
+ pip install https://github.com/jdegenstein/nlopt-python/releases/download/2.7.1.3/nlopt-2.7.1-cp311-cp311-macosx_11_0_arm64.whl
+ pip install --pre git+https://github.com/cadquery/cadquery casadi
+ pip install pyinstaller>=5.6
+ pip install path
+ pip uninstall -y PyQt5
+ pip install PyQt5==5.15.10
+ pip install PyQtWebEngine==5.15.6
+ pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
+ pip install git+https://github.com/gumyr/bd_warehouse
+ pip install git+https://github.com/meadiode/cq_gears.git@main
+ pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
+ pip install git+https://github.com/gumyr/build123d
+ pip install git+https://github.com/JustinSDK/cqMore
+ pip list
+ - name: Run build
+ shell: bash --login {0}
+ run: |
+ micromamba activate test
+ micromamba info
+ pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
+ cp /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor-mac.sh /Users/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/CQ-editor.sh
+ - uses: actions/upload-artifact@v4
+ with:
+ name: CQ-editor-MacOS-arm64
+ path: dist
+ build-windows:
+ runs-on: windows-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: mamba-org/setup-micromamba@v1
+ with:
+ environment-name: test
+ environment-file: environment.yml
+ init-shell: >-
+ bash
+ - name: pip install cadquery CQ-editor ... etc
+ shell: bash --login {0}
+ run: |
+ micromamba info
+ pip install git+https://github.com/jdegenstein/jmwright-CQ-Editor
+ pip install --pre git+https://github.com/cadquery/cadquery casadi
+ pip install pyinstaller>=5.6
+ pip install path
+ pip install git+https://github.com/gumyr/cq_warehouse.git#egg=cq_warehouse
+ pip install git+https://github.com/gumyr/bd_warehouse
+ pip install git+https://github.com/meadiode/cq_gears.git@main
+ pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache"
+ pip install git+https://github.com/gumyr/build123d
+ pip install git+https://github.com/JustinSDK/cqMore
+ pip list
+ - name: Run build
+ shell: bash --login {0}
+ run: |
+ micromamba activate test
+ micromamba info
+ pyinstaller pyinstaller_pip.spec ${{ github.event.inputs.type }}
+ cp D:/a/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.cmd D:/a/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/
+
+ - name: 7zip artifact (workaround for too many files during artifact upload)
+ shell: bash --login {0}
+ run: 7z a release.zip ./dist/*
+
+ - uses: actions/upload-artifact@v4
+ with:
+ name: CQ-editor-Windows
+ path: release.zip
diff --git a/README.md b/README.md
index 3a128889..7a0a5679 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,16 @@
# CadQuery editor
+<<<<<<< HEAD
+This is a fork of [jmwright's fork](https://github.com/jmwright/CQ-editor) of [CadQuery/CQ-editor](https://github.com/CadQuery/CQ-editor). This fork includes changes that enable dark mode for CQ-editor (see screenshot below). Under the GitHub Actions menu this fork also contains static builds of CQ-editor for Linux/MacOS/Windows that include the [cq_gears](https://github.com/meadiode/cq_gears), [cq_cache](https://github.com/CadQuery/cadquery-plugins/tree/main/plugins/cq_cache), [cq_more](https://github.com/JustinSDK/cqMore), [cq_warehouse](https://github.com/gumyr/cq_warehouse), [bd_warehouse](https://github.com/gumyr/bd_warehouse), and [build123d](https://github.com/gumyr/build123d) libraries. Note you need to change color preferences to enable dark mode for all panes (see Edit -> Preferences).
+
+This fork also contains additional changes to the `show_object` function in CQ-editor that make it easier to display and export build123d objects and object lists.
+
+Running into issues? Please click here to join the [***CadQuery, CQ-Editor, and build123d Discord***](https://discord.com/invite/Bj9AQPsCfx)
+
+
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
[](https://ci.appveyor.com/project/adam-urbanczyk/cq-editor/branch/master)
[](https://codecov.io/gh/CadQuery/CQ-editor)
[](https://dev.azure.com/cadquery/CQ-editor/_build/latest?definitionId=3&branchName=master)
@@ -13,7 +24,10 @@ CadQuery GUI editor based on PyQT supports Linux, Windows and Mac.
## Notable features
+<<<<<<< HEAD
+=======
* Automatic code reloading - you can use your favourite editor
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
* OCCT based
* Graphical debugger for CadQuery scripts
* Step through script and watch how your model changes
@@ -26,6 +40,27 @@ CadQuery GUI editor based on PyQT supports Linux, Windows and Mac.
## Installation - Pre-Built Packages (Recommended)
+<<<<<<< HEAD
+### Release Packages
+
+Stable release builds which do not require Anaconda are attached to the [latest release](https://github.com/jdegenstein/jmwright-CQ-editor/releases). Download the zip file for your operating system, extract it, and run the CQ-editor script for your OS (CQ-editor.cmd for Windows, CQ-editor.sh for Linux and MacOS). On Windows you should be able to simply double-click on CQ-editor.cmd. On Linux and MacOS you may need to make the script executable with `chmod +x CQ-editor.sh` and run the script from the command line. On later MacOS versions you may also need `xattr -r -d com.apple.quarantine path/to/CQ-editor-MacOS`. The script contains an environment variable export that may be required to get CQ-editor to launch correctly on MacOS Big Sur, so it is better to use the script than to launch CQ-editor directly.
+
+### Development Packages
+
+Development builds are also available, but can be unstable and should be used at your own risk. Click on the newest build with a green checkmark [here](https://github.com/jdegenstein/jmwright-CQ-editor/actions), wait for the _Artifacts_ section at the bottom of the page to load, and then click on the appropriate download for your operating system. Extract the archive file and run the shell (Linux/MacOS) or cmd (Windows) script in the root CQ-editor directory. The CQ-editor window should launch.
+
+## Installation (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 "cadquery>=2.2"
+pip install git+https://github.com/gumyr/build123d
+=======
~~### Release Packages~~
~~Stable release builds which do not require Anaconda are attached to the [latest release](https://github.com/CadQuery/CQ-editor/releases). Download installer for your operating system, extract it, and run the CQ-editor script for your OS (CQ-editor.cmd for Windows, CQ-editor.sh for Linux and MacOS). On Windows you should be able to simply double-click on CQ-editor.cmd. On Linux and MacOS you may need to make the script executable with `chmod +x CQ-editor.sh` and run the script from the command line. The script contains an environment variable export that may be required to get CQ-editor to launch correctly on MacOS Big Sur, so it is better to use the script than to launch CQ-editor directly.~~
@@ -66,6 +101,7 @@ sudo apt install libglu1-mesa libgl1-mesa-dri mesa-common-dev libglu1-mesa-dev
On Fedora 29 the packages can be installed as follows:
```
dnf install -y mesa-libGLU mesa-libGL mesa-libGLU-devel
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
```
## Usage
@@ -76,6 +112,11 @@ By default, CQ-editor will display a 3D representation of all `Workplane` object
```python
show_object(result, name="somename", options={"alpha":0.5, "color": (64, 164, 223)})
+<<<<<<< HEAD
+# or using rand_color:
+show_object(result, name="somename", options=rand_color(alpha=.5))
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
```
Note that `show_object` works for `Shape` and `TopoDS_Shape` objects too. In order to display objects from the embedded Python console use `show`.
diff --git a/appveyor.yml b/appveyor.yml
index 60ee6084..d99eb304 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -2,6 +2,10 @@ shallow_clone: false
image:
- Ubuntu2004
+<<<<<<< HEAD
+ - Ubuntu1804
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
- Visual Studio 2015
environment:
@@ -13,11 +17,19 @@ environment:
install:
- sh: if [[ $APPVEYOR_BUILD_WORKER_IMAGE != "macOS"* ]]; then sudo apt update; sudo apt -y --force-yes install libglu1-mesa xvfb libgl1-mesa-dri mesa-common-dev libglu1-mesa-dev; fi
+<<<<<<< HEAD
+ - sh: if [[ $APPVEYOR_BUILD_WORKER_IMAGE != "macOS"* ]]; then curl -fsSL -o miniconda.sh https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh; fi
+ - sh: if [[ $APPVEYOR_BUILD_WORKER_IMAGE == "macOS"* ]]; then curl -fsSL -o miniconda.sh https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-MacOSX-x86_64.sh; fi
+ - sh: bash miniconda.sh -b -p $HOME/miniconda
+ - sh: source $HOME/miniconda/bin/activate
+ - cmd: curl -fsSL -o miniconda.exe https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Windows-x86_64.exe
+=======
- sh: if [[ $APPVEYOR_BUILD_WORKER_IMAGE != "macOS"* ]]; then curl -fsSL -o miniconda.sh https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Linux-x86_64.sh; fi
- sh: if [[ $APPVEYOR_BUILD_WORKER_IMAGE == "macOS"* ]]; then curl -fsSL -o miniconda.sh https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Darwin-x86_64.sh; fi
- sh: bash miniconda.sh -b -p $HOME/miniconda
- sh: source $HOME/miniconda/bin/activate
- cmd: curl -fsSL -o miniconda.exe https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-Windows-x86_64.exe
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
- cmd: miniconda.exe /S /InstallationType=JustMe /D=%MINICONDA_DIRNAME%
- cmd: set "PATH=%MINICONDA_DIRNAME%;%MINICONDA_DIRNAME%\\Scripts;%PATH%"
- cmd: activate
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 77e02d43..c0891187 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -3,8 +3,11 @@ trigger:
include:
- master
- refs/tags/*
+<<<<<<< HEAD
+=======
exclude:
- refs/tags/nightly
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
pr:
- master
@@ -13,13 +16,48 @@ resources:
repositories:
- repository: templates
type: github
+<<<<<<< HEAD
+ name: jmwright/conda-packages
+=======
name: CadQuery/conda-packages
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
endpoint: CadQuery
parameters:
- name: minor
type: object
default:
+<<<<<<< HEAD
+ - 8
+ - 9
+ - 10
+
+jobs:
+- ${{ each minor in parameters.minor }}:
+ - template: conda-build.yml@templates
+ parameters:
+ name: Linux
+ vmImage: 'ubuntu-18.04'
+ py_maj: 3
+ py_min: ${{minor}}
+ conda_bld: 3.21.6
+
+ - template: conda-build.yml@templates
+ parameters:
+ name: macOS
+ vmImage: 'macOS-10.15'
+ py_maj: 3
+ py_min: ${{minor}}
+ conda_bld: 3.21.6
+
+ - template: conda-build.yml@templates
+ parameters:
+ name: Windows
+ vmImage: 'windows-latest'
+ py_maj: 3
+ py_min: ${{minor}}
+ conda_bld: 3.21.6
+=======
- 11
stages:
@@ -86,3 +124,4 @@ stages:
artifact: installer_ubuntu-latest
- bash: cp $(Pipeline.Workspace)/installer*/*.* .
- bash: sh ./CQ-editor-master-Linux-x86_64.sh -b -p dummy && cd dummy && ./run.sh
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/conda/meta.yaml b/conda/meta.yaml
index b6a95f53..3b2553c2 100644
--- a/conda/meta.yaml
+++ b/conda/meta.yaml
@@ -6,19 +6,31 @@ source:
path: ..
build:
+<<<<<<< HEAD
+ string: {{ 'py'+environ.get('PYTHON_VERSION')}}
+=======
string: {{ GIT_DESCRIBE_TAG }}_{{ GIT_BUILD_STR }}
noarch: python
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
script: python setup.py install --single-version-externally-managed --record=record.txt
entry_points:
- cq-editor = cq_editor.__main__:main
- CQ-editor = cq_editor.__main__:main
requirements:
build:
+<<<<<<< HEAD
+ - python {{ environ.get('PYTHON_VERSION') }}
+ - setuptools
+
+ run:
+ - python {{ environ.get('PYTHON_VERSION') }}
+=======
- python >=3.8
- setuptools
run:
- python >=3.8
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
- cadquery=master
- ocp
- logbook
@@ -28,7 +40,11 @@ requirements:
- path
- logbook
- requests
+<<<<<<< HEAD
+
+=======
- qtconsole=5.4.1
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
test:
imports:
- cq_editor
diff --git a/cq_editor/cq_utils.py b/cq_editor/cq_utils.py
index 2378a82c..035205fe 100644
--- a/cq_editor/cq_utils.py
+++ b/cq_editor/cq_utils.py
@@ -1,3 +1,238 @@
+<<<<<<< HEAD
+import cadquery as cq
+from cadquery.occ_impl.assembly import toCAF
+
+from typing import List, Union
+from importlib import reload
+from types import SimpleNamespace
+
+from OCP.XCAFPrs import XCAFPrs_AISObject
+from OCP.TopoDS import TopoDS_Shape
+from OCP.AIS import AIS_InteractiveObject, AIS_Shape
+from OCP.Quantity import (
+ Quantity_TOC_RGB as TOC_RGB,
+ Quantity_Color,
+ Quantity_NOC_GOLD as GOLD,
+)
+from OCP.Graphic3d import Graphic3d_NOM_JADE, Graphic3d_MaterialAspect
+
+from PyQt5.QtGui import QColor
+
+DEFAULT_FACE_COLOR = Quantity_Color(GOLD)
+DEFAULT_MATERIAL = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE)
+
+
+def find_cq_objects(results: dict):
+
+ return {
+ k: SimpleNamespace(shape=v, options={})
+ for k, v in results.items()
+ if isinstance(v, cq.Workplane)
+ }
+
+
+def to_compound(
+ obj: Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch],
+):
+
+ vals = []
+
+ if isinstance(obj, cq.Workplane):
+ vals.extend(obj.vals())
+ elif isinstance(obj, cq.Shape):
+ vals.append(obj)
+ elif isinstance(obj, list) and isinstance(obj[0], cq.Workplane):
+ for o in obj:
+ vals.extend(o.vals())
+ elif isinstance(obj, list) and isinstance(obj[0], cq.Shape):
+ vals.extend(obj)
+ elif isinstance(obj, TopoDS_Shape):
+ vals.append(cq.Shape.cast(obj))
+ elif isinstance(obj, list) and isinstance(obj[0], TopoDS_Shape):
+ vals.extend(cq.Shape.cast(o) for o in obj)
+ elif hasattr(obj, "wrapped") and isinstance(obj.wrapped, TopoDS_Shape):
+ vals.append(cq.Shape.cast(obj.wrapped))
+ elif (
+ isinstance(obj, list)
+ and hasattr(obj[0], "wrapped")
+ and isinstance(obj[0].wrapped, TopoDS_Shape)
+ ):
+ vals.extend(o for o in obj)
+ elif (
+ hasattr(obj, "_obj")
+ and hasattr(obj._obj, "wrapped")
+ and isinstance(obj._obj.wrapped, TopoDS_Shape)
+ ):
+ vals.append(cq.Shape.cast(obj._obj.wrapped))
+ elif (
+ isinstance(obj, list)
+ and hasattr(obj[0], "_obj")
+ and hasattr(obj[0]._obj, "wrapped")
+ and isinstance(obj[0]._obj.wrapped, TopoDS_Shape)
+ ):
+ vals.append(o for o in obj)
+ elif isinstance(obj, cq.Sketch):
+ if obj._faces:
+ vals.append(obj._faces)
+ else:
+ vals.extend(obj._edges)
+ else:
+ raise ValueError(f"Invalid type {type(obj)}")
+
+ return cq.Compound.makeCompound(vals)
+
+
+def to_workplane(obj: cq.Shape):
+
+ rv = cq.Workplane("XY")
+ rv.objects = [
+ obj,
+ ]
+
+ return rv
+
+
+def make_AIS(
+ obj: Union[
+ cq.Workplane,
+ List[cq.Workplane],
+ cq.Shape,
+ List[cq.Shape],
+ cq.Assembly,
+ AIS_InteractiveObject,
+ ],
+ options={},
+):
+
+ shape = None
+
+ if isinstance(obj, cq.Assembly):
+ label, shape = toCAF(obj)
+ ais = XCAFPrs_AISObject(label)
+ elif isinstance(obj, AIS_InteractiveObject):
+ ais = obj
+ else:
+ shape = to_compound(obj)
+ ais = AIS_Shape(shape.wrapped)
+
+ set_material(ais, DEFAULT_MATERIAL)
+ set_color(ais, DEFAULT_FACE_COLOR)
+
+ if "alpha" in options:
+ set_transparency(ais, options["alpha"])
+ if "color" in options:
+ set_color(ais, to_occ_color(options["color"]))
+ if "rgba" in options:
+ r, g, b, a = options["rgba"]
+ set_color(ais, to_occ_color((r, g, b)))
+ set_transparency(ais, a)
+
+ return ais, shape
+
+
+def export(
+ obj: Union[cq.Workplane, List[cq.Workplane]], type: str, file, precision=1e-1
+):
+
+ comp = to_compound(obj)
+
+ if type == "stl":
+ comp.exportStl(file, tolerance=precision)
+ elif type == "step":
+ comp.exportStep(file)
+ elif type == "brep":
+ comp.exportBrep(file)
+
+
+def to_occ_color(color) -> Quantity_Color:
+
+ if not isinstance(color, QColor):
+ if isinstance(color, tuple):
+ if isinstance(color[0], int):
+ color = QColor(*color)
+ elif isinstance(color[0], float):
+ color = QColor.fromRgbF(*color)
+ else:
+ raise ValueError("Unknown color format")
+ else:
+ color = QColor(color)
+
+ return Quantity_Color(color.redF(), color.greenF(), color.blueF(), TOC_RGB)
+
+
+def get_occ_color(obj: Union[AIS_InteractiveObject, Quantity_Color]) -> QColor:
+
+ if isinstance(obj, AIS_InteractiveObject):
+ color = Quantity_Color()
+ obj.Color(color)
+ else:
+ color = obj
+
+ return QColor.fromRgbF(color.Red(), color.Green(), color.Blue())
+
+
+def set_color(ais: AIS_Shape, color: Quantity_Color) -> AIS_Shape:
+
+ drawer = ais.Attributes()
+ drawer.SetupOwnShadingAspect()
+ drawer.ShadingAspect().SetColor(color)
+
+ return ais
+
+
+def set_material(ais: AIS_Shape, material: Graphic3d_MaterialAspect) -> AIS_Shape:
+
+ drawer = ais.Attributes()
+ drawer.SetupOwnShadingAspect()
+ drawer.ShadingAspect().SetMaterial(material)
+
+ return ais
+
+
+def set_transparency(ais: AIS_Shape, alpha: float) -> AIS_Shape:
+
+ drawer = ais.Attributes()
+ drawer.SetupOwnShadingAspect()
+ drawer.ShadingAspect().SetTransparency(alpha)
+
+ return ais
+
+
+def reload_cq():
+
+ # NB: order of reloads is important
+ reload(cq.types)
+ reload(cq.occ_impl.geom)
+ reload(cq.occ_impl.shapes)
+ reload(cq.occ_impl.importers.dxf)
+ reload(cq.occ_impl.importers)
+ reload(cq.occ_impl.solver)
+ reload(cq.occ_impl.assembly)
+ reload(cq.occ_impl.sketch_solver)
+ reload(cq.hull)
+ reload(cq.selectors)
+ reload(cq.sketch)
+ reload(cq.occ_impl.exporters.svg)
+ reload(cq.cq)
+ # reload(cq.occ_impl.exporters.utils)
+ reload(cq.occ_impl.exporters.dxf)
+ reload(cq.occ_impl.exporters.amf)
+ reload(cq.occ_impl.exporters.json)
+ # reload(cq.occ_impl.exporters.assembly)
+ reload(cq.occ_impl.exporters)
+ reload(cq.assembly)
+ reload(cq)
+
+
+def is_obj_empty(obj: Union[cq.Workplane, cq.Shape]) -> bool:
+
+ rv = False
+
+ if isinstance(obj, cq.Workplane):
+ rv = True if isinstance(obj.val(), cq.Vector) else False
+
+ return rv
+=======
import cadquery as cq
from cadquery.occ_impl.assembly import toCAF
@@ -198,3 +433,4 @@ def is_obj_empty(obj : Union[cq.Workplane,cq.Shape]) -> bool:
rv = True if isinstance(obj.val(), cq.Vector) else False
return rv
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/cq_editor/editor.py b/cq_editor/editor.py
new file mode 100644
index 00000000..30b4629d
--- /dev/null
+++ b/cq_editor/editor.py
@@ -0,0 +1,303 @@
+import os
+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 path import Path
+
+import sys
+
+from pyqtgraph.parametertree import Parameter
+
+from ..mixins import ComponentMixin
+from ..utils import get_save_filename, get_open_filename, confirm
+
+from ..icons import icon
+
+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'}])
+
+ EXTENSIONS = 'py'
+
+ def __init__(self,parent=None):
+
+ self._watched_file = None
+
+ 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._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.setSingleShot(True)
+ self._file_watcher.fileChanged.connect(
+ 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)
+ menu.removeAction(self.run_cell_and_advance_action)
+ 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'])
+
+ font = self.font()
+ font.setPointSize(self.preferences['Font size'])
+ self.set_font(font)
+
+ self.findChild(QAction, 'autoreload') \
+ .setChecked(self.preferences['Autoreload'])
+
+ self._file_watch_timer.setInterval(self.preferences['Autoreload delay'])
+
+ 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?')
+ else:
+ rv = True
+
+ return rv
+
+ def new(self):
+
+ if not self.confirm_discard(): return
+
+ self.set_text('')
+ self.filename = ''
+ self.reset_modified()
+
+ def open(self):
+
+ if not self.confirm_discard(): return
+
+ curr_dir = Path(self.filename).absolute().dirname()
+ fname = get_open_filename(self.EXTENSIONS, curr_dir)
+ if fname != '':
+ self.load_from_file(fname)
+
+ def load_from_file(self,fname):
+
+ self.set_text_from_file(fname)
+ self.filename = fname
+ self.reset_modified()
+
+ def determine_encoding(self, fname):
+ if os.path.exists(fname):
+ # 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', '')
+ else:
+ return 'utf-8'
+
+ def save(self):
+
+ 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:
+ f.write(encoded)
+
+ if self.preferences['Autoreload']:
+ self._file_watcher.blockSignals(False)
+ self.triggerRerender.emit(True)
+
+ self.reset_modified()
+
+ else:
+ 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:
+ 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']):
+ self._clear_watched_paths()
+ self._watched_file = None
+ if self.preferences['Autoreload'] and self.filename and self.filename != self._watched_file:
+ self._watched_file = self._filename
+ self._watch_paths()
+
+ @property
+ def filename(self):
+ return self._filename
+
+ @filename.setter
+ def filename(self, fname):
+ self._filename = fname
+ self._update_filewatcher()
+ self.sigFilenameChanged.emit(fname)
+
+ def _clear_watched_paths(self):
+ paths = self._file_watcher.files()
+ if paths:
+ self._file_watcher.removePaths(paths)
+
+ 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 module_paths:
+ self._file_watcher.addPaths(module_paths)
+
+ # callback triggered by QFileSystemWatcher
+ def _file_changed(self):
+ # neovim writes a file by removing it first so must re-add each time
+ self._watch_paths()
+ self.set_text_from_file(self._filename)
+ self.triggerRerender.emit(True)
+
+ # Turn autoreload on/off.
+ def autoreload(self, 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):
+
+ if self.filename != '':
+ store.setValue(self.name+'/state',self.filename)
+
+ def restoreComponentState(self,store):
+
+ filename = store.value(self.name+'/state')
+
+ if filename and self.filename == '':
+ try:
+ self.load_from_file(filename)
+ except IOError:
+ 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}')
+ except Exception as err:
+ self._logger.warning(
+ 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 path is not None and os.path.isfile(path):
+ imported_modules.append(path)
+
+ return imported_modules
+
+
+if __name__ == "__main__":
+
+ from PyQt5.QtWidgets import QApplication
+
+ app = QApplication(sys.argv)
+ editor = Editor()
+ editor.show()
+
+ sys.exit(app.exec_())
diff --git a/cq_editor/main_window.py b/cq_editor/main_window.py
index 4bbee280..61f3c89c 100644
--- a/cq_editor/main_window.py
+++ b/cq_editor/main_window.py
@@ -19,6 +19,32 @@
from .icons import icon
from .preferences import PreferencesWidget
+<<<<<<< HEAD
+#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([])
+# Force the style to be the same on all OSs:
+app.setStyle("Fusion")
+# Now use a palette to switch to dark colors:
+palette = QPalette()
+palette.setColor(QPalette.Window, QColor(53, 53, 53))
+palette.setColor(QPalette.WindowText, Qt.white)
+palette.setColor(QPalette.Base, QColor(25, 25, 25))
+palette.setColor(QPalette.AlternateBase, QColor(53, 53, 53))
+palette.setColor(QPalette.ToolTipBase, Qt.black)
+palette.setColor(QPalette.ToolTipText, Qt.white)
+palette.setColor(QPalette.Text, Qt.white)
+palette.setColor(QPalette.Button, QColor(53, 53, 53))
+palette.setColor(QPalette.ButtonText, Qt.white)
+palette.setColor(QPalette.BrightText, Qt.red)
+palette.setColor(QPalette.Link, QColor(42, 130, 218))
+palette.setColor(QPalette.Highlight, QColor(42, 130, 218))
+palette.setColor(QPalette.HighlightedText, Qt.black)
+app.setPalette(palette)
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
class MainWindow(QMainWindow,MainMixin):
@@ -32,12 +58,15 @@ def __init__(self,parent=None, filename=None):
self.setWindowIcon(icon('app'))
+<<<<<<< HEAD
+=======
# Windows workaround - makes the correct task bar icon show up.
if sys.platform == "win32":
import ctypes
myappid = 'cq-editor' # arbitrary string
ctypes.windll.shell32.SetCurrentProcessExplicitAppUserModelID(myappid)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
self.viewer = OCCViewer(self)
self.setCentralWidget(self.viewer.canvas)
@@ -285,7 +314,11 @@ def prepare_console(self):
'rand_color' : self.components['debugger']._rand_color,
'cq' : cq,
'log' : Logger(self.name).info})
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def fill_dummy(self):
self.components['editor']\
diff --git a/cq_editor/widgets/console.py b/cq_editor/widgets/console.py
index 77fd1dcc..c2b45a02 100644
--- a/cq_editor/widgets/console.py
+++ b/cq_editor/widgets/console.py
@@ -17,6 +17,29 @@ def __init__(self, customBanner=None, namespace=dict(), *args, **kwargs):
# self.banner = customBanner
self.font_size = 6
+<<<<<<< HEAD
+ self.style_sheet = '''
+ '''
+ self.syntax_style = 'zenburn' #CHANGES FOR DARKMODE
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
self.kernel_manager = kernel_manager = QtInProcessKernelManager()
kernel_manager.start_kernel(show_banner=False)
kernel_manager.kernel.gui = 'qt'
diff --git a/cq_editor/widgets/debugger.py b/cq_editor/widgets/debugger.py
index 70d5795f..ecc5dcab 100644
--- a/cq_editor/widgets/debugger.py
+++ b/cq_editor/widgets/debugger.py
@@ -1,3 +1,418 @@
+<<<<<<< HEAD
+import sys
+from contextlib import ExitStack, contextmanager
+from enum import Enum, auto
+from types import SimpleNamespace, FrameType, ModuleType
+from typing import List
+
+import cadquery as cq
+from PyQt5 import QtCore
+from PyQt5.QtCore import (
+ Qt,
+ QObject,
+ pyqtSlot,
+ pyqtSignal,
+ QEventLoop,
+ QAbstractTableModel,
+)
+from PyQt5.QtWidgets import QAction, QTableView
+from logbook import info
+from path import Path
+from pyqtgraph.parametertree import Parameter
+from spyder.utils.icon_manager import icon
+from random import randrange as rrr, seed
+
+from ..cq_utils import find_cq_objects, reload_cq
+from ..mixins import ComponentMixin
+
+DUMMY_FILE = ""
+
+
+class DbgState(Enum):
+ STEP = auto()
+ CONT = auto()
+ STEP_IN = auto()
+ RETURN = auto()
+
+
+class DbgEevent(object):
+ LINE = "line"
+ CALL = "call"
+ RETURN = "return"
+
+
+class LocalsModel(QAbstractTableModel):
+ HEADER = ("Name", "Type", "Value")
+
+ def __init__(self, parent):
+ super(LocalsModel, self).__init__(parent)
+ self.frame = None
+
+ def update_frame(self, frame):
+ self.frame = [
+ (k, type(v).__name__, str(v))
+ for k, v in frame.items()
+ if not k.startswith("_")
+ ]
+
+ def rowCount(self, parent=QtCore.QModelIndex()):
+ if self.frame:
+ return len(self.frame)
+ else:
+ return 0
+
+ def columnCount(self, parent=QtCore.QModelIndex()):
+ return 3
+
+ def headerData(self, section, orientation, role=Qt.DisplayRole):
+ if role == Qt.DisplayRole and orientation == Qt.Horizontal:
+ return self.HEADER[section]
+ return QAbstractTableModel.headerData(self, section, orientation, role)
+
+ def data(self, index, role):
+ if role == QtCore.Qt.DisplayRole:
+ i = index.row()
+ j = index.column()
+ return self.frame[i][j]
+ else:
+ return QtCore.QVariant()
+
+
+class LocalsView(QTableView, ComponentMixin):
+ name = "Variables"
+
+ def __init__(self, parent):
+ super(LocalsView, self).__init__(parent)
+ ComponentMixin.__init__(self)
+
+ header = self.horizontalHeader()
+ header.setStretchLastSection(True)
+
+ vheader = self.verticalHeader()
+ vheader.setVisible(False)
+
+ @pyqtSlot(dict)
+ def update_frame(self, frame):
+ model = LocalsModel(self)
+ model.update_frame(frame)
+
+ self.setModel(model)
+
+
+class Debugger(QObject, ComponentMixin):
+ name = "Debugger"
+
+ preferences = Parameter.create(
+ name="Preferences",
+ children=[
+ {"name": "Reload CQ", "type": "bool", "value": False},
+ {"name": "Add script dir to path", "type": "bool", "value": True},
+ {"name": "Change working dir to script dir", "type": "bool", "value": True},
+ {"name": "Reload imported modules", "type": "bool", "value": True},
+ ],
+ )
+
+ sigRendered = pyqtSignal(dict)
+ sigLocals = pyqtSignal(dict)
+ sigTraceback = pyqtSignal(object, str)
+
+ sigFrameChanged = pyqtSignal(object)
+ sigLineChanged = pyqtSignal(int)
+ sigLocalsChanged = pyqtSignal(dict)
+ sigCQChanged = pyqtSignal(dict, bool)
+ sigDebugging = pyqtSignal(bool)
+
+ _frames: List[FrameType]
+
+ def __init__(self, parent):
+ super(Debugger, self).__init__(parent)
+ ComponentMixin.__init__(self)
+
+ self.inner_event_loop = QEventLoop(self)
+
+ self._actions = {
+ "Run": [
+ QAction(
+ icon("run"), "Render", self, shortcut="F5", triggered=self.render
+ ),
+ QAction(
+ icon("debug"),
+ "Debug",
+ self,
+ checkable=True,
+ shortcut="ctrl+F5",
+ triggered=self.debug,
+ ),
+ QAction(
+ icon("arrow-step-over"),
+ "Step",
+ self,
+ shortcut="ctrl+F10",
+ triggered=lambda: self.debug_cmd(DbgState.STEP),
+ ),
+ QAction(
+ icon("arrow-step-in"),
+ "Step in",
+ self,
+ shortcut="ctrl+F11",
+ triggered=lambda: self.debug_cmd(DbgState.STEP_IN),
+ ),
+ QAction(
+ icon("arrow-continue"),
+ "Continue",
+ self,
+ shortcut="ctrl+F12",
+ triggered=lambda: self.debug_cmd(DbgState.CONT),
+ ),
+ ]
+ }
+
+ self._frames = []
+
+ def get_current_script(self):
+ return self.parent().components["editor"].get_text_with_eol()
+
+ def get_breakpoints(self):
+ return self.parent().components["editor"].debugger.get_breakpoints()
+
+ def compile_code(self, cq_script):
+ try:
+ module = ModuleType("temp")
+ cq_code = compile(cq_script, "", "exec")
+ return cq_code, module
+ except Exception:
+ self.sigTraceback.emit(sys.exc_info(), cq_script)
+ return None, None
+
+ def _exec(self, code, locals_dict, globals_dict):
+ with ExitStack() as stack:
+ fname = self.parent().components["editor"].filename
+ p = Path(fname if fname else "").absolute().dirname()
+
+ if self.preferences["Add script dir to path"] and p.exists():
+ sys.path.insert(0, p)
+ stack.callback(sys.path.remove, p)
+ if self.preferences["Change working dir to script dir"] and p.exists():
+ stack.enter_context(p)
+ if self.preferences["Reload imported modules"]:
+ stack.enter_context(module_manager())
+
+ exec(code, locals_dict, globals_dict)
+
+ def _rand_color(self, alpha=0.0, cfloat=False):
+ # helper function to generate a random color dict
+ # for CQ-editor's show_object function
+ lower = 10
+ upper = 100 # not too high to keep color brightness in check
+ if cfloat: # for two output types depending on need
+ return (
+ (rrr(lower, upper) / 255),
+ (rrr(lower, upper) / 255),
+ (rrr(lower, upper) / 255),
+ alpha,
+ )
+ return {
+ "alpha": alpha,
+ "color": (
+ rrr(lower, upper),
+ rrr(lower, upper),
+ rrr(lower, upper),
+ ),
+ }
+
+ def _inject_locals(self, module):
+ cq_objects = {}
+
+ def _show_object(
+ obj,
+ name=None,
+ options={}, # all following inputs are ignored by cq-editor
+ parent=1,
+ clear=True,
+ port=3939,
+ axes=False,
+ axes0=False,
+ grid=False,
+ ticks=10,
+ ortho=True,
+ transparent=False,
+ default_color=(232, 176, 36),
+ reset_camera=True,
+ zoom=1.0,
+ default_edgecolor=(128, 128, 128),
+ render_edges=True,
+ render_normals=False,
+ render_mates=False,
+ mate_scale=1.0,
+ deviation=0.1,
+ angular_tolerance=0.2,
+ edge_accuracy=5.0,
+ ambient_intensity=1.0,
+ direct_intensity=0.12,
+ ):
+ if name:
+ cq_objects.update({name: SimpleNamespace(shape=obj, options=options)})
+ else:
+ cq_objects.update(
+ {str(id(obj)): SimpleNamespace(shape=obj, options=options)}
+ )
+
+ def _debug(obj, name=None):
+ _show_object(obj, name, options=dict(color="red", alpha=0.2))
+
+ module.__dict__["show_object"] = _show_object
+ module.__dict__["debug"] = _debug
+ module.__dict__["rand_color"] = self._rand_color
+ module.__dict__["log"] = lambda x: info(str(x))
+ module.__dict__["cq"] = cq
+
+ return cq_objects, set(module.__dict__) - {"cq"}
+
+ def _cleanup_locals(self, module, injected_names):
+ for name in injected_names:
+ module.__dict__.pop(name)
+
+ @pyqtSlot(bool)
+ def render(self):
+ seed(
+ 59798267586177
+ ) # reset the seed every time render is called (preserves colors run to run)
+ if self.preferences["Reload CQ"]:
+ reload_cq()
+
+ cq_script = self.get_current_script()
+ cq_code, module = self.compile_code(cq_script)
+
+ if cq_code is None:
+ return
+
+ cq_objects, injected_names = self._inject_locals(module)
+
+ try:
+ self._exec(cq_code, module.__dict__, module.__dict__)
+
+ # remove the special methods
+ self._cleanup_locals(module, injected_names)
+
+ # collect all CQ objects if no explicit show_object was called
+ if len(cq_objects) == 0:
+ cq_objects = find_cq_objects(module.__dict__)
+ self.sigRendered.emit(cq_objects)
+ self.sigTraceback.emit(None, cq_script)
+ self.sigLocals.emit(module.__dict__)
+ except Exception:
+ exc_info = sys.exc_info()
+ sys.last_traceback = exc_info[-1]
+ self.sigTraceback.emit(exc_info, cq_script)
+
+ @property
+ def breakpoints(self):
+ return [el[0] for el in self.get_breakpoints()]
+
+ @pyqtSlot(bool)
+ def debug(self, value):
+ previous_trace = sys.gettrace()
+
+ if value:
+ self.sigDebugging.emit(True)
+ self.state = DbgState.STEP
+
+ self.script = self.get_current_script()
+ code, module = self.compile_code(self.script)
+
+ if code is None:
+ self.sigDebugging.emit(False)
+ self._actions["Run"][1].setChecked(False)
+ return
+
+ cq_objects, injected_names = self._inject_locals(module)
+
+ # clear possible traceback
+ self.sigTraceback.emit(None, self.script)
+
+ try:
+ sys.settrace(self.trace_callback)
+ exec(code, module.__dict__, module.__dict__)
+ except Exception:
+ exc_info = sys.exc_info()
+ sys.last_traceback = exc_info[-1]
+ self.sigTraceback.emit(exc_info, self.script)
+ finally:
+ sys.settrace(previous_trace)
+ self.sigDebugging.emit(False)
+ self._actions["Run"][1].setChecked(False)
+
+ if len(cq_objects) == 0:
+ cq_objects = find_cq_objects(module.__dict__)
+ self.sigRendered.emit(cq_objects)
+
+ self._cleanup_locals(module, injected_names)
+ self.sigLocals.emit(module.__dict__)
+
+ self._frames = []
+ else:
+ sys.settrace(previous_trace)
+ self.inner_event_loop.exit(0)
+
+ def debug_cmd(self, state=DbgState.STEP):
+ self.state = state
+ self.inner_event_loop.exit(0)
+
+ def trace_callback(self, frame, event, arg):
+ filename = frame.f_code.co_filename
+
+ if filename == DUMMY_FILE:
+ if not self._frames:
+ self._frames.append(frame)
+ self.trace_local(frame, event, arg)
+ return self.trace_callback
+
+ else:
+ return None
+
+ def trace_local(self, frame, event, arg):
+ lineno = frame.f_lineno
+
+ if event in (DbgEevent.LINE,):
+ if (
+ self.state in (DbgState.STEP, DbgState.STEP_IN)
+ and frame is self._frames[-1]
+ ) or (lineno in self.breakpoints):
+ if lineno in self.breakpoints:
+ self._frames.append(frame)
+
+ self.sigLineChanged.emit(lineno)
+ self.sigFrameChanged.emit(frame)
+ self.sigLocalsChanged.emit(frame.f_locals)
+ self.sigCQChanged.emit(find_cq_objects(frame.f_locals), True)
+
+ self.inner_event_loop.exec_()
+
+ elif event in (DbgEevent.RETURN):
+ self.sigLocalsChanged.emit(frame.f_locals)
+ self._frames.pop()
+
+ elif event == DbgEevent.CALL:
+ func_filename = frame.f_code.co_filename
+ if self.state == DbgState.STEP_IN and func_filename == DUMMY_FILE:
+ self.sigLineChanged.emit(lineno)
+ self.sigFrameChanged.emit(frame)
+ self.state = DbgState.STEP
+ self._frames.append(frame)
+
+
+@contextmanager
+def module_manager():
+ """unloads any modules loaded while the context manager is active"""
+ loaded_modules = set(sys.modules.keys())
+
+ try:
+ yield
+ finally:
+ new_modules = set(sys.modules.keys()) - loaded_modules
+ for module_name in new_modules:
+ del sys.modules[module_name]
+=======
import sys
from contextlib import ExitStack, contextmanager
from enum import Enum, auto
@@ -417,3 +832,4 @@ def module_manager():
new_modules = set(sys.modules.keys()) - loaded_modules
for module_name in new_modules:
del sys.modules[module_name]
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/cq_editor/widgets/editor.py b/cq_editor/widgets/editor.py
index 20bee81f..a5963a47 100644
--- a/cq_editor/widgets/editor.py
+++ b/cq_editor/widgets/editor.py
@@ -1,5 +1,8 @@
import os
+<<<<<<< HEAD
+=======
import spyder.utils.encoding
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
from modulefinder import ModuleFinder
from spyder.plugins.editor.widgets.codeeditor import CodeEditor
@@ -159,6 +162,8 @@ def load_from_file(self,fname):
self.filename = fname
self.reset_modified()
+<<<<<<< HEAD
+=======
def determine_encoding(self, fname):
if os.path.exists(fname):
# this function returns the encoding spyder used to read the file
@@ -168,6 +173,7 @@ def determine_encoding(self, fname):
else:
return 'utf-8'
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def save(self):
if self._filename != '':
@@ -176,10 +182,15 @@ def save(self):
self._file_watcher.blockSignals(True)
self._file_watch_timer.stop()
+<<<<<<< HEAD
+ with open(self._filename, 'w') as f:
+ f.write(self.toPlainText())
+=======
encoding = self.determine_encoding(self._filename)
encoded = self.toPlainText().encode(encoding)
with open(self._filename, 'wb') as f:
f.write(encoded)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
if self.preferences['Autoreload']:
self._file_watcher.blockSignals(False)
@@ -194,9 +205,14 @@ def save_as(self):
fname = get_save_filename(self.EXTENSIONS)
if fname != '':
+<<<<<<< HEAD
+ with open(fname,'w') as f:
+ f.write(self.toPlainText())
+=======
encoded = self.toPlainText().encode('utf-8')
with open(fname, 'wb') as f:
f.write(encoded)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
self.filename = fname
self.reset_modified()
@@ -237,7 +253,10 @@ def _file_changed(self):
# neovim writes a file by removing it first so must re-add each time
self._watch_paths()
self.set_text_from_file(self._filename)
+<<<<<<< HEAD
+=======
self.reset_modified()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
self.triggerRerender.emit(True)
# Turn autoreload on/off.
diff --git a/cq_editor/widgets/log.py b/cq_editor/widgets/log.py
index 18d5162c..823e9b84 100644
--- a/cq_editor/widgets/log.py
+++ b/cq_editor/widgets/log.py
@@ -1,12 +1,31 @@
import logbook as logging
+<<<<<<< HEAD
+=======
import sys
import re
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
from PyQt5.QtWidgets import QPlainTextEdit
from PyQt5 import QtCore
from ..mixins import ComponentMixin
+<<<<<<< HEAD
+class QtLogHandler(logging.Handler,logging.StringFormatterHandlerMixin):
+
+ def __init__(self, log_widget,*args,**kwargs):
+
+ super(QtLogHandler,self).__init__(*args,**kwargs)
+
+ log_format_string = '[{record.time:%H:%M:%S.%f%z}] {record.level_name}: {record.message}'
+
+ logging.StringFormatterHandlerMixin.__init__(self,log_format_string)
+ self.log_widget = log_widget
+
+ def emit(self, record):
+
+ msg = self.format(record)
+=======
def strip_escape_sequences(input_string):
# Regular expression pattern to match ANSI escape codes
escape_pattern = re.compile(r'\x1B\[[0-?]*[ -/]*[@-~]')
@@ -34,6 +53,7 @@ def emit(self, record):
msg = strip_escape_sequences(msg)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
QtCore.QMetaObject\
.invokeMethod(self.log_widget,
'appendPlainText',
diff --git a/cq_editor/widgets/object_tree.py b/cq_editor/widgets/object_tree.py
index 1778bfd5..e65d0dd6 100644
--- a/cq_editor/widgets/object_tree.py
+++ b/cq_editor/widgets/object_tree.py
@@ -1,3 +1,429 @@
+<<<<<<< HEAD
+from PyQt5.QtWidgets import (
+ QTreeWidget,
+ QTreeWidgetItem,
+ QAction,
+ QMenu,
+ QWidget,
+ QAbstractItemView,
+)
+from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
+from pyqtgraph.parametertree import Parameter, ParameterTree
+
+from OCP.AIS import AIS_Line
+from OCP.Geom import Geom_Line
+from OCP.gp import gp_Dir, gp_Pnt, gp_Ax1
+
+from ..mixins import ComponentMixin
+from ..icons import icon
+from ..cq_utils import (
+ make_AIS,
+ export,
+ to_occ_color,
+ is_obj_empty,
+ get_occ_color,
+ set_color,
+)
+from .viewer import DEFAULT_FACE_COLOR
+from ..utils import splitter, layout, get_save_filename
+
+
+class TopTreeItem(QTreeWidgetItem):
+ def __init__(self, *args, **kwargs):
+ super(TopTreeItem, self).__init__(*args, **kwargs)
+
+
+class ObjectTreeItem(QTreeWidgetItem):
+ props = [
+ {"name": "Name", "type": "str", "value": ""},
+ {"name": "Color", "type": "color", "value": "#f4a824"},
+ {"name": "Alpha", "type": "float", "value": 0, "limits": (0, 1), "step": 1e-1},
+ {"name": "Visible", "type": "bool", "value": True},
+ ]
+
+ def __init__(
+ self,
+ name,
+ ais=None,
+ shape=None,
+ shape_display=None,
+ sig=None,
+ alpha=0.0,
+ color="#f4a824",
+ **kwargs
+ ):
+ super(ObjectTreeItem, self).__init__([name], **kwargs)
+ self.setFlags(self.flags() | Qt.ItemIsUserCheckable)
+ self.setCheckState(0, Qt.Checked)
+
+ self.ais = ais
+ self.shape = shape
+ self.shape_display = shape_display
+ self.sig = sig
+
+ self.properties = Parameter.create(name="Properties", children=self.props)
+
+ self.properties["Name"] = name
+ self.properties["Alpha"] = ais.Transparency()
+ self.properties["Color"] = (
+ get_occ_color(ais)
+ if ais and ais.HasColor()
+ else get_occ_color(DEFAULT_FACE_COLOR)
+ )
+ self.properties.sigTreeStateChanged.connect(self.propertiesChanged)
+
+ def propertiesChanged(self, properties, changed):
+ changed_prop = changed[0][0]
+
+ self.setData(0, 0, self.properties["Name"])
+ self.ais.SetTransparency(self.properties["Alpha"])
+
+ if changed_prop.name() == "Color":
+ set_color(self.ais, to_occ_color(self.properties["Color"]))
+
+ self.ais.Redisplay()
+
+ if self.properties["Visible"]:
+ self.setCheckState(0, Qt.Checked)
+ else:
+ self.setCheckState(0, Qt.Unchecked)
+
+ if self.sig:
+ self.sig.emit()
+
+
+class CQRootItem(TopTreeItem):
+ def __init__(self, *args, **kwargs):
+ super(CQRootItem, self).__init__(["CQ models"], *args, **kwargs)
+
+
+class HelpersRootItem(TopTreeItem):
+ def __init__(self, *args, **kwargs):
+ super(HelpersRootItem, self).__init__(["Helpers"], *args, **kwargs)
+
+
+class ObjectTree(QWidget, ComponentMixin):
+ name = "Object Tree"
+ _stash = []
+
+ preferences = Parameter.create(
+ name="Preferences",
+ children=[
+ {"name": "Preserve properties on reload", "type": "bool", "value": False},
+ {"name": "Clear all before each run", "type": "bool", "value": True},
+ {"name": "STL precision", "type": "float", "value": 0.1},
+ ],
+ )
+
+ sigObjectsAdded = pyqtSignal([list], [list, bool])
+ sigObjectsRemoved = pyqtSignal(list)
+ sigCQObjectSelected = pyqtSignal(object)
+ sigAISObjectsSelected = pyqtSignal(list)
+ sigItemChanged = pyqtSignal(QTreeWidgetItem, int)
+ sigObjectPropertiesChanged = pyqtSignal()
+
+ def __init__(self, parent):
+ super(ObjectTree, self).__init__(parent)
+
+ self.tree = tree = QTreeWidget(
+ self, selectionMode=QAbstractItemView.ExtendedSelection
+ )
+ self.properties_editor = ParameterTree(self)
+
+ tree.setHeaderHidden(True)
+ tree.setItemsExpandable(False)
+ tree.setRootIsDecorated(False)
+ tree.setContextMenuPolicy(Qt.ActionsContextMenu)
+
+ # forward itemChanged singal
+ tree.itemChanged.connect(lambda item, col: self.sigItemChanged.emit(item, col))
+ # handle visibility changes form tree
+ tree.itemChanged.connect(self.handleChecked)
+
+ self.CQ = CQRootItem()
+ self.Helpers = HelpersRootItem()
+
+ root = tree.invisibleRootItem()
+ root.addChild(self.CQ)
+ root.addChild(self.Helpers)
+
+ tree.expandToDepth(1)
+
+ self._export_STL_action = QAction(
+ "Export as STL",
+ self,
+ enabled=False,
+ triggered=lambda: self.export("stl", self.preferences["STL precision"]),
+ )
+
+ self._export_STEP_action = QAction(
+ "Export as STEP", self, enabled=False, triggered=lambda: self.export("step")
+ )
+
+ self._clear_current_action = QAction(
+ icon("delete"),
+ "Clear current",
+ self,
+ enabled=False,
+ triggered=self.removeSelected,
+ )
+
+ self._toolbar_actions = [
+ QAction(
+ icon("delete-many"), "Clear all", self, triggered=self.removeObjects
+ ),
+ self._clear_current_action,
+ ]
+
+ self.prepareMenu()
+
+ tree.itemSelectionChanged.connect(self.handleSelection)
+ tree.customContextMenuRequested.connect(self.showMenu)
+
+ self.prepareLayout()
+
+ def prepareMenu(self):
+ self.tree.setContextMenuPolicy(Qt.CustomContextMenu)
+
+ self._context_menu = QMenu(self)
+ self._context_menu.addActions(self._toolbar_actions)
+ self._context_menu.addActions(
+ (self._export_STL_action, self._export_STEP_action)
+ )
+
+ def prepareLayout(self):
+ self._splitter = splitter(
+ (self.tree, self.properties_editor),
+ stretch_factors=(2, 1),
+ orientation=Qt.Vertical,
+ )
+ layout(self, (self._splitter,), top_widget=self)
+
+ self._splitter.show()
+
+ def showMenu(self, position):
+ self._context_menu.exec_(self.tree.viewport().mapToGlobal(position))
+
+ def menuActions(self):
+ return {"Tools": [self._export_STL_action, self._export_STEP_action]}
+
+ def toolbarActions(self):
+ return self._toolbar_actions
+
+ def addLines(self):
+ origin = (0, 0, 0)
+ ais_list = []
+
+ for name, color, direction in zip(
+ ("X", "Y", "Z"),
+ ((0.2, 0, 0), "lawngreen", "blue"),
+ ((1, 0, 0), (0, 1, 0), (0, 0, 1)),
+ ):
+ line_placement = Geom_Line(gp_Ax1(gp_Pnt(*origin), gp_Dir(*direction)))
+ line = AIS_Line(line_placement)
+ line.SetColor(to_occ_color(color))
+
+ self.Helpers.addChild(ObjectTreeItem(name, ais=line))
+
+ ais_list.append(line)
+
+ self.sigObjectsAdded.emit(ais_list)
+
+ def _current_properties(self):
+ current_params = {}
+ for i in range(self.CQ.childCount()):
+ child = self.CQ.child(i)
+ current_params[child.properties["Name"]] = child.properties
+
+ return current_params
+
+ def _restore_properties(self, obj, properties):
+ for p in properties[obj.properties["Name"]]:
+ obj.properties[p.name()] = p.value()
+
+ @pyqtSlot(dict, bool)
+ @pyqtSlot(dict)
+ def addObjects(self, objects, clean=False, root=None):
+ if root is None:
+ root = self.CQ
+
+ request_fit_view = True if root.childCount() == 0 else False
+ preserve_props = self.preferences["Preserve properties on reload"]
+
+ if preserve_props:
+ current_props = self._current_properties()
+
+ if clean or self.preferences["Clear all before each run"]:
+ self.removeObjects()
+
+ ais_list = []
+
+ # remove empty objects
+ objects_f = {k: v for k, v in objects.items() if not is_obj_empty(v.shape)}
+
+ for name, obj in objects_f.items():
+ ais, shape_display = make_AIS(obj.shape, obj.options)
+
+ child = ObjectTreeItem(
+ name,
+ shape=obj.shape,
+ shape_display=shape_display,
+ ais=ais,
+ sig=self.sigObjectPropertiesChanged,
+ )
+
+ if preserve_props and name in current_props:
+ self._restore_properties(child, current_props)
+
+ if child.properties["Visible"]:
+ ais_list.append(ais)
+
+ root.addChild(child)
+
+ if request_fit_view:
+ self.sigObjectsAdded[list, bool].emit(ais_list, True)
+ else:
+ self.sigObjectsAdded[list].emit(ais_list)
+
+ @pyqtSlot(object, str, object)
+ def addObject(
+ self,
+ obj,
+ name="",
+ options={}, # all following inputs are ignored by cq-editor
+ parent=1,
+ clear=True,
+ port=3939,
+ axes=False,
+ axes0=False,
+ grid=False,
+ ticks=10,
+ ortho=True,
+ transparent=False,
+ default_color=(232, 176, 36),
+ reset_camera=True,
+ zoom=1.0,
+ default_edgecolor=(128, 128, 128),
+ render_edges=True,
+ render_normals=False,
+ render_mates=False,
+ mate_scale=1.0,
+ deviation=0.1,
+ angular_tolerance=0.2,
+ edge_accuracy=5.0,
+ ambient_intensity=1.0,
+ direct_intensity=0.12,
+ ):
+ root = self.CQ
+
+ ais, shape_display = make_AIS(obj, options)
+
+ root.addChild(
+ ObjectTreeItem(
+ name,
+ shape=obj,
+ shape_display=shape_display,
+ ais=ais,
+ sig=self.sigObjectPropertiesChanged,
+ )
+ )
+
+ self.sigObjectsAdded.emit([ais])
+
+ @pyqtSlot(list)
+ @pyqtSlot()
+ def removeObjects(self, objects=None):
+ if objects:
+ removed_items_ais = [self.CQ.takeChild(i).ais for i in objects]
+ else:
+ removed_items_ais = [ch.ais for ch in self.CQ.takeChildren()]
+
+ self.sigObjectsRemoved.emit(removed_items_ais)
+
+ @pyqtSlot(bool)
+ def stashObjects(self, action: bool):
+ if action:
+ self._stash = self.CQ.takeChildren()
+ removed_items_ais = [ch.ais for ch in self._stash]
+ self.sigObjectsRemoved.emit(removed_items_ais)
+ else:
+ self.removeObjects()
+ self.CQ.addChildren(self._stash)
+ ais_list = [el.ais for el in self._stash]
+ self.sigObjectsAdded.emit(ais_list)
+
+ @pyqtSlot()
+ def removeSelected(self):
+ ixs = self.tree.selectedIndexes()
+ rows = [ix.row() for ix in ixs]
+
+ self.removeObjects(rows)
+
+ def export(self, export_type, precision=None):
+ items = self.tree.selectedItems()
+
+ # if CQ models is selected get all children
+ if [item for item in items if item is self.CQ]:
+ CQ = self.CQ
+ shapes = [CQ.child(i).shape for i in range(CQ.childCount())]
+ # otherwise collect all selected children of CQ
+ else:
+ shapes = [item.shape for item in items if item.parent() is self.CQ]
+
+ fname = get_save_filename(export_type)
+ if fname != "":
+ export(shapes, export_type, fname, precision)
+
+ @pyqtSlot()
+ def handleSelection(self):
+ items = self.tree.selectedItems()
+ if len(items) == 0:
+ self._export_STL_action.setEnabled(False)
+ self._export_STEP_action.setEnabled(False)
+ return
+
+ # emit list of all selected ais objects (might be empty)
+ ais_objects = [item.ais for item in items if item.parent() is self.CQ]
+ self.sigAISObjectsSelected.emit(ais_objects)
+
+ # handle context menu and emit last selected CQ object (if present)
+ item = items[-1]
+ if item.parent() is self.CQ:
+ self._export_STL_action.setEnabled(True)
+ self._export_STEP_action.setEnabled(True)
+ self._clear_current_action.setEnabled(True)
+ self.sigCQObjectSelected.emit(item.shape)
+ self.properties_editor.setParameters(item.properties, showTop=False)
+ self.properties_editor.setEnabled(True)
+ elif item is self.CQ and item.childCount() > 0:
+ self._export_STL_action.setEnabled(True)
+ self._export_STEP_action.setEnabled(True)
+ else:
+ self._export_STL_action.setEnabled(False)
+ self._export_STEP_action.setEnabled(False)
+ self._clear_current_action.setEnabled(False)
+ self.properties_editor.setEnabled(False)
+ self.properties_editor.clear()
+
+ @pyqtSlot(list)
+ def handleGraphicalSelection(self, shapes):
+ self.tree.clearSelection()
+
+ CQ = self.CQ
+ for i in range(CQ.childCount()):
+ item = CQ.child(i)
+ for shape in shapes:
+ if item.ais.Shape().IsEqual(shape):
+ item.setSelected(True)
+
+ @pyqtSlot(QTreeWidgetItem, int)
+ def handleChecked(self, item, col):
+ if type(item) is ObjectTreeItem:
+ if item.checkState(0):
+ item.properties["Visible"] = True
+ else:
+ item.properties["Visible"] = False
+=======
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAction, QMenu, QWidget, QAbstractItemView
from PyQt5.QtCore import Qt, pyqtSlot, pyqtSignal
@@ -395,3 +821,4 @@ def handleChecked(self,item,col):
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/cq_editor/widgets/occt_widget.py b/cq_editor/widgets/occt_widget.py
old mode 100755
new mode 100644
index 172755ea..1a7dc8b5
--- a/cq_editor/widgets/occt_widget.py
+++ b/cq_editor/widgets/occt_widget.py
@@ -10,7 +10,11 @@
from OCP.OpenGl import OpenGl_GraphicDriver
from OCP.V3d import V3d_Viewer
from OCP.AIS import AIS_InteractiveContext, AIS_DisplayMode
+<<<<<<< HEAD
+from OCP.Quantity import Quantity_Color, Quantity_TOC_RGB as TOC_RGB
+=======
from OCP.Quantity import Quantity_Color
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
ZOOM_STEP = 0.9
@@ -54,6 +58,11 @@ def prepare_display(self):
Aspect_TypeOfTriedronPosition.Aspect_TOTP_RIGHT_LOWER,
Quantity_Color(), 0.1)
+<<<<<<< HEAD
+ view.ZBufferTriedronSetup(Quantity_Color(*(0.2, 0.0, 0.0), TOC_RGB))
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
viewer = self.viewer
viewer.SetDefaultLights()
@@ -95,8 +104,13 @@ def mouseMoveEvent(self,event):
self.old_pos.y() - y, theToStart=True)
elif event.buttons() == Qt.RightButton:
+<<<<<<< HEAD
+ self.view.Pan(x - self.old_pos.x(),
+ self.old_pos.y() - y, theToStart=True)
+=======
self.view.ZoomAtPoint(self.old_pos.x(), y,
x, self.old_pos.y())
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
self.old_pos = pos
diff --git a/cq_editor/widgets/traceback_viewer.py b/cq_editor/widgets/traceback_viewer.py
index 7d1051a0..5cfb0642 100644
--- a/cq_editor/widgets/traceback_viewer.py
+++ b/cq_editor/widgets/traceback_viewer.py
@@ -1,5 +1,8 @@
from traceback import extract_tb, format_exception_only
+<<<<<<< HEAD
+=======
from itertools import dropwhile
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
from PyQt5.QtWidgets import (QWidget, QTreeWidget, QTreeWidgetItem, QAction,
QLabel)
@@ -56,16 +59,26 @@ def addTraceback(self,exc_info,code):
root = self.tree.root
code = code.splitlines()
+<<<<<<< HEAD
+ tb = [t for t in extract_tb(tb) if '' in t.filename] #ignore highest frames (debug, exec)
+
+ for el in tb:
+=======
for el in dropwhile(
lambda el: 'string>' not in el.filename, extract_tb(tb)
):
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
#workaround of the traceback module
if el.line == '':
line = code[el.lineno-1].strip()
else:
line = el.line
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
root.addChild(QTreeWidgetItem([el.filename,
str(el.lineno),
line]))
diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py
index 9c5d620b..f6ecefc6 100644
--- a/cq_editor/widgets/viewer.py
+++ b/cq_editor/widgets/viewer.py
@@ -22,8 +22,11 @@
from pyqtgraph.parametertree import Parameter
import qtawesome as qta
+<<<<<<< HEAD
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
DEFAULT_EDGE_COLOR = Quantity_Color(BLACK)
DEFAULT_EDGE_WIDTH = 2
@@ -62,10 +65,16 @@ def __init__(self,parent=None):
[self.canvas,],
top_widget=self,
margin=0)
+<<<<<<< HEAD
+ self.setup_default_drawer() #misspelled in original
+ self.updatePreferences()
+
+=======
self.setup_default_drawer()
self.updatePreferences()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def setup_default_drawer(self):
# set the default color and material
@@ -79,7 +88,11 @@ def setup_default_drawer(self):
line_aspect = self.canvas.context.DefaultDrawer().FaceBoundaryAspect()
line_aspect.SetWidth(DEFAULT_EDGE_WIDTH)
line_aspect.SetColor(DEFAULT_EDGE_COLOR)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def updatePreferences(self,*args):
color1 = to_occ_color(self.preferences['Background color'])
@@ -88,9 +101,15 @@ def updatePreferences(self,*args):
if not self.preferences['Use gradient']:
color2 = color1
self.canvas.view.SetBgGradientColors(color1,color2,theToUpdate=True)
+<<<<<<< HEAD
+
+ self.canvas.update()
+
+=======
self.canvas.update()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
ctx = self.canvas.context
ctx.SetDeviationCoefficient(self.preferences['Deviation'])
ctx.SetDeviationAngle(self.preferences['Angular deviation'])
@@ -255,13 +274,21 @@ def top_view(self):
def front_view(self):
v = self._get_view()
+<<<<<<< HEAD
+ v.SetProj(0,-1,0)
+=======
v.SetProj(0,1,0)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
v.SetTwist(0)
def back_view(self):
v = self._get_view()
+<<<<<<< HEAD
+ v.SetProj(0,1,0)
+=======
v.SetProj(0,-1,0)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
v.SetTwist(0)
def left_view(self):
diff --git a/cqgui_env.yml b/cqgui_env.yml
index 98cae565..b5c84af4 100644
--- a/cqgui_env.yml
+++ b/cqgui_env.yml
@@ -5,10 +5,17 @@ channels:
dependencies:
- pyqt=5
- pyqtgraph
+<<<<<<< HEAD
+ - python=3.11
+=======
- python=3.10
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
- spyder=5
- path
- logbook
- requests
- cadquery=master
+<<<<<<< HEAD
+=======
- qtconsole=5.4.1
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/environment.yml b/environment.yml
new file mode 100644
index 00000000..992943fc
--- /dev/null
+++ b/environment.yml
@@ -0,0 +1,6 @@
+name: test
+channels:
+ - conda-forge
+ - defaults
+dependencies:
+ - python=3.11
diff --git a/pyinstaller/CQ-editor-mac.sh b/pyinstaller/CQ-editor-mac.sh
new file mode 100644
index 00000000..476f9582
--- /dev/null
+++ b/pyinstaller/CQ-editor-mac.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+export QT_MAC_WANTS_LAYER=1
+chmod u+x ./CQ-editor/CQ-editor
+QT_QPA_PLATFORM=cocoa PYOPENGL_PLATFORM=x11 ./CQ-editor/CQ-editor
diff --git a/pyinstaller/CQ-editor.cmd b/pyinstaller/CQ-editor.cmd
new file mode 100644
index 00000000..954e27b1
--- /dev/null
+++ b/pyinstaller/CQ-editor.cmd
@@ -0,0 +1 @@
+call .\CQ-editor\CQ-editor.exe
\ No newline at end of file
diff --git a/pyinstaller/CQ-editor.sh b/pyinstaller/CQ-editor.sh
new file mode 100644
index 00000000..f1a5abc6
--- /dev/null
+++ b/pyinstaller/CQ-editor.sh
@@ -0,0 +1,4 @@
+#!/bin/sh
+export QT_MAC_WANTS_LAYER=1
+chmod u+x ./CQ-editor/CQ-editor
+QT_QPA_PLATFORM=xcb PYOPENGL_PLATFORM=x11 ./CQ-editor/CQ-editor
diff --git a/pyinstaller/extrahooks/hook-casadi.py b/pyinstaller/extrahooks/hook-casadi.py
new file mode 100644
index 00000000..47db8b12
--- /dev/null
+++ b/pyinstaller/extrahooks/hook-casadi.py
@@ -0,0 +1,9 @@
+# hook-casadi.py
+from PyInstaller.utils.hooks import collect_dynamic_libs
+
+binaries = collect_dynamic_libs('casadi')
+
+# Something about legacy import codepaths in casadi.casadi causes PyInstaller's analysis to pick up
+# casadi._casadi as a top-level _casadi module, which is wrong.
+hiddenimports = ['casadi._casadi']
+excludedimports = ['_casadi']
diff --git a/pyinstaller/extrahooks/hook-py_lib3mf.py b/pyinstaller/extrahooks/hook-py_lib3mf.py
new file mode 100644
index 00000000..8c665e91
--- /dev/null
+++ b/pyinstaller/extrahooks/hook-py_lib3mf.py
@@ -0,0 +1,4 @@
+# hook-py_lib3mf.py
+from PyInstaller.utils.hooks import collect_dynamic_libs
+
+binaries = collect_dynamic_libs('py_lib3mf')
diff --git a/pyinstaller_pip.spec b/pyinstaller_pip.spec
new file mode 100644
index 00000000..52b7331e
--- /dev/null
+++ b/pyinstaller_pip.spec
@@ -0,0 +1,89 @@
+# -*- mode: python -*-
+
+import sys, site, os
+from path import Path
+from PyInstaller.utils.hooks import collect_all, collect_submodules
+
+block_cipher = None
+
+spyder_data = Path(site.getsitepackages()[-1]) / 'spyder'
+parso_grammar = (Path(site.getsitepackages()[-1]) / 'parso/python').glob('grammar*')
+cqw_path = Path(site.getsitepackages()[-1]) / 'cq_warehouse'
+bdw_path = Path(site.getsitepackages()[-1]) / 'bd_warehouse'
+cq_path = Path(site.getsitepackages()[-1]) / 'cadquery'
+
+if sys.platform == 'linux':
+ occt_dir = os.path.join(Path(sys.prefix), 'share', 'opencascade')
+ ocp_path = [(os.path.join(HOMEPATH, 'OCP.cpython-311-x86_64-linux-gnu.so'), '.')]
+elif sys.platform == 'darwin':
+ occt_dir = os.path.join(Path(sys.prefix), 'share', 'opencascade')
+ ocp_path = [(os.path.join(HOMEPATH, 'OCP.cpython-311-darwin.so'), '.')]
+elif sys.platform == 'win32':
+ occt_dir = os.path.join(Path(sys.prefix), 'Library', 'share', 'opencascade')
+ ocp_path = [(os.path.join(HOMEPATH, 'OCP.cp311-win_amd64.pyd'), '.')]
+
+datas1, binaries1, hiddenimports1 = collect_all('debugpy')
+hiddenimports2 = collect_submodules('xmlrpc')
+
+a = Analysis(['run.py'],
+ pathex=['.'],
+ binaries=ocp_path + binaries1,
+ datas=[(spyder_data, 'spyder'),
+ (cqw_path, 'cq_warehouse'),
+ (bdw_path, 'bd_warehouse'),
+ (cq_path, 'cadquery')] +
+ [(p, 'parso/python') for p in parso_grammar] + datas1,
+ hiddenimports=['ipykernel.datapub', 'debugpy', 'vtkmodules', 'vtkmodules.all',
+ 'pyqtgraph.graphicsItems.ViewBox.axisCtrlTemplate_pyqt5',
+ 'pyqtgraph.graphicsItems.PlotItem.plotConfigTemplate_pyqt5',
+ 'pyqtgraph.imageview.ImageViewTemplate_pyqt5', 'xmlrpc',
+ 'zmq.backend', 'cq_warehouse', 'cq_warehouse.bearing', 'cq_warehouse.chain',
+ 'cq_warehouse.drafting', 'cq_warehouse.extensions', 'cq_warehouse.fastener',
+ 'cq_warehouse.sprocket', 'cq_warehouse.thread', 'cq_gears', 'cq_cache',
+ 'build123d', 'cqmore', 'bd_warehouse', 'bd_warehouse.pipe', 'bd_warehouse.flange',
+ 'bd_warehouse.thread', 'bd_warehouse.gears'] + hiddenimports1 + hiddenimports2,
+ hookspath=['pyinstaller/extrahooks/'],
+ runtime_hooks=['pyinstaller/pyi_rth_occ.py',
+ 'pyinstaller/pyi_rth_fontconfig.py'],
+ excludes=['_tkinter'],
+ win_no_prefer_redirects=False,
+ win_private_assemblies=False,
+ cipher=block_cipher,
+ noarchive=False)
+
+# 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'),
+# ]
+
+
+pyz = PYZ(a.pure, a.zipped_data,
+ cipher=block_cipher)
+exe = EXE(pyz,
+ a.scripts,
+ [],
+ exclude_binaries=True,
+ name='CQ-editor',
+ debug=False,
+ bootloader_ignore_signals=False,
+ strip=False,
+ upx=True,
+ console=True,
+ icon='icons/cadquery_logo_dark.ico')
+
+exclude = ()
+#exclude = ('libGL','libEGL','libbsd')
+a.binaries = TOC([x for x in a.binaries if not x[0].startswith(exclude)])
+
+coll = COLLECT(exe,
+ a.binaries,
+ a.zipfiles,
+ a.datas,
+ strip=False,
+ upx=True,
+ name='CQ-editor')
diff --git a/setup.py b/setup.py
index 8943e67f..68c51e6a 100644
--- a/setup.py
+++ b/setup.py
@@ -3,6 +3,18 @@
from setuptools import setup, find_packages
+<<<<<<< HEAD
+
+def read(rel_path):
+ here = os.path.abspath(os.path.dirname(__file__))
+ with codecs.open(os.path.join(here, rel_path), "r") as fp:
+ return fp.read()
+
+
+def get_version(rel_path):
+ for line in read(rel_path).splitlines():
+ if line.startswith("__version__"):
+=======
def read(rel_path):
here = os.path.abspath(os.path.dirname(__file__))
with codecs.open(os.path.join(here, rel_path), 'r') as fp:
@@ -11,11 +23,37 @@ def read(rel_path):
def get_version(rel_path):
for line in read(rel_path).splitlines():
if line.startswith('__version__'):
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
delim = '"' if '"' in line else "'"
return line.split(delim)[1]
else:
raise RuntimeError("Unable to find version string.")
+<<<<<<< HEAD
+
+setup(
+ name="CQ-editor",
+ version=get_version("cq_editor/_version.py"),
+ packages=find_packages(),
+ entry_points={
+ "gui_scripts": [
+ "cq-editor = cq_editor.__main__:main",
+ "CQ-editor = cq_editor.__main__:main",
+ ]
+ },
+ python_requires=">=3.10,<3.14",
+ install_requires=[
+ "logbook>=1",
+ "ipython",
+ "path>=16",
+ "PyQt5>=5",
+ "requests>=2,<3",
+ "spyder>=5,<6",
+ "pyqtgraph",
+ "numpy >= 2, <3",
+ ],
+)
+=======
setup(name='CQ-editor',
version=get_version('cq_editor/_version.py'),
packages=find_packages(),
@@ -25,3 +63,4 @@ def get_version(rel_path):
'CQ-editor = cq_editor.__main__:main'
]}
)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
diff --git a/tests/test_app.py b/tests/test_app.py
index 2ec4baa7..67fd5996 100644
--- a/tests/test_app.py
+++ b/tests/test_app.py
@@ -84,6 +84,8 @@
r1 = cq.Workplane(solid1).translate((10, 0, 0))
"""
+<<<<<<< HEAD
+=======
code_show_all = """import cadquery as cq
b = cq.Workplane().box(1,1,1)
sh = b.val()
@@ -91,6 +93,7 @@
sk = cq.Sketch().rect(1,1)
"""
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def _modify_file(code, path="test.py"):
with open(path, "w", 1) as f:
f.write(code)
@@ -117,10 +120,17 @@ def get_bottom_left(widget):
return pos
def get_rgba(ais):
+<<<<<<< HEAD
+
+ alpha = ais.Transparency()
+ color = get_occ_color(ais)
+
+=======
alpha = ais.Transparency()
color = get_occ_color(ais)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
return color.redF(), color.greenF(), color.blueF(), alpha
@pytest.fixture
@@ -151,7 +161,11 @@ def main_clean(qtbot,mocker):
qtbot.addWidget(win)
qtbot.waitForWindowShown(win)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
editor = win.components['editor']
editor.set_text(code)
@@ -167,7 +181,11 @@ def main_clean_do_not_close(qtbot,mocker):
qtbot.addWidget(win)
qtbot.waitForWindowShown(win)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
editor = win.components['editor']
editor.set_text(code)
@@ -184,7 +202,11 @@ def main_multi(qtbot,mocker):
qtbot.addWidget(win)
qtbot.waitForWindowShown(win)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
editor = win.components['editor']
editor.set_text(code_multi)
@@ -202,7 +224,11 @@ def test_render(main):
debugger = win.components['debugger']
console = win.components['console']
log = win.components['log']
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# enable CQ reloading
debugger.preferences['Reload CQ'] = True
@@ -249,6 +275,22 @@ def test_render(main):
qtbot.wait(100)
assert(obj_tree_comp.CQ.child(0).text(0) == 'test')
assert('test' in log.toPlainText().splitlines()[-1])
+<<<<<<< HEAD
+
+ # cq reloading check
+ obj_tree_comp._toolbar_actions[0].triggered.emit()
+ assert(obj_tree_comp.CQ.childCount() == 0)
+
+ editor.set_text(code_reload_issue)
+ debugger._actions['Run'][0].triggered.emit()
+
+ qtbot.wait(100)
+ assert(obj_tree_comp.CQ.childCount() == 1)
+
+ debugger._actions['Run'][0].triggered.emit()
+ qtbot.wait(100)
+ assert(obj_tree_comp.CQ.childCount() == 1)
+=======
# cq reloading check
obj_tree_comp._toolbar_actions[0].triggered.emit()
@@ -263,6 +305,7 @@ def test_render(main):
debugger._actions['Run'][0].triggered.emit()
qtbot.wait(100)
assert(obj_tree_comp.CQ.childCount() == 3)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def test_export(main,mocker):
@@ -384,6 +427,8 @@ def assert_func(x):
variables = win.components['variables_viewer']
+<<<<<<< HEAD
+=======
traceback_view = win.components['traceback_viewer']
def check_no_error_occured():
@@ -391,6 +436,7 @@ def check_no_error_occured():
'''
assert( '' == traceback_view.current_exception.text())
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
viewer = win.components['viewer']
assert(number_visible_items(viewer) == 3)
@@ -401,31 +447,49 @@ def check_no_error_occured():
assert(debugger._frames == [])
#test step through
+<<<<<<< HEAD
+ ev = event_loop([lambda: (assert_func(variables.model().rowCount() == 4),
+ assert_func(number_visible_items(viewer) == 3),
+ step.triggered.emit()),
+ lambda: (assert_func(variables.model().rowCount() == 4),
+=======
ev = event_loop([lambda: (assert_func(variables.model().rowCount() == 5),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 3),
step.triggered.emit()),
lambda: (assert_func(variables.model().rowCount() == 5),
assert_func(number_visible_items(viewer) == 3),
step.triggered.emit()),
+<<<<<<< HEAD
+ lambda: (assert_func(variables.model().rowCount() == 5),
+=======
lambda: (assert_func(variables.model().rowCount() == 6),
assert_func(number_visible_items(viewer) == 3),
step.triggered.emit()),
lambda: (assert_func(variables.model().rowCount() == 6),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 4),
cont.triggered.emit())])
patch_debugger(debugger,ev)
debug.triggered.emit(True)
+<<<<<<< HEAD
+=======
check_no_error_occured()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert(variables.model().rowCount() == 2)
assert(number_visible_items(viewer) == 4)
#test exit debug
ev = event_loop([lambda: (step.triggered.emit(),),
+<<<<<<< HEAD
+ lambda: (assert_func(variables.model().rowCount() == 1),
+=======
lambda: (assert_func(variables.model().rowCount() == 5),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 3),
debug.triggered.emit(False),)])
@@ -433,14 +497,21 @@ def check_no_error_occured():
debug.triggered.emit(True)
+<<<<<<< HEAD
+=======
check_no_error_occured()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert(variables.model().rowCount() == 1)
assert(number_visible_items(viewer) == 3)
#test breakpoint
ev = event_loop([lambda: (cont.triggered.emit(),),
+<<<<<<< HEAD
+ lambda: (assert_func(variables.model().rowCount() == 5),
+=======
lambda: (assert_func(variables.model().rowCount() == 6),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 4),
cont.triggered.emit(),)])
@@ -450,6 +521,14 @@ def check_no_error_occured():
debug.triggered.emit(True)
+<<<<<<< HEAD
+ assert(variables.model().rowCount() == 2)
+ assert(number_visible_items(viewer) == 4)
+
+ #test breakpoint without using singals
+ ev = event_loop([lambda: (cont.triggered.emit(),),
+ lambda: (assert_func(variables.model().rowCount() == 5),
+=======
check_no_error_occured()
assert(variables.model().rowCount() == 2)
@@ -458,6 +537,7 @@ def check_no_error_occured():
#test breakpoint without using singals
ev = event_loop([lambda: (cont.triggered.emit(),),
lambda: (assert_func(variables.model().rowCount() == 6),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 4),
cont.triggered.emit(),)])
@@ -467,6 +547,14 @@ def check_no_error_occured():
debugger.debug(True)
+<<<<<<< HEAD
+ assert(variables.model().rowCount() == 2)
+ assert(number_visible_items(viewer) == 4)
+
+ #test debug() without using singals
+ ev = event_loop([lambda: (cont.triggered.emit(),),
+ lambda: (assert_func(variables.model().rowCount() == 5),
+=======
check_no_error_occured()
assert(variables.model().rowCount() == 2)
@@ -475,6 +563,7 @@ def check_no_error_occured():
#test debug() without using singals
ev = event_loop([lambda: (cont.triggered.emit(),),
lambda: (assert_func(variables.model().rowCount() == 6),
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
assert_func(number_visible_items(viewer) == 4),
cont.triggered.emit(),)])
@@ -484,11 +573,17 @@ def check_no_error_occured():
editor.debugger.set_breakpoints([(4,None)])
debugger.debug(True)
+<<<<<<< HEAD
+
+ CQ = obj_tree.CQ
+
+=======
check_no_error_occured()
CQ = obj_tree.CQ
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# object 1 (defualt color)
r,g,b,a = get_rgba(CQ.child(0).ais)
assert( a == pytest.approx(0.2) )
@@ -511,10 +606,13 @@ def check_no_error_occured():
result = cq.Workplane("XY" ).box(3, 3, 0.5).edges("|Z").fillet(0.125)
f()
'''
+<<<<<<< HEAD
+=======
code_err3 =\
'''import cadquery as cq
result = cq.Workplane("XY" ).box(3, 3, 0)
'''
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def test_traceback(main):
@@ -545,6 +643,21 @@ def test_traceback(main):
assert('NameError' in traceback_view.current_exception.text())
assert(hasattr(sys, 'last_traceback'))
+<<<<<<< HEAD
+
+ del sys.last_traceback
+ assert(not hasattr(sys, 'last_traceback'))
+
+
+ #test last_traceback with debug
+ ev = event_loop([lambda: (cont.triggered.emit(),)])
+ patch_debugger(debugger,ev)
+
+ debugger.debug(True)
+
+ assert('NameError' in traceback_view.current_exception.text())
+ assert(hasattr(sys, 'last_traceback'))
+=======
del sys.last_traceback
assert(not hasattr(sys, 'last_traceback'))
@@ -559,10 +672,13 @@ def test_traceback(main):
assert('NameError' in traceback_view.current_exception.text())
assert(hasattr(sys, 'last_traceback'))
assert(traceback_view.tree.root.childCount() == 1)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# restore the tracing function
sys.settrace(trace_function)
+<<<<<<< HEAD
+=======
# check if errors deeper in CQ are reported too
editor.set_text(code_err3)
run.triggered.emit()
@@ -570,6 +686,7 @@ def test_traceback(main):
assert('Standard_DomainError' in traceback_view.current_exception.text())
assert(traceback_view.tree.root.childCount() == 3) # 1 in user code + 2 in CQ code
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
@pytest.fixture
def editor(qtbot):
@@ -581,7 +698,11 @@ def editor(qtbot):
return qtbot, win
def conv_line_ends(text):
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
return '\n'.join(text.splitlines())
def test_editor(monkeypatch,editor):
@@ -708,9 +829,15 @@ def test_editor_autoreload(monkeypatch,editor):
# Saving a file with autoreload enabled should trigger a rerender.
with qtbot.waitSignal(editor.triggerRerender, timeout=TIMEOUT):
editor.save()
+<<<<<<< HEAD
+
+def test_autoreload_nested(editor):
+
+=======
def test_autoreload_nested(editor):
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
qtbot, editor = editor
TIMEOUT = 500
@@ -798,8 +925,13 @@ def approx_view_properties(eye,proj,scale):
return pytest.approx(eye+proj+(scale,))
+<<<<<<< HEAD
+ qtbot, win = main_clean
+
+=======
qtbot, win = main_clean
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
editor = win.components['editor']
debugger = win.components['debugger']
viewer = win.components['viewer']
@@ -901,7 +1033,11 @@ def test_selection(main_multi,mocker):
ctx = viewer._get_context()
ctx.InitSelected()
shapes = []
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
while ctx.MoreSelected():
shapes.append(ctx.SelectedShape())
ctx.NextSelected()
@@ -974,7 +1110,11 @@ def test_screenshot(main,mocker):
qtbot,win = main
mocker.patch.object(QFileDialog, 'getSaveFileName', return_value=('out.png',''))
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
viewer = win.components['viewer']
viewer._actions['Tools'][0].triggered.emit()
@@ -982,9 +1122,15 @@ def test_screenshot(main,mocker):
def test_resize(main):
+<<<<<<< HEAD
+ qtbot,win = main
+ editor = win.components['editor']
+
+=======
qtbot,win = main
editor = win.components['editor']
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
editor.hide()
qtbot.wait(50)
editor.show()
@@ -1050,9 +1196,15 @@ def test_render_colors(main_clean):
editor.set_text(code_color)
debugger._actions['Run'][0].triggered.emit()
+<<<<<<< HEAD
+
+ CQ = obj_tree.CQ
+
+=======
CQ = obj_tree.CQ
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# object 1 (defualt color)
assert not CQ.child(0).ais.HasColor()
@@ -1085,7 +1237,11 @@ def test_render_colors(main_clean):
# check if error occured
qtbot.wait(100)
assert('Unknown color format' in log.toPlainText().splitlines()[-1])
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
def test_render_colors_console(main_clean):
qtbot, win = main_clean
@@ -1095,12 +1251,21 @@ def test_render_colors_console(main_clean):
console = win.components['console']
console.execute_command(code_color)
+<<<<<<< HEAD
+
+ CQ = obj_tree.CQ
+
+ # object 1 (defualt color)
+ assert not CQ.child(0).ais.HasColor()
+
+=======
CQ = obj_tree.CQ
# object 1 (defualt color)
assert not CQ.child(0).ais.HasColor()
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# object 2
r,g,b,a = get_rgba(CQ.child(1).ais)
assert( a == 0.5 )
@@ -1125,7 +1290,11 @@ def test_render_colors_console(main_clean):
r,g,b,a = get_rgba(CQ.child(5).ais)
assert( a == 0.5 )
assert( r == 1.0 )
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# check if error occured
qtbot.wait(100)
assert('Unknown color format' in log.toPlainText().splitlines()[-1])
@@ -1141,7 +1310,11 @@ def test_render_colors_console(main_clean):
'''
def test_shading_aspect(main_clean):
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
qtbot, win = main_clean
obj_tree = win.components['object_tree']
@@ -1169,7 +1342,11 @@ def test_confirm_new(monkeypatch,editor):
editor.document().setPlainText(code)
assert(editor.modified == True)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
#monkeypatch the confirmation dialog and run both scenarios
def cancel(*args, **kwargs):
return QMessageBox.No
@@ -1179,6 +1356,20 @@ def ok(*args, **kwargs):
monkeypatch.setattr(QMessageBox, 'question',
staticmethod(cancel))
+<<<<<<< HEAD
+
+ editor.new()
+ assert(editor.modified == True)
+ assert(conv_line_ends(editor.get_text_with_eol()) == code)
+
+ monkeypatch.setattr(QMessageBox, 'question',
+ staticmethod(ok))
+
+ editor.new()
+ assert(editor.modified == False)
+ assert(editor.get_text_with_eol() == '')
+
+=======
editor.new()
assert(editor.modified == True)
@@ -1191,6 +1382,7 @@ def ok(*args, **kwargs):
assert(editor.modified == False)
assert(editor.get_text_with_eol() == '')
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
code_show_topods = \
'''
import cadquery as cq
@@ -1219,6 +1411,18 @@ def test_render_topods(main):
editor.set_text(code_show_topods)
debugger._actions['Run'][0].triggered.emit()
assert(obj_tree_comp.CQ.childCount() == 1)
+<<<<<<< HEAD
+
+ # test rendering of topods object via console
+ console.execute('show(result.val().wrapped)')
+ assert(obj_tree_comp.CQ.childCount() == 2)
+
+ # test rendering of list of topods object via console
+ console.execute('show([result.val().wrapped,result.val().wrapped])')
+ assert(obj_tree_comp.CQ.childCount() == 3)
+
+
+=======
# test rendering of topods object via console
console.execute('show(result.val().wrapped)')
@@ -1229,6 +1433,7 @@ def test_render_topods(main):
assert(obj_tree_comp.CQ.childCount() == 3)
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
code_show_shape_list = \
'''
import cadquery as cq
@@ -1242,7 +1447,11 @@ def test_render_topods(main):
def test_render_shape_list(main):
qtbot, win = main
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
log = win.components['log']
obj_tree_comp = win.components['object_tree']
@@ -1258,7 +1467,11 @@ def test_render_shape_list(main):
editor.set_text(code_show_shape_list)
debugger._actions['Run'][0].triggered.emit()
assert(obj_tree_comp.CQ.childCount() == 2)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
# test rendering of Shape via console
console.execute('show(result1)')
console.execute('show([result1,result2])')
@@ -1337,7 +1550,11 @@ def test_render_ais(main):
console.execute('show(ais)')
qtbot.wait(500)
assert(obj_tree_comp.CQ.childCount() == 2)
+<<<<<<< HEAD
+
+=======
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b
code_show_sketch = \
'''import cadquery as cq
@@ -1500,6 +1717,8 @@ def test_modulefinder(tmp_path, main):
qtbot.wait(100)
assert("Cannot determine imported modules" in log.toPlainText().splitlines()[-1])
+<<<<<<< HEAD
+=======
def test_show_all(main):
@@ -1582,3 +1801,4 @@ def test_show_without_name(main):
# Check that the name of the seconf object is an int
int(object_tree.CQ.child(1).text(0))
+>>>>>>> d746c8e8e6d80f53dc931ddc4910d4c791d7218b