Merge pull request #2305 from mlampert/bugfix/pocket-shape-extensions
Path: Bugfix/pocket shape extensions
This commit is contained in:
@@ -334,7 +334,6 @@ class ObjectOp(PathOp.ObjectOp):
|
||||
areaOpUseProjection(obj) ... return true if operation can use projection
|
||||
instead.'''
|
||||
PathLog.track()
|
||||
PathLog.info("\n----- opExecute() in PathAreaOp.py")
|
||||
# PathLog.debug("OpDepths are Start: {}, and Final: {}".format(obj.OpStartDepth.Value, obj.OpFinalDepth.Value))
|
||||
# PathLog.debug("Depths are Start: {}, and Final: {}".format(obj.StartDepth.Value, obj.FinalDepth.Value))
|
||||
# PathLog.debug("initOpDepths are Start: {}, and Final: {}".format(self.initOpStartDepth, self.initOpFinalDepth))
|
||||
|
||||
@@ -825,7 +825,6 @@ class TaskPanel(object):
|
||||
else:
|
||||
for page in reversed(self.featurePages):
|
||||
toolbox.addItem(page.form, page.getTitle(obj))
|
||||
PathLog.info("Title: '%s'" % opTitle)
|
||||
toolbox.setWindowTitle(opTitle)
|
||||
if opPage.getIcon(obj):
|
||||
toolbox.setWindowIcon(QtGui.QIcon(opPage.getIcon(obj)))
|
||||
|
||||
@@ -47,7 +47,7 @@ __title__ = "Path Pocket Shape Operation"
|
||||
__author__ = "sliptonic (Brad Collette)"
|
||||
__url__ = "http://www.freecadweb.org"
|
||||
__doc__ = "Class and implementation of shape based Pocket operation."
|
||||
__contributors__ = "mlampert [FreeCAD], russ4262 (Russell Johnson)"
|
||||
__contributors__ = "russ4262 (Russell Johnson)"
|
||||
__created__ = "2017"
|
||||
__scriptVersion__ = "2g testing"
|
||||
__lastModified__ = "2019-06-12 23:29 CST"
|
||||
@@ -77,8 +77,11 @@ def endPoints(edgeOrWire):
|
||||
if 1 == cnt:
|
||||
unique.append(p)
|
||||
return unique
|
||||
return [edgeOrWire.valueAt(edgeOrWire.FirstParameter), edgeOrWire.valueAt(edgeOrWire.LastParameter)]
|
||||
|
||||
pfirst = edgeOrWire.valueAt(edgeOrWire.FirstParameter)
|
||||
plast = edgeOrWire.valueAt(edgeOrWire.LastParameter)
|
||||
if PathGeom.pointsCoincide(pfirst, plast):
|
||||
return None
|
||||
return [pfirst, plast]
|
||||
|
||||
def includesPoint(p, pts):
|
||||
'''includesPoint(p, pts) ... answer True if the collection of pts includes the point p'''
|
||||
@@ -102,42 +105,29 @@ def selectOffsetWire(feature, wires):
|
||||
|
||||
def extendWire(feature, wire, length):
|
||||
'''extendWire(wire, length) ... return a closed Wire which extends wire by length'''
|
||||
try:
|
||||
off2D = wire.makeOffset2D(length)
|
||||
except Exception as e:
|
||||
PathLog.error("extendWire(): wire.makeOffset2D()")
|
||||
PathLog.error(e)
|
||||
return False
|
||||
off2D = wire.makeOffset2D(length)
|
||||
endPts = endPoints(wire)
|
||||
edges = [e for e in off2D.Edges if Part.Circle != type(e.Curve) or not includesPoint(e.Curve.Center, endPts)]
|
||||
wires = [Part.Wire(e) for e in Part.sortEdges(edges)]
|
||||
offset = selectOffsetWire(feature, wires)
|
||||
ePts = endPoints(offset)
|
||||
l0 = (ePts[0] - endPts[0]).Length
|
||||
l1 = (ePts[1] - endPts[0]).Length
|
||||
edges = wire.Edges
|
||||
if l0 < l1:
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[0], ePts[0])))
|
||||
edges.extend(offset.Edges)
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[1], ePts[1])))
|
||||
else:
|
||||
endPts = endPoints(wire)
|
||||
edges = [e for e in off2D.Edges if not isinstance(e.Curve, Part.Circle) or not includesPoint(e.Curve.Center, endPts)]
|
||||
wires = [Part.Wire(e) for e in Part.sortEdges(edges)]
|
||||
offset = selectOffsetWire(feature, wires)
|
||||
ePts = endPoints(offset)
|
||||
try:
|
||||
l0 = (ePts[0] - endPts[0]).Length
|
||||
except Exception as ee:
|
||||
PathLog.error("extendWire(): (ePts[0] - endPts[0]).Length")
|
||||
PathLog.error(ee)
|
||||
return False
|
||||
else:
|
||||
l1 = (ePts[1] - endPts[0]).Length
|
||||
edges = wire.Edges
|
||||
if l0 < l1:
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[0], ePts[0])))
|
||||
edges.extend(offset.Edges)
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[1], ePts[1])))
|
||||
else:
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[1], ePts[0])))
|
||||
edges.extend(offset.Edges)
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[0], ePts[1])))
|
||||
return Part.Wire(edges)
|
||||
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[1], ePts[0])))
|
||||
edges.extend(offset.Edges)
|
||||
edges.append(Part.Edge(Part.LineSegment(endPts[0], ePts[1])))
|
||||
return Part.Wire(edges)
|
||||
|
||||
class Extension(object):
|
||||
DirectionNormal = 0
|
||||
DirectionX = 1
|
||||
DirectionY = 2
|
||||
DirectionX = 1
|
||||
DirectionY = 2
|
||||
|
||||
def __init__(self, obj, feature, sub, length, direction):
|
||||
self.obj = obj
|
||||
@@ -149,7 +139,8 @@ class Extension(object):
|
||||
def getSubLink(self):
|
||||
return "%s:%s" % (self.feature, self.sub)
|
||||
|
||||
def extendEdge(self, feature, e0, direction):
|
||||
def _extendEdge(self, feature, e0, direction):
|
||||
PathLog.track(feature, e0, direction)
|
||||
if isinstance(e0.Curve, Part.Line) or isinstance(e0.Curve, Part.LineSegment):
|
||||
e2 = e0.copy()
|
||||
off = self.length.Value * direction
|
||||
@@ -162,49 +153,74 @@ class Extension(object):
|
||||
return wire
|
||||
return extendWire(feature, Part.Wire([e0]), self.length.Value)
|
||||
|
||||
def getEdgeNumbers(self):
|
||||
def _getEdgeNumbers(self):
|
||||
if 'Wire' in self.sub:
|
||||
return [nr for nr in self.sub[5:-1].split(',')]
|
||||
return [self.sub[4:]]
|
||||
|
||||
def getEdgeNames(self):
|
||||
return ["Edge%s" % nr for nr in self.getEdgeNumbers()]
|
||||
def _getEdgeNames(self):
|
||||
return ["Edge%s" % nr for nr in self._getEdgeNumbers()]
|
||||
|
||||
def getEdges(self):
|
||||
return [self.obj.Shape.getElement(sub) for sub in self.getEdgeNames()]
|
||||
def _getEdges(self):
|
||||
return [self.obj.Shape.getElement(sub) for sub in self._getEdgeNames()]
|
||||
|
||||
def getDirection(self, wire):
|
||||
def _getDirectedNormal(self, p0, normal):
|
||||
poffPlus = p0 + 0.01 * normal
|
||||
poffMinus = p0 - 0.01 * normal
|
||||
if not self.obj.Shape.isInside(poffPlus, 0.005, True):
|
||||
return normal
|
||||
if not self.obj.Shape.isInside(poffMinus, 0.005, True):
|
||||
return normal.negative()
|
||||
return None
|
||||
|
||||
def _getDirection(self, wire):
|
||||
e0 = wire.Edges[0]
|
||||
midparam = e0.FirstParameter + 0.5 * (e0.LastParameter - e0.FirstParameter)
|
||||
tangent = e0.tangentAt(midparam)
|
||||
try:
|
||||
normal = tangent.cross(FreeCAD.Vector(0, 0, 1)).normalize()
|
||||
except Exception as e:
|
||||
PathLog.error('getDirection(): tangent.cross(FreeCAD.Vector(0, 0, 1)).normalize()')
|
||||
PathLog.error(e)
|
||||
return None
|
||||
else:
|
||||
poffPlus = e0.valueAt(midparam) + 0.01 * normal
|
||||
poffMinus = e0.valueAt(midparam) - 0.01 * normal
|
||||
if not self.obj.Shape.isInside(poffPlus, 0.005, True):
|
||||
return normal
|
||||
if not self.obj.Shape.isInside(poffMinus, 0.005, True):
|
||||
return normal.negative()
|
||||
PathLog.track('tangent', tangent, self.feature, self.sub)
|
||||
normal = tangent.cross(FreeCAD.Vector(0, 0, 1))
|
||||
if PathGeom.pointsCoincide(normal, FreeCAD.Vector(0, 0, 0)):
|
||||
return None
|
||||
return self._getDirectedNormal(e0.valueAt(midparam), normal.normalize())
|
||||
|
||||
def getWire(self):
|
||||
PathLog.track()
|
||||
if PathGeom.isRoughly(0, self.length.Value) or not self.sub:
|
||||
return None
|
||||
|
||||
feature = self.obj.Shape.getElement(self.feature)
|
||||
edges = self.getEdges()
|
||||
edges = self._getEdges()
|
||||
sub = Part.Wire(edges)
|
||||
|
||||
if 1 == len(edges):
|
||||
direction = self.getDirection(sub)
|
||||
if direction is None:
|
||||
return None
|
||||
return self.extendEdge(feature, edges[0], direction)
|
||||
edge = edges[0]
|
||||
if Part.Circle == type(edge.Curve) and not endPoints(edge):
|
||||
circle = edge.Curve
|
||||
# for a circle we have to figure out if it's a hole or a cylinder
|
||||
p0 = edge.valueAt(edge.FirstParameter)
|
||||
normal = (edge.Curve.Center - p0).normalize()
|
||||
direction = self._getDirectedNormal(p0, normal)
|
||||
|
||||
if direction is None:
|
||||
return None
|
||||
if PathGeom.pointsCoincide(normal, direction):
|
||||
r = circle.Radius - self.length.Value
|
||||
else:
|
||||
r = circle.Radius + self.length.Value
|
||||
# assuming the offset produces a valid circle - go for it
|
||||
if r > 0:
|
||||
c1 = Part.makeCircle(r, circle.Center, circle.Axis, edge.FirstParameter * 180 / math.pi, edge.LastParameter * 180 / math.pi)
|
||||
return [Part.Wire([edge]), Part.Wire([c1])]
|
||||
# the extension is bigger than the hole - so let's just cover the whole hole
|
||||
return Part.Wire([edge])
|
||||
|
||||
else:
|
||||
PathLog.track(self.feature, self.sub, type(edge.Curve), endPoints(edge))
|
||||
direction = self._getDirection(sub)
|
||||
if direction is None:
|
||||
return None
|
||||
# return self._extendEdge(feature, edge, direction)
|
||||
return self._extendEdge(feature, edges[0], direction)
|
||||
return extendWire(feature, sub, self.length.Value)
|
||||
|
||||
|
||||
@@ -212,7 +228,6 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
'''Proxy object for Pocket operation.'''
|
||||
|
||||
def areaOpFeatures(self, obj):
|
||||
# return super(self.__class__, self).areaOpFeatures(obj) | PathOp.FeatureLocations | PathOp.FeatureRotation
|
||||
return super(self.__class__, self).areaOpFeatures(obj) | PathOp.FeatureLocations
|
||||
|
||||
def initPocketOp(self, obj):
|
||||
@@ -225,6 +240,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
obj.addProperty('App::PropertyLinkSubListGlobal', 'ExtensionFeature', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'List of features to extend.'))
|
||||
if not hasattr(obj, 'ExtensionCorners'):
|
||||
obj.addProperty('App::PropertyBool', 'ExtensionCorners', 'Extension', QtCore.QT_TRANSLATE_NOOP('PathPocketShape', 'When enabled connected extension edges are combined to wires.'))
|
||||
obj.ExtensionCorners = True
|
||||
|
||||
if not hasattr(obj, 'ReverseDirection'):
|
||||
obj.addProperty('App::PropertyBool', 'ReverseDirection', 'Rotation', QtCore.QT_TRANSLATE_NOOP('App::Property', 'Reverse direction of pocket operation.'))
|
||||
@@ -237,7 +253,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
|
||||
obj.setEditorMode('ExtensionFeature', 2)
|
||||
|
||||
def opOnDocumentRestored(self, obj):
|
||||
def areaOpOnDocumentRestored(self, obj):
|
||||
'''opOnDocumentRestored(obj) ... adds the UseOutline property if it doesn't exist.'''
|
||||
self.initPocketOp(obj)
|
||||
|
||||
@@ -531,7 +547,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
# add faces for extensions
|
||||
self.exts = []
|
||||
for ext in self.getExtensions(obj):
|
||||
wire = Part.Face(ext.getWire())
|
||||
wire = ext.getWire()
|
||||
if wire:
|
||||
face = Part.Face(wire)
|
||||
self.horiz.append(face)
|
||||
@@ -602,7 +618,7 @@ class ObjectPocket(PathPocketBase.ObjectPocket):
|
||||
'''areaOpSetDefaultValues(obj, job) ... set default values'''
|
||||
obj.StepOver = 100
|
||||
obj.ZigZagAngle = 45
|
||||
obj.ExtensionCorners = False
|
||||
obj.ExtensionCorners = True
|
||||
obj.UseOutline = False
|
||||
obj.ReverseDirection = False
|
||||
obj.InverseAngle = False
|
||||
|
||||
@@ -80,8 +80,14 @@ class _Extension(object):
|
||||
if not ext is None:
|
||||
wire = ext.getWire()
|
||||
if wire:
|
||||
poly = [p for p in wire.discretize(Deflection=0.01)][:-1]
|
||||
polygon = [(p.x, p.y, p.z) for p in poly]
|
||||
if isinstance(wire, (list, tuple)):
|
||||
p0 = [p for p in wire[0].discretize(Deflection=0.02)]
|
||||
p1 = [p for p in wire[1].discretize(Deflection=0.02)]
|
||||
p2 = list(reversed(p1))
|
||||
polygon = [(p.x, p.y, p.z) for p in (p0 + p2)]
|
||||
else:
|
||||
poly = [p for p in wire.discretize(Deflection=0.02)][:-1]
|
||||
polygon = [(p.x, p.y, p.z) for p in poly]
|
||||
crd.point.setValues(polygon)
|
||||
else:
|
||||
return None
|
||||
@@ -166,17 +172,27 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
else:
|
||||
self.form.showExtensions.setCheckState(QtCore.Qt.Unchecked)
|
||||
|
||||
self.blockUpdateData = False
|
||||
|
||||
global Page
|
||||
Page = self
|
||||
|
||||
def cleanupPage(self, obj):
|
||||
self.obj.ViewObject.RootNode.removeChild(self.switch)
|
||||
# If the object was already destroyed we can't access obj.Name.
|
||||
# This is the case if this was a new op and the user hit Cancel.
|
||||
# Unfortunately there's no direct way to determine the object's
|
||||
# livelihood without causing an error so we look for the object
|
||||
# in the document and clean up if it still exists.
|
||||
for o in self.obj.Document.getObjectsByLabel(self.obj.Label):
|
||||
if o == obj:
|
||||
self.obj.ViewObject.RootNode.removeChild(self.switch)
|
||||
return
|
||||
PathLog.debug("%s already destroyed - no cleanup required" % (obj.Label))
|
||||
|
||||
def getForm(self):
|
||||
return FreeCADGui.PySideUic.loadUi(":/panels/PageOpPocketExtEdit.ui")
|
||||
|
||||
def forAllItemsCall(self, cb):
|
||||
PathLog.track()
|
||||
for modelRow in range(self.model.rowCount()):
|
||||
model = self.model.item(modelRow, 0)
|
||||
for featureRow in range(model.rowCount()):
|
||||
@@ -186,23 +202,29 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
ext = item.data(self.DataObject)
|
||||
cb(item, ext)
|
||||
|
||||
def currentExtensions(self):
|
||||
extensions = []
|
||||
def extractExtension(item, ext):
|
||||
if ext and ext.edge and item.checkState() == QtCore.Qt.Checked:
|
||||
extensions.append(ext.ext)
|
||||
self.forAllItemsCall(extractExtension)
|
||||
PathLog.track('extensions', extensions)
|
||||
return extensions
|
||||
|
||||
def updateProxyExtensions(self, obj):
|
||||
self.extensions = self.currentExtensions()
|
||||
obj.Proxy.setExtensions(obj, self.extensions)
|
||||
|
||||
def getFields(self, obj):
|
||||
PathLog.track(obj.Label, self.model.rowCount(), self.model.columnCount())
|
||||
self.blockUpdateData = True
|
||||
|
||||
if obj.ExtensionCorners != self.form.extendCorners.isChecked():
|
||||
obj.ExtensionCorners = self.form.extendCorners.isChecked()
|
||||
self.defaultLength.updateProperty()
|
||||
|
||||
extensions = []
|
||||
|
||||
def extractExtension(item, ext):
|
||||
if ext and ext.edge and item.checkState() == QtCore.Qt.Checked:
|
||||
extensions.append(ext.ext)
|
||||
|
||||
self.forAllItemsCall(extractExtension)
|
||||
|
||||
self.extensions = extensions
|
||||
obj.Proxy.setExtensions(obj, extensions)
|
||||
self.updateProxyExtensions(obj)
|
||||
self.blockUpdateData = False
|
||||
|
||||
def setFields(self, obj):
|
||||
PathLog.track(obj.Label)
|
||||
@@ -319,9 +341,13 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
if hasattr(self, 'selectionModel') and selectedExtensions:
|
||||
self.restoreSelection(selectedExtensions)
|
||||
|
||||
self.form.extensionTree.blockSignals(False)
|
||||
|
||||
def updateData(self, obj, prop):
|
||||
if prop in ['Base', 'ExtensionLengthDefault']:
|
||||
self.setExtensions(obj.Proxy.getExtensions(obj))
|
||||
PathLog.track(obj.Label, prop, self.blockUpdateData)
|
||||
if not self.blockUpdateData:
|
||||
if prop in ['Base', 'ExtensionLengthDefault']:
|
||||
self.setExtensions(obj.Proxy.getExtensions(obj))
|
||||
|
||||
def restoreSelection(self, selection):
|
||||
PathLog.track()
|
||||
@@ -350,7 +376,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
|
||||
def setSelectionVisuals(item, ext):
|
||||
if selectItem(item, ext):
|
||||
self.selectionModel.select(item.index(), QtGui.QItemSelectionModel.Select)
|
||||
self.selectionModel.select(item.index(), QtCore.QItemSelectionModel.Select)
|
||||
|
||||
selected = self.selectionModel.isSelected(item.index())
|
||||
if selected:
|
||||
@@ -377,6 +403,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
self.setDirty()
|
||||
|
||||
def _extensionsSetState(self, state):
|
||||
PathLog.track(state)
|
||||
for index in self.selectionModel.selectedIndexes():
|
||||
item = self.model.itemFromIndex(index)
|
||||
ext = item.data(self.DataObject)
|
||||
@@ -392,11 +419,13 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
self._extensionsSetState(QtCore.Qt.Checked)
|
||||
|
||||
def updateItemEnabled(self, item):
|
||||
PathLog.track(item)
|
||||
ext = item.data(self.DataObject)
|
||||
if item.checkState() == QtCore.Qt.Checked:
|
||||
ext.enable()
|
||||
else:
|
||||
ext.disable()
|
||||
self.updateProxyExtensions(self.obj)
|
||||
self.setDirty()
|
||||
|
||||
def showHideExtension(self):
|
||||
@@ -412,6 +441,7 @@ class TaskPanelExtensionPage(PathOpGui.TaskPanelPage):
|
||||
#self.setDirty()
|
||||
|
||||
def toggleExtensionCorners(self):
|
||||
PathLog.track()
|
||||
self.setExtensions(self.obj.Proxy.getExtensions(self.obj))
|
||||
self.selectionChanged()
|
||||
self.setDirty()
|
||||
|
||||
Reference in New Issue
Block a user