| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| |
|
| |
|
| | from PySide import QtCore, QtGui |
| | from collections import Counter |
| | from contextlib import contextmanager |
| | from pivy import coin |
| | import FreeCAD |
| | import FreeCADGui |
| | import Path |
| | import Path.Base.Gui.SetupSheet as PathSetupSheetGui |
| | import Path.Base.Util as PathUtil |
| | import Path.GuiInit as PathGuiInit |
| | import Path.Main.Gui.JobCmd as PathJobCmd |
| | import Path.Main.Gui.JobDlg as PathJobDlg |
| | import Path.Main.Job as PathJob |
| | import Path.Main.Stock as PathStock |
| | import Path.Tool.Gui.Controller as PathToolControllerGui |
| | import PathScripts.PathUtils as PathUtils |
| | from Path.Tool.toolbit.ui.selector import ToolBitSelector |
| | import math |
| | import traceback |
| | from PySide import QtWidgets |
| |
|
| | import MatGui |
| | import Materials |
| |
|
| | |
| | from lazy_loader.lazy_loader import LazyLoader |
| |
|
| | Draft = LazyLoader("Draft", globals(), "Draft") |
| | Part = LazyLoader("Part", globals(), "Part") |
| | DraftVecUtils = LazyLoader("DraftVecUtils", globals(), "DraftVecUtils") |
| |
|
| | translate = FreeCAD.Qt.translate |
| |
|
| | if False: |
| | Path.Log.setLevel(Path.Log.Level.DEBUG, Path.Log.thisModule()) |
| | Path.Log.trackModule(Path.Log.thisModule()) |
| | else: |
| | Path.Log.setLevel(Path.Log.Level.INFO, Path.Log.thisModule()) |
| |
|
| |
|
| | def _OpenCloseResourceEditor(obj, vobj, edit): |
| | job = PathUtils.findParentJob(obj) |
| | if job and job.ViewObject and job.ViewObject.Proxy: |
| | if edit: |
| | job.ViewObject.Proxy.editObject(obj) |
| | else: |
| | job.ViewObject.Proxy.uneditObject(obj) |
| | else: |
| | missing = "Job" |
| | if job: |
| | missing = "ViewObject" |
| | if job.ViewObject: |
| | missing = "Proxy" |
| | Path.Log.warning("Cannot edit %s - no %s" % (obj.Label, missing)) |
| |
|
| |
|
| | @contextmanager |
| | def selectionEx(): |
| | sel = FreeCADGui.Selection.getSelectionEx() |
| | try: |
| | yield sel |
| | finally: |
| | FreeCADGui.Selection.clearSelection() |
| | for s in sel: |
| | if s.SubElementNames: |
| | FreeCADGui.Selection.addSelection(s.Object, s.SubElementNames) |
| | else: |
| | FreeCADGui.Selection.addSelection(s.Object) |
| |
|
| |
|
| | class ViewProvider: |
| | def __init__(self, vobj): |
| | mode = 2 |
| | vobj.setEditorMode("BoundingBox", mode) |
| | vobj.setEditorMode("DisplayMode", mode) |
| | vobj.setEditorMode("Selectable", mode) |
| | vobj.setEditorMode("ShapeAppearance", mode) |
| | vobj.setEditorMode("Transparency", mode) |
| | self.deleteOnReject = True |
| |
|
| | |
| | self.axs = None |
| | self.mat = None |
| | self.obj = None |
| | self.sca = None |
| | self.scs = None |
| | self.sep = None |
| | self.sph = None |
| | self.switch = None |
| | self.taskPanel = None |
| | self.vobj = None |
| | self.baseVisibility = {} |
| | self.stockVisibility = False |
| |
|
| | def attach(self, vobj): |
| | self.vobj = vobj |
| | self.obj = vobj.Object |
| | self.taskPanel = None |
| | if not hasattr(self, "baseVisibility"): |
| | self.baseVisibility = {} |
| | if not hasattr(self, "stockVisibility"): |
| | self.stockVisibility = False |
| |
|
| | |
| | self.switch = coin.SoSwitch() |
| | self.sep = coin.SoType.fromName("So3DAnnotation").createInstance() |
| |
|
| | self.axs = coin.SoType.fromName("SoFCPlacementIndicatorKit").createInstance() |
| | self.axs.axisLength.setValue(1.2) |
| |
|
| | |
| | parts = FreeCADGui.PlacementIndicatorParts |
| | self.axs.parts.setValue( |
| | parts.Axes | parts.Labels | parts.ArrowHeads | parts.OriginIndicator |
| | ) |
| |
|
| | self.sep.addChild(self.axs) |
| | self.switch.addChild(self.sep) |
| |
|
| | vobj.RootNode.addChild(self.switch) |
| | self.showOriginAxis(True) |
| |
|
| | for base in self.obj.Model.Group: |
| | Path.Log.debug(f"{base.Name}: {base.ViewObject.Visibility}") |
| |
|
| | def onChanged(self, vobj, prop): |
| | if prop == "Visibility": |
| | self.showOriginAxis(vobj.Visibility) |
| |
|
| | |
| | |
| | |
| | |
| |
|
| | if self.obj.Document.Restoring: |
| | return |
| |
|
| | if vobj.Visibility: |
| | self.restoreOperationsVisibility() |
| | self.restoreModelsVisibility() |
| | self.restoreStockVisibility() |
| | self.restoreToolsVisibility() |
| | else: |
| | self.hideOperations() |
| | self.hideModels() |
| | self.hideStock() |
| | self.hideTools() |
| |
|
| | def hideOperations(self): |
| | self.operationsVisibility = {} |
| | for op in self.obj.Operations.Group: |
| | self.operationsVisibility[op.Name] = op.Visibility |
| | op.Visibility = False |
| |
|
| | def restoreOperationsVisibility(self): |
| | if hasattr(self, "operationsVisibility"): |
| | for op in self.obj.Operations.Group: |
| | if self.operationsVisibility.get(op.Name, True): |
| | op.Visibility = True |
| | else: |
| | for op in self.obj.Operations.Group: |
| | op.Visibility = True |
| |
|
| | def hideModels(self): |
| | self.modelsVisibility = {} |
| | for model in self.obj.Model.Group: |
| | self.modelsVisibility[model.Name] = model.Visibility |
| | model.Visibility = False |
| |
|
| | def restoreModelsVisibility(self): |
| | if hasattr(self, "modelsVisibility"): |
| | for model in self.obj.Model.Group: |
| | if self.modelsVisibility.get(model.Name, True): |
| | model.Visibility = True |
| | else: |
| | for model in self.obj.Model.Group: |
| | model.Visibility = True |
| |
|
| | def hideStock(self): |
| | self.stockVisibility = self.obj.Stock.Visibility |
| | self.obj.Stock.Visibility = False |
| |
|
| | def restoreStockVisibility(self): |
| | if hasattr(self, "stockVisibility"): |
| | if self.stockVisibility: |
| | self.obj.Stock.Visibility = True |
| |
|
| | def hideTools(self): |
| | self.toolsVisibility = {} |
| | for tc in self.obj.Tools.Group: |
| | self.toolsVisibility[tc.Tool.Name] = tc.Tool.Visibility |
| | tc.Tool.Visibility = False |
| |
|
| | def restoreToolsVisibility(self): |
| | if hasattr(self, "toolsVisibility"): |
| | for tc in self.obj.Tools.Group: |
| | if self.toolsVisibility.get(tc.Tool.Name, True): |
| | tc.Tool.Visibility = True |
| |
|
| | def showOriginAxis(self, yes): |
| | sw = coin.SO_SWITCH_ALL if yes else coin.SO_SWITCH_NONE |
| | self.switch.whichChild = sw |
| |
|
| | def dumps(self): |
| | return None |
| |
|
| | def loads(self, state): |
| | return None |
| |
|
| | def deleteObjectsOnReject(self): |
| | return hasattr(self, "deleteOnReject") and self.deleteOnReject |
| |
|
| | def setEdit(self, vobj=None, mode=0): |
| | Path.Log.track(mode) |
| | if 0 == mode: |
| | job = self.vobj.Object |
| | if not job.Proxy.integrityCheck(job): |
| | return False |
| | self.openTaskPanel() |
| | return True |
| |
|
| | def openTaskPanel(self, activate=None): |
| | self.taskPanel = TaskPanel(self.vobj, self.deleteObjectsOnReject()) |
| | FreeCADGui.Control.closeDialog() |
| | FreeCADGui.Control.showDialog(self.taskPanel) |
| | self.taskPanel.setupUi(activate) |
| | self.showOriginAxis(True) |
| | self.deleteOnReject = False |
| |
|
| | def resetTaskPanel(self): |
| | self.showOriginAxis(False) |
| | self.taskPanel = None |
| |
|
| | def unsetEdit(self, arg1, arg2): |
| | if self.taskPanel: |
| | self.taskPanel.reject(False) |
| |
|
| | def editObject(self, obj): |
| | if obj: |
| | |
| | if hasattr(obj, "IsBoundary") and getattr(obj, "IsBoundary", False): |
| | return False |
| | if obj in self.obj.Model.Group: |
| | return self.openTaskPanel("Model") |
| | if obj == self.obj.Stock: |
| | return self.openTaskPanel("Stock") |
| | Path.Log.info("Expected a specific object to edit - %s not recognized" % obj.Label) |
| | return self.openTaskPanel() |
| |
|
| | def uneditObject(self, obj=None): |
| | self.unsetEdit(None, None) |
| |
|
| | def getIcon(self): |
| | return ":/icons/CAM_Job.svg" |
| |
|
| | def claimChildren(self): |
| | children = [] |
| | if hasattr(self.obj, "Operations"): |
| | children.append(self.obj.Operations) |
| | if hasattr(self.obj, "Model"): |
| | |
| | |
| | |
| | children.append(self.obj.Model) |
| | if hasattr(self.obj, "Stock"): |
| | children.append(self.obj.Stock) |
| | if hasattr(self.obj, "SetupSheet"): |
| | |
| | children.append(self.obj.SetupSheet) |
| | if hasattr(self.obj, "Tools"): |
| | children.append(self.obj.Tools) |
| | return children |
| |
|
| | def onDelete(self, vobj, arg2=None): |
| | Path.Log.track(vobj.Object.Label, arg2) |
| | self.obj.Proxy.onDelete(self.obj, arg2) |
| | return True |
| |
|
| | def updateData(self, obj, prop): |
| | Path.Log.track(obj.Label, prop) |
| | |
| | if prop == "Model" and self.obj.Model: |
| | for base in self.obj.Model.Group: |
| | if base.ViewObject and hasattr(base.ViewObject, "Proxy") and base.ViewObject.Proxy: |
| | base.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) |
| | if ( |
| | prop == "Stock" |
| | and self.obj.Stock |
| | and self.obj.Stock.ViewObject |
| | and self.obj.Stock.ViewObject.Proxy |
| | ): |
| | self.obj.Stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) |
| |
|
| | def rememberBaseVisibility(self, obj, base): |
| | Path.Log.track() |
| | if base.ViewObject: |
| | orig = PathUtil.getPublicObject(obj.Proxy.baseObject(obj, base)) |
| | self.baseVisibility[base.Name] = ( |
| | base, |
| | base.ViewObject.Visibility, |
| | orig, |
| | orig.ViewObject.Visibility, |
| | ) |
| | orig.ViewObject.Visibility = False |
| | base.ViewObject.Visibility = True |
| |
|
| | def forgetBaseVisibility(self, obj, base): |
| | Path.Log.track() |
| | |
| | |
| | |
| | |
| | |
| |
|
| | def setupEditVisibility(self, obj): |
| | Path.Log.track() |
| | self.baseVisibility = {} |
| | for base in obj.Model.Group: |
| | self.rememberBaseVisibility(obj, base) |
| |
|
| | self.stockVisibility = False |
| | if obj.Stock and obj.Stock.ViewObject: |
| | self.stockVisibility = obj.Stock.ViewObject.Visibility |
| | self.obj.Stock.ViewObject.Visibility = True |
| |
|
| | def resetEditVisibility(self, obj): |
| | Path.Log.track() |
| | for base in obj.Model.Group: |
| | self.forgetBaseVisibility(obj, base) |
| | if obj.Stock and obj.Stock.ViewObject: |
| | obj.Stock.ViewObject.Visibility = self.stockVisibility |
| |
|
| | def setupContextMenu(self, vobj, menu): |
| | Path.Log.track() |
| | for action in menu.actions(): |
| | menu.removeAction(action) |
| | action = QtGui.QAction(translate("CAM_Job", "Edit"), menu) |
| | action.triggered.connect(self._editInContextMenuTriggered) |
| | menu.addAction(action) |
| |
|
| | def _editInContextMenuTriggered(self, checked): |
| | self.setEdit() |
| |
|
| |
|
| | class MaterialDialog(QtWidgets.QDialog): |
| | def __init__(self, parent=None): |
| | super(MaterialDialog, self).__init__(parent) |
| |
|
| | self.setWindowTitle("Assign Material") |
| |
|
| | self.materialTree = FreeCADGui.UiLoader().createWidget("MatGui::MaterialTreeWidget") |
| | self.materialTreeWidget = MatGui.MaterialTreeWidget(self.materialTree) |
| |
|
| | material_filter = Materials.MaterialFilter() |
| | material_filter.Name = "Machining Materials" |
| | material_filter.RequiredModels = [Materials.UUIDs().Machinability] |
| | self.materialTreeWidget.setFilter(material_filter) |
| | self.materialTreeWidget.selectFilter("Machining Materials") |
| |
|
| | |
| | self.okButton = QtWidgets.QPushButton("OK") |
| | self.cancelButton = QtWidgets.QPushButton("Cancel") |
| |
|
| | |
| | self.okButton.clicked.connect(self.accept) |
| | self.cancelButton.clicked.connect(self.reject) |
| |
|
| | |
| | layout = QtWidgets.QVBoxLayout() |
| | layout.addWidget(self.materialTree) |
| |
|
| | buttonLayout = QtWidgets.QHBoxLayout() |
| | buttonLayout.addStretch() |
| | buttonLayout.addWidget(self.okButton) |
| | buttonLayout.addWidget(self.cancelButton) |
| |
|
| | layout.addLayout(buttonLayout) |
| | self.setLayout(layout) |
| | self.materialTree.onMaterial.connect(self.onMaterial) |
| |
|
| | def onMaterial(self, uuid): |
| | try: |
| | print("Selected '{0}'".format(uuid)) |
| | self.uuid = uuid |
| | except Exception as e: |
| | print(e) |
| |
|
| |
|
| | class StockEdit(object): |
| | Index = -1 |
| | StockType = PathStock.StockType.Unknown |
| |
|
| | def __init__(self, obj, form, force): |
| | Path.Log.track(obj.Label, force) |
| | self.obj = obj |
| | self.form = form |
| | self.force = force |
| | self.setupUi(obj) |
| |
|
| | @classmethod |
| | def IsStock(cls, obj): |
| | return PathStock.StockType.FromStock(obj.Stock) == cls.StockType |
| |
|
| | def activate(self, obj, select=False): |
| | Path.Log.track(obj.Label, select) |
| |
|
| | def showHide(widget, activeWidget): |
| | if widget == activeWidget: |
| | widget.show() |
| | else: |
| | widget.hide() |
| |
|
| | if select: |
| | self.form.stock.setCurrentIndex(self.Index) |
| | editor = self.editorFrame() |
| | showHide(self.form.stockFromExisting, editor) |
| | showHide(self.form.stockFromBase, editor) |
| | showHide(self.form.stockCreateBox, editor) |
| | showHide(self.form.stockCreateCylinder, editor) |
| | self.setFields(obj) |
| |
|
| | def setStock(self, obj, stock): |
| | Path.Log.track(obj.Label, stock) |
| | if obj.Stock: |
| | Path.Log.track(obj.Stock.Name) |
| | obj.Document.removeObject(obj.Stock.Name) |
| | Path.Log.track(stock.Name) |
| | obj.Stock = stock |
| | if stock.ViewObject and stock.ViewObject.Proxy: |
| | stock.ViewObject.Proxy.onEdit(_OpenCloseResourceEditor) |
| |
|
| | def setLengthField(self, widget, prop): |
| | widget.setText(FreeCAD.Units.Quantity(prop.Value, FreeCAD.Units.Length).UserString) |
| |
|
| | |
| | def editorFrame(self): |
| | return None |
| |
|
| | def setFields(self, obj): |
| | pass |
| |
|
| | def setupUi(self, obj): |
| | pass |
| |
|
| |
|
| | class StockFromBaseBoundBoxEdit(StockEdit): |
| | Index = 2 |
| | StockType = PathStock.StockType.FromBase |
| |
|
| | def __init__(self, obj, form, force): |
| | super(StockFromBaseBoundBoxEdit, self).__init__(obj, form, force) |
| |
|
| | self.trackXpos = None |
| | self.trackYpos = None |
| | self.trackZpos = None |
| |
|
| | def editorFrame(self): |
| | Path.Log.track() |
| | return self.form.stockFromBase |
| |
|
| | def getFieldsStock(self, stock, fields=None): |
| | if fields is None: |
| | fields = ["xneg", "xpos", "yneg", "ypos", "zneg", "zpos"] |
| | try: |
| | if "xneg" in fields: |
| | stock.ExtXneg = FreeCAD.Units.Quantity(self.form.stockExtXneg.text()) |
| | if "xpos" in fields: |
| | stock.ExtXpos = FreeCAD.Units.Quantity(self.form.stockExtXpos.text()) |
| | if "yneg" in fields: |
| | stock.ExtYneg = FreeCAD.Units.Quantity(self.form.stockExtYneg.text()) |
| | if "ypos" in fields: |
| | stock.ExtYpos = FreeCAD.Units.Quantity(self.form.stockExtYpos.text()) |
| | if "zneg" in fields: |
| | stock.ExtZneg = FreeCAD.Units.Quantity(self.form.stockExtZneg.text()) |
| | if "zpos" in fields: |
| | stock.ExtZpos = FreeCAD.Units.Quantity(self.form.stockExtZpos.text()) |
| | except Exception: |
| | pass |
| |
|
| | def getFields(self, obj, fields=None): |
| | if fields is None: |
| | fields = ["xneg", "xpos", "yneg", "ypos", "zneg", "zpos"] |
| | Path.Log.track(obj.Label, fields) |
| | if self.IsStock(obj): |
| | self.getFieldsStock(obj.Stock, fields) |
| | else: |
| | Path.Log.error("Stock not from Base bound box!") |
| |
|
| | def setFields(self, obj): |
| | Path.Log.track() |
| | if self.force or not self.IsStock(obj): |
| | Path.Log.track() |
| | stock = PathStock.CreateFromBase(obj) |
| | if self.force and self.editorFrame().isVisible(): |
| | self.getFieldsStock(stock) |
| | self.setStock(obj, stock) |
| | self.force = False |
| | self.setLengthField(self.form.stockExtXneg, obj.Stock.ExtXneg) |
| | self.setLengthField(self.form.stockExtXpos, obj.Stock.ExtXpos) |
| | self.setLengthField(self.form.stockExtYneg, obj.Stock.ExtYneg) |
| | self.setLengthField(self.form.stockExtYpos, obj.Stock.ExtYpos) |
| | self.setLengthField(self.form.stockExtZneg, obj.Stock.ExtZneg) |
| | self.setLengthField(self.form.stockExtZpos, obj.Stock.ExtZpos) |
| |
|
| | def setupUi(self, obj): |
| | Path.Log.track() |
| | self.setFields(obj) |
| | self.checkXpos() |
| | self.checkYpos() |
| | self.checkZpos() |
| | self.form.stockExtXneg.textChanged.connect(self.updateXpos) |
| | self.form.stockExtYneg.textChanged.connect(self.updateYpos) |
| | self.form.stockExtZneg.textChanged.connect(self.updateZpos) |
| | self.form.stockExtXpos.textChanged.connect(self.checkXpos) |
| | self.form.stockExtYpos.textChanged.connect(self.checkYpos) |
| | self.form.stockExtZpos.textChanged.connect(self.checkZpos) |
| | if hasattr(self.form, "linkStockAndModel"): |
| | self.form.linkStockAndModel.setChecked(False) |
| |
|
| | def checkXpos(self): |
| | self.trackXpos = self.form.stockExtXneg.text() == self.form.stockExtXpos.text() |
| | self.getFields(self.obj, ["xpos"]) |
| |
|
| | def checkYpos(self): |
| | self.trackYpos = self.form.stockExtYneg.text() == self.form.stockExtYpos.text() |
| | self.getFields(self.obj, ["ypos"]) |
| |
|
| | def checkZpos(self): |
| | self.trackZpos = self.form.stockExtZneg.text() == self.form.stockExtZpos.text() |
| | self.getFields(self.obj, ["zpos"]) |
| |
|
| | def updateXpos(self): |
| | fields = ["xneg"] |
| | if self.trackXpos: |
| | self.form.stockExtXpos.setText(self.form.stockExtXneg.text()) |
| | fields.append("xpos") |
| | self.getFields(self.obj, fields) |
| |
|
| | def updateYpos(self): |
| | fields = ["yneg"] |
| | if self.trackYpos: |
| | self.form.stockExtYpos.setText(self.form.stockExtYneg.text()) |
| | fields.append("ypos") |
| | self.getFields(self.obj, fields) |
| |
|
| | def updateZpos(self): |
| | fields = ["zneg"] |
| | if self.trackZpos: |
| | self.form.stockExtZpos.setText(self.form.stockExtZneg.text()) |
| | fields.append("zpos") |
| | self.getFields(self.obj, fields) |
| |
|
| |
|
| | class StockCreateBoxEdit(StockEdit): |
| | Index = 0 |
| | StockType = PathStock.StockType.CreateBox |
| |
|
| | def editorFrame(self): |
| | return self.form.stockCreateBox |
| |
|
| | def getFields(self, obj, fields=None): |
| | if fields is None: |
| | fields = ["length", "width", "height"] |
| | try: |
| | if self.IsStock(obj): |
| | if "length" in fields: |
| | obj.Stock.Length = FreeCAD.Units.Quantity(self.form.stockBoxLength.text()) |
| | if "width" in fields: |
| | obj.Stock.Width = FreeCAD.Units.Quantity(self.form.stockBoxWidth.text()) |
| | if "height" in fields: |
| | obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockBoxHeight.text()) |
| | else: |
| | Path.Log.error("Stock not a box!") |
| | except Exception: |
| | pass |
| |
|
| | def setFields(self, obj): |
| | if self.force or not self.IsStock(obj): |
| | self.setStock(obj, PathStock.CreateBox(obj)) |
| | self.force = False |
| | self.setLengthField(self.form.stockBoxLength, obj.Stock.Length) |
| | self.setLengthField(self.form.stockBoxWidth, obj.Stock.Width) |
| | self.setLengthField(self.form.stockBoxHeight, obj.Stock.Height) |
| |
|
| | def setupUi(self, obj): |
| | self.setFields(obj) |
| | self.form.stockBoxLength.textChanged.connect(lambda: self.getFields(obj, ["length"])) |
| | self.form.stockBoxWidth.textChanged.connect(lambda: self.getFields(obj, ["width"])) |
| | self.form.stockBoxHeight.textChanged.connect(lambda: self.getFields(obj, ["height"])) |
| |
|
| |
|
| | class StockCreateCylinderEdit(StockEdit): |
| | Index = 1 |
| | StockType = PathStock.StockType.CreateCylinder |
| |
|
| | def editorFrame(self): |
| | return self.form.stockCreateCylinder |
| |
|
| | def getFields(self, obj, fields=None): |
| | if fields is None: |
| | fields = ["radius", "height"] |
| | try: |
| | if self.IsStock(obj): |
| | if "radius" in fields: |
| | obj.Stock.Radius = FreeCAD.Units.Quantity(self.form.stockCylinderRadius.text()) |
| | if "height" in fields: |
| | obj.Stock.Height = FreeCAD.Units.Quantity(self.form.stockCylinderHeight.text()) |
| | else: |
| | Path.Log.error(translate("CAM_Job", "Stock not a cylinder!")) |
| | except Exception: |
| | pass |
| |
|
| | def setFields(self, obj): |
| | if self.force or not self.IsStock(obj): |
| | self.setStock(obj, PathStock.CreateCylinder(obj)) |
| | self.force = False |
| | self.setLengthField(self.form.stockCylinderRadius, obj.Stock.Radius) |
| | self.setLengthField(self.form.stockCylinderHeight, obj.Stock.Height) |
| |
|
| | def setupUi(self, obj): |
| | self.setFields(obj) |
| | self.form.stockCylinderRadius.textChanged.connect(lambda: self.getFields(obj, ["radius"])) |
| | self.form.stockCylinderHeight.textChanged.connect(lambda: self.getFields(obj, ["height"])) |
| |
|
| |
|
| | class StockFromExistingEdit(StockEdit): |
| | Index = 3 |
| | StockType = PathStock.StockType.Unknown |
| | StockLabelPrefix = "Stock" |
| |
|
| | def editorFrame(self): |
| | return self.form.stockFromExisting |
| |
|
| | def getFields(self, obj): |
| | stock = self.form.stockExisting.itemData(self.form.stockExisting.currentIndex()) |
| | if not ( |
| | hasattr(obj.Stock, "Objects") |
| | and len(obj.Stock.Objects) == 1 |
| | and obj.Stock.Objects[0] == stock |
| | ): |
| | if stock: |
| | stock = PathJob.createResourceClone(obj, stock, self.StockLabelPrefix, "Stock") |
| | stock.ViewObject.Visibility = True |
| | PathStock.SetupStockObject(stock, PathStock.StockType.Unknown) |
| | stock.Proxy.execute(stock) |
| | self.setStock(obj, stock) |
| |
|
| | def candidates(self, obj): |
| | solids = [o for o in obj.Document.Objects if PathUtil.isSolid(o)] |
| | if hasattr(obj, "Model"): |
| | job = obj |
| | else: |
| | job = PathUtils.findParentJob(obj) |
| | for base in job.Model.Group: |
| | if base in solids and PathJob.isResourceClone(job, base, "Model"): |
| | solids.remove(base) |
| | if job.Stock in solids: |
| | |
| | solids.remove(job.Stock) |
| | excludeIndexes = [] |
| | for index, model in enumerate(solids): |
| | if [ob.Name for ob in model.InListRecursive if "Tools" in ob.Name]: |
| | excludeIndexes.append(index) |
| | elif hasattr(model, "PathResource"): |
| | excludeIndexes.append(index) |
| | elif model.InList and hasattr(model.InList[0], "ToolBitID"): |
| | excludeIndexes.append(index) |
| | elif hasattr(model, "ToolBitID"): |
| | excludeIndexes.append(index) |
| | elif model.TypeId == "App::DocumentObjectGroup": |
| | excludeIndexes.append(index) |
| | elif hasattr(model, "StockType"): |
| | excludeIndexes.append(index) |
| | elif not model.ViewObject.ShowInTree: |
| | excludeIndexes.append(index) |
| |
|
| | for i in sorted(excludeIndexes, reverse=True): |
| | del solids[i] |
| |
|
| | return sorted(solids, key=lambda c: c.Label) |
| |
|
| | def setFields(self, obj): |
| | |
| | |
| | |
| | |
| | |
| | try: |
| | self.form.stockExisting.blockSignals(True) |
| | self.form.stockExisting.clear() |
| | stockName = obj.Stock.Label if obj.Stock else None |
| | index = -1 |
| | for i, solid in enumerate(self.candidates(obj)): |
| | self.form.stockExisting.addItem(solid.Label, solid) |
| | label = "{}-{}".format(self.StockLabelPrefix, solid.Label) |
| |
|
| | |
| | |
| | |
| | |
| | |
| | if label in stockName: |
| | index = i |
| |
|
| | self.form.stockExisting.setCurrentIndex(index if index != -1 else 0) |
| | finally: |
| | self.form.stockExisting.blockSignals(False) |
| |
|
| | if not self.IsStock(obj): |
| | self.getFields(obj) |
| |
|
| | def setupUi(self, obj): |
| | self.setFields(obj) |
| | self.form.stockExisting.currentIndexChanged.connect(lambda: self.getFields(obj)) |
| |
|
| |
|
| | class TaskPanel: |
| | DataObject = QtCore.Qt.ItemDataRole.UserRole |
| | DataProperty = QtCore.Qt.ItemDataRole.UserRole + 1 |
| |
|
| | def __init__(self, vobj, deleteOnReject): |
| | FreeCAD.ActiveDocument.openTransaction("Edit Job") |
| | self.vobj = vobj |
| | self.vproxy = vobj.Proxy |
| | self.obj = vobj.Object |
| | self.deleteOnReject = deleteOnReject |
| | self.form = FreeCADGui.PySideUic.loadUi(":/panels/PathEdit.ui") |
| | self.template = PathJobDlg.JobTemplateExport(self.obj, self.form.jobBox.widget(1)) |
| | self.name = self.obj.Name |
| |
|
| | vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] |
| | self.form.toolControllerList.horizontalHeaderItem(1).setText("#") |
| | self.form.toolControllerList.horizontalHeaderItem(2).setText( |
| | translate("Path", "H", "H is horizontal feed rate. Must be as short as possible") |
| | ) |
| | self.form.toolControllerList.horizontalHeaderItem(3).setText( |
| | translate("Path", "V", "V is vertical feed rate. Must be as short as possible") |
| | ) |
| | self.form.toolControllerList.horizontalHeader().setResizeMode(0, QtGui.QHeaderView.Stretch) |
| | self.form.toolControllerList.horizontalHeaderItem(1).setToolTip( |
| | translate("Path", "Tool number") + " " |
| | ) |
| | self.form.toolControllerList.horizontalHeaderItem(2).setToolTip( |
| | translate("Path", "Horizontal feedrate") + " " + vUnit |
| | ) |
| | self.form.toolControllerList.horizontalHeaderItem(3).setToolTip( |
| | translate("Path", "Vertical feedrate") + " " + vUnit |
| | ) |
| | self.form.toolControllerList.horizontalHeaderItem(4).setToolTip( |
| | translate("Path", "Spindle RPM") + " " |
| | ) |
| |
|
| | |
| | self.form.toolControllerList.setWordWrap(False) |
| |
|
| | self.form.toolControllerList.resizeColumnsToContents() |
| |
|
| | currentPostProcessor = self.obj.PostProcessor |
| | postProcessors = Path.Preferences.allEnabledPostProcessors(["", currentPostProcessor]) |
| | for post in postProcessors: |
| | self.form.postProcessor.addItem(post) |
| | |
| | self.obj.PostProcessor = postProcessors |
| | self.obj.PostProcessor = currentPostProcessor |
| |
|
| | self.postProcessorDefaultTooltip = self.form.postProcessor.toolTip() |
| | self.postProcessorArgsDefaultTooltip = self.form.postProcessorArguments.toolTip() |
| |
|
| | |
| | comboToPropertyMap = [("orderBy", "OrderOutputBy")] |
| | enumTups = PathJob.ObjectJob.propertyEnumerations(dataType="raw") |
| | self.populateCombobox(self.form, enumTups, comboToPropertyMap) |
| |
|
| | self.vproxy.setupEditVisibility(self.obj) |
| |
|
| | self.stockFromBase = None |
| | self.stockFromExisting = None |
| | self.stockCreateBox = None |
| | self.stockCreateCylinder = None |
| | self.stockEdit = None |
| |
|
| | self.setupGlobal = PathSetupSheetGui.GlobalEditor(self.obj.SetupSheet, self.form) |
| | self.setupOps = PathSetupSheetGui.OpsDefaultEditor(self.obj.SetupSheet, self.form) |
| |
|
| | def populateCombobox(self, form, enumTups, comboBoxesPropertyMap): |
| | """populateCombobox(form, enumTups, comboBoxesPropertyMap) ... populate comboboxes with translated enumerations |
| | ** comboBoxesPropertyMap will be unnecessary if UI files use strict combobox naming protocol. |
| | Args: |
| | form = UI form |
| | enumTups = list of (translated_text, data_string) tuples |
| | comboBoxesPropertyMap = list of (translated_text, data_string) tuples |
| | """ |
| | |
| | for cb, prop in comboBoxesPropertyMap: |
| | box = getattr(form, cb) |
| | box.clear() |
| | for text, data in enumTups[prop]: |
| | box.addItem(text, data) |
| |
|
| | def assignMaterial(self): |
| | dialog = MaterialDialog() |
| | result = dialog.exec_() |
| |
|
| | if result == QtWidgets.QDialog.Accepted: |
| | FreeCAD.Console.PrintMessage("Material assigned\n") |
| | |
| |
|
| | if dialog.uuid is not None: |
| | material_manager = Materials.MaterialManager() |
| | material = material_manager.getMaterial(dialog.uuid) |
| | self.obj.Stock.ShapeMaterial = material |
| |
|
| | def preCleanup(self): |
| | Path.Log.track() |
| | FreeCADGui.Selection.removeObserver(self) |
| | self.vproxy.resetEditVisibility(self.obj) |
| | self.vproxy.resetTaskPanel() |
| |
|
| | def accept(self, resetEdit=True): |
| | Path.Log.track() |
| | self._jobIntegrityCheck() |
| | self.preCleanup() |
| | self.getFields() |
| | self.setupGlobal.accept() |
| | self.setupOps.accept() |
| | FreeCAD.ActiveDocument.commitTransaction() |
| | self.cleanup(resetEdit) |
| |
|
| | def reject(self, resetEdit=True): |
| | Path.Log.track() |
| | self.preCleanup() |
| | self.setupGlobal.reject() |
| | self.setupOps.reject() |
| | FreeCAD.ActiveDocument.abortTransaction() |
| | if self.deleteOnReject and FreeCAD.ActiveDocument.getObject(self.name): |
| | Path.Log.info("Uncreate Job") |
| | FreeCAD.ActiveDocument.openTransaction("Uncreate Job") |
| | if self.obj.ViewObject.Proxy.onDelete(self.obj.ViewObject, None): |
| | FreeCAD.ActiveDocument.removeObject(self.obj.Name) |
| | FreeCAD.ActiveDocument.commitTransaction() |
| | else: |
| | Path.Log.track( |
| | self.name, |
| | self.deleteOnReject, |
| | FreeCAD.ActiveDocument.getObject(self.name), |
| | ) |
| | self.cleanup(resetEdit) |
| | return True |
| |
|
| | def cleanup(self, resetEdit): |
| | Path.Log.track() |
| | FreeCADGui.Control.closeDialog() |
| | if resetEdit: |
| | FreeCADGui.ActiveDocument.resetEdit() |
| | FreeCAD.ActiveDocument.recompute() |
| |
|
| | def updateTooltips(self): |
| | if ( |
| | hasattr(self.obj, "Proxy") |
| | and hasattr(self.obj.Proxy, "tooltip") |
| | and self.obj.Proxy.tooltip |
| | ): |
| | self.form.postProcessor.setToolTip(self.obj.Proxy.tooltip) |
| | if hasattr(self.obj.Proxy, "tooltipArgs") and self.obj.Proxy.tooltipArgs: |
| | self.form.postProcessorArguments.setToolTip(self.obj.Proxy.tooltipArgs) |
| | else: |
| | self.form.postProcessorArguments.setToolTip(self.postProcessorArgsDefaultTooltip) |
| | else: |
| | self.form.postProcessor.setToolTip(self.postProcessorDefaultTooltip) |
| | self.form.postProcessorArguments.setToolTip(self.postProcessorArgsDefaultTooltip) |
| |
|
| | def getFields(self): |
| | """sets properties in the object to match the form""" |
| | if self.obj: |
| | self.obj.PostProcessor = str(self.form.postProcessor.currentText()) |
| | self.obj.PostProcessorArgs = str(self.form.postProcessorArguments.displayText()) |
| | self.obj.PostProcessorOutputFile = str(self.form.postProcessorOutputFile.text()) |
| |
|
| | self.obj.Label = str(self.form.jobLabel.text()) |
| | self.obj.Description = str(self.form.jobDescription.toPlainText()) |
| | self.obj.Operations.Group = [ |
| | self.form.operationsList.item(i).data(self.DataObject) |
| | for i in range(self.form.operationsList.count()) |
| | ] |
| | try: |
| | self.obj.SplitOutput = self.form.splitOutput.isChecked() |
| | self.obj.OrderOutputBy = str(self.form.orderBy.currentData()) |
| |
|
| | flist = [] |
| | for i in range(self.form.wcslist.count()): |
| | if self.form.wcslist.item(i).checkState() == QtCore.Qt.CheckState.Checked: |
| | flist.append(self.form.wcslist.item(i).text()) |
| | self.obj.Fixtures = flist |
| | except Exception as e: |
| | Path.Log.debug(e) |
| | FreeCAD.Console.PrintWarning( |
| | "The Job was created without fixture support. Please delete and recreate the job\r\n" |
| | ) |
| |
|
| | self.updateTooltips() |
| | self.stockEdit.getFields(self.obj) |
| |
|
| | self.obj.Proxy.execute(self.obj) |
| |
|
| | self.setupGlobal.getFields() |
| | self.setupOps.getFields() |
| |
|
| | def selectComboBoxText(self, widget, text): |
| | """selectInComboBox(name, combo) ... |
| | helper function to select a specific value in a combo box.""" |
| | index = widget.currentIndex() |
| |
|
| | |
| | newindex = widget.findData(text) |
| | if newindex >= 0: |
| |
|
| | widget.blockSignals(True) |
| | widget.setCurrentIndex(newindex) |
| | widget.blockSignals(False) |
| | return |
| |
|
| | |
| | newindex = widget.findText(text, QtCore.Qt.MatchFixedString) |
| | if newindex >= 0: |
| | widget.blockSignals(True) |
| | widget.setCurrentIndex(newindex) |
| | widget.blockSignals(False) |
| | return |
| |
|
| | widget.blockSignals(True) |
| | widget.setCurrentIndex(index) |
| | widget.blockSignals(False) |
| | return |
| |
|
| | def updateToolController(self): |
| | tcRow = self.form.toolControllerList.currentRow() |
| | tcCol = self.form.toolControllerList.currentColumn() |
| |
|
| | self.form.toolControllerList.blockSignals(True) |
| | self.form.toolControllerList.clearContents() |
| | self.form.toolControllerList.setRowCount(0) |
| |
|
| | self.form.activeToolController.blockSignals(True) |
| | index = self.form.activeToolController.currentIndex() |
| | select = None if index == -1 else self.form.activeToolController.itemData(index) |
| | self.form.activeToolController.clear() |
| |
|
| | vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] |
| |
|
| | for row, tc in enumerate(sorted(self.obj.Tools.Group, key=lambda tc: tc.Label)): |
| | self.form.activeToolController.addItem(tc.Label, tc) |
| | if tc == select: |
| | index = row |
| |
|
| | self.form.toolControllerList.insertRow(row) |
| |
|
| | item = QtGui.QTableWidgetItem(tc.Label) |
| | item.setData(self.DataObject, tc) |
| | item.setData(self.DataProperty, "Label") |
| | self.form.toolControllerList.setItem(row, 0, item) |
| |
|
| | item = QtGui.QTableWidgetItem("%d" % tc.ToolNumber) |
| | item.setTextAlignment(QtCore.Qt.AlignRight) |
| | item.setData(self.DataObject, tc) |
| | item.setData(self.DataProperty, "Number") |
| | self.form.toolControllerList.setItem(row, 1, item) |
| |
|
| | item = QtGui.QTableWidgetItem("%g" % tc.HorizFeed.getValueAs(vUnit)) |
| | item.setTextAlignment(QtCore.Qt.AlignRight) |
| | item.setData(self.DataObject, tc) |
| | item.setData(self.DataProperty, "HorizFeed") |
| | self.form.toolControllerList.setItem(row, 2, item) |
| |
|
| | item = QtGui.QTableWidgetItem("%g" % tc.VertFeed.getValueAs(vUnit)) |
| | item.setTextAlignment(QtCore.Qt.AlignRight) |
| | item.setData(self.DataObject, tc) |
| | item.setData(self.DataProperty, "VertFeed") |
| | self.form.toolControllerList.setItem(row, 3, item) |
| |
|
| | item = QtGui.QTableWidgetItem( |
| | "%s%g" % ("+" if tc.SpindleDir == "Forward" else "-", tc.SpindleSpeed) |
| | ) |
| | item.setTextAlignment(QtCore.Qt.AlignRight) |
| | item.setData(self.DataObject, tc) |
| | item.setData(self.DataProperty, "Spindle") |
| | self.form.toolControllerList.setItem(row, 4, item) |
| |
|
| | if index != -1: |
| | self.form.activeToolController.setCurrentIndex(index) |
| | if tcRow != -1 and tcCol != -1: |
| | self.form.toolControllerList.setCurrentCell(tcRow, tcCol) |
| |
|
| | self.form.activeToolController.blockSignals(False) |
| | self.form.toolControllerList.blockSignals(False) |
| |
|
| | def setFields(self): |
| | """sets fields in the form to match the object""" |
| |
|
| | self.form.jobLabel.setText(self.obj.Label) |
| | self.form.jobDescription.setPlainText(self.obj.Description) |
| |
|
| | if hasattr(self.obj, "SplitOutput"): |
| | self.form.splitOutput.setChecked(self.obj.SplitOutput) |
| | if hasattr(self.obj, "OrderOutputBy"): |
| | self.selectComboBoxText(self.form.orderBy, self.obj.OrderOutputBy) |
| |
|
| | if hasattr(self.obj, "Fixtures"): |
| | for f in self.obj.Fixtures: |
| | item = self.form.wcslist.findItems(f, QtCore.Qt.MatchExactly)[0] |
| | item.setCheckState(QtCore.Qt.Checked) |
| |
|
| | self.form.postProcessorOutputFile.setText(self.obj.PostProcessorOutputFile) |
| | self.selectComboBoxText(self.form.postProcessor, self.obj.PostProcessor) |
| | self.form.postProcessorArguments.setText(self.obj.PostProcessorArgs) |
| | |
| | self.updateTooltips() |
| |
|
| | self.form.operationsList.clear() |
| | for child in self.obj.Operations.Group: |
| | item = QtGui.QListWidgetItem(child.Label) |
| | item.setData(self.DataObject, child) |
| | self.form.operationsList.addItem(item) |
| |
|
| | self.form.jobModel.clear() |
| | for name, count in Counter( |
| | [self.obj.Proxy.baseObject(self.obj, o).Label for o in self.obj.Model.Group] |
| | ).items(): |
| | if count == 1: |
| | self.form.jobModel.addItem(name) |
| | else: |
| | self.form.jobModel.addItem("%s (%d)" % (name, count)) |
| |
|
| | self.updateToolController() |
| | self.stockEdit.setFields(self.obj) |
| | self.setupGlobal.setFields() |
| | self.setupOps.setFields() |
| |
|
| | def setPostProcessorOutputFile(self): |
| | filename = QtGui.QFileDialog.getSaveFileName( |
| | self.form, |
| | translate("CAM_Job", "Select Output File"), |
| | None, |
| | translate("CAM_Job", "All Files (*.*)"), |
| | ) |
| | if filename and filename[0]: |
| | self.obj.PostProcessorOutputFile = str(filename[0]) |
| | self.setFields() |
| |
|
| | def operationSelect(self): |
| | if self.form.operationsList.selectedItems(): |
| | self.form.operationModify.setEnabled(True) |
| | self.form.operationMove.setEnabled(True) |
| | row = self.form.operationsList.currentRow() |
| | self.form.operationUp.setEnabled(row > 0) |
| | self.form.operationDown.setEnabled(row < self.form.operationsList.count() - 1) |
| | else: |
| | self.form.operationModify.setEnabled(False) |
| | self.form.operationMove.setEnabled(False) |
| |
|
| | def objectDelete(self, widget): |
| | for item in widget.selectedItems(): |
| | obj = item.data(self.DataObject) |
| | if ( |
| | obj.ViewObject |
| | and hasattr(obj.ViewObject, "Proxy") |
| | and hasattr(obj.ViewObject.Proxy, "onDelete") |
| | ): |
| | obj.ViewObject.Proxy.onDelete(obj.ViewObject, None) |
| | FreeCAD.ActiveDocument.removeObject(obj.Name) |
| | self.setFields() |
| |
|
| | def operationDelete(self): |
| | self.objectDelete(self.form.operationsList) |
| |
|
| | def operationMoveUp(self): |
| | row = self.form.operationsList.currentRow() |
| | if row > 0: |
| | item = self.form.operationsList.takeItem(row) |
| | self.form.operationsList.insertItem(row - 1, item) |
| | self.form.operationsList.setCurrentRow(row - 1) |
| | self.getFields() |
| |
|
| | def operationMoveDown(self): |
| | row = self.form.operationsList.currentRow() |
| | if row < self.form.operationsList.count() - 1: |
| | item = self.form.operationsList.takeItem(row) |
| | self.form.operationsList.insertItem(row + 1, item) |
| | self.form.operationsList.setCurrentRow(row + 1) |
| | self.getFields() |
| |
|
| | def toolControllerSelect(self): |
| | def canDeleteTC(tc): |
| | |
| | return len(tc.InList) == 1 |
| |
|
| | |
| | edit = True if self.form.toolControllerList.selectedItems() else False |
| | self.form.toolControllerEdit.setEnabled(edit) |
| |
|
| | |
| | delete = edit |
| | |
| | if len(self.obj.Tools.Group) == len(self.form.toolControllerList.selectedItems()): |
| | delete = False |
| | |
| | if delete: |
| | for item in self.form.toolControllerList.selectedItems(): |
| | if not canDeleteTC(item.data(self.DataObject)): |
| | delete = False |
| | break |
| | self.form.toolControllerDelete.setEnabled(delete) |
| |
|
| | def toolControllerEdit(self): |
| | for item in self.form.toolControllerList.selectedItems(): |
| | tc = item.data(self.DataObject) |
| | dlg = PathToolControllerGui.DlgToolControllerEdit(tc) |
| | dlg.exec_() |
| | self.setFields() |
| | self.toolControllerSelect() |
| |
|
| | def toolControllerAdd(self): |
| | selector = ToolBitSelector(compact=True) |
| | if not selector.exec_(): |
| | return |
| | toolbit = selector.get_selected_tool() |
| | toolbit.attach_to_doc(FreeCAD.ActiveDocument) |
| | toolNum = self.obj.Proxy.nextToolNumber() |
| | tc = PathToolControllerGui.Create( |
| | name=f"TC: {toolbit.label}", tool=toolbit.obj, toolNumber=toolNum |
| | ) |
| | self.obj.Proxy.addToolController(tc) |
| |
|
| | FreeCAD.ActiveDocument.recompute() |
| | self.updateToolController() |
| |
|
| | def toolControllerDelete(self): |
| | self.objectDelete(self.form.toolControllerList) |
| |
|
| | def toolControllerChanged(self, item): |
| | tc = item.data(self.DataObject) |
| | prop = item.data(self.DataProperty) |
| | if "Label" == prop: |
| | tc.Label = item.text() |
| | item.setText(tc.Label) |
| | elif "Number" == prop: |
| | try: |
| | tc.ToolNumber = int(item.text()) |
| | except Exception: |
| | pass |
| | item.setText("%d" % tc.ToolNumber) |
| | elif "Spindle" == prop: |
| | try: |
| | speed = float(item.text()) |
| | rot = "Forward" |
| | if speed < 0: |
| | rot = "Reverse" |
| | speed = -speed |
| | tc.SpindleDir = rot |
| | tc.SpindleSpeed = speed |
| | except Exception: |
| | pass |
| | item.setText("%s%g" % ("+" if tc.SpindleDir == "Forward" else "-", tc.SpindleSpeed)) |
| | elif "HorizFeed" == prop or "VertFeed" == prop: |
| | vUnit = FreeCAD.Units.Quantity(1, FreeCAD.Units.Velocity).getUserPreferred()[2] |
| | try: |
| | val = FreeCAD.Units.Quantity(item.text()) |
| | if FreeCAD.Units.Velocity == val.Unit: |
| | setattr(tc, prop, val) |
| | elif FreeCAD.Units.Unit() == val.Unit: |
| | val = FreeCAD.Units.Quantity(item.text() + vUnit) |
| | setattr(tc, prop, val) |
| | except Exception: |
| | pass |
| | item.setText("%g" % getattr(tc, prop).getValueAs(vUnit)) |
| | else: |
| | try: |
| | val = FreeCAD.Units.Quantity(item.text()) |
| | setattr(tc, prop, val) |
| | except Exception: |
| | pass |
| | item.setText("%g" % getattr(tc, prop).Value) |
| |
|
| | self.template.updateUI() |
| |
|
| | def modelSetAxis(self, axis): |
| | Path.Log.track(axis) |
| |
|
| | def alignSel(sel, normal, flip=False): |
| | Path.Log.track("Vector(%.2f, %.2f, %.2f)" % (normal.x, normal.y, normal.z), flip) |
| | v = axis |
| | if flip: |
| | v = axis.negative() |
| |
|
| | if Path.Geom.pointsCoincide(abs(v), abs(normal)): |
| | |
| | |
| | |
| | |
| | |
| | r = FreeCAD.Vector(v.y, v.z, v.x) |
| | a = 180 |
| | else: |
| | r = v.cross(normal) |
| | a = DraftVecUtils.angle(normal, v, r) * 180 / math.pi |
| | Path.Log.debug( |
| | "oh boy: (%.2f, %.2f, %.2f) x (%.2f, %.2f, %.2f) -> (%.2f, %.2f, %.2f) -> %.2f" |
| | % (v.x, v.y, v.z, normal.x, normal.y, normal.z, r.x, r.y, r.z, a) |
| | ) |
| | Draft.rotate(sel.Object, a, axis=r) |
| |
|
| | selObject = None |
| | selFeature = None |
| | with selectionEx() as selection: |
| | for sel in selection: |
| | selObject = sel.Object |
| | for feature in sel.SubElementNames: |
| | selFeature = feature |
| | Path.Log.track(selObject.Label, feature) |
| | sub = sel.Object.Shape.getElement(feature) |
| |
|
| | if "Face" == sub.ShapeType: |
| | normal = sub.normalAt(0, 0) |
| | if sub.Orientation == "Reversed": |
| | normal = FreeCAD.Vector() - normal |
| | Path.Log.debug( |
| | "(%.2f, %.2f, %.2f) -> reversed (%s)" |
| | % (normal.x, normal.y, normal.z, sub.Orientation) |
| | ) |
| | else: |
| | Path.Log.debug( |
| | "(%.2f, %.2f, %.2f) -> forward (%s)" |
| | % (normal.x, normal.y, normal.z, sub.Orientation) |
| | ) |
| |
|
| | if Path.Geom.pointsCoincide(axis, normal): |
| | alignSel(sel, normal, True) |
| | elif Path.Geom.pointsCoincide(axis, FreeCAD.Vector() - normal): |
| | alignSel(sel, FreeCAD.Vector() - normal, True) |
| | else: |
| | alignSel(sel, normal) |
| |
|
| | elif "Edge" == sub.ShapeType: |
| | normal = (sub.Vertexes[1].Point - sub.Vertexes[0].Point).normalize() |
| | if Path.Geom.pointsCoincide(axis, normal) or Path.Geom.pointsCoincide( |
| | axis, FreeCAD.Vector() - normal |
| | ): |
| | |
| | |
| | alignSel(sel, normal, True) |
| | else: |
| | alignSel(sel, normal) |
| |
|
| | else: |
| | Path.Log.track(sub.ShapeType) |
| |
|
| | if selObject and selFeature: |
| | FreeCADGui.Selection.clearSelection() |
| | FreeCADGui.Selection.addSelection(selObject, selFeature) |
| |
|
| | def restoreSelection(self, selection): |
| | FreeCADGui.Selection.clearSelection() |
| | for sel in selection: |
| | FreeCADGui.Selection.addSelection(sel.Object, sel.SubElementNames) |
| |
|
| | def modelSet0(self, axis): |
| | Path.Log.track(axis) |
| | with selectionEx() as selection: |
| | for sel in selection: |
| | selObject = sel.Object |
| | Path.Log.track(selObject.Label) |
| | for name in sel.SubElementNames: |
| | Path.Log.track(selObject.Label, name) |
| | feature = selObject.Shape.getElement(name) |
| | bb = feature.BoundBox |
| | offset = FreeCAD.Vector(axis.x * bb.XMax, axis.y * bb.YMax, axis.z * bb.ZMax) |
| | Path.Log.track(feature.BoundBox.ZMax, offset) |
| | p = selObject.Placement |
| | p.move(offset) |
| | selObject.Placement = p |
| |
|
| | if self.form.linkStockAndModel.isChecked(): |
| | |
| | |
| | |
| | for model in self.obj.Model.Group: |
| | if model != selObject: |
| | Draft.move(model, offset) |
| | if selObject != self.obj.Stock and self.obj.Stock: |
| | Draft.move(self.obj.Stock, offset) |
| |
|
| | def modelMove(self, axis): |
| | scale = self.form.modelMoveValue.value() |
| | with selectionEx() as selection: |
| | for sel in selection: |
| | offset = axis * scale |
| | Draft.move(sel.Object, offset) |
| |
|
| | def modelRotate(self, axis): |
| | angle = self.form.modelRotateValue.value() |
| | with selectionEx() as selection: |
| | if self.form.modelRotateCompound.isChecked() and len(selection) > 1: |
| | bb = PathStock.shapeBoundBox([sel.Object for sel in selection]) |
| | for sel in selection: |
| | Draft.rotate(sel.Object, angle, bb.Center, axis) |
| | else: |
| | for sel in selection: |
| | Draft.rotate(sel.Object, angle, sel.Object.Shape.BoundBox.Center, axis) |
| |
|
| | def alignSetOrigin(self): |
| | (obj, by) = self.alignMoveToOrigin() |
| |
|
| | for base in self.obj.Model.Group: |
| | if base != obj: |
| | Draft.move(base, by) |
| |
|
| | if obj != self.obj.Stock and self.obj.Stock: |
| | Draft.move(self.obj.Stock, by) |
| |
|
| | placement = FreeCADGui.ActiveDocument.ActiveView.viewPosition() |
| | placement.Base = placement.Base + by |
| | FreeCADGui.ActiveDocument.ActiveView.viewPosition(placement, 0) |
| |
|
| | def alignMoveToOrigin(self): |
| | selObject = None |
| | selFeature = None |
| | p = None |
| | for sel in FreeCADGui.Selection.getSelectionEx(): |
| | selObject = sel.Object |
| | for feature in sel.SubElementNames: |
| | selFeature = feature |
| | sub = sel.Object.Shape.getElement(feature) |
| | if "Vertex" == sub.ShapeType: |
| | p = FreeCAD.Vector() - sub.Point |
| | if "Edge" == sub.ShapeType: |
| | p = FreeCAD.Vector() - sub.Curve.Location |
| | if "Face" == sub.ShapeType: |
| | p = FreeCAD.Vector() - sub.BoundBox.Center |
| |
|
| | if p: |
| | Draft.move(sel.Object, p) |
| |
|
| | if selObject and selFeature: |
| | FreeCADGui.Selection.clearSelection() |
| | FreeCADGui.Selection.addSelection(selObject, selFeature) |
| | return (selObject, p) |
| |
|
| | def updateStockEditor(self, index, force=False): |
| | def setupFromBaseEdit(): |
| | Path.Log.track(index, force) |
| | if force or not self.stockFromBase: |
| | self.stockFromBase = StockFromBaseBoundBoxEdit(self.obj, self.form, force) |
| | self.stockEdit = self.stockFromBase |
| |
|
| | def setupCreateBoxEdit(): |
| | Path.Log.track(index, force) |
| | if force or not self.stockCreateBox: |
| | self.stockCreateBox = StockCreateBoxEdit(self.obj, self.form, force) |
| | self.stockEdit = self.stockCreateBox |
| |
|
| | def setupCreateCylinderEdit(): |
| | Path.Log.track(index, force) |
| | if force or not self.stockCreateCylinder: |
| | self.stockCreateCylinder = StockCreateCylinderEdit(self.obj, self.form, force) |
| | self.stockEdit = self.stockCreateCylinder |
| |
|
| | def setupFromExisting(): |
| | Path.Log.track(index, force) |
| | if force or not self.stockFromExisting: |
| | self.stockFromExisting = StockFromExistingEdit(self.obj, self.form, force) |
| | if self.stockFromExisting.candidates(self.obj): |
| | self.stockEdit = self.stockFromExisting |
| | return True |
| | return False |
| |
|
| | if index == -1: |
| | if self.obj.Stock is None or StockFromBaseBoundBoxEdit.IsStock(self.obj): |
| | setupFromBaseEdit() |
| | elif StockCreateBoxEdit.IsStock(self.obj): |
| | setupCreateBoxEdit() |
| | elif StockCreateCylinderEdit.IsStock(self.obj): |
| | setupCreateCylinderEdit() |
| | elif StockFromExistingEdit.IsStock(self.obj): |
| | setupFromExisting() |
| | else: |
| | Path.Log.error( |
| | translate("CAM_Job", "Unsupported stock object %s") % self.obj.Stock.Label |
| | ) |
| | else: |
| | if index == StockFromBaseBoundBoxEdit.Index: |
| | setupFromBaseEdit() |
| | elif index == StockCreateBoxEdit.Index: |
| | setupCreateBoxEdit() |
| | elif index == StockCreateCylinderEdit.Index: |
| | setupCreateCylinderEdit() |
| | elif index == StockFromExistingEdit.Index: |
| | if not setupFromExisting(): |
| | setupFromBaseEdit() |
| | index = -1 |
| | else: |
| | Path.Log.error( |
| | translate("CAM_Job", "Unsupported stock type %s (%d)") |
| | % (self.form.stock.currentText(), index) |
| | ) |
| | self.stockEdit.activate(self.obj, index == -1) |
| |
|
| | if -1 != index: |
| | self.template.updateUI() |
| |
|
| | def refreshStock(self): |
| | self.updateStockEditor(self.form.stock.currentIndex(), True) |
| |
|
| | def alignCenterInStock(self): |
| | bbs = self.obj.Stock.Shape.BoundBox |
| | for sel in FreeCADGui.Selection.getSelectionEx(): |
| | bbb = sel.Object.Shape.BoundBox |
| | by = bbs.Center - bbb.Center |
| | Draft.move(sel.Object, by) |
| |
|
| | def alignCenterInStockXY(self): |
| | bbs = self.obj.Stock.Shape.BoundBox |
| | for sel in FreeCADGui.Selection.getSelectionEx(): |
| | bbb = sel.Object.Shape.BoundBox |
| | by = bbs.Center - bbb.Center |
| | by.z = 0 |
| | Draft.move(sel.Object, by) |
| |
|
| | def isValidDatumSelection(self, sel): |
| | if sel.ShapeType in ["Vertex", "Edge", "Face"]: |
| | if hasattr(sel, "Curve") and not isinstance(sel.Curve, Part.Circle): |
| | return False |
| | return True |
| |
|
| | |
| | return False |
| |
|
| | def isValidAxisSelection(self, sel): |
| | if sel.ShapeType in ["Vertex", "Edge", "Face"]: |
| | if hasattr(sel, "Curve") and isinstance(sel.Curve, Part.Circle): |
| | return False |
| | if hasattr(sel, "Surface") and sel.Surface.curvature(0, 0, "Max") != 0: |
| | return False |
| | return True |
| |
|
| | |
| | return False |
| |
|
| | def updateSelection(self): |
| | |
| | if self.obj in FreeCADGui.Selection.getSelection(): |
| | FreeCADGui.Selection.removeSelection(self.obj) |
| |
|
| | sel = FreeCADGui.Selection.getSelectionEx() |
| |
|
| | self.form.setOrigin.setEnabled(False) |
| | self.form.moveToOrigin.setEnabled(False) |
| | self.form.modelSetXAxis.setEnabled(False) |
| | self.form.modelSetYAxis.setEnabled(False) |
| | self.form.modelSetZAxis.setEnabled(False) |
| |
|
| | if len(sel) == 1 and len(sel[0].SubObjects) == 1: |
| | subObj = sel[0].SubObjects[0] |
| | if self.isValidDatumSelection(subObj): |
| | self.form.setOrigin.setEnabled(True) |
| | self.form.moveToOrigin.setEnabled(True) |
| | if self.isValidAxisSelection(subObj): |
| | self.form.modelSetXAxis.setEnabled(True) |
| | self.form.modelSetYAxis.setEnabled(True) |
| | self.form.modelSetZAxis.setEnabled(True) |
| |
|
| | if len(sel) == 0 or self.obj.Stock in [s.Object for s in sel]: |
| | self.form.centerInStock.setEnabled(False) |
| | self.form.centerInStockXY.setEnabled(False) |
| | else: |
| | self.form.centerInStock.setEnabled(True) |
| | self.form.centerInStockXY.setEnabled(True) |
| |
|
| | if len(sel) > 0: |
| | self.form.modelSetX0.setEnabled(True) |
| | self.form.modelSetY0.setEnabled(True) |
| | self.form.modelSetZ0.setEnabled(True) |
| | self.form.modelMoveGroup.setEnabled(True) |
| | self.form.modelRotateGroup.setEnabled(True) |
| | self.form.modelRotateCompound.setEnabled(len(sel) > 1) |
| | else: |
| | self.form.modelSetX0.setEnabled(False) |
| | self.form.modelSetY0.setEnabled(False) |
| | self.form.modelSetZ0.setEnabled(False) |
| | self.form.modelMoveGroup.setEnabled(False) |
| | self.form.modelRotateGroup.setEnabled(False) |
| |
|
| | def jobModelEdit(self): |
| | dialog = PathJobDlg.JobCreate() |
| | dialog.setupTitle(translate("CAM_Job", "Model Selection")) |
| | dialog.setupModel(self.obj) |
| | if dialog.exec_() == 1: |
| | models = dialog.getModels() |
| | if models: |
| | obj = self.obj |
| | proxy = obj.Proxy |
| |
|
| | want = Counter(models) |
| | have = Counter([proxy.baseObject(obj, o) for o in obj.Model.Group]) |
| |
|
| | obsolete = have - want |
| | additions = want - have |
| |
|
| | |
| | for model, count in obsolete.items(): |
| | for i in range(count): |
| | |
| | base = [b for b in obj.Model.Group if proxy.baseObject(obj, b) == model][-1] |
| | self.vproxy.forgetBaseVisibility(obj, base) |
| | self.obj.Proxy.removeBase(obj, base, True) |
| | |
| |
|
| | |
| | for model, count in additions.items(): |
| | for i in range(count): |
| | base = PathJob.createModelResourceClone(obj, model) |
| | obj.Model.addObject(base) |
| | self.vproxy.rememberBaseVisibility(obj, base) |
| |
|
| | |
| | if obsolete or additions: |
| | self.setFields() |
| | else: |
| | Path.Log.track("no changes to model") |
| |
|
| | def tabPageChanged(self, index): |
| | if index == 0: |
| | |
| | self.getFields() |
| | self.setupGlobal.accept() |
| | self.setupOps.accept() |
| | self.obj.Document.recompute() |
| | self.template.updateUI() |
| |
|
| | def setupUi(self, activate): |
| | self.setupGlobal.setupUi() |
| | try: |
| | self.setupOps.setupUi() |
| | except Exception as ee: |
| | Path.Log.error(str(ee)) |
| | self.updateStockEditor(-1, False) |
| | self.setFields() |
| |
|
| | |
| | self.form.jobLabel.editingFinished.connect(self.getFields) |
| | self.form.jobModelEdit.clicked.connect(self.jobModelEdit) |
| |
|
| | |
| | self.form.postProcessor.currentIndexChanged.connect(self.getFields) |
| | self.form.postProcessorArguments.editingFinished.connect(self.getFields) |
| | self.form.postProcessorOutputFile.editingFinished.connect(self.getFields) |
| | self.form.postProcessorSetOutputFile.clicked.connect(self.setPostProcessorOutputFile) |
| |
|
| | |
| | self.form.operationsList.itemSelectionChanged.connect(self.operationSelect) |
| | self.form.operationsList.indexesMoved.connect(self.getFields) |
| | self.form.operationDelete.clicked.connect(self.operationDelete) |
| | self.form.operationUp.clicked.connect(self.operationMoveUp) |
| | self.form.operationDown.clicked.connect(self.operationMoveDown) |
| |
|
| | self.form.operationEdit.hide() |
| | self.form.activeToolGroup.hide() |
| |
|
| | |
| | self.form.toolControllerList.itemSelectionChanged.connect(self.toolControllerSelect) |
| | self.form.toolControllerList.itemChanged.connect(self.toolControllerChanged) |
| | self.form.toolControllerEdit.clicked.connect(self.toolControllerEdit) |
| | self.form.toolControllerDelete.clicked.connect(self.toolControllerDelete) |
| | self.form.toolControllerAdd.clicked.connect(self.toolControllerAdd) |
| |
|
| | self.operationSelect() |
| | self.toolControllerSelect() |
| |
|
| | |
| | self.form.btnMaterial.clicked.connect(self.assignMaterial) |
| | self.form.centerInStock.clicked.connect(self.alignCenterInStock) |
| | self.form.centerInStockXY.clicked.connect(self.alignCenterInStockXY) |
| |
|
| | self.form.stock.currentIndexChanged.connect(self.updateStockEditor) |
| | self.form.refreshStock.clicked.connect(self.refreshStock) |
| |
|
| | self.form.modelSetXAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(1, 0, 0))) |
| | self.form.modelSetYAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(0, 1, 0))) |
| | self.form.modelSetZAxis.clicked.connect(lambda: self.modelSetAxis(FreeCAD.Vector(0, 0, 1))) |
| | self.form.modelSetX0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(-1, 0, 0))) |
| | self.form.modelSetY0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(0, -1, 0))) |
| | self.form.modelSetZ0.clicked.connect(lambda: self.modelSet0(FreeCAD.Vector(0, 0, -1))) |
| |
|
| | self.form.setOrigin.clicked.connect(self.alignSetOrigin) |
| | self.form.moveToOrigin.clicked.connect(self.alignMoveToOrigin) |
| |
|
| | self.form.modelMoveLeftUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(-1, 1, 0))) |
| | self.form.modelMoveLeft.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(-1, 0, 0))) |
| | self.form.modelMoveLeftDown.clicked.connect( |
| | lambda: self.modelMove(FreeCAD.Vector(-1, -1, 0)) |
| | ) |
| |
|
| | self.form.modelMoveUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(0, 1, 0))) |
| | self.form.modelMoveDown.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(0, -1, 0))) |
| |
|
| | self.form.modelMoveRightUp.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(1, 1, 0))) |
| | self.form.modelMoveRight.clicked.connect(lambda: self.modelMove(FreeCAD.Vector(1, 0, 0))) |
| | self.form.modelMoveRightDown.clicked.connect( |
| | lambda: self.modelMove(FreeCAD.Vector(1, -1, 0)) |
| | ) |
| |
|
| | self.form.modelRotateLeft.clicked.connect(lambda: self.modelRotate(FreeCAD.Vector(0, 0, 1))) |
| | self.form.modelRotateRight.clicked.connect( |
| | lambda: self.modelRotate(FreeCAD.Vector(0, 0, -1)) |
| | ) |
| |
|
| | self.updateSelection() |
| |
|
| | |
| | if activate in ["Layout", "Stock"]: |
| | self.form.setCurrentIndex(0) |
| | if activate in ["General", "Model"]: |
| | self.form.setCurrentIndex(1) |
| | if activate in ["Output", "Post Processor"]: |
| | self.form.setCurrentIndex(2) |
| | if activate in ["Tools", "Tool Controller"]: |
| | self.form.setCurrentIndex(3) |
| | if activate in ["Workplan", "Operations"]: |
| | self.form.setCurrentIndex(4) |
| |
|
| | self.form.currentChanged.connect(self.tabPageChanged) |
| | self.template.exportButton().clicked.connect(self.templateExport) |
| |
|
| | def templateExport(self): |
| | self.getFields() |
| | PathJobCmd.CommandJobTemplateExport.SaveDialog(self.obj, self.template) |
| |
|
| | def open(self): |
| | FreeCADGui.Selection.addObserver(self) |
| |
|
| | def _jobIntegrityCheck(self): |
| | """_jobIntegrityCheck() ... Check Job object for existence of Model and Tools |
| | If either Model or Tools is empty, change GUI tab, issue appropriate warning, |
| | and offer chance to add appropriate item.""" |
| |
|
| | def _displayWarningWindow(msg): |
| | """Display window with warning message and Add action button. |
| | Return action state.""" |
| | txtHeader = translate("CAM_Job", "Warning") |
| | txtPleaseAddOne = translate("CAM_Job", "Please add one.") |
| | txtOk = translate("CAM_Job", "Ok") |
| | txtAdd = translate("CAM_Job", "Add") |
| |
|
| | msgbox = QtGui.QMessageBox( |
| | QtGui.QMessageBox.Warning, txtHeader, msg + " " + txtPleaseAddOne |
| | ) |
| | msgbox.addButton(txtOk, QtGui.QMessageBox.AcceptRole) |
| | msgbox.addButton(txtAdd, QtGui.QMessageBox.ActionRole) |
| | return msgbox.exec_() |
| |
|
| | |
| | if len(self.obj.Model.Group) == 0: |
| | self.form.setCurrentIndex(1) |
| | no_model_txt = translate("CAM_Job", "This job has no base model.") |
| | if _displayWarningWindow(no_model_txt) == 1: |
| | self.jobModelEdit() |
| |
|
| | |
| | if len(self.obj.Tools.Group) == 0: |
| | self.form.setCurrentIndex(3) |
| | no_tool_txt = translate("CAM_Job", "This job has no tool.") |
| | if _displayWarningWindow(no_tool_txt) == 1: |
| | self.toolControllerAdd() |
| |
|
| | |
| | def addSelection(self, doc, obj, sub, pnt): |
| | self.updateSelection() |
| |
|
| | def removeSelection(self, doc, obj, sub): |
| | self.updateSelection() |
| |
|
| | def setSelection(self, doc): |
| | self.updateSelection() |
| |
|
| | def clearSelection(self, doc): |
| | self.updateSelection() |
| |
|
| |
|
| | def Create(base, template=None, openTaskPanel=True): |
| | """Create(base, template) ... creates a job instance for the given base object |
| | using template to configure it.""" |
| | FreeCADGui.addModule("Path.Main.Job") |
| | FreeCAD.ActiveDocument.openTransaction("Create Job") |
| | try: |
| | obj = PathJob.Create("Job", base, template) |
| | obj.ViewObject.Proxy = ViewProvider(obj.ViewObject) |
| | obj.ViewObject.addExtension("Gui::ViewProviderGroupExtensionPython") |
| | FreeCAD.ActiveDocument.commitTransaction() |
| | obj.Document.recompute() |
| | if openTaskPanel: |
| | obj.ViewObject.Proxy.editObject(obj.Stock) |
| | else: |
| | obj.ViewObject.Proxy.deleteOnReject = False |
| | return obj |
| | except Exception as exc: |
| | Path.Log.error(exc) |
| | traceback.print_exc() |
| | FreeCAD.ActiveDocument.abortTransaction() |
| |
|
| |
|
| | |
| | PathGuiInit.Startup() |
| |
|