add unused parameters to show_object for compat with ocp_vscode

This commit is contained in:
jdegenstein
2023-04-12 10:49:10 -05:00
committed by GitHub
parent 151b8b8e36
commit 9a60cf7802
2 changed files with 617 additions and 557 deletions

View File

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

View File

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