Download src/Mod/CAM/Path/Main/Gui/Job.py from AbdulElahGwaith/FreeCAD: direct link, hf CLI and curl.
- Browser
- Download file 70.1 kB
-
https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Main/Gui/Job.py
- Command line
-
hf download hf://AbdulElahGwaith/FreeCAD/src/Mod/CAM/Path/Main/Gui/Job.py
-
curl -L -o Job.py https://huggingface.co/AbdulElahGwaith/FreeCAD/resolve/main/src/Mod/CAM/Path/Main/Gui/Job.py
70.1 kB
| # SPDX-License-Identifier: LGPL-2.1-or-later | |
| # *************************************************************************** | |
| # * Copyright (c) 2017 sliptonic <shopinthewoods@gmail.com> * | |
| # * * | |
| # * This program is free software; you can redistribute it and/or modify * | |
| # * it under the terms of the GNU Lesser General Public License (LGPL) * | |
| # * as published by the Free Software Foundation; either version 2 of * | |
| # * the License, or (at your option) any later version. * | |
| # * for detail see the LICENCE text file. * | |
| # * * | |
| # * This program is distributed in the hope that it will be useful, * | |
| # * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
| # * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
| # * GNU Library General Public License for more details. * | |
| # * * | |
| # * You should have received a copy of the GNU Library General Public * | |
| # * License along with this program; if not, write to the Free Software * | |
| # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * | |
| # * USA * | |
| # * * | |
| # *************************************************************************** | |
| 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 | |
| # lazily loaded modules | |
| 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)) | |
| 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 | |
| # initialized later | |
| 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 | |
| # Setup the axis display at the origin | |
| self.switch = coin.SoSwitch() | |
| self.sep = coin.SoType.fromName("So3DAnnotation").createInstance() | |
| self.axs = coin.SoType.fromName("SoFCPlacementIndicatorKit").createInstance() | |
| self.axs.axisLength.setValue(1.2) | |
| # enum values for SoFCPlacementIndicatorKit | |
| 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 we're currently restoring the document we do NOT want to call | |
| # hideXXX as this would mark all currently hidden children as | |
| # explicitly hidden by the user and prevent showing them when | |
| # showing the job | |
| 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: | |
| # Block editing for Boundary objects | |
| 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"): | |
| # unfortunately this function is called before the object has been fully loaded | |
| # which means we could be dealing with an old job which doesn't have the new Model | |
| # yet. | |
| children.append(self.obj.Model) | |
| if hasattr(self.obj, "Stock"): | |
| children.append(self.obj.Stock) | |
| if hasattr(self.obj, "SetupSheet"): | |
| # when loading a job that didn't have a setup sheet they might not've been created yet | |
| 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) | |
| # make sure the resource view providers are setup properly | |
| 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() | |
| # if self.baseVisibility.get(base.Name): | |
| # visibility = self.baseVisibility[base.Name] | |
| # visibility[0].ViewObject.Visibility = visibility[1] | |
| # visibility[2].ViewObject.Visibility = visibility[3] | |
| # del self.baseVisibility[base.Name] | |
| 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") | |
| # Create OK and Cancel buttons | |
| self.okButton = QtWidgets.QPushButton("OK") | |
| self.cancelButton = QtWidgets.QPushButton("Cancel") | |
| # Connect buttons to their actions | |
| self.okButton.clicked.connect(self.accept) | |
| self.cancelButton.clicked.connect(self.reject) | |
| # Layout setup | |
| 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) | |
| 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) | |
| # the following members must be overwritten by subclasses | |
| 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: | |
| # regardless, what stock is/was, it's not a valid choice | |
| 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): | |
| # Block signal propagation during stock dropdown population. This prevents | |
| # the `currentIndexChanged` signal from being emitted while populating the | |
| # dropdown list. This is important because the `currentIndexChanged` signal | |
| # will in the end result in the stock object being recreated in `getFields` | |
| # method, discarding any changes made (like position in respect to origin). | |
| 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) | |
| # stockName has index suffix (since cloned), label has no index | |
| # => ridgid string comparison fails | |
| # Instead of ridgid string comparison use partial (needle in haystack) | |
| # string comparison | |
| # if label == stockName: # ridgid string comparison | |
| if label in stockName: # partial string comparison | |
| 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") + " " | |
| ) | |
| # ensure correct ellisis behaviour on tool controller names. | |
| 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) | |
| # update the enumeration values, just to make sure all selections are valid | |
| self.obj.PostProcessor = postProcessors | |
| self.obj.PostProcessor = currentPostProcessor | |
| self.postProcessorDefaultTooltip = self.form.postProcessor.toolTip() | |
| self.postProcessorArgsDefaultTooltip = self.form.postProcessorArguments.toolTip() | |
| # Populate the other comboboxes with enums from the job class | |
| 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 | |
| """ | |
| # Load appropriate enumerations in each combobox | |
| for cb, prop in comboBoxesPropertyMap: | |
| box = getattr(form, cb) # Get the combobox | |
| box.clear() # clear the combobox | |
| for text, data in enumTups[prop]: # load enumerations | |
| box.addItem(text, data) | |
| def assignMaterial(self): | |
| dialog = MaterialDialog() | |
| result = dialog.exec_() | |
| if result == QtWidgets.QDialog.Accepted: | |
| FreeCAD.Console.PrintMessage("Material assigned\n") | |
| # Add code to handle the material assignment | |
| 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() # Check existence of Model and Tools | |
| 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() # Save initial index | |
| # Search using currentData and return if found | |
| newindex = widget.findData(text) | |
| if newindex >= 0: | |
| widget.blockSignals(True) | |
| widget.setCurrentIndex(newindex) | |
| widget.blockSignals(False) | |
| return | |
| # if not found, search using current text | |
| 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.obj.Proxy.onChanged(self.obj, "PostProcessor") | |
| 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): | |
| # if the TC is referenced anywhere but the job we don't want to delete it | |
| return len(tc.InList) == 1 | |
| # if anything is selected it can be edited | |
| edit = True if self.form.toolControllerList.selectedItems() else False | |
| self.form.toolControllerEdit.setEnabled(edit) | |
| # can only delete what is selected | |
| delete = edit | |
| # ... but we want to make sure there's at least one TC left | |
| if len(self.obj.Tools.Group) == len(self.form.toolControllerList.selectedItems()): | |
| delete = False | |
| # ... also don't want to delete any TCs that are already used | |
| 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)): | |
| # Selection is already aligned with the axis of rotation leading | |
| # to a (0,0,0) cross product for rotation. | |
| # --> Need to flip the object around one of the "other" axis. | |
| # Simplest way to achieve that is to rotate the coordinate system | |
| # of the axis and use that to rotate the object. | |
| r = FreeCAD.Vector(v.y, v.z, v.x) | |
| a = 180 | |
| else: | |
| r = v.cross(normal) # rotation axis | |
| 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 | |
| ): | |
| # Don't really know the orientation of an edge, so let's just flip the object | |
| # and if the user doesn't like it they can flip again | |
| 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(): | |
| # Also move the objects not selected | |
| # if selection is not model, move the model too | |
| # if the selection is not stock and there is a stock, move the stock too | |
| 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 | |
| # no valid selection | |
| 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 | |
| # no valid selection | |
| return False | |
| def updateSelection(self): | |
| # Remove Job object if present in Selection: source of phantom paths | |
| 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 | |
| # first remove all obsolete base models | |
| for model, count in obsolete.items(): | |
| for i in range(count): | |
| # it seems natural to remove the last of all the base objects for a given model | |
| 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) | |
| # do not access any of the retired objects after this point, they don't exist anymore | |
| # then add all rookie base models | |
| 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) | |
| # refresh the view | |
| if obsolete or additions: | |
| self.setFields() | |
| else: | |
| Path.Log.track("no changes to model") | |
| def tabPageChanged(self, index): | |
| if index == 0: | |
| # update the template with potential changes | |
| 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() | |
| # Info | |
| self.form.jobLabel.editingFinished.connect(self.getFields) | |
| self.form.jobModelEdit.clicked.connect(self.jobModelEdit) | |
| # Post Processor | |
| 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) | |
| # Workplan | |
| 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() # not supported yet | |
| self.form.activeToolGroup.hide() # not supported yet | |
| # Tool controller | |
| 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() | |
| # Stock, Orientation and Alignment | |
| 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() | |
| # set active page | |
| 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) # Add 'Ok' button | |
| msgbox.addButton(txtAdd, QtGui.QMessageBox.ActionRole) # Add 'Add' button | |
| return msgbox.exec_() | |
| # Check if at least on base model is present | |
| if len(self.obj.Model.Group) == 0: | |
| self.form.setCurrentIndex(1) # Change tab to General tab | |
| no_model_txt = translate("CAM_Job", "This job has no base model.") | |
| if _displayWarningWindow(no_model_txt) == 1: | |
| self.jobModelEdit() | |
| # Check if at least one tool is present | |
| if len(self.obj.Tools.Group) == 0: | |
| self.form.setCurrentIndex(3) # Change tab to Tools tab | |
| no_tool_txt = translate("CAM_Job", "This job has no tool.") | |
| if _displayWarningWindow(no_tool_txt) == 1: | |
| self.toolControllerAdd() | |
| # SelectionObserver interface | |
| 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() | |
| # make sure the UI has been initialized | |
| PathGuiInit.Startup() | |