diff --git a/src/Mod/Path/PathScripts/PathSlot.py b/src/Mod/Path/PathScripts/PathSlot.py index 4795b9b1d1..113e46260c 100644 --- a/src/Mod/Path/PathScripts/PathSlot.py +++ b/src/Mod/Path/PathScripts/PathSlot.py @@ -41,9 +41,8 @@ import math from lazy_loader.lazy_loader import LazyLoader Part = LazyLoader('Part', globals(), 'Part') Arcs = LazyLoader('draftgeoutils.arcs', globals(), 'draftgeoutils.arcs') - if FreeCAD.GuiUp: - import FreeCADGui + FreeCADGui = LazyLoader('FreeCADGui', globals(), 'FreeCADGui') # Qt translation handling @@ -597,7 +596,7 @@ class ObjectSlot(PathOp.ObjectOp): return CMDS def _finishLine(self, obj, pnts, featureCnt): - """This method finishes an Line Slot operation.""" + """This method finishes a Line Slot operation.""" # Apply perpendicular rotation if requested perpZero = True if obj.PathOrientation == 'Perpendicular': @@ -608,11 +607,27 @@ class ObjectSlot(PathOp.ObjectOp): BE = self.bottomEdges[0] pnts = self._processSingleVertFace(obj, BE) perpZero = False + elif self.shapeType1 == 'Edge' and self.shapeType2 == 'Edge': + PathLog.debug('_finishLine() Perp, featureCnt == 2') if perpZero: (p1, p2) = pnts pnts = self._makePerpendicular(p1, p2, 10.0) # 10.0 offset below else: - perpZero = False + # Modify path points if user selected two parallel edges + if (featureCnt == 2 and self.shapeType1 == 'Edge' and + self.shapeType2 == 'Edge' and self._isParallel(self.dYdX1, self.dYdX2)): + (p1, p2) = pnts + edg1_len = self.shape1.Length + edg2_len = self.shape2.Length + set_length = max(edg1_len, edg2_len) + pnts = self._makePerpendicular(p1, p2, 10.0 + set_length) # 10.0 offset below + if edg1_len != edg2_len: + msg = obj.Label + ' ' + msg += translate('PathSlot', + 'Verify slot path start and end points.') + FreeCAD.Console.PrintWarning(msg + '\n') + else: + perpZero = False # Reverse direction of path if requested if obj.ReverseDirection: @@ -770,11 +785,16 @@ class ObjectSlot(PathOp.ObjectOp): # Sort tuples by edge angle eTups.sort(key=lambda tup: tup[2]) + # Identify parallel edges - pairs = list() + parallel_edge_pairs = list() + parallel_edge_flags = list() + flag = 1 eCnt = len(shape.Edges) lstE = eCnt - 1 - for i in range(0, eCnt): + for i in range(0, eCnt): # populate empty parrallel edge flag list + parallel_edge_flags.append(0) + for i in range(0, eCnt): # Cycle through edges to identify parallel pairs if i < lstE: ni = i + 1 A = eTups[i] @@ -789,19 +809,24 @@ class ObjectSlot(PathOp.ObjectOp): if eB.Curve.TypeId not in lineTypes: debug = eB.Curve.TypeId else: - pairs.append((eA, eB)) + parallel_edge_pairs.append((eA, eB)) + # set parallel flags for this pair of edges + parallel_edge_flags[A[0]] = flag + parallel_edge_flags[B[0]] = flag + flag += 1 if debug: msg = 'Erroneous Curve.TypeId: {}'.format(debug) PathLog.debug(msg) - pairCnt = len(pairs) + pairCnt = len(parallel_edge_pairs) if pairCnt > 1: - pairs.sort(key=lambda tup: tup[0].Length, reverse=True) + parallel_edge_pairs.sort(key=lambda tup: tup[0].Length, reverse=True) if self.isDebug: PathLog.debug(' -pairCnt: {}'.format(pairCnt)) - for (a, b) in pairs: + for (a, b) in parallel_edge_pairs: PathLog.debug(' -pair: {}, {}'.format(round(a.Length, 4), round(b.Length,4))) + PathLog.debug(' -parallel_edge_flags: {}'.format(parallel_edge_flags)) if pairCnt == 0: msg = translate('PathSlot', @@ -809,12 +834,24 @@ class ObjectSlot(PathOp.ObjectOp): FreeCAD.Console.PrintError(msg + '\n') return False elif pairCnt == 1: - same = pairs[0] + # One pair of parallel edges identified + if eCnt == 4: + flag_set = list() + for i in range(0, 4): + e = parallel_edge_flags[i] + if e == 0: + flag_set.append(shape.Edges[i]) + if len(flag_set) == 2: + same = (flag_set[0], flag_set[1]) + else: + same = parallel_edge_pairs[0] + else: + same = parallel_edge_pairs[0] else: if obj.Reference1 == 'Long Edge': - same = pairs[1] + same = parallel_edge_pairs[1] elif obj.Reference1 == 'Short Edge': - same = pairs[0] + same = parallel_edge_pairs[0] else: msg = 'Reference1 ' msg += translate('PathSlot', @@ -863,6 +900,12 @@ class ObjectSlot(PathOp.ObjectOp): b1 = v0.sub(perpVect) b2 = v1.sub(perpVect) (p1, p2) = self._getCutSidePoints(obj, v0, v1, a1, a2, b1, b2) + + msg = obj.Label + ' ' + msg += translate('PathSlot', + 'Verify slot path start and end points.') + FreeCAD.Console.PrintWarning(msg + '\n') + return (p1, p2) def _processSingleEdge(self, obj, edge): @@ -972,9 +1015,9 @@ class ObjectSlot(PathOp.ObjectOp): # Methods for processing double geometry def _processDouble(self, obj, shape_1, sub1, shape_2, sub2): - PathLog.debug('_processDouble()') """This is the control method for slots based on a two Base Geometry features.""" + PathLog.debug('_processDouble()') p1 = None p2 = None @@ -1006,12 +1049,13 @@ class ObjectSlot(PathOp.ObjectOp): # Parallel check for twin face, and face-edge cases if dYdX1 and dYdX2: + PathLog.debug('dYdX1, dYdX2: {}, {}'.format(dYdX1, dYdX2)) if not self._isParallel(dYdX1, dYdX2): - PathLog.debug('dYdX1, dYdX2: {}, {}'.format(dYdX1, dYdX2)) - msg = translate('PathSlot', - 'Selected geometry not parallel.') - FreeCAD.Console.PrintError(msg + '\n') - return False + if self.shapeType1 != 'Edge' or self.shapeType2 != 'Edge': + msg = translate('PathSlot', + 'Selected geometry not parallel.') + FreeCAD.Console.PrintError(msg + '\n') + return False if p2: return (p1, p2) @@ -1020,6 +1064,8 @@ class ObjectSlot(PathOp.ObjectOp): # Support methods def _dXdYdZ(self, E): + """_dXdYdZ(E) Calculates delta-X, delta-Y, and delta-Z between two vertexes + of edge passed in. Returns these three values as vector.""" v1 = E.Vertexes[0] v2 = E.Vertexes[1] dX = v2.X - v1.X @@ -1028,6 +1074,8 @@ class ObjectSlot(PathOp.ObjectOp): return FreeCAD.Vector(dX, dY, dZ) def _normalizeVector(self, v): + """_normalizeVector(v)... + Returns a copy of the vector recieved with values rounded to 10 decimal places.""" posTol = 0.0000000001 negTol = -1 * posTol V = FreeCAD.Vector(v.x, v.y, v.z) @@ -1060,6 +1108,7 @@ class ObjectSlot(PathOp.ObjectOp): return FreeCAD.Vector(x, y, z) def _getLowestPoint(self, shape_1): + """_getLowestPoint(shape)... Returns lowest vertex of shape as vector.""" # find lowest vertex vMin = shape_1.Vertexes[0] zmin = vMin.Z @@ -1080,6 +1129,7 @@ class ObjectSlot(PathOp.ObjectOp): return FreeCAD.Vector(V.X, V.Y, V.Z) def _getHighestPoint(self, shape_1): + """_getHighestPoint(shape)... Returns highest vertex of shape as vector.""" # find highest vertex vMax = shape_1.Vertexes[0] zmax = vMax.Z @@ -1100,9 +1150,15 @@ class ObjectSlot(PathOp.ObjectOp): return FreeCAD.Vector(V.X, V.Y, V.Z) def _processFeature(self, obj, shape, sub, pNum): + """_processFeature(obj, shape, sub, pNum)... + This function analyzes a shape and returns a three item tuple containing: + working point, + shape orientation/slope, + shape category as face, edge, or vert.""" p = None dYdX = None cat = sub[:4] + PathLog.debug('sub-feature is {}'.format(cat)) Ref = getattr(obj, 'Reference' + str(pNum)) if cat == 'Face': BE = self._getBottomEdge(shape) @@ -1162,6 +1218,10 @@ class ObjectSlot(PathOp.ObjectOp): return False def _extendArcSlot(self, p1, p2, cent, begExt, endExt): + """_extendArcSlot(p1, p2, cent, begExt, endExt)... + This function extends an arc defined by two end points, p1 and p2, and the center. + The arc is extended along the circumferance with begExt and endExt values. + The function returns the new end points as tuple (n1, n2) to replace p1 and p2.""" cancel = True n1 = p1 n2 = p2 @@ -1222,6 +1282,10 @@ class ObjectSlot(PathOp.ObjectOp): return (n1, n2) def _makeOffsetArc(self, p1, p2, center, newRadius): + """_makeOffsetArc(p1, p2, center, newRadius)... + This function offsets an arc defined by endpoints, p1 and p2, and the center. + New end points are returned at the radius passed by newRadius. + The angle of the original arc is maintained.""" n1 = p1.sub(center).normalize() n2 = p2.sub(center).normalize() n1.multiply(newRadius) @@ -1231,6 +1295,9 @@ class ObjectSlot(PathOp.ObjectOp): return (p1, p2) def _extendLineSlot(self, p1, p2, begExt, endExt): + """_extendLineSlot(p1, p2, begExt, endExt)... + This function extends a line defined by endpoints, p1 and p2. + The beginning is extended by begExt value and the end by endExt value.""" if begExt: beg = p1.sub(p2) beg.normalize() @@ -1248,7 +1315,8 @@ class ObjectSlot(PathOp.ObjectOp): return (n1, n2) def _getOppMidPoints(self, same): - # Find mid-points between ends of equal, oppossing edges + """_getOppMidPoints(same)... + Find mid-points between ends of equal, oppossing edges passed in tuple (edge1, edge2).""" com1 = same[0].CenterOfMass com2 = same[1].CenterOfMass p1 = FreeCAD.Vector(com1.x, com1.y, 0.0) @@ -1256,6 +1324,7 @@ class ObjectSlot(PathOp.ObjectOp): return (p1, p2) def _isParallel(self, dYdX1, dYdX2): + """Determine if two orientation vectors are parallel.""" if dYdX1.add(dYdX2).Length == 0: return True if ((dYdX1.x + dYdX2.x) / 2.0 == dYdX1.x and @@ -1264,6 +1333,9 @@ class ObjectSlot(PathOp.ObjectOp): return False def _makePerpendicular(self, p1, p2, length): + """_makePerpendicular(p1, p2, length)... + Using a line defined by p1 and p2, returns a perpendicular vector centered + at the midpoint of the line, with length value.""" line = Part.makeLine(p1, p2) midPnt = line.CenterOfMass