Added boundary dressup to restrict path to another shape.

This commit is contained in:
Markus Lampert
2019-11-01 17:37:07 -07:00
parent 17b2cf2e96
commit d0e16a58ef
8 changed files with 612 additions and 6 deletions

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,238 @@
<?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>263</width>
<height>508</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QGroupBox" name="boundaryGroup">
<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="boundary">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</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="boundaryFromExisting">
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="1">
<widget class="QComboBox" name="boundaryExisting"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="boundaryFromBase">
<layout class="QGridLayout" name="gridLayout_9">
<item row="1" column="3">
<widget class="Gui::InputField" name="boundaryExtYpos"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="boundaryExtXLabel">
<property name="text">
<string>Ext. X</string>
</property>
</widget>
</item>
<item row="0" column="3">
<widget class="Gui::InputField" name="boundaryExtXpos">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="boundaryExtYLabel">
<property name="text">
<string>Ext. Y</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="Gui::InputField" name="boundaryExtXneg"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="boundaryExtZLabel">
<property name="text">
<string>Ext. Z</string>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="Gui::InputField" name="boundaryExtZneg"/>
</item>
<item row="2" column="3">
<widget class="Gui::InputField" name="boundaryExtZpos"/>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="boundaryExtYneg"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="boundaryCreateCylinder">
<layout class="QGridLayout" name="gridLayout_10">
<item row="0" column="2">
<widget class="Gui::InputField" name="boundaryCylinderRadius"/>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="boundaryCylinderHeight"/>
</item>
<item row="0" column="1">
<widget class="QLabel" name="boundaryCylinderRadiusLabel">
<property name="text">
<string>Radius</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="boundaryCylinderHeightLabel">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QFrame" name="boundaryCreateBox">
<layout class="QGridLayout" name="gridLayout_11">
<item row="0" column="1">
<widget class="QLabel" name="boundaryBoxLengthLabel">
<property name="text">
<string>Length</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="boundaryBoxWidthLabel">
<property name="text">
<string>Width</string>
</property>
</widget>
</item>
<item row="0" column="2">
<widget class="Gui::InputField" name="boundaryBoxLength"/>
</item>
<item row="2" column="2">
<widget class="Gui::InputField" name="boundaryBoxHeight"/>
</item>
<item row="1" column="2">
<widget class="Gui::InputField" name="boundaryBoxWidth"/>
</item>
<item row="2" column="1">
<widget class="QLabel" name="boundaryBoxHeightLabel">
<property name="text">
<string>Height</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QCheckBox" name="boundaryInside">
<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>
<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,192 @@
# -*- 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.DEBUG, 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", "Boundary", "Boundary", QtCore.QT_TRANSLATE_NOOP("Path_DressupPathBoundary", "Solid object to be used to limit the generated Path."))
obj.Boundary = 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.Boundary:
obj.Document.removeObject(obj.Boundary.Name)
obj.Boundary = None
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.Boundary.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)
return obj

View File

@@ -0,0 +1,169 @@
# -*- 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.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')
self.visibilityBase = obj.Base.ViewObject.Visibility if obj.Base else None
self.visibilityBoundary = obj.Boundary.ViewObject.Visibility if obj.Boundary else None
def getStandardButtons(self):
return int(QtGui.QDialogButtonBox.Ok | QtGui.QDialogButtonBox.Apply | QtGui.QDialogButtonBox.Cancel)
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.Base:
self.obj.Base.ViewObject.Visibility = self.visibilityBase
if self.obj.Boundary:
self.obj.Boundary.ViewObject.Visibility = self.visibilityBoundary
def getFields(self):
pass
def setFields(self):
pass
def setupUi(self):
pass
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.Boundary]
def onDelete(self, vobj, args=None):
vobj.Object.Proxy.onDelete(vobj.Object, args)
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)
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

@@ -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.'''