Merge pull request #2696 from mlampert/feature/path-constraint-dressup

Path: Feature/path boundary dressup
This commit is contained in:
sliptonic
2019-11-06 07:30:48 -06:00
committed by GitHub
12 changed files with 791 additions and 14 deletions

View File

@@ -40,6 +40,8 @@ SET(PathScripts_SRCS
PathScripts/PathDressupDragknife.py
PathScripts/PathDressupHoldingTags.py
PathScripts/PathDressupLeadInOut.py
PathScripts/PathDressupPathBoundary.py
PathScripts/PathDressupPathBoundaryGui.py
PathScripts/PathDressupRampEntry.py
PathScripts/PathDressupTag.py
PathScripts/PathDressupTagGui.py

View File

@@ -88,6 +88,7 @@
<file>panels/DlgToolEdit.ui</file>
<file>panels/DlgTCChooser.ui</file>
<file>panels/DogboneEdit.ui</file>
<file>panels/DressupPathBoundary.ui</file>
<file>panels/HoldingTagsEdit.ui</file>
<file>panels/PageBaseGeometryEdit.ui</file>
<file>panels/PageBaseHoleGeometryEdit.ui</file>

View File

@@ -0,0 +1,307 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>417</width>
<height>647</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="stockGroup">
<property name="title">
<string>Boundary Body</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
<number>0</number>
</property>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QComboBox" name="stock">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select what type of shape to use to constrain the underlying Path.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="currentIndex">
<number>2</number>
</property>
<item>
<property name="text">
<string>Create Box</string>
</property>
</item>
<item>
<property name="text">
<string>Create Cylinder</string>
</property>
</item>
<item>
<property name="text">
<string>Extend Model's Bound Box</string>
</property>
</item>
<item>
<property name="text">
<string>Use Existing Solid</string>
</property>
</item>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>6</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QFrame" name="stockFromExisting">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="1">
<widget class="QComboBox" name="stockExisting">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Select the body to be used to constrain the underlying Path.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="stockFromBase">
<layout class="QGridLayout" name="gridLayout_9">
<item row="1" column="3">
<widget class="Gui::InputField" name="stockExtYpos">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MaxY.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="stockExtXLabel">
<property name="text">
<string>Ext. X</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="Gui::InputField" name="stockExtXpos">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MaxX.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="stockExtYLabel">
<property name="text">
<string>Ext. Y</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="Gui::InputField" name="stockExtXneg">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MinX.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="stockExtZLabel">
<property name="text">
<string>Ext. Z</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="Gui::InputField" name="stockExtZneg">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MinZ.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="Gui::InputField" name="stockExtZpos">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MaxZ.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="stockExtYneg">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Extension of BoundBox's MinY.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="stockCreateCylinder">
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="2">
<widget class="Gui::InputField" name="stockCylinderRadius">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Radius of the Cylinder.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="stockCylinderHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height of the Cylinder.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="stockCylinderRadiusLabel">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="stockCylinderHeightLabel">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="stockCreateBox">
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="1">
<widget class="QLabel" name="stockBoxLengthLabel">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="stockBoxWidthLabel">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="Gui::InputField" name="stockBoxLength">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Length of the Box.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="Gui::InputField" name="stockBoxHeight">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Height of the Box.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="stockBoxWidth">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;Width of the Box.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLabel" name="stockBoxHeightLabel">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="stockInside">
<property name="toolTip">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;I checked the path is constrained by the solid. Otherwise the the volume of the solid describes a &amp;quot;keep out&amp;quot; zone.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>Constrained to Inside</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>Gui::InputField</class>
<extends>QLineEdit</extends>
<header>Gui/InputField.h</header>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>stock</tabstop>
<tabstop>stockExisting</tabstop>
<tabstop>stockExtXneg</tabstop>
<tabstop>stockExtXpos</tabstop>
<tabstop>stockExtYneg</tabstop>
<tabstop>stockExtYpos</tabstop>
<tabstop>stockExtZneg</tabstop>
<tabstop>stockExtZpos</tabstop>
<tabstop>stockCylinderRadius</tabstop>
<tabstop>stockCylinderHeight</tabstop>
<tabstop>stockBoxLength</tabstop>
<tabstop>stockBoxWidth</tabstop>
<tabstop>stockBoxHeight</tabstop>
<tabstop>stockInside</tabstop>
</tabstops>
<resources/>
<connections/>
</ui>

View File

@@ -82,7 +82,7 @@ class PathWorkbench (Workbench):
threedopcmdlist = ["Path_Pocket_3D"]
engravecmdlist = ["Path_Engrave", "Path_Deburr"]
modcmdlist = ["Path_OperationCopy", "Path_Array", "Path_SimpleCopy" ]
dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag"]
dressupcmdlist = ["Path_DressupAxisMap", "Path_DressupPathBoundary", "Path_DressupDogbone", "Path_DressupDragKnife", "Path_DressupLeadInOut", "Path_DressupRampEntry", "Path_DressupTag"]
extracmdlist = []
#modcmdmore = ["Path_Hop",]
#remotecmdlist = ["Path_Remote"]

View File

@@ -0,0 +1,199 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2019 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 *
# * *
# ***************************************************************************
import FreeCAD
import Path
import PathScripts.PathGeom as PathGeom
import PathScripts.PathLog as PathLog
import PathScripts.PathStock as PathStock
import PathScripts.PathUtil as PathUtil
import PathScripts.PathUtils as PathUtils
from PySide import QtCore
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
#PathLog.trackModule(PathLog.thisModule())
def _vstr(v):
if v:
return "(%.2f, %.2f, %.2f)" % (v.x, v.y, v.z)
return '-'
class DressupPathBoundary(object):
def __init__(self, obj, base, job):
obj.addProperty("App::PropertyLink", "Base", "Base", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "The base path to modify"))
obj.Base = base
obj.addProperty("App::PropertyLink", "Stock", "Boundary", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "Solid object to be used to limit the generated Path."))
obj.Stock = PathStock.CreateFromBase(job)
obj.addProperty("App::PropertyBool", "Inside", "Boundary", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "Determines if Boundary describes an inclusion or exclusion mask."))
obj.Inside = True
self.obj = obj
self.safeHeight = None
self.clearanceHeight = None
def __getstate__(self):
return None
def __setstate__(self, state):
return None
def onDcoumentRestored(self, obj):
self.obj = obj
def onDelete(self, obj, args):
if obj.Base:
job = PathUtils.findParentJob(obj)
job.Proxy.addOperation(obj.Base, obj)
if obj.Base.ViewObject:
obj.Base.ViewObject.Visibility = True
obj.Base = None
if obj.Stock:
obj.Document.removeObject(obj.Stock.Name)
obj.Stock = None
return True
def boundaryCommands(self, obj, begin, end):
PathLog.track(_vstr(begin), _vstr(end))
if end and PathGeom.pointsCoincide(begin, end):
return []
cmds = []
if begin.z < self.safeHeight:
cmds.append(Path.Command('G1', {'Z': self.safeHeight}))
if begin.z < self.clearanceHeight:
cmds.append(Path.Command('G0', {'Z': self.clearanceHeight}))
if end:
cmds.append(Path.Command('G0', {'X': end.x, 'Y': end.y}))
if end.z < self.clearanceHeight:
cmds.append(Path.Command('G0', {'Z': max(self.safeHeight, end.z)}))
if end.z < self.safeHeight:
cmds.append(Path.Command('G1', {'Z': end.z}))
return cmds
def execute(self, obj):
if not obj.Base or not obj.Base.isDerivedFrom('Path::Feature') or not obj.Base.Path:
return
if len(obj.Base.Path.Commands) > 0:
self.safeHeight = float(PathUtil.opProperty(obj.Base, 'SafeHeight'))
self.clearanceHeight = float(PathUtil.opProperty(obj.Base, 'ClearanceHeight'))
boundary = obj.Stock.Shape
cmd = obj.Base.Path.Commands[0]
pos = cmd.Placement.Base
commands = [cmd]
lastExit = None
for cmd in obj.Base.Path.Commands[1:]:
if cmd.Name in PathGeom.CmdMoveAll:
edge = PathGeom.edgeForCmd(cmd, pos)
inside = edge.common(boundary).Edges
outside = edge.cut(boundary).Edges
if not obj.Inside:
t = inside
inside = outside
outside = t
# it's really a shame that one cannot trust the sequence and/or
# orientation of edges
if 1 == len(inside) and 0 == len(outside):
PathLog.track(_vstr(pos), _vstr(lastExit), ' + ', cmd)
# cmd fully included by boundary
if lastExit:
commands.extend(self.boundaryCommands(obj, lastExit, pos))
lastExit = None
commands.append(cmd)
pos = PathGeom.commandEndPoint(cmd, pos)
elif 0 == len(inside) and 1 == len(outside):
PathLog.track(_vstr(pos), _vstr(lastExit), ' - ', cmd)
# cmd fully excluded by boundary
if not lastExit:
lastExit = pos
pos = PathGeom.commandEndPoint(cmd, pos)
else:
PathLog.track(_vstr(pos), _vstr(lastExit), len(inside), len(outside), cmd)
# cmd pierces boundary
while inside or outside:
ie = [e for e in inside if PathGeom.edgeConnectsTo(e, pos)]
PathLog.track(ie)
if ie:
e = ie[0]
ptL = e.valueAt(e.LastParameter)
flip = PathGeom.pointsCoincide(pos, ptL)
newPos = e.valueAt(e.FirstParameter) if flip else ptL
# inside edges are taken at this point (see swap of inside/outside
# above - so we can just connect the dots ...
if lastExit:
commands.extend(self.boundaryCommands(obj, lastExit, pos))
lastExit = None
PathLog.track(e, flip)
commands.extend(PathGeom.cmdsForEdge(e, flip, False))
inside.remove(e)
pos = newPos
lastExit = newPos
else:
oe = [e for e in outside if PathGeom.edgeConnectsTo(e, pos)]
PathLog.track(oe)
if oe:
e = oe[0]
ptL = e.valueAt(e.LastParameter)
flip = PathGeom.pointsCoincide(pos, ptL)
newPos = e.valueAt(e.FirstParameter) if flip else ptL
# outside edges are never taken at this point (see swap of
# inside/oustide above) - so just move along ...
outside.remove(e)
pos = newPos
else:
PathLog.error('huh?')
import Part
Part.show(Part.Vertex(pos), 'pos')
for e in inside:
Part.show(e, 'ei')
for e in outside:
Part.show(e, 'eo')
raise Exception('This is not supposed to happen')
#pos = PathGeom.commandEndPoint(cmd, pos)
else:
PathLog.track('no-move', cmd)
commands.append(cmd)
if lastExit:
commands.extend(self.boundaryCommands(obj, lastExit, None))
lastExit = None
else:
PathLog.warning("No Path Commands for %s" % obj.Base.Label)
commands = []
PathLog.track(commands)
obj.Path = Path.Path(commands)
def Create(base, name='DressupPathBoundary'):
'''Create(base, name='DressupPathBoundary') ... creates a dressup limiting base's Path to a boundary.'''
if not base.isDerivedFrom('Path::Feature'):
PathLog.error(translate('Path_DressupPathBoundary', 'The selected object is not a path')+'\n')
return None
obj = FreeCAD.ActiveDocument.addObject('Path::FeaturePython', name)
job = PathUtils.findParentJob(base)
obj.Proxy = DressupPathBoundary(obj, base, job)
job.Proxy.addOperation(obj, base, True)
return obj

View File

@@ -0,0 +1,243 @@
# -*- coding: utf-8 -*-
# ***************************************************************************
# * *
# * Copyright (c) 2019 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 *
# * *
# ***************************************************************************
import FreeCAD
import FreeCADGui
import PathScripts.PathDressupPathBoundary as PathDressupPathBoundary
import PathScripts.PathJobGui as PathJobGui
import PathScripts.PathLog as PathLog
from PySide import QtGui, QtCore
PathLog.setLevel(PathLog.Level.INFO, PathLog.thisModule())
# PathLog.trackModule()
# Qt translation handling
def translate(context, text, disambig=None):
return QtCore.QCoreApplication.translate(context, text, disambig)
class TaskPanel(object):
def __init__(self, obj, viewProvider):
self.obj = obj
self.viewProvider = viewProvider
self.form = FreeCADGui.PySideUic.loadUi(':/panels/DressupPathBoundary.ui')
if obj.Stock:
self.visibilityBoundary = obj.Stock.ViewObject.Visibility
obj.Stock.ViewObject.Visibility = True
else:
self.visibilityBoundary = False
self.stockFromBase = None
self.stockFromExisting = None
self.stockCreateBox = None
self.stockCreateCylinder = None
self.stockEdit = None
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel)
def clicked(self, button):
# callback for standard buttons
if button == QtGui.QDialogButtonBox.Apply:
self.obj.Proxy.execute(self.obj)
def abort(self):
FreeCAD.ActiveDocument.abortTransaction()
self.cleanup(False)
def reject(self):
FreeCAD.ActiveDocument.abortTransaction()
self.cleanup(True)
def accept(self):
FreeCAD.ActiveDocument.commitTransaction()
self.cleanup(True)
#if self.isDirty:
# self.getFields()
# FreeCAD.ActiveDocument.recompute()
def cleanup(self, gui):
self.viewProvider.clearTaskPanel()
if gui:
FreeCADGui.ActiveDocument.resetEdit()
FreeCADGui.Control.closeDialog()
FreeCAD.ActiveDocument.recompute()
if self.obj.Stock:
self.obj.Stock.ViewObject.Visibility = self.visibilityBoundary
def getFields(self):
pass
def setFields(self):
pass
def updateStockEditor(self, index, force=False):
import PathScripts.PathStock as PathStock
def setupFromBaseEdit():
PathLog.track(index, force)
if force or not self.stockFromBase:
self.stockFromBase = PathJobGui.StockFromBaseBoundBoxEdit(self.obj, self.form, force)
self.stockEdit = self.stockFromBase
def setupCreateBoxEdit():
PathLog.track(index, force)
if force or not self.stockCreateBox:
self.stockCreateBox = PathJobGui.StockCreateBoxEdit(self.obj, self.form, force)
self.stockEdit = self.stockCreateBox
def setupCreateCylinderEdit():
PathLog.track(index, force)
if force or not self.stockCreateCylinder:
self.stockCreateCylinder = PathJobGui.StockCreateCylinderEdit(self.obj, self.form, force)
self.stockEdit = self.stockCreateCylinder
def setupFromExisting():
PathLog.track(index, force)
if force or not self.stockFromExisting:
self.stockFromExisting = PathJobGui.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 PathJobGui.StockFromBaseBoundBoxEdit.IsStock(self.obj):
setupFromBaseEdit()
elif PathJobGui.StockCreateBoxEdit.IsStock(self.obj):
setupCreateBoxEdit()
elif PathJobGui.StockCreateCylinderEdit.IsStock(self.obj):
setupCreateCylinderEdit()
elif PathJobGui.StockFromExistingEdit.IsStock(self.obj):
setupFromExisting()
else:
PathLog.error(translate('PathJob', "Unsupported stock object %s") % self.obj.Stock.Label)
else:
if index == PathJobGui.StockFromBaseBoundBoxEdit.Index:
setupFromBaseEdit()
elif index == PathJobGui.StockCreateBoxEdit.Index:
setupCreateBoxEdit()
elif index == PathJobGui.StockCreateCylinderEdit.Index:
setupCreateCylinderEdit()
elif index == PathJobGui.StockFromExistingEdit.Index:
if not setupFromExisting():
setupFromBaseEdit()
index = -1
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, False)
self.form.stock.currentIndexChanged.connect(self.updateStockEditor)
class DressupPathBoundaryViewProvider(object):
def __init__(self, vobj):
self.attach(vobj)
def __getstate__(self):
return None
def __setstate_(self, state):
return None
def attach(self, vobj):
self.vobj = vobj
self.obj = vobj.Object
self.panel = None
def claimChildren(self):
return [self.obj.Base, self.obj.Stock]
def onDelete(self, vobj, args=None):
vobj.Object.Proxy.onDelete(vobj.Object, args)
return True
def setEdit(self, vobj, mode=0):
panel = TaskPanel(vobj.Object, self)
self.setupTaskPanel(panel)
return True
def unsetEdit(self, vobj, mode=0):
if self.panel:
self.panel.abort()
def setupTaskPanel(self, panel):
self.panel = panel
FreeCADGui.Control.closeDialog()
FreeCADGui.Control.showDialog(panel)
panel.setupUi()
def clearTaskPanel(self):
self.panel = None
def Create(base, name='DressupPathBoundary'):
FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupPathBoundary', 'Create a Boundary dressup'))
obj = PathDressupPathBoundary.Create(base, name)
obj.ViewObject.Proxy = DressupPathBoundaryViewProvider(obj.ViewObject)
obj.Base.ViewObject.Visibility = False
obj.Stock.ViewObject.Visibility = False
FreeCAD.ActiveDocument.commitTransaction()
obj.ViewObject.Document.setEdit(obj.ViewObject, 0)
return obj
class CommandPathDressupPathBoundary:
# pylint: disable=no-init
def GetResources(self):
return {'Pixmap': 'Path-Dressup',
'MenuText': QtCore.QT_TRANSLATE_NOOP('Path_DressupPathBoundary', 'Boundary Dress-up'),
'ToolTip': QtCore.QT_TRANSLATE_NOOP('Path_DressupPathBoundary', 'Creates a Path Boundary Dress-up object from a selected path')}
def IsActive(self):
if FreeCAD.ActiveDocument is not None:
for o in FreeCAD.ActiveDocument.Objects:
if o.Name[:3] == 'Job':
return True
return False
def Activated(self):
# check that the selection contains exactly what we want
selection = FreeCADGui.Selection.getSelection()
if len(selection) != 1:
PathLog.error(translate('Path_DressupPathBoundary', 'Please select one path object')+'\n')
return
baseObject = selection[0]
# everything ok!
FreeCAD.ActiveDocument.openTransaction(translate('Path_DressupPathBoundary', 'Create Path Boundary Dress-up'))
FreeCADGui.addModule('PathScripts.PathDressupPathBoundaryGui')
FreeCADGui.doCommand("PathScripts.PathDressupPathBoundaryGui.Create(App.ActiveDocument.%s)" % baseObject.Name)
FreeCAD.ActiveDocument.commitTransaction()
FreeCAD.ActiveDocument.recompute()
if FreeCAD.GuiUp:
# register the FreeCAD command
FreeCADGui.addCommand('Path_DressupPathBoundary', CommandPathDressupPathBoundary())
PathLog.notice('Loading PathDressupPathBoundaryGui... done\n')

View File

@@ -87,6 +87,7 @@ CmdMoveCW = ['G2', 'G02']
CmdMoveCCW = ['G3', 'G03']
CmdMoveArc = CmdMoveCW + CmdMoveCCW
CmdMove = CmdMoveStraight + CmdMoveArc
CmdMoveAll = CmdMove + CmdMoveRapid
def isRoughly(float1, float2, error=Tolerance):
"""isRoughly(float1, float2, [error=Tolerance])

View File

@@ -48,6 +48,7 @@ def Startup():
from PathScripts import PathDressupDogbone
from PathScripts import PathDressupDragknife
from PathScripts import PathDressupRampEntry
from PathScripts import PathDressupPathBoundaryGui
from PathScripts import PathDressupTagGui
from PathScripts import PathDressupLeadInOut
from PathScripts import PathDrillingGui

View File

@@ -344,12 +344,14 @@ class ObjectJob:
def execute(self, obj):
obj.Path = obj.Operations.Path
def addOperation(self, op, before = None):
def addOperation(self, op, before = None, removeBefore = False):
group = self.obj.Operations.Group
if op not in group:
if before:
try:
group.insert(group.index(before), op)
if removeBefore:
group.remove(before)
except Exception as e: # pylint: disable=broad-except
PathLog.error(e)
group.append(op)

View File

@@ -527,12 +527,16 @@ class StockFromExistingEdit(StockEdit):
def candidates(self, obj):
solids = [o for o in obj.Document.Objects if PathUtil.isSolid(o)]
for base in obj.Model.Group:
if base in solids and PathJob.isResourceClone(obj, base, 'Model'):
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 obj.Stock in solids:
if job.Stock in solids:
# regardless, what stock is/was, it's not a valid choice
solids.remove(obj.Stock)
solids.remove(job.Stock)
return sorted(solids, key=lambda c: c.Label)
def setFields(self, obj):

View File

@@ -233,9 +233,22 @@ def SetupStockObject(obj, stockType):
obj.ViewObject.Transparency = 90
obj.ViewObject.DisplayMode = 'Wireframe'
class FakeJob(object):
def __init__(self, base):
self.Group = [base]
def _getBase(job):
if job and hasattr(job, 'Model'):
return job.Model
if job:
import PathScripts.PathUtils as PathUtils
job = PathUtils.findParentJob(job)
return job.Model if job else None
return None
def CreateFromBase(job, neg=None, pos=None, placement=None):
PathLog.track(job.Label, neg, pos, placement)
base = job.Model if job else None
base = _getBase(job)
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
obj.Proxy = StockFromBase(obj, base)
@@ -258,7 +271,7 @@ def CreateFromBase(job, neg=None, pos=None, placement=None):
return obj
def CreateBox(job, extent=None, placement=None):
base = job.Model if job else None
base = _getBase(job)
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
obj.Proxy = StockCreateBox(obj)
@@ -283,7 +296,7 @@ def CreateBox(job, extent=None, placement=None):
return obj
def CreateCylinder(job, radius=None, height=None, placement=None):
base = job.Model if job else None
base = _getBase(job)
obj = FreeCAD.ActiveDocument.addObject('Part::FeaturePython', 'Stock')
obj.Proxy = StockCreateCylinder(obj)

View File

@@ -68,15 +68,19 @@ def isSolid(obj):
shape = Part.getShape(obj)
return not shape.isNull() and shape.Volume and shape.isClosed()
def opProperty(op, prop):
'''opProperty(op, prop) ... return the value of property prop of the underlying operation (or None if prop does not exist)'''
if hasattr(op, prop):
return getattr(op, prop)
if hasattr(op, 'Base'):
return opProperty(op.Base, prop)
return None
def toolControllerForOp(op):
'''toolControllerForOp(op) ... return the tool controller used by the op.
If the op doesn't have its own tool controller but has a Base object, return its tool controller.
Otherwise return None.'''
if hasattr(op, 'ToolController'):
return op.ToolController
if hasattr(op, 'Base'):
return toolControllerForOp(op.Base)
return None
return opProperty(op, 'ToolController')
def getPublicObject(obj):
'''getPublicObject(obj) ... returns the object which should be used to reference a feature of the given object.'''