| |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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() |
|
|