Merge pull request #2305 from mlampert/bugfix/pocket-shape-extensions

Path: Bugfix/pocket shape extensions
This commit is contained in:
sliptonic
2019-06-30 14:05:19 -05:00
committed by GitHub
4 changed files with 127 additions and 83 deletions

View File

@@ -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))

View File

@@ -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)))

View File

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

View File

@@ -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()