diff --git a/src/Mod/Path/Gui/Resources/panels/PathEdit.ui b/src/Mod/Path/Gui/Resources/panels/PathEdit.ui
index 868d8b26be..6efce1fc23 100644
--- a/src/Mod/Path/Gui/Resources/panels/PathEdit.ui
+++ b/src/Mod/Path/Gui/Resources/panels/PathEdit.ui
@@ -7,7 +7,7 @@
0
0
400
- 608
+ 924
@@ -24,7 +24,7 @@
-
- 0
+ 1
@@ -226,67 +226,184 @@
0
0
378
- 403
+ 719
Layout
-
+
-
Stock
-
-
-
-
-
- Ext. X
+
+
+ 0
+
+
-
+
+
+ 2
+
-
+
+ Create Box
+
+
+ -
+
+ Create Cylinder
+
+
+ -
+
+ Extend Base Bound Box
+
+
+ -
+
+ Use Existing
+
+
- -
-
-
- Ext. Y
+
-
+
+
+ Qt::Horizontal
+
+
+ 40
+ 6
+
+
+
+
+ -
+
+
+
-
+
+
+
- -
-
-
-
- 0
- 0
-
-
+
-
+
+
+
-
+
+
+ -
+
+
+ Ext. X
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ -
+
+
+ Ext. Y
+
+
+
+ -
+
+
+ -
+
+
+ Ext. Z
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
- -
-
-
- -
-
-
- Ext. Z
-
+
-
+
+
+
-
+
+
+ -
+
+
+ -
+
+
+ Radius
+
+
+
+ -
+
+
+ Height
+
+
+
+
- -
-
-
- -
-
-
- -
-
-
- -
-
-
- -
-
+
-
+
+
+
-
+
+
+ Length
+
+
+
+ -
+
+
+ Width
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ Height
+
+
+
+
+
@@ -612,7 +729,7 @@
-
- 1
+ 0
@@ -862,6 +979,63 @@
+
+ infoLabel
+ infoModel
+ infoMaterial
+ stock
+ stockExisting
+ stockExtXneg
+ stockExtXpos
+ stockExtYneg
+ stockExtYpos
+ stockExtZneg
+ stockExtZpos
+ stockCylinderRadius
+ stockCylinderHeight
+ stockBoxLength
+ stockBoxWidth
+ stockBoxHeight
+ orientXAxis
+ orientYAxis
+ orientZAxis
+ setOrigin
+ moveToOrigin
+ centerInStock
+ centerInStockXY
+ postProcessorOutputFile
+ postProcessorSetOutputFile
+ postProcessor
+ postProcessorArguments
+ templateDefaultValues
+ templatePostProcessor
+ buttonBox
+ templateGeneral
+ templateMaterial
+ templateSetup
+ templateStock
+ templateOperations
+ templateTools
+ toolControllerList
+ toolControllerEdit
+ toolControllerAdd
+ toolControllerDelete
+ activeToolController
+ operationsList
+ operationEdit
+ operationDelete
+ groupBox_5
+ defaultSafeHeight
+ defaultClearanceHeight
+ groupBox_6
+ defaultStepDown
+ groupBox
+ lineEdit
+ lineEdit_2
+ groupBox_7
+ defaultMillingOp
+ defaultToolCompensation
+
diff --git a/src/Mod/Path/PathScripts/PathJob.py b/src/Mod/Path/PathScripts/PathJob.py
index 3731de666e..1be27313c4 100644
--- a/src/Mod/Path/PathScripts/PathJob.py
+++ b/src/Mod/Path/PathScripts/PathJob.py
@@ -27,6 +27,7 @@ import Draft
import FreeCAD
import PathScripts.PathIconViewProvider as PathIconViewProvider
import PathScripts.PathLog as PathLog
+import PathScripts.PathStock as PathStock
import PathScripts.PathToolController as PathToolController
import PathScripts.PathUtil as PathUtil
import xml.etree.ElementTree as xml
@@ -58,6 +59,24 @@ class JobTemplate:
Description = 'desc'
ToolController = 'ToolController'
+def isResourceClone(obj, propName, resourceName):
+ '''isResourceClone(obj, propName, resourceName) ... Return True if the given property of obj is a clone of type resourceName.'''
+ if hasattr(obj, propName):
+ propLink = getattr(obj, propName)
+ if hasattr(propLink, 'PathResource') and resourceName == propLink.PathResource:
+ return True
+ return False
+
+def createResourceClone(obj, orig, name, icon):
+ clone = Draft.clone(orig)
+ clone.Label = "%s-%s" % (name, orig.Label)
+ clone.addProperty('App::PropertyString', 'PathResource')
+ clone.PathResource = name
+ if clone.ViewObject:
+ PathIconViewProvider.ViewProvider(clone.ViewObject, icon)
+ clone.ViewObject.Visibility = False
+ return clone
+
class ObjectJob:
def __init__(self, obj, base, template = None):
@@ -91,10 +110,14 @@ class ObjectJob:
obj.setEditorMode('Operations', 2) # hide
obj.setEditorMode('Placement', 2)
- self.createResourceClone(obj, base, 'Base')
+ obj.Base = createResourceClone(obj, base, 'Base', 'BaseGeometry')
obj.Proxy = self
self.assignTemplate(obj, template)
+ if not obj.Stock:
+ obj.Stock = PathStock.CreateFromBase(obj)
+ if obj.Stock.ViewObject:
+ obj.Stock.ViewObject.Visibility = False
def onDelete(self, obj, arg2=None):
'''Called by the view provider, there doesn't seem to be a callback on the obj itself.'''
@@ -115,36 +138,18 @@ class ObjectJob:
doc.removeObject(obj.Stock.Name)
obj.Stock = None
- def isResourceClone(self, obj, propName, resourceName):
- if hasattr(obj, propName):
- propLink = getattr(obj, propName)
- if hasattr(propLink, 'PathResource') and resourceName == propLink.PathResource:
- return True
- return False
-
- def createResourceClone(self, obj, orig, name):
- clone = Draft.clone(orig)
- clone.Label = "%s-%s" % (name, orig.Label)
- clone.addProperty('App::PropertyString', 'PathResource')
- clone.PathResource = name
- if clone.ViewObject:
- PathIconViewProvider.ViewProvider(clone.ViewObject, 'BaseGeometry')
- clone.ViewObject.Visibility = False
- setattr(obj, name, clone)
-
- def fixupResourceClone(self, obj, name):
- if not self.isResourceClone(obj, name, name):
+ def fixupResourceClone(self, obj, name, icon):
+ if not isResourceClone(obj, name, name):
orig = getattr(obj, name)
if orig:
- self.createResourceClone(obj, orig, name)
+ setattr(obj, name, createResourceClone(obj, orig, name, icon))
def onDocumentRestored(self, obj):
- self.fixupResourceClone(obj, 'Base')
- self.fixupResourceClone(obj, 'Stock')
+ self.fixupResourceClone(obj, 'Base', 'BaseGeometry')
def baseObject(self, obj):
'''Return the base object, not its clone.'''
- if self.isResourceClone(obj, 'Base', 'Base'):
+ if isResourceClone(obj, 'Base', 'Base'):
return obj.Base.Objects[0]
return obj.Base
diff --git a/src/Mod/Path/PathScripts/PathJobGui.py b/src/Mod/Path/PathScripts/PathJobGui.py
index 65b7b7a309..8defe4fb2c 100644
--- a/src/Mod/Path/PathScripts/PathJobGui.py
+++ b/src/Mod/Path/PathScripts/PathJobGui.py
@@ -28,8 +28,10 @@ import FreeCAD
import FreeCADGui
import PathScripts.PathJob as PathJob
import PathScripts.PathLog as PathLog
+import PathScripts.PathStock as PathStock
import PathScripts.PathToolController as PathToolController
import PathScripts.PathToolLibraryManager as PathToolLibraryManager
+import PathScripts.PathUtil as PathUtil
import math
import sys
@@ -104,6 +106,223 @@ class ViewProvider:
self.obj.Proxy.onDelete(self.obj, arg2)
return True
+class StockEdit(object):
+
+ def __init__(self, obj, form):
+ self.obj = obj
+ self.form = form
+ self.setupUi(obj)
+
+ def activate(self, obj, select = False):
+ PathLog.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):
+ if obj.Stock:
+ obj.Document.removeObject(self.obj.Stock.Name)
+ obj.Stock = stock
+
+ def setLengthField(self, widget, prop):
+ widget.setText(FreeCAD.Units.Quantity(prop.Value, FreeCAD.Units.Length).UserString)
+
+class StockFromBaseBoundBoxEdit(StockEdit):
+ Index = 2
+ StockType = 'FromBase'
+
+ @classmethod
+ def IsStock(cls, obj):
+ return hasattr(obj.Stock, 'ExtXneg') and hasattr(obj.Stock, 'ExtZpos')
+
+ def editorFrame(self):
+ return self.form.stockFromBase
+
+ def getFields(self, obj, fields = ['xneg', 'xpos', 'yneg', 'ypos', 'zneg', 'zpos']):
+ PathLog.track(obj.Label, fields)
+ if self.IsStock(obj):
+ if 'xneg' in fields:
+ obj.Stock.ExtXneg = FreeCAD.Units.Quantity(self.form.stockExtXneg.text())
+ if 'xpos' in fields:
+ obj.Stock.ExtXpos = FreeCAD.Units.Quantity(self.form.stockExtXpos.text())
+ if 'yneg' in fields:
+ obj.Stock.ExtYneg = FreeCAD.Units.Quantity(self.form.stockExtYneg.text())
+ if 'ypos' in fields:
+ obj.Stock.ExtYpos = FreeCAD.Units.Quantity(self.form.stockExtYpos.text())
+ if 'zneg' in fields:
+ obj.Stock.ExtZneg = FreeCAD.Units.Quantity(self.form.stockExtZneg.text())
+ if 'zpos' in fields:
+ obj.Stock.ExtZpos = FreeCAD.Units.Quantity(self.form.stockExtZpos.text())
+ else:
+ PathLog.error(translate('PathJob', 'Stock not from Base bound box!'))
+
+ def setFields(self, obj):
+ if not self.IsStock(obj):
+ self.setStock(obj, PathStock.CreateFromBase(obj))
+ 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):
+ self.setFields(obj)
+ self.checkXpos()
+ self.checkYpos()
+ self.checkZpos()
+ self.form.stockExtXneg.editingFinished.connect(self.updateXpos)
+ self.form.stockExtYneg.editingFinished.connect(self.updateYpos)
+ self.form.stockExtZneg.editingFinished.connect(self.updateZpos)
+ self.form.stockExtXpos.editingFinished.connect(self.checkXpos)
+ self.form.stockExtYpos.editingFinished.connect(self.checkYpos)
+ self.form.stockExtZpos.editingFinished.connect(self.checkZpos)
+
+ 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 = 'CreateBox'
+
+ @classmethod
+ def IsStock(cls, obj):
+ return hasattr(obj.Stock, 'Length') and hasattr(obj.Stock, 'Width') and hasattr(obj.Stock, 'Height') and not StockFromBaseBoundBoxEdit.IsStock(obj)
+
+ def editorFrame(self):
+ return self.form.stockCreateBox
+
+ def getFields(self, obj, fields = ['length', 'widht', 'height']):
+ 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:
+ PathLog.error(translate('PathJob', 'Stock not a box!'))
+
+ def setFields(self, obj):
+ if not self.IsStock(obj):
+ self.setStock(obj, PathStock.CreateBox(obj))
+ 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.editingFinished.connect(lambda: self.getFields(obj, ['length']))
+ self.form.stockBoxWidth.editingFinished.connect(lambda: self.getFields(obj, ['width']))
+ self.form.stockBoxHeight.editingFinished.connect(lambda: self.getFields(obj, ['height']))
+
+class StockCreateCylinderEdit(StockEdit):
+ Index = 1
+
+ @classmethod
+ def IsStock(cls, obj):
+ return hasattr(obj.Stock, 'Radius') and hasattr(obj.Stock, 'Height')
+
+ def editorFrame(self):
+ return self.form.stockCreateCylinder
+
+ def getFields(self, obj, fields = ['radius', 'height']):
+ 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:
+ PathLog.error(translate('PathJob', 'Stock not a cylinder!'))
+
+ def setFields(self, obj):
+ if not self.IsStock(obj):
+ self.setStock(obj, PathStock.CreateCylinder(obj))
+ 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.editingFinished.connect(lambda: self.getFields(obj, ['radius']))
+ self.form.stockCylinderHeight.editingFinished.connect(lambda: self.getFields(obj, ['height']))
+
+class StockFromExistingEdit(StockEdit):
+ Index = 3
+
+ def IsStock(cls, obj):
+ return PathJob.isResourceClone(obj, 'Stock', '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, 'Stock', 'Stock')
+ stock.ViewObject.Visibility = True
+ PathStock.SetupStockObject(stock, False)
+ stock.Proxy.execute(stock)
+ self.setStock(obj, stock)
+
+ def setFields(self, obj):
+ candidates = [o for o in obj.Document.Objects if PathUtil.isSolid(o)]
+ candidates.remove(obj.Base)
+ if PathJob.isResourceClone(obj, 'Stock', 'Stock'):
+ candidates.remove(obj.Stock)
+ stockName = obj.Stock.Label if obj.Stock else None
+
+ self.form.stockExisting.clear()
+ index = -1
+ for i, c in enumerate(candidates):
+ self.form.stockExisting.addItem(c.Label, c)
+ if c.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
@@ -144,6 +363,17 @@ class TaskPanel:
self.baseVisibility = self.obj.Base.ViewObject.Visibility
self.baseObjectSaveVisibility(self.obj)
+ self.stockVisibility = False
+ if self.obj.Stock and self.obj.Stock.ViewObject:
+ self.stockVisibility = self.obj.Stock.ViewObject.Visibility
+ self.obj.Stock.ViewObject.Visibility = True
+
+ self.stockFromBase = None
+ self.stockFromExisting = None
+ self.stockCreateBox = None
+ self.stockCreateCylinder = None
+ self.stockEdit = None
+
def baseObjectViewObject(self, obj):
base = obj.Proxy.baseObject(obj)
body = base.getParentGeoFeatureGroup()
@@ -165,6 +395,8 @@ class TaskPanel:
if self.obj.Base and self.obj.Base.ViewObject:
self.obj.Base.ViewObject.Visibility = self.baseVisibility
self.baseObjectRestoreVisibility(self.obj)
+ if self.obj.Stock and self.obj.Stock.ViewObject:
+ self.obj.Stock.ViewObject.Visibility = self.stockVisibility
def accept(self, resetEdit=True):
PathLog.track()
@@ -222,8 +454,8 @@ class TaskPanel:
self.baseObjectSaveVisibility(self.obj)
self.updateTooltips()
-
- self.obj.Proxy.execute(self.obj)
+ self.stockEdit.getFields(self.obj)
+ self.obj.Proxy.execute(self.obj)
def selectComboBoxText(self, widget, text):
index = widget.findText(text, QtCore.Qt.MatchFixedString)
@@ -316,6 +548,8 @@ class TaskPanel:
self.form.infoModel.setCurrentIndex(baseindex)
self.updateToolController()
+ self.stockEdit.setFields(self.obj)
+
def setPostProcessorOutputFile(self):
filename = QtGui.QFileDialog.getSaveFileName(self.form, translate("Path_Job", "Select Output File"), None, translate("Path_Job", "All Files (*.*)"))
@@ -501,7 +735,58 @@ class TaskPanel:
self.form.orientGroup.setEnabled(False)
self.form.alignGroup.setEnabled(False)
+ def updateStockEditor(self, index):
+ PathLog.track(self.obj.Label, index)
+ def setupFromBaseEdit():
+ PathLog.track()
+ if not self.stockFromBase:
+ self.stockFromBase = StockFromBaseBoundBoxEdit(self.obj, self.form)
+ self.stockEdit = self.stockFromBase
+ def setupCreateBoxEdit():
+ PathLog.track()
+ if not self.stockCreateBox:
+ self.stockCreateBox = StockCreateBoxEdit(self.obj, self.form)
+ self.stockEdit = self.stockCreateBox
+ def setupCreateCylinderEdit():
+ PathLog.track()
+ if not self.stockCreateCylinder:
+ self.stockCreateCylinder = StockCreateCylinderEdit(self.obj, self.form)
+ self.stockEdit = self.stockCreateCylinder
+ def setupFromExisting():
+ PathLog.track()
+ if not self.stockFromExisting:
+ self.stockFromExisting = StockFromExistingEdit(self.obj, self.form)
+ self.stockEdit = self.stockFromExisting
+
+ if index == -1:
+ PathLog.track('wtf')
+ 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:
+ PathLog.error(translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label)
+ else:
+ PathLog.track(self.obj.Label, index)
+ if index == StockFromBaseBoundBoxEdit.Index:
+ setupFromBaseEdit()
+ elif index == StockCreateBoxEdit.Index:
+ setupCreateBoxEdit()
+ elif index == StockCreateCylinderEdit.Index:
+ setupCreateCylinderEdit()
+ elif index == StockFromExistingEdit.Index:
+ setupFromExisting()
+ else:
+ PathLog.error(translate('PathJob', "Unsupported stock type %s (%d)") % (self.form.stock.currentText(), index))
+ self.stockEdit.activate(self.obj, index == -1)
+
+
def setupUi(self):
+ self.updateStockEditor(-1)
self.setFields()
# Info
@@ -528,10 +813,11 @@ class TaskPanel:
self.toolControllerSelect()
# Stock, Orientation and Alignment
- self.form.stockGroup.hide()
self.form.centerInStock.hide()
self.form.centerInStockXY.hide()
+ self.form.stock.currentIndexChanged.connect(self.updateStockEditor)
+
self.form.orientXAxis.clicked.connect(lambda: self.orientSelected(FreeCAD.Vector(1, 0, 0)))
self.form.orientYAxis.clicked.connect(lambda: self.orientSelected(FreeCAD.Vector(0, 1, 0)))
self.form.orientZAxis.clicked.connect(lambda: self.orientSelected(FreeCAD.Vector(0, 0, 1)))
diff --git a/src/Mod/Path/PathScripts/PathStock.py b/src/Mod/Path/PathScripts/PathStock.py
index b95be0c961..9dbb46c6fc 100644
--- a/src/Mod/Path/PathScripts/PathStock.py
+++ b/src/Mod/Path/PathScripts/PathStock.py
@@ -24,23 +24,36 @@
import FreeCAD
import FreeCADGui
-from FreeCAD import Vector
-from PySide import QtCore, QtGui
import Part
+import PathIconViewProvider
+
+from PySide import QtCore
# Qt tanslation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
-class Stock:
+class StockFromBase:
- def __init__(self, obj):
+ def __init__(self, obj, base):
"Make stock"
- obj.addProperty("App::PropertyFloat","Length_Allowance","Stock",QtCore.QT_TRANSLATE_NOOP("App::Property","extra allowance from part width")).Length_Allowance = 1.0
- obj.addProperty("App::PropertyFloat","Width_Allowance","Stock",QtCore.QT_TRANSLATE_NOOP("App::Property","extra allowance from part width")).Width_Allowance = 1.0
- obj.addProperty("App::PropertyFloat","Height_Allowance","Stock",QtCore.QT_TRANSLATE_NOOP("App::Property","extra allowance from part width")).Height_Allowance = 1.0
- obj.addProperty("App::PropertyLink","Base","Base",QtCore.QT_TRANSLATE_NOOP("App::Property","The base object this represents"))
+ obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("PathStock", "The base object this stock is derived from"))
+ obj.addProperty("App::PropertyLength", "ExtXneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative X direction"))
+ obj.addProperty("App::PropertyLength", "ExtXpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive X direction"))
+ obj.addProperty("App::PropertyLength", "ExtYneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Y direction"))
+ obj.addProperty("App::PropertyLength", "ExtYpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Y direction"))
+ obj.addProperty("App::PropertyLength", "ExtZneg", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in negative Z direction"))
+ obj.addProperty("App::PropertyLength", "ExtZpos", "Stock", QtCore.QT_TRANSLATE_NOOP("PathStock", "Extra allowance from part bound box in positive Z direction"))
+
+ obj.Base = base
+ obj.ExtXneg= 1.0
+ obj.ExtXpos= 1.0
+ obj.ExtYneg= 1.0
+ obj.ExtYpos= 1.0
+ obj.ExtZneg= 1.0
+ obj.ExtZpos= 1.0
+
obj.Proxy = self
def __getstate__(self):
@@ -50,23 +63,70 @@ class Stock:
return None
def execute(self, obj):
- self.Xmin = obj.Base.Shape.BoundBox.XMin
- self.Xmax = obj.Base.Shape.BoundBox.XMax
+ bb = obj.Base.Shape.BoundBox
- self.Ymin = obj.Base.Shape.BoundBox.YMin
- self.Ymax = obj.Base.Shape.BoundBox.YMax
+ origin = FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin)
+ self.origin = origin - FreeCAD.Vector(obj.ExtXneg.Value, obj.ExtYneg.Value, obj.ExtZneg.Value)
- self.Zmin = obj.Base.Shape.BoundBox.ZMin
- self.Zmax = obj.Base.Shape.BoundBox.ZMax
+ self.length = bb.XLength + obj.ExtXneg.Value + obj.ExtXpos.Value
+ self.width = bb.YLength + obj.ExtYneg.Value + obj.ExtYpos.Value
+ self.height = bb.ZLength + obj.ExtZneg.Value + obj.ExtZpos.Value
- self.length = self.Xmax - self.Xmin + obj.Length_Allowance * 2.0
- self.width = self.Ymax - self.Ymin + obj.Width_Allowance * 2.0
- self.height = self.Zmax - self.Zmin + obj.Height_Allowance * 2.0
- self.pnt = Vector(self.Xmin - obj.Length_Allowance, self.Ymin -
- obj.Width_Allowance, self.Zmin - obj.Height_Allowance)
+ obj.Shape = Part.makeBox(self.length, self.width, self.height, self.origin)
- obj.Shape = Part.makeBox(
- self.length, self.width, self.height, self.pnt)
+def SetupStockObject(obj, addVPProxy):
+ if FreeCAD.GuiUp and obj.ViewObject:
+ if addVPProxy:
+ PathIconViewProvider.ViewProvider(obj.ViewObject, 'Stock')
+ obj.ViewObject.Transparency = 90
+ obj.ViewObject.DisplayMode = 'Wireframe'
+
+def CreateFromBase(job):
+ obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
+ proxy = StockFromBase(obj, job.Base)
+ SetupStockObject(obj, True)
+ proxy.execute(obj)
+ obj.purgeTouched()
+ return obj
+
+def CreateBox(job, extent=None, at=None):
+ obj = FreeCAD.ActiveDocument.addObject('Part::Box', 'Stock')
+ if extent:
+ obj.Length = extent.x
+ obj.Width = extent.y
+ obj.Height = extent.z
+ elif job.Base:
+ bb = job.Base.Shape.BoundBox
+ obj.Length = bb.XLength
+ obj.Width = bb.YLength
+ obj.Height = bb.ZLength
+ if at:
+ obj.Placement = FreeCAD.Placement(at, FreeCAD.Vector(), 0)
+ else:
+ bb = job.Base.Shape.BoundBox
+ origin = FreeCAD.Vector(bb.XMin, bb.YMin, bb.ZMin)
+ obj.Placement = FreeCAD.Placement(origin, FreeCAD.Vector(), 0)
+ SetupStockObject(obj, False)
+ return obj
+
+def CreateCylinder(job, radius=None, height=None, at=None):
+ obj = FreeCAD.ActiveDocument.addObject('Part::Cylinder', 'Stock')
+ if radius:
+ obj.Radius = radius
+ if height:
+ obj.Height = height
+ elif job.Base:
+ bb = job.Base.Shape.BoundBox
+ obj.Radius = max(bb.XLength, bb.YLength) * 0.7072 # 1/sqrt(2)
+ obj.Height = bb.ZLength
+ if at:
+ obj.Placement = FreeCAD.Placement(at, FreeCAD.Vector(), 0)
+ else:
+ bb = job.Base.Shape.BoundBox
+ origin = FreeCAD.Vector((bb.XMin + bb.XMax)/2, (bb.YMin + bb.YMax)/2, bb.ZMin)
+ obj.Placement = FreeCAD.Placement(origin, FreeCAD.Vector(), 0)
+ SetupStockObject(obj, False)
+ return obj
class _ViewProviderStock:
diff --git a/src/Mod/Path/PathScripts/PathUtil.py b/src/Mod/Path/PathScripts/PathUtil.py
index cff2c0230c..7260d62918 100644
--- a/src/Mod/Path/PathScripts/PathUtil.py
+++ b/src/Mod/Path/PathScripts/PathUtil.py
@@ -37,9 +37,9 @@ import PathScripts.PathLog as PathLog
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
NotValidBaseTypeIds = ['Sketcher::SketchObject']
-def isValidBaseObject(obj):
- '''isSolid(obj) ... returns true if an object represents a solid.'''
+def isValidBaseObject(obj):
+ '''isValidBaseObject(obj) ... returns true if the object can be used as a base for a job.'''
if not hasattr(obj, 'Shape'):
return False
if obj.TypeId in NotValidBaseTypeIds:
@@ -48,9 +48,23 @@ def isValidBaseObject(obj):
return False
return True
+def isSolid(obj):
+ '''isSolid(obj) ... return True if the object is a valid solid.'''
+ if hasattr(obj, 'Tip'):
+ return isSolid(obj.Tip)
+ if hasattr(obj, 'Shape'):
+ if obj.Shape.ShapeType == 'Solid' and obj.Shape.isClosed():
+ return True
+ if obj.Shape.ShapeType == 'Compound':
+ if hasattr(obj, 'Base') and hasattr(obj, 'Tool'):
+ return isSolid(obj.Base) and isSolid(obj.Tool)
+ return False
+
def toolControllerForOp(op):
if hasattr(op, 'ToolController'):
return op.ToolController
if hasattr(op, 'Base'):
return toolControllerForOp(op.Base)
return None
+
+