diff --git a/.github/workflows/pyinstaller-builds-actions-mmamba.yml b/.github/workflows/pyinstaller-builds-actions-mmamba.yml index 5896eb4..0456ba6 100644 --- a/.github/workflows/pyinstaller-builds-actions-mmamba.yml +++ b/.github/workflows/pyinstaller-builds-actions-mmamba.yml @@ -36,13 +36,13 @@ jobs: pip install git+https://github.com/meadiode/cq_gears.git@main pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache" pip install git+https://github.com/gumyr/build123d.git#egg=build123d + pip install git+https://github.com/JustinSDK/cqMore - name: Run build shell: bash --login {0} run: | micromamba info pyinstaller pyinstaller.spec ${{ github.event.inputs.type }} cp /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/pyinstaller/CQ-editor.sh /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/ - rm /home/runner/work/jmwright-CQ-Editor/jmwright-CQ-Editor/dist/CQ-editor/libstdc++.so.6 - uses: actions/upload-artifact@v2 with: name: CQ-editor-Linux-x86_64 @@ -73,6 +73,7 @@ jobs: pip install git+https://github.com/meadiode/cq_gears.git@main pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache" pip install git+https://github.com/gumyr/build123d.git#egg=build123d + pip install git+https://github.com/JustinSDK/cqMore - name: Run build shell: bash --login {0} run: | @@ -107,6 +108,7 @@ jobs: pip install git+https://github.com/meadiode/cq_gears.git@main pip install -e "git+https://github.com/CadQuery/cadquery-plugins.git#egg=cq_cache&subdirectory=plugins/cq_cache" pip install git+https://github.com/gumyr/build123d.git#egg=build123d + pip install git+https://github.com/JustinSDK/cqMore - name: Run build shell: powershell run: | diff --git a/cq_editor/cq_utils.py b/cq_editor/cq_utils.py index e32ba6c..0c30d94 100644 --- a/cq_editor/cq_utils.py +++ b/cq_editor/cq_utils.py @@ -7,7 +7,7 @@ from types import SimpleNamespace from OCP.XCAFPrs import XCAFPrs_AISObject from OCP.TopoDS import TopoDS_Shape -from OCP.AIS import AIS_InteractiveObject, AIS_Shape, AIS_ColoredShape +from OCP.AIS import AIS_InteractiveObject, AIS_Shape from OCP.Quantity import \ Quantity_TOC_RGB as TOC_RGB, Quantity_Color @@ -33,6 +33,10 @@ def to_compound(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq. vals.append(cq.Shape.cast(obj)) elif isinstance(obj,list) and isinstance(obj[0],TopoDS_Shape): vals.extend(cq.Shape.cast(o) for o in obj) + elif hasattr(obj, "wrapped") and isinstance(obj.wrapped, TopoDS_Shape): + vals.append(cq.Shape.cast(obj.wrapped)) + elif hasattr(obj, "_obj") and hasattr(obj._obj, "wrapped") and isinstance(obj._obj.wrapped, TopoDS_Shape): + vals.append(cq.Shape.cast(obj._obj.wrapped)) elif isinstance(obj, cq.Sketch): if obj._faces: vals.append(obj._faces) @@ -62,7 +66,7 @@ def make_AIS(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Sha ais = obj else: shape = to_compound(obj) - ais = AIS_ColoredShape(shape.wrapped) + ais = AIS_Shape(shape.wrapped) if 'alpha' in options: ais.SetTransparency(options['alpha']) @@ -105,13 +109,23 @@ def to_occ_color(color) -> Quantity_Color: color.blueF(), TOC_RGB) -def get_occ_color(ais : AIS_ColoredShape) -> QColor: - - color = Quantity_Color() - ais.Color(color) +def get_occ_color(obj : Union[AIS_InteractiveObject, Quantity_Color]) -> QColor: + + if isinstance(obj, AIS_InteractiveObject): + color = Quantity_Color() + obj.Color(color) + else: + color = obj return QColor.fromRgbF(color.Red(), color.Green(), color.Blue()) +def set_color(ais : AIS_Shape, color : Quantity_Color) -> AIS_Shape: + + drawer = ais.Attributes() + drawer.ShadingAspect().SetColor(color) + + return ais + def reload_cq(): # NB: order of reloads is important diff --git a/cq_editor/widgets/cq_utils.py b/cq_editor/widgets/cq_utils.py deleted file mode 100644 index cad702f..0000000 --- a/cq_editor/widgets/cq_utils.py +++ /dev/null @@ -1,149 +0,0 @@ -import cadquery as cq -from cadquery.occ_impl.assembly import toCAF - -from typing import List, Union -from imp import reload -from types import SimpleNamespace - -from OCP.XCAFPrs import XCAFPrs_AISObject -from OCP.TopoDS import TopoDS_Shape -from OCP.AIS import AIS_InteractiveObject, AIS_Shape, AIS_ColoredShape -from OCP.Quantity import \ - Quantity_TOC_RGB as TOC_RGB, Quantity_Color - -from PyQt5.QtGui import QColor - -def find_cq_objects(results : dict): - - return {k:SimpleNamespace(shape=v,options={}) for k,v in results.items() if isinstance(v,cq.Workplane)} - -def to_compound(obj : Union[cq.Workplane, List[cq.Workplane], cq.Shape, List[cq.Shape], cq.Sketch]): - - vals = [] - - if isinstance(obj,cq.Workplane): - vals.extend(obj.vals()) - elif isinstance(obj,cq.Shape): - vals.append(obj) - elif isinstance(obj,list) and isinstance(obj[0],cq.Workplane): - for o in obj: vals.extend(o.vals()) - elif isinstance(obj,list) and isinstance(obj[0],cq.Shape): - vals.extend(obj) - elif isinstance(obj, TopoDS_Shape): - vals.append(cq.Shape.cast(obj)) - elif isinstance(obj,list) and isinstance(obj[0],TopoDS_Shape): - vals.extend(cq.Shape.cast(o) for o in obj) - elif 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_ColoredShape(shape.wrapped) - - if 'alpha' in options: - ais.SetTransparency(options['alpha']) - if 'color' in options: - ais.SetColor(to_occ_color(options['color'])) - if 'rgba' in options: - r,g,b,a = options['rgba'] - ais.SetColor(to_occ_color((r,g,b))) - ais.SetTransparency(a) - - return ais,shape - -def export(obj : Union[cq.Workplane, List[cq.Workplane]], type : str, - file, precision=1e-1): - - comp = to_compound(obj) - - if type == 'stl': - comp.exportStl(file, tolerance=precision) - elif type == 'step': - comp.exportStep(file) - elif type == 'brep': - comp.exportBrep(file) - -def to_occ_color(color) -> Quantity_Color: - - if not isinstance(color, QColor): - if isinstance(color, tuple): - if isinstance(color[0], int): - color = QColor(*color) - elif isinstance(color[0], float): - color = QColor.fromRgbF(*color) - else: - raise ValueError('Unknown color format') - else: - color = QColor(color) - - return Quantity_Color(color.redF(), - color.greenF(), - color.blueF(), - TOC_RGB) - -def get_occ_color(ais : AIS_ColoredShape) -> QColor: - - color = Quantity_Color() - ais.Color(color) - - return QColor.fromRgbF(color.Red(), color.Green(), color.Blue()) - -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.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 diff --git a/cq_editor/widgets/object_tree.py b/cq_editor/widgets/object_tree.py index 5e83158..15e52ba 100644 --- a/cq_editor/widgets/object_tree.py +++ b/cq_editor/widgets/object_tree.py @@ -9,7 +9,8 @@ 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 +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): @@ -49,14 +50,19 @@ class ObjectTreeItem(QTreeWidgetItem): self.properties['Name'] = name self.properties['Alpha'] = ais.Transparency() - self.properties['Color'] = get_occ_color(ais) if ais else color + 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,*args): + def propertiesChanged(self, properties, changed): + + changed_prop = changed[0][0] self.setData(0,0,self.properties['Name']) self.ais.SetTransparency(self.properties['Alpha']) - self.ais.SetColor(to_occ_color(self.properties['Color'])) + + if changed_prop.name() == 'Color': + set_color(self.ais, to_occ_color(self.properties['Color'])) + self.ais.Redisplay() if self.properties['Visible']: diff --git a/cq_editor/widgets/viewer.py b/cq_editor/widgets/viewer.py index 2cf2d64..33a085d 100644 --- a/cq_editor/widgets/viewer.py +++ b/cq_editor/widgets/viewer.py @@ -1,22 +1,16 @@ -# -*- coding: utf-8 -*- +from PyQt5.QtWidgets import QWidget, QDialog, QTreeWidgetItem, QApplication, QAction -from OCP.Graphic3d import Graphic3d_Camera, Graphic3d_StereoMode -from PyQt5.QtWidgets import (QWidget, QPushButton, QDialog, QTreeWidget, - QTreeWidgetItem, QVBoxLayout, QFileDialog, - QHBoxLayout, QFrame, QLabel, QApplication, - QToolBar, QAction) - -from PyQt5.QtCore import QSize, pyqtSlot, pyqtSignal, QMetaObject, Qt +from PyQt5.QtCore import pyqtSlot, pyqtSignal from PyQt5.QtGui import QIcon -from OCP.AIS import AIS_Shaded,AIS_WireFrame, AIS_ColoredShape, \ - AIS_Axis, AIS_Line -from OCP.Aspect import Aspect_GDM_Lines, Aspect_GT_Rectangular, Aspect_GFM_VER -from OCP.Quantity import Quantity_NOC_BLACK as BLACK, \ +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_NOC_GOLD as GOLD,\ Quantity_TOC_RGB as TOC_RGB, Quantity_Color -from OCP.Geom import Geom_CylindricalSurface, Geom_Plane, Geom_Circle,\ - Geom_TrimmedCurve, Geom_Axis1Placement, Geom_Axis2Placement, Geom_Line -from OCP.gp import gp_Trsf, gp_Vec, gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1 +from OCP.Geom import Geom_Axis1Placement +from OCP.gp import gp_Ax3, gp_Dir, gp_Pnt, gp_Ax1 from ..utils import layout, get_save_filename from ..mixins import ComponentMixin @@ -27,7 +21,9 @@ from .occt_widget import OCCTWidget from pyqtgraph.parametertree import Parameter import qtawesome as qta - +DEFAULT_FACE_COLOR = Quantity_Color(GOLD) +DEFAULT_EDGE_COLOR = Quantity_Color(BLACK) +DEFAULT_EDGE_WIDTH = 2 class OCCViewer(QWidget,ComponentMixin): @@ -64,9 +60,23 @@ class OCCViewer(QWidget,ComponentMixin): [self.canvas,], top_widget=self, margin=0) - + self.setup_default_drawer() #misspelled in original self.updatePreferences() + + def setup_default_drawer(self): + # set the default color and material + material = Graphic3d_MaterialAspect(Graphic3d_NOM_JADE) + + shading_aspect = self.canvas.context.DefaultDrawer().ShadingAspect() + shading_aspect.SetMaterial(material) + shading_aspect.SetColor(DEFAULT_FACE_COLOR) + + # face edge lw + 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']) diff --git a/pyinstaller.spec b/pyinstaller.spec index 0298fc3..e476e97 100644 --- a/pyinstaller.spec +++ b/pyinstaller.spec @@ -38,7 +38,7 @@ a = Analysis(['run.py'], 'pyqtgraph.imageview.ImageViewTemplate_pyqt5', 'xmlrpc', 'zmq.backend', 'cq_warehouse', 'cq_warehouse.bearing', 'cq_warehouse.chain', 'cq_warehouse.drafting', 'cq_warehouse.extensions', 'cq_warehouse.fastener', - 'cq_warehouse.sprocket', 'cq_warehouse.thread', 'cq_gears', 'cq_cache', 'build123d'] + hiddenimports1 + hiddenimports2, + 'cq_warehouse.sprocket', 'cq_warehouse.thread', 'cq_gears', 'cq_cache', 'build123d', 'cqmore'] + hiddenimports1 + hiddenimports2, hookspath=[], runtime_hooks=['pyinstaller/pyi_rth_occ.py', 'pyinstaller/pyi_rth_fontconfig.py'], diff --git a/tests/test_app.py b/tests/test_app.py index 5222ba1..c70eecd 100644 --- a/tests/test_app.py +++ b/tests/test_app.py @@ -114,7 +114,7 @@ def get_rgba(ais): alpha = ais.Transparency() color = get_occ_color(ais) - return color.redF(),color.redF(),color.redF(),alpha + return color.redF(), color.greenF(), color.blueF(), alpha @pytest.fixture def main(qtbot,mocker): @@ -1017,15 +1017,14 @@ def test_render_colors(main_clean): CQ = obj_tree.CQ # object 1 (defualt color) - r,g,b,a = get_rgba(CQ.child(0).ais) - assert( a == 0 ) - assert( r != 1.0 ) + assert not CQ.child(0).ais.HasColor() # object 2 r,g,b,a = get_rgba(CQ.child(1).ais) assert( a == 0.5 ) assert( r == 1.0 ) - + assert( g == 0.0 ) + # object 3 r,g,b,a = get_rgba(CQ.child(2).ais) assert( a == 0.5) @@ -1059,20 +1058,11 @@ def test_render_colors_console(main_clean): console = win.components['console'] console.execute_command(code_color) - - def get_rgba(ais): - - alpha = ais.Transparency() - color = get_occ_color(ais) - - return color.redF(),color.redF(),color.redF(),alpha - + CQ = obj_tree.CQ # object 1 (defualt color) - r,g,b,a = get_rgba(CQ.child(0).ais) - assert( a == 0 ) - assert( r != 1.0 ) + assert not CQ.child(0).ais.HasColor() # object 2 r,g,b,a = get_rgba(CQ.child(1).ais)