diff --git a/src/Mod/Path/PathScripts/PathAreaOp.py b/src/Mod/Path/PathScripts/PathAreaOp.py index 7b9208b28b..2bfdd770c0 100644 --- a/src/Mod/Path/PathScripts/PathAreaOp.py +++ b/src/Mod/Path/PathScripts/PathAreaOp.py @@ -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)) diff --git a/src/Mod/Path/PathScripts/PathOpGui.py b/src/Mod/Path/PathScripts/PathOpGui.py index dd1feff1d1..fa86aecce4 100644 --- a/src/Mod/Path/PathScripts/PathOpGui.py +++ b/src/Mod/Path/PathScripts/PathOpGui.py @@ -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))) diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index faa5b06d04..492528d238 100644 --- a/src/Mod/Path/PathScripts/PathPocketShape.py +++ b/src/Mod/Path/PathScripts/PathPocketShape.py @@ -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 diff --git a/src/Mod/Path/PathScripts/PathPocketShapeGui.py b/src/Mod/Path/PathScripts/PathPocketShapeGui.py index ff4b4ee96c..e883aaaf8a 100644 --- a/src/Mod/Path/PathScripts/PathPocketShapeGui.py +++ b/src/Mod/Path/PathScripts/PathPocketShapeGui.py @@ -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()