diff --git a/src/Mod/Path/PathScripts/PathPocketShape.py b/src/Mod/Path/PathScripts/PathPocketShape.py index b5587266a8..f32284ad12 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,73 @@ 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: + 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])] return None - return self.extendEdge(feature, edges[0], direction) + + 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 +227,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 +239,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.')) @@ -602,7 +617,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 fd5b518d0d..6a14c63d72 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