1696 lines
67 KiB
Python
1696 lines
67 KiB
Python
# -*- coding: utf-8 -*-
|
|
# ***************************************************************************
|
|
# * 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.Bit as PathToolBitGui
|
|
import Path.Tool.Gui.Controller as PathToolControllerGui
|
|
import PathScripts.PathUtils as PathUtils
|
|
import json
|
|
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))
|
|
|
|
|
|
@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
|
|
|
|
# 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.SoSeparator()
|
|
self.axs = coin.SoType.fromName("SoAxisCrossKit").createInstance()
|
|
|
|
# Adjust the axis heads if needed, the scale here is just for the head
|
|
self.axs.set("xHead.transform", "scaleFactor 1.5 1.5 1")
|
|
self.axs.set("yHead.transform", "scaleFactor 1.5 1.5 1")
|
|
self.axs.set("zHead.transform", "scaleFactor 1.5 1.5 1")
|
|
|
|
# Adjust the axis heads if needed, the scale here is just for the head
|
|
self.axs.set("xHead.transform", "translation 50 0 0")
|
|
self.axs.set("yHead.transform", "translation 0 50 0")
|
|
self.axs.set("zHead.transform", "translation 0 0 50")
|
|
|
|
# Adjust the axis line width if needed
|
|
self.axs.set("xAxis.transform", "scaleFactor 0.5 0.5 1")
|
|
self.axs.set("xAxis.appearance.drawStyle", "lineWidth 9")
|
|
self.axs.set("yAxis.transform", "scaleFactor 0.5 0.5 1")
|
|
self.axs.set("yAxis.appearance.drawStyle", "lineWidth 9")
|
|
self.axs.set("zAxis.transform", "scaleFactor 0.5 0.5 1")
|
|
self.axs.set("zAxis.appearance.drawStyle", "lineWidth 9")
|
|
|
|
self.sca = coin.SoType.fromName("SoShapeScale").createInstance()
|
|
self.sca.setPart("shape", self.axs)
|
|
self.sca.scaleFactor.setValue(1) # Keep or adjust if needed
|
|
|
|
self.mat = coin.SoMaterial()
|
|
# Set sphere color to bright yellow
|
|
self.mat.diffuseColor = coin.SbColor(1, 1, 0)
|
|
self.mat.transparency = 0.35 # Keep or adjust if needed
|
|
|
|
self.sph = coin.SoSphere()
|
|
self.scs = coin.SoType.fromName("SoShapeScale").createInstance()
|
|
self.scs.setPart("shape", self.sph)
|
|
# Increase the scaleFactor to make the sphere larger
|
|
self.scs.scaleFactor.setValue(10) # Adjust this value as needed
|
|
|
|
self.sep.addChild(self.sca)
|
|
self.sep.addChild(self.mat)
|
|
self.sep.addChild(self.scs)
|
|
self.switch.addChild(self.sep)
|
|
|
|
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 vobj.Visibility:
|
|
self.rememberStockVisibility()
|
|
self.obj.Stock.ViewObject.Visibility = True
|
|
|
|
self.KeepBaseVisibility()
|
|
for base in self.obj.Model.Group:
|
|
base.ViewObject.Visibility = True
|
|
else:
|
|
self.restoreStockVisibility()
|
|
self.RestoreBaseVisibility()
|
|
|
|
def rememberStockVisibility(self):
|
|
self.stockVisibility = self.obj.Stock.ViewObject.Visibility
|
|
|
|
def restoreStockVisibility(self):
|
|
self.obj.Stock.ViewObject.Visibility = self.stockVisibility
|
|
|
|
def KeepBaseVisibility(self):
|
|
Path.Log.debug("KeepBaseVisibility")
|
|
self.visibilitystate = {}
|
|
for base in self.obj.Model.Group:
|
|
Path.Log.debug(f"{base.Name}: {base.ViewObject.Visibility}")
|
|
self.visibilitystate[base.Name] = base.ViewObject.Visibility
|
|
Path.Log.debug(self.visibilitystate)
|
|
|
|
def RestoreBaseVisibility(self):
|
|
Path.Log.debug("RestoreBaseVisibility")
|
|
if hasattr(self, "visibilitystate"):
|
|
for base in self.obj.Model.Group:
|
|
base.ViewObject.Visibility = self.visibilitystate[base.Name]
|
|
Path.Log.debug(self.visibilitystate)
|
|
|
|
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 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 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.setEdit)
|
|
menu.addAction(action)
|
|
|
|
|
|
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)
|
|
|
|
@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)
|
|
|
|
# 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)
|
|
return sorted(solids, key=lambda c: c.Label)
|
|
|
|
def setFields(self, obj):
|
|
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 == stockName:
|
|
index = i
|
|
self.form.stockExisting.setCurrentIndex(index if index != -1 else 0)
|
|
|
|
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):
|
|
# adding a TC from a toolbit directly.
|
|
# Try to find a tool number from the currently selected lib. Otherwise
|
|
# use next available number
|
|
|
|
tools = PathToolBitGui.LoadTools()
|
|
|
|
curLib = Path.Preferences.lastFileToolLibrary()
|
|
|
|
library = None
|
|
if curLib is not None:
|
|
with open(curLib) as fp:
|
|
library = json.load(fp)
|
|
|
|
for tool in tools:
|
|
toolNum = self.obj.Proxy.nextToolNumber()
|
|
if library is not None:
|
|
for toolBit in library["tools"]:
|
|
|
|
if toolBit["path"] == tool.File:
|
|
toolNum = toolBit["nr"]
|
|
|
|
tc = PathToolControllerGui.Create(name=tool.Label, tool=tool, 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 type(sel.Curve) not in [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 type(sel.Curve) in [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 ["General", "Model"]:
|
|
self.form.setCurrentIndex(0)
|
|
if activate in ["Output", "Post Processor"]:
|
|
self.form.setCurrentIndex(1)
|
|
if activate in ["Layout", "Stock"]:
|
|
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(0) # 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()
|