def test30(self): """Verify proper geometry for arcs with rising and fall ing Z-axis are created.""" #print("------ rising helix -------") p1 = Vector(0, 1, 0) p2 = Vector(1, 0, 2) self.assertCurve( PathGeom.edgeForCmd( Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 0, 'J': -1, 'K': 1}), p1), p1, Vector(1/math.sqrt(2), 1/math.sqrt(2), 1), p2) p1 = Vector(-1, 0, 0) p2 = Vector(0, -1, 2) self.assertCurve( PathGeom.edgeForCmd( Path.Command('G3', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 1, 'J': 0, 'K': 1}), p1), p1, Vector(-1/math.sqrt(2), -1/math.sqrt(2), 1), p2) #print("------ falling helix -------") p1 = Vector(0, -1, 2) p2 = Vector(-1, 0, 0) self.assertCurve( PathGeom.edgeForCmd( Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 0, 'J': 1, 'K': -1}), p1), p1, Vector(-1/math.sqrt(2), -1/math.sqrt(2), 1), p2) p1 = Vector(-1, 0, 2) p2 = Vector(0, -1, 0) self.assertCurve( PathGeom.edgeForCmd( Path.Command('G3', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 1, 'J': 0, 'K': -1}), p1), p1, Vector(-1/math.sqrt(2), -1/math.sqrt(2), 1), p2)
def commandsForEdges(self): if self.edges: try: shape = self.shell().common(self.tag.solid) commands = [] rapid = None for e, flip in self.orderAndFlipEdges(self.cleanupEdges(shape.Edges)): debugEdge(e, '++++++++ %s' % ('<' if flip else '>'), False) p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if self.tag.isSquare and (PathGeom.isRoughly(p1.z, self.maxZ) or p1.z > self.maxZ) and (PathGeom.isRoughly(p2.z, self.maxZ) or p2.z > self.maxZ): rapid = p1 if flip else p2 else: if rapid: commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z})) rapid = None commands.extend(PathGeom.cmdsForEdge(e, flip, False, self.segm)) if rapid: commands.append(Path.Command('G0', {'X': rapid.x, 'Y': rapid.y, 'Z': rapid.z})) rapid = None return commands except Exception as e: PathLog.error("Exception during processing tag @(%.2f, %.2f) (%s) - disabling the tag" % (self.tag.x, self.tag.y, e.args[0])) self.tag.enabled = False commands = [] for e in self.edges: commands.extend(PathGeom.cmdsForEdge(e)) return commands return []
def test65(self): """Verify splitEdgeAt.""" e = PathGeom.splitEdgeAt(Part.Edge(Part.LineSegment(Vector(), Vector(2, 4, 6))), Vector(1, 2, 3)) self.assertLine(e[0], Vector(), Vector(1,2,3)) self.assertLine(e[1], Vector(1,2,3), Vector(2,4,6)) # split an arc p1 = Vector(10,-10,1) p2 = Vector(0,0,1) p3 = Vector(10,10,1) arc = Part.Edge(Part.Arc(p1, p2, p3)) e = PathGeom.splitEdgeAt(arc, p2) o = 10*math.sin(math.pi/4) p12 = Vector(10 - o, -o, 1) p23 = Vector(10 - o, +o, 1) self.assertCurve(e[0], p1, p12, p2) self.assertCurve(e[1], p2, p23, p3) # split a helix p1 = Vector(10,-10,0) p2 = Vector(0,0,5) p3 = Vector(10,10,10) h = PathGeom.arcToHelix(arc, 0, 10) self.assertCurve(h, p1, p2, p3) e = PathGeom.splitEdgeAt(h, p2) o = 10*math.sin(math.pi/4) p12 = Vector(10 - o, -o, 2.5) p23 = Vector(10 - o, +o, 7.5) pf = e[0].valueAt((e[0].FirstParameter + e[0].LastParameter)/2) pl = e[1].valueAt((e[1].FirstParameter + e[1].LastParameter)/2) self.assertCurve(e[0], p1, p12, p2) self.assertCurve(e[1], p2, p23, p3)
def __init__(self, edge, tag, i, segm, maxZ): debugEdge(edge, 'MapWireToTag(%.2f, %.2f, %.2f)' % (i.x, i.y, i.z)) self.tag = tag self.segm = segm self.maxZ = maxZ if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), i): tail = edge self.commands = [] debugEdge(tail, '.........=') elif PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), i): debugEdge(edge, '++++++++ .') self.commands = PathGeom.cmdsForEdge(edge, segm=segm) tail = None else: e, tail = PathGeom.splitEdgeAt(edge, i) debugEdge(e, '++++++++ .') self.commands = PathGeom.cmdsForEdge(e, segm=segm) debugEdge(tail, '.........-') self.initialEdge = edge self.tail = tail self.edges = [] self.entry = i if tail: PathLog.debug("MapWireToTag(%s - %s)" % (i, tail.valueAt(tail.FirstParameter))) else: PathLog.debug("MapWireToTag(%s - )" % i) self.complete = False self.haveProblem = False
def horizontalEdgeLoop(obj, edge): '''horizontalEdgeLoop(obj, edge) ... returns a wire in the horizontal plane, if that is the only horizontal wire the given edge is a part of.''' h = edge.hashCode() wires = [w for w in obj.Shape.Wires if any(e.hashCode() == h for e in w.Edges)] loops = [w for w in wires if all(PathGeom.isHorizontal(e) for e in w.Edges) and PathGeom.isHorizontal(Part.Face(w))] if len(loops) == 1: return loops[0] return None
def isValidTagStartIntersection(self, edge, i): if PathGeom.pointsCoincide(i, edge.valueAt(edge.LastParameter)): return False p1 = edge.valueAt(edge.FirstParameter) p2 = edge.valueAt(edge.LastParameter) if PathGeom.pointsCoincide(PathGeom.xy(p1), PathGeom.xy(p2)): # if this vertical goes up, it can't be the start of a tag intersection if p1.z < p2.z: return False return True
def isRapid(self, edge): if type(edge.Curve) == Part.Line or type(edge.Curve) == Part.LineSegment: v0 = edge.Vertexes[0] v1 = edge.Vertexes[1] for r in self.rapid: r0 = r.Vertexes[0] r1 = r.Vertexes[1] if PathGeom.isRoughly(r0.X, v0.X) and PathGeom.isRoughly(r0.Y, v0.Y) and PathGeom.isRoughly(r0.Z, v0.Z) and PathGeom.isRoughly(r1.X, v1.X) and PathGeom.isRoughly(r1.Y, v1.Y) and PathGeom.isRoughly(r1.Z, v1.Z): return True return False
def generateHelix(self): edges = self.wire.Edges minZ = self.findMinZ(edges) outedges = [] i = 0 while i < len(edges): edge = edges[i] israpid = False for redge in self.rapids: if PathGeom.edgesMatch(edge, redge): israpid = True if not israpid: bb = edge.BoundBox p0 = edge.Vertexes[0].Point p1 = edge.Vertexes[1].Point if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z: # plungelen = abs(p0.z-p1.z) PathLog.debug("Found plunge move at X:{} Y:{} From Z:{} to Z{}, Searching for closed loop".format(p0.x, p0.y, p0.z, p1.z)) # next need to determine how many edges in the path after plunge are needed to cover the length: loopFound = False rampedges = [] j = i + 1 while not loopFound: candidate = edges[j] cp0 = candidate.Vertexes[0].Point cp1 = candidate.Vertexes[1].Point if PathGeom.pointsCoincide(p1, cp1): # found closed loop loopFound = True rampedges.append(candidate) break if abs(cp0.z - cp1.z) > 1e-6: # this edge is not parallel to XY plane, not qualified for ramping. break # PathLog.debug("Next edge length {}".format(candidate.Length)) rampedges.append(candidate) j = j + 1 if j >= len(edges): break if len(rampedges) == 0 or not loopFound: PathLog.debug("No suitable helix found") outedges.append(edge) else: outedges.extend(self.createHelix(rampedges, p0, p1)) if not PathGeom.isRoughly(p1.z, minZ): # the edges covered by the helix not handled again, # unless reached the bottom height i = j else: outedges.append(edge) else: outedges.append(edge) i = i + 1 return outedges
def updateDepths(self, obj, ignoreErrors=False): '''updateDepths(obj) ... base implementation calculating depths depending on base geometry. Should not be overwritten.''' def faceZmin(bb, fbb): if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face return fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut return fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall return fbb.ZMin elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf return fbb.ZMin return bb.ZMin if not self._setBaseAndStock(obj, ignoreErrors): return False stockBB = self.stock.Shape.BoundBox zmin = stockBB.ZMin zmax = stockBB.ZMax if hasattr(obj, 'Base') and obj.Base: for base, sublist in obj.Base: bb = base.Shape.BoundBox zmax = max(zmax, bb.ZMax) for sub in sublist: fbb = base.Shape.getElement(sub).BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) else: # clearing with stock boundaries job = PathUtils.findParentJob(obj) zmax = stockBB.ZMax zmin = job.Base.Shape.BoundBox.ZMax if FeatureDepths & self.opFeatures(obj): # first set update final depth, it's value is not negotiable if not PathGeom.isRoughly(obj.OpFinalDepth.Value, zmin): obj.OpFinalDepth = zmin zmin = obj.OpFinalDepth.Value def minZmax(z): if hasattr(obj, 'StepDown') and not PathGeom.isRoughly(obj.StepDown.Value, 0): return z + obj.StepDown.Value else: return z + 1 # ensure zmax is higher than zmin if (zmax - 0.0001) <= zmin: zmax = minZmax(zmin) # update start depth if requested and required if not PathGeom.isRoughly(obj.OpStartDepth.Value, zmax): obj.OpStartDepth = zmax
def isEntryOrExitStrut(self, e): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if PathGeom.pointsCoincide(p1, self.entry) and p2.z >= self.entry.z: return 1 if PathGeom.pointsCoincide(p2, self.entry) and p1.z >= self.entry.z: return 1 if PathGeom.pointsCoincide(p1, self.exit) and p2.z >= self.exit.z: return 2 if PathGeom.pointsCoincide(p2, self.exit) and p1.z >= self.exit.z: return 2 return 0
def test20(self): """Verify proper geometry for arcs in the XY-plane are created.""" p1 = Vector(0, -1, 2) p2 = Vector(-1, 0, 2) self.assertArc( PathGeom.edgeForCmd( Path.Command('G2', {'X': p2.x, 'Y': p2.y, 'Z': p2.z, 'I': 0, 'J': 1, 'K': 0}), p1), p1, p2, 'CW') self.assertArc( PathGeom.edgeForCmd( Path.Command('G3', {'X': p1.x, 'Y': p1.y, 'z': p1.z, 'I': -1, 'J': 0, 'K': 0}), p2), p2, p1, 'CCW')
def isPointOnEdge(self, pt, edge): param = edge.Curve.parameter(pt) if edge.FirstParameter <= param <= edge.LastParameter: return True if edge.LastParameter <= param <= edge.FirstParameter: return True if PathGeom.isRoughly(edge.FirstParameter, param) or PathGeom.isRoughly(edge.LastParameter, param): return True # print("-------- X %.2f <= %.2f <=%.2f (%.2f, %.2f, %.2f) %.2f:%.2f" % (edge.FirstParameter, param, edge.LastParameter, pt.x, pt.y, pt.z, edge.Curve.parameter(edge.valueAt(edge.FirstParameter)), edge.Curve.parameter(edge.valueAt(edge.LastParameter)))) # p1 = edge.Vertexes[0] # f1 = edge.Curve.parameter(FreeCAD.Vector(p1.X, p1.Y, p1.Z)) # p2 = edge.Vertexes[1] # f2 = edge.Curve.parameter(FreeCAD.Vector(p2.X, p2.Y, p2.Z)) return False
def orientSelected(self, axis): def flipSel(sel): PathLog.debug("flip") p = sel.Object.Placement loc = sel.Object.Placement.Base rot = FreeCAD.Rotation(FreeCAD.Vector(1-axis.x, 1-axis.y, 1-axis.z), 180) sel.Object.Placement = FreeCAD.Placement(loc, p.Rotation.multiply(rot)) def rotateSel(sel, n): p = sel.Object.Placement loc = sel.Object.Placement.Base r = axis.cross(n) # rotation axis a = DraftVecUtils.angle(n, axis, r) * 180 / math.pi PathLog.debug("oh boy: (%.2f, %.2f, %.2f) -> %.2f" % (r.x, r.y, r.z, a)) Draft.rotate(sel.Object, a, axis=r) selObject = None selFeature = None for sel in FreeCADGui.Selection.getSelectionEx(): selObject = sel.Object for feature in sel.SubElementNames: selFeature = feature sub = sel.Object.Shape.getElement(feature) if 'Face' == sub.ShapeType: n = sub.Surface.Axis if sub.Orientation == 'Reversed': n = FreeCAD.Vector() - n PathLog.debug("(%.2f, %.2f, %.2f) -> reversed (%s)" % (n.x, n.y, n.z, sub.Orientation)) else: PathLog.debug("(%.2f, %.2f, %.2f) -> forward (%s)" % (n.x, n.y, n.z, sub.Orientation)) if PathGeom.pointsCoincide(axis, n): PathLog.debug("face properly oriented (%.2f, %.2f, %.2f)" % (n.x, n.y, n.z)) else: if PathGeom.pointsCoincide(axis, FreeCAD.Vector() - n): flipSel(sel) else: rotateSel(sel, n) if 'Edge' == sub.ShapeType: n = (sub.Vertexes[1].Point - sub.Vertexes[0].Point).normalize() if PathGeom.pointsCoincide(axis, n) or PathGeom.pointsCoincide(axis, FreeCAD.Vector() - n): # Don't really know the orientation of an edge, so let's just flip the object # and if the user doesn't like it they can flip again flipSel(sel) else: rotateSel(sel, n) if selObject and selFeature: FreeCADGui.Selection.clearSelection() FreeCADGui.Selection.addSelection(selObject, selFeature)
def checkIgnoreAbove(self, edge): if self.ignoreAboveEnabled: p0 = edge.Vertexes[0].Point p1 = edge.Vertexes[1].Point if p0.z > self.ignoreAbove and (p1.z > self.ignoreAbove or PathGeom.isRoughly(p1.z, self.ignoreAbove.Value)): PathLog.debug("Whole plunge move above 'ignoreAbove', ignoring") return (edge, True) elif p0.z > self.ignoreAbove and not PathGeom.isRoughly(p0.z, self.ignoreAbove.Value): PathLog.debug("Plunge move partially above 'ignoreAbove', splitting into two") newPoint = FreeCAD.Base.Vector(p0.x, p0.y, self.ignoreAbove) return (Part.makeLine(p0, newPoint), False) else: return None, False else: return None, False
def __init__(self, obj): PathLog.track(obj.Base.Name) self.obj = obj self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) self.rapid = _RapidEdges(rapid) self.edges = self.wire.Edges self.baseWire = self.findBottomWire(self.edges)
def execute(self, obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): return if not obj.Base.Path: return if obj.Angle >= 90: obj.Angle = 89.9 elif obj.Angle <= 0: obj.Angle = 0.1 if hasattr(obj, 'UseStartDepth'): self.ignoreAboveEnabled = obj.UseStartDepth self.ignoreAbove = obj.DressupStartDepth else: self.ignoreAboveEnabled = False self.ignoreAbove = 0 self.angle = obj.Angle self.method = obj.Method self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) if self.method in ['RampMethod1', 'RampMethod2', 'RampMethod3']: self.outedges = self.generateRamps() else: self.outedges = self.generateHelix() obj.Path = self.createCommands(obj, self.outedges)
def onDocumentRestored(self, obj): features = self.opFeatures(obj) if FeatureBaseGeometry & features and 'App::PropertyLinkSubList' == obj.getTypeIdOfProperty('Base'): PathLog.info("Replacing link property with global link (%s)." % obj.State) base = obj.Base obj.removeProperty('Base') self.addBaseProperty(obj) obj.Base = base obj.touch() obj.Document.recompute() if FeatureTool & features and not hasattr(obj, 'OpToolDiameter'): self.addOpValues(obj, ['tooldia']) if FeatureStepDown & features and not hasattr(obj, 'OpStartDepth'): if PathGeom.isRoughly(obj.StepDown.Value, 1): obj.setExpression('StepDown', 'OpToolDiameter') if FeatureDepths & features and not hasattr(obj, 'OpStartDepth'): self.addOpValues(obj, ['start', 'final']) if not hasattr(obj, 'StartDepthLock') or not obj.StartDepthLock: obj.setExpression('StartDepth', 'OpStartDepth') if FeatureNoFinalDepth & features: obj.setEditorMode('OpFinalDepth', 2) elif not hasattr(obj, 'FinalDepthLock') or not obj.FinalDepthLock: obj.setExpression('FinalDepth', 'OpFinalDepth')
def createCommands(self, obj, edges): commands = [] for edge in edges: israpid = False for redge in self.rapids: if PathGeom.edgesMatch(edge, redge): israpid = True if israpid: v = edge.valueAt(edge.LastParameter) commands.append(Path.Command('G0', {'X': v.x, 'Y': v.y, 'Z': v.z})) else: commands.extend(PathGeom.cmdsForEdge(edge)) lastCmd = Path.Command('G0', {'X': 0.0, 'Y': 0.0, 'Z': 0.0}) outCommands = [] horizFeed = obj.ToolController.HorizFeed.Value vertFeed = obj.ToolController.VertFeed.Value horizRapid = obj.ToolController.HorizRapid.Value vertRapid = obj.ToolController.VertRapid.Value for cmd in commands: params = cmd.Parameters zVal = params.get('Z', None) zVal2 = lastCmd.Parameters.get('Z', None) zVal = zVal and round(zVal, 8) zVal2 = zVal2 and round(zVal2, 8) if cmd.Name in ['G1', 'G2', 'G3', 'G01', 'G02', 'G03']: if zVal is not None and zVal2 != zVal: params['F'] = vertFeed else: params['F'] = horizFeed lastCmd = cmd elif cmd.Name in ['G0', 'G00']: if zVal is not None and zVal2 != zVal: params['F'] = vertRapid else: params['F'] = horizRapid lastCmd = cmd outCommands.append(Path.Command(cmd.Name, params)) return Path.Path(outCommands)
def buildpathocc(self, obj, wires, zValues): '''buildpathocc(obj, wires, zValues) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) for wire in wires: offset = wire # reorder the wire offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) last = None for z in zValues: if last: self.commandlist.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed})) for edge in offset.Edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point if len(offset.Edges) > 1: e2 = offset.Edges[1] if not PathGeom.pointsCoincide(edge.Vertexes[-1].Point, e2.Vertexes[0].Point) and not PathGeom.pointsCoincide(edge.Vertexes[-1].Point, e2.Vertexes[-1].Point): PathLog.debug("flip first edge") last = edge.Vertexes[-1].Point else: PathLog.debug("original first edge") else: PathLog.debug("not enough edges to flip") self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': obj.ClearanceHeight.Value, 'F': self.horizRapid})) self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': obj.SafeHeight.Value, 'F': self.vertRapid})) self.commandlist.append(Path.Command('G0', {'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed})) if PathGeom.pointsCoincide(last, edge.Vertexes[0].Point): for cmd in PathGeom.cmdsForEdge(edge): params = cmd.Parameters params.update({'Z': z, 'F': self.horizFeed}) self.commandlist.append(Path.Command(cmd.Name, params)) last = edge.Vertexes[-1].Point else: for cmd in PathGeom.cmdsForEdge(edge, True): params = cmd.Parameters params.update({'Z': z, 'F': self.horizFeed}) self.commandlist.append(Path.Command(cmd.Name, params)) last = edge.Vertexes[0].Point self.commandlist.append(Path.Command('G0', {'Z': obj.ClearanceHeight.Value, 'F': self.vertRapid})) if self.commandlist: self.commandlist.pop()
def cloneAt(self, pos): clone = self.solid.copy() pos.z = 0 angle = -PathGeom.getAngle(pos - self.baseCenter) * 180 / math.pi clone.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), angle) pos.z = self.z - self.actualHeight * 0.01 clone.translate(pos) return clone
def test00(self): """Verify getAngle functionality.""" self.assertRoughly(PathGeom.getAngle(Vector(1, 0, 0)), 0) self.assertRoughly(PathGeom.getAngle(Vector(1, 1, 0)), math.pi/4) self.assertRoughly(PathGeom.getAngle(Vector(0, 1, 0)), math.pi/2) self.assertRoughly(PathGeom.getAngle(Vector(-1, 1, 0)), 3*math.pi/4) self.assertRoughly(PathGeom.getAngle(Vector(-1, 0, 0)), math.pi) self.assertRoughly(PathGeom.getAngle(Vector(-1, -1, 0)), -3*math.pi/4) self.assertRoughly(PathGeom.getAngle(Vector(0, -1, 0)), -math.pi/2) self.assertRoughly(PathGeom.getAngle(Vector(1, -1, 0)), -math.pi/4)
def pointIsOnPath(self, p): v = Part.Vertex(self.pointAtBottom(p)) PathLog.debug("pt = (%f, %f, %f)" % (v.X, v.Y, v.Z)) for e in self.bottomEdges: indent = "{} ".format(e.distToShape(v)[0]) debugEdge(e, indent, True) if PathGeom.isRoughly(0.0, v.distToShape(e)[0], 0.1): return True return False
def orderAndFlipEdges(self, edges): PathLog.track("entry(%.2f, %.2f, %.2f), exit(%.2f, %.2f, %.2f)" % (self.entry.x, self.entry.y, self.entry.z, self.exit.x, self.exit.y, self.exit.z)) self.edgesOrder = [] outputEdges = [] p0 = self.entry lastP = p0 while edges: # print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in edges: p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) if PathGeom.pointsCoincide(p1, p0): outputEdges.append((e, False)) edges.remove(e) lastP = None p0 = p2 debugEdge(e, ">>>>> no flip") break elif PathGeom.pointsCoincide(p2, p0): outputEdges.append((e, True)) edges.remove(e) lastP = None p0 = p1 debugEdge(e, ">>>>> flip") break else: debugEdge(e, "<<<<< (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z)) if lastP == p0: self.edgesOrder.append(outputEdges) self.edgesOrder.append(edges) print('ordered edges:') for e, flip in outputEdges: debugEdge(e, ' %c ' % ('<' if flip else '>'), False) print('remaining edges:') for e in edges: debugEdge(e, ' ', False) raise ValueError("No connection to %s" % (p0)) elif lastP: PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) (%.2f, %.2f, %.2f)" % (p0.x, p0.y, p0.z, lastP.x, lastP.y, lastP.z)) else: PathLog.debug("xxxxxx (%.2f, %.2f, %.2f) -" % (p0.x, p0.y, p0.z)) lastP = p0 PathLog.track("-") return outputEdges
def filterIntersections(self, pts, face): if type(face.Surface) == Part.Cone or type(face.Surface) == Part.Cylinder or type(face.Surface) == Part.Toroid: PathLog.track("it's a cone/cylinder, checking z") return filter(lambda pt: pt.z >= self.bottom() and pt.z <= self.top(), pts) if type(face.Surface) == Part.Plane: PathLog.track("it's a plane, checking R") c = face.Edges[0].Curve if (type(c) == Part.Circle): return filter(lambda pt: (pt - c.Center).Length <= c.Radius or PathGeom.isRoughly((pt - c.Center).Length, c.Radius), pts) print("==== we got a %s" % face.Surface)
def selectionZLevel(self, sel): if len(sel) == 1 and len(sel[0].SubObjects) == 1: sub = sel[0].SubObjects[0] if PathGeom.isHorizontal(sub): if 'Vertex' == sub.ShapeType: return sub.Z if 'Edge' == sub.ShapeType: return sub.Vertexes[0].Z if 'Face' == sub.ShapeType: return sub.BoundBox.ZMax return None
def tagAtPoint(self, point, matchZ): x = point[0] y = point[1] z = point[2] if self.tags and not matchZ: z = self.tags[0].point.z p = FreeCAD.Vector(x, y, z) for i, tag in enumerate(self.tags): if PathGeom.pointsCoincide(p, tag.point, tag.sphere.radius.getValue() * 1.3): return i return -1
def shell(self): if len(self.edges) > 1: wire = Part.Wire(self.initialEdge) else: edge = self.edges[0] if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): wire = Part.Wire(self.finalEdge) elif hasattr(self, 'initialEdge') and PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.initialEdge.valueAt(self.initialEdge.FirstParameter)): wire = Part.Wire(self.initialEdge) else: wire = Part.Wire(edge) for edge in self.edges[1:]: if PathGeom.pointsCoincide(edge.valueAt(edge.FirstParameter), self.finalEdge.valueAt(self.finalEdge.FirstParameter)): wire.add(self.finalEdge) else: wire.add(edge) shell = wire.extrude(FreeCAD.Vector(0, 0, self.tag.height + 1)) return shell.removeShape(filter(lambda f: PathGeom.isRoughly(f.Area, 0), shell.Faces))
def execute(self, obj): if not obj.Base: return if not obj.Base.isDerivedFrom("Path::Feature"): return if not obj.Base.Path: return if obj.Length < 0: PathLog.error(translate("Length/Radius positiv not Null\n")) obj.Length = 0.1 self.wire, self.rapids = PathGeom.wireForPath(obj.Base.Path) obj.Path = self.generateLeadInOutCurve(obj)
def findBottomWire(self, edges): (minZ, maxZ) = self.findZLimits(edges) self.minZ = minZ self.maxZ = maxZ bottom = [e for e in edges if PathGeom.isRoughly(e.Vertexes[0].Point.z, minZ) and PathGeom.isRoughly(e.Vertexes[1].Point.z, minZ)] self.bottomEdges = bottom try: wire = Part.Wire(bottom) if wire.isClosed(): return wire except: return None
def test60(self): """Verify arcToHelix returns proper helix.""" p1 = Vector(10,-10,0) p2 = Vector(0,0,0) p3 = Vector(10,10,0) e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 2) self.assertCurve(e, p1, p2 + Vector(0,0,1), p3 + Vector(0,0,2)) e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 3, 7) self.assertCurve(e, p1 + Vector(0,0,3), p2 + Vector(0,0,5), p3 + Vector(0,0,7)) e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 9, 1) self.assertCurve(e, p1 + Vector(0,0,9), p2 + Vector(0,0,5), p3 + Vector(0,0,1)) dz = Vector(0,0,3) p11 = p1 + dz p12 = p2 + dz p13 = p3 + dz e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 0, 8) self.assertCurve(e, p1, p2 + Vector(0,0,4), p3 + Vector(0,0,8)) e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p11, p12, p13)), 2, -2) self.assertCurve(e, p1 + Vector(0,0,2), p2, p3 + Vector(0,0,-2)) o = 10*math.sin(math.pi/4) p1 = Vector(10, -10, 1) p2 = Vector(10 - 10*math.sin(math.pi/4), -10*math.cos(math.pi/4), 1) p3 = Vector(0, 0, 1) e = PathGeom.arcToHelix(Part.Edge(Part.Arc(p1, p2, p3)), 0, 5) self.assertCurve(e, Vector(10,-10,0), Vector(p2.x,p2.y,2.5), Vector(0, 0, 5))
def makeAreaCurve(edges, direction, startpt=None, endpt=None): curveobj = area.Curve() cleanededges = Part.__sortEdges__(PathUtils.cleanedges(edges, 0.01)) # for e in cleanededges: # print str(e.valueAt(e.FirstParameter)) + "," + # str(e.valueAt(e.LastParameter)) edgelist = [] if len(cleanededges) == 1: # user selected a single edge. edgelist = cleanededges else: # edgelist = [] #Multiple edges. Need to sequence the vetexes. # First get the first segment oriented correctly. # We first compare the last parameter of the first segment to see if it # matches either end of the second segment. If not, it must need # flipping. p0L = cleanededges[0].valueAt(cleanededges[0].LastParameter) if PathGeom.pointsCoincide( p0L, cleanededges[1].valueAt(cleanededges[1].FirstParameter) ) or PathGeom.pointsCoincide( p0L, cleanededges[1].valueAt(cleanededges[1].LastParameter)): edge0 = cleanededges[0] else: edge0 = PathUtils.reverseEdge(cleanededges[0]) edgelist.append(edge0) # Now iterate the rest of the edges matching the last parameter of the # previous segment. for edge in cleanededges[1:]: if PathGeom.pointsCoincide( edge.valueAt(edge.FirstParameter), edgelist[-1].valueAt(edgelist[-1].LastParameter)): nextedge = edge else: nextedge = PathUtils.reverseEdge(edge) edgelist.append(nextedge) # print "makeareacurve 87: " + "area.Point(" + # str(edgelist[0].Vertexes[0].X) + ", " + # str(edgelist[0].Vertexes[0].Y)+")" curveobj.append( area.Point(edgelist[0].Vertexes[0].X, edgelist[0].Vertexes[0].Y)) # seglist =[] # if direction=='CW': # edgelist.reverse() # for e in edgelist: # seglist.append(PathUtils.reverseEdge(e)) #swap end points on every segment # else: # for e in edgelist: # seglist.append(e) for s in edgelist: curveobj.append(makeAreaVertex(s)) if startpt: # future nearest point code yet to be worked out -fixme # v1 = Vector(startpt.X,startpt.Y,startpt.Z) # perppoint1 = DraftGeomUtils.findPerpendicular(v1,firstedge) # perppoint1 = DraftGeomUtils.findDistance(v1,firstedge) # if perppoint1: # curveobj.ChangeStart(area.Point(perppoint1[0].x,perppoint1[0].y)) # else: # curveobj.ChangeStart(area.Point(startpt.X,startpt.Y)) curveobj.ChangeStart(area.Point(startpt.x, startpt.y)) if endpt: # future nearest point code yet to be worked out -fixme # v2 = Vector(endpt.X,endpt.Y,endpt.Z) # perppoint2 = DraftGeomUtils.findPerpendicular(v2,lastedge) # if perppoint2: # curveobj.ChangeEnd(area.Point(perppoint2[0].x,perppoint2[0].y)) # else: # curveobj.ChangeEnd(area.Point(endpt.X,endpt.Y)) curveobj.ChangeEnd(area.Point(endpt.x, endpt.y)) if curveobj.IsClockwise() and direction == 'CCW': curveobj.Reverse() elif not curveobj.IsClockwise() and direction == 'CW': curveobj.Reverse() return curveobj
def isStrut(self, edge): p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) return PathGeom.pointsCoincide(p1, p2)
def isAPlungeMove(self): return not PathGeom.isRoughly(self.End.z, self.Start.z)
def test02(self): """Verify isVertical/isHorizontal for Vector""" self.assertTrue(PathGeom.isVertical(Vector(0, 0, 1))) self.assertTrue(PathGeom.isVertical(Vector(0, 0, -1))) self.assertFalse(PathGeom.isVertical(Vector(1, 0, 1))) self.assertFalse(PathGeom.isVertical(Vector(1, 0, -1))) self.assertTrue(PathGeom.isHorizontal(Vector(1, 0, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(-1, 0, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(0, 1, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(0, -1, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(1, 1, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(-1, 1, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(1, -1, 0))) self.assertTrue(PathGeom.isHorizontal(Vector(-1, -1, 0))) self.assertFalse(PathGeom.isHorizontal(Vector(0, 1, 1))) self.assertFalse(PathGeom.isHorizontal(Vector(0, -1, 1))) self.assertFalse(PathGeom.isHorizontal(Vector(0, 1, -1))) self.assertFalse(PathGeom.isHorizontal(Vector(0, -1, -1)))
def createPath(self, obj, pathData, tags): PathLog.track() commands = [] lastEdge = 0 lastTag = 0 # sameTag = None t = 0 # inters = None edge = None segm = 50 if hasattr(obj, 'SegmentationFactor'): segm = obj.SegmentationFactor if segm <= 0: segm = 50 obj.SegmentationFactor = 50 self.mappers = [] mapper = None while edge or lastEdge < len(pathData.edges): PathLog.debug("------- lastEdge = %d/%d.%d/%d" % (lastEdge, lastTag, t, len(tags))) if not edge: edge = pathData.edges[lastEdge] debugEdge( edge, "======= new edge: %d/%d" % (lastEdge, len(pathData.edges))) lastEdge += 1 # sameTag = None if mapper: mapper.add(edge) if mapper.mappingComplete(): commands.extend(mapper.commands) edge = mapper.tail mapper = None else: edge = None if edge: tIndex = (t + lastTag) % len(tags) t += 1 i = tags[tIndex].intersects(edge, edge.FirstParameter) if i and self.isValidTagStartIntersection(edge, i): mapper = MapWireToTag(edge, tags[tIndex], i, segm, pathData.maxZ) self.mappers.append(mapper) edge = mapper.tail if not mapper and t >= len(tags): # gone through all tags, consume edge and move on if edge: debugEdge(edge, '++++++++') if pathData.rapid.isRapid(edge): v = edge.Vertexes[1] if not commands and PathGeom.isRoughly( 0, v.X) and PathGeom.isRoughly( 0, v.Y) and not PathGeom.isRoughly(0, v.Z): # The very first move is just to move to ClearanceHeight commands.append(Path.Command('G0', {'Z': v.Z})) else: commands.append( Path.Command('G0', { 'X': v.X, 'Y': v.Y, 'Z': v.Z })) else: commands.extend(PathGeom.cmdsForEdge(edge, segm=segm)) edge = None t = 0 lastCmd = Path.Command('G0', {'X': 0.0, 'Y': 0.0, 'Z': 0.0}) outCommands = [] tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value horizRapid = tc.HorizRapid.Value vertRapid = tc.VertRapid.Value for cmd in commands: params = cmd.Parameters zVal = params.get('Z', None) zVal2 = lastCmd.Parameters.get('Z', None) zVal = zVal and round(zVal, 8) zVal2 = zVal2 and round(zVal2, 8) if cmd.Name in ['G1', 'G2', 'G3', 'G01', 'G02', 'G03']: if False and zVal is not None and zVal2 != zVal: params['F'] = vertFeed else: params['F'] = horizFeed lastCmd = cmd elif cmd.Name in ['G0', 'G00']: if zVal is not None and zVal2 != zVal: params['F'] = vertRapid else: params['F'] = horizRapid lastCmd = cmd outCommands.append(Path.Command(cmd.Name, params)) return Path.Path(outCommands)
def tboneHorizontal(self, bone): angle = bone.angle() boneAngle = 0 if PathGeom.isRoughly(angle, math.pi) or math.fabs(angle) > math.pi/2: boneAngle = -math.pi return self.inOutBoneCommands(bone, boneAngle, self.toolRadius)
def test04(self): """Verify isVertical/isHorizontal for faces""" # planes xPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(1, 0, 0)) yPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(0, 1, 0)) zPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(0, 0, 1)) xyPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(1, 1, 0)) xzPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(1, 0, 1)) yzPlane = Part.makePlane(100, 100, FreeCAD.Vector(), FreeCAD.Vector(0, 1, 1)) self.assertTrue(PathGeom.isVertical(xPlane)) self.assertTrue(PathGeom.isVertical(yPlane)) self.assertFalse(PathGeom.isVertical(zPlane)) self.assertTrue(PathGeom.isVertical(xyPlane)) self.assertFalse(PathGeom.isVertical(xzPlane)) self.assertFalse(PathGeom.isVertical(yzPlane)) self.assertFalse(PathGeom.isHorizontal(xPlane)) self.assertFalse(PathGeom.isHorizontal(yPlane)) self.assertTrue(PathGeom.isHorizontal(zPlane)) self.assertFalse(PathGeom.isHorizontal(xyPlane)) self.assertFalse(PathGeom.isHorizontal(xzPlane)) self.assertFalse(PathGeom.isHorizontal(yzPlane)) # cylinders xCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(1, 0, 0)).Faces if type(f.Surface) == Part.Cylinder ][0] yCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(0, 1, 0)).Faces if type(f.Surface) == Part.Cylinder ][0] zCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(0, 0, 1)).Faces if type(f.Surface) == Part.Cylinder ][0] xyCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(1, 1, 0)).Faces if type(f.Surface) == Part.Cylinder ][0] xzCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(1, 0, 1)).Faces if type(f.Surface) == Part.Cylinder ][0] yzCylinder = [ f for f in Part.makeCylinder(1, 1, FreeCAD.Vector(), FreeCAD.Vector(0, 1, 1)).Faces if type(f.Surface) == Part.Cylinder ][0] self.assertTrue(PathGeom.isHorizontal(xCylinder)) self.assertTrue(PathGeom.isHorizontal(yCylinder)) self.assertFalse(PathGeom.isHorizontal(zCylinder)) self.assertTrue(PathGeom.isHorizontal(xyCylinder)) self.assertFalse(PathGeom.isHorizontal(xzCylinder)) self.assertFalse(PathGeom.isHorizontal(yzCylinder))
def createRampMethod2(self, rampedges, p0, projectionlen, rampangle): """ This method generates ramp with following pattern: 1. Start from the original startpoint of the plunge 2. Calculate the distance on the path which is needed to implement the ramp and travel that distance while maintaining start depth 3. Start ramping while travelling the original path backwards until reaching the original plunge end point 4. Continue with the original path """ outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge if PathGeom.pointsCoincide(PathGeom.xy(p0), PathGeom.xy(rampedges[-1].valueAt(rampedges[-1].LastParameter))): PathLog.debug("The ramp forms a closed wire, needless to move on original Z height") else: for i, redge in enumerate(rampedges): if redge.Length >= rampremaining: # this edge needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) PathLog.debug("Got split edges with lengths: {}, {}".format(splitEdge[0].Length, splitEdge[1].Length)) # ramp starts at the last point of first edge p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter) p1.z = p0.z outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1)) # now we have reached the beginning of the ramp. # start that by going to the beginning of this splitEdge deltaZ = splitEdge[0].Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(splitEdge[0].valueAt(splitEdge[0].FirstParameter).x, splitEdge[0].valueAt(splitEdge[0].FirstParameter).y, p1.z - deltaZ) outedges.append(self.createRampEdge(splitEdge[0], p1, newPoint)) curPoint = newPoint elif i == len(rampedges) - 1: # last ramp element but still did not reach the full length? # Probably a rounding issue on floats. # Lets start the ramp anyway p1 = redge.valueAt(redge.LastParameter) p1.z = p0.z outedges.append(self.createRampEdge(redge, curPoint, p1)) # and go back that edge deltaZ = redge.Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.FirstParameter).x, redge.valueAt(redge.FirstParameter).y, p1.z - deltaZ) outedges.append(self.createRampEdge(redge, p1, newPoint)) curPoint = newPoint else: # we are travelling on start depth newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.LastParameter).x, redge.valueAt(redge.LastParameter).y, p0.z) outedges.append(self.createRampEdge(redge, curPoint, newPoint)) curPoint = newPoint rampremaining = rampremaining - redge.Length # the last edge got handled previously rampedges.pop() # ramp backwards to the plunge position for i, redge in enumerate(reversed(rampedges)): deltaZ = redge.Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.FirstParameter).x, redge.valueAt(redge.FirstParameter).y, curPoint.z - deltaZ) if i == len(rampedges) - 1: # make sure that the last point of the ramps ends to the original position newPoint = redge.valueAt(redge.FirstParameter) outedges.append(self.createRampEdge(redge, curPoint, newPoint)) curPoint = newPoint return outedges
def createRampMethod3(self, rampedges, p0, projectionlen, rampangle): """ This method generates ramp with following pattern: 1. Start from the original startpoint of the plunge 2. Ramp down along the path that comes after the plunge until traveled half of the Z distance 3. Change direction and ramp backwards to the origianal plunge end point 4. Continue with the original path This method causes unecessarily many moves with tool down """ outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge done = False while not done: for i, redge in enumerate(rampedges): if redge.Length >= rampremaining: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) PathLog.debug("Got split edge (index: {}) with lengths: {}, {}".format(i, splitEdge[0].Length, splitEdge[1].Length)) # ramp ends to the last point of first edge p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter) deltaZ = splitEdge[0].Length / math.tan(math.radians(rampangle)) p1.z = curPoint.z - deltaZ outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1)) curPoint.z = p1.z - deltaZ # now we have reached the end of the ramp. Reverse direction of ramp # start that by going back to the beginning of this splitEdge outedges.append(self.createRampEdge(splitEdge[0], p1, curPoint)) done = True break elif i == len(rampedges) - 1: # last ramp element but still did not reach the full length? # Probably a rounding issue on floats. p1 = redge.valueAt(redge.LastParameter) deltaZ = redge.Length / math.tan(math.radians(rampangle)) p1.z = curPoint.z - deltaZ outedges.append(self.createRampEdge(redge, curPoint, p1)) # and go back that edge newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.FirstParameter).x, redge.valueAt(redge.FirstParameter).y, p1.z - deltaZ) outedges.append(self.createRampEdge(redge, p1, newPoint)) curPoint = newPoint done = True else: deltaZ = redge.Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.LastParameter).x, redge.valueAt(redge.LastParameter).y, curPoint.z - deltaZ) outedges.append(self.createRampEdge(redge, curPoint, newPoint)) curPoint = newPoint rampremaining = rampremaining - redge.Length returnedges = self.getreversed(rampedges[:i]) # ramp backwards to the plunge position for i, redge in enumerate(returnedges): deltaZ = redge.Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.LastParameter).x, redge.valueAt(redge.LastParameter).y, curPoint.z - deltaZ) if i == len(rampedges) - 1: # make sure that the last point of the ramps ends to the original position newPoint = redge.valueAt(redge.LastParameter) outedges.append(self.createRampEdge(redge, curPoint, newPoint)) curPoint = newPoint return outedges
def test01(self): """Verify diffAngle functionality.""" self.assertRoughly(PathGeom.diffAngle(0, +0*math.pi/4, 'CW') / math.pi, 0/4.) self.assertRoughly(PathGeom.diffAngle(0, +3*math.pi/4, 'CW') / math.pi, 5/4.) self.assertRoughly(PathGeom.diffAngle(0, -3*math.pi/4, 'CW') / math.pi, 3/4.) self.assertRoughly(PathGeom.diffAngle(0, +4*math.pi/4, 'CW') / math.pi, 4/4.) self.assertRoughly(PathGeom.diffAngle(0, +0*math.pi/4, 'CCW')/ math.pi, 0/4.) self.assertRoughly(PathGeom.diffAngle(0, +3*math.pi/4, 'CCW')/ math.pi, 3/4.) self.assertRoughly(PathGeom.diffAngle(0, -3*math.pi/4, 'CCW')/ math.pi, 5/4.) self.assertRoughly(PathGeom.diffAngle(0, +4*math.pi/4, 'CCW')/ math.pi, 4/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, +0*math.pi/4, 'CW') / math.pi, 1/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, +3*math.pi/4, 'CW') / math.pi, 6/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, -1*math.pi/4, 'CW') / math.pi, 2/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, +0*math.pi/4, 'CW') / math.pi, 7/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, +3*math.pi/4, 'CW') / math.pi, 4/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, -1*math.pi/4, 'CW') / math.pi, 0/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, +0*math.pi/4, 'CCW') / math.pi, 7/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, +3*math.pi/4, 'CCW') / math.pi, 2/4.) self.assertRoughly(PathGeom.diffAngle(+math.pi/4, -1*math.pi/4, 'CCW') / math.pi, 6/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, +0*math.pi/4, 'CCW') / math.pi, 1/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, +3*math.pi/4, 'CCW') / math.pi, 4/4.) self.assertRoughly(PathGeom.diffAngle(-math.pi/4, -1*math.pi/4, 'CCW') / math.pi, 0/4.)
def createCommands(self, obj, edges): commands = [] for edge in edges: israpid = False for redge in self.rapids: if PathGeom.edgesMatch(edge, redge): israpid = True if israpid: v = edge.valueAt(edge.LastParameter) commands.append(Path.Command('G0', {'X': v.x, 'Y': v.y, 'Z': v.z})) else: commands.extend(PathGeom.cmdsForEdge(edge)) lastCmd = Path.Command('G0', {'X': 0.0, 'Y': 0.0, 'Z': 0.0}) outCommands = [] horizFeed = obj.ToolController.HorizFeed.Value vertFeed = obj.ToolController.VertFeed.Value if obj.RampFeedRate == "Horizontal Feed Rate": rampFeed = obj.ToolController.HorizFeed.Value elif obj.RampFeedRate == "Vertical Feed Rate": rampFeed = obj.ToolController.VertFeed.Value else: rampFeed = obj.CustomFeedRate.Value horizRapid = obj.ToolController.HorizRapid.Value vertRapid = obj.ToolController.VertRapid.Value for cmd in commands: params = cmd.Parameters zVal = params.get('Z', None) zVal2 = lastCmd.Parameters.get('Z', None) xVal = params.get('X', None) xVal2 = lastCmd.Parameters.get('X', None) yVal2 = lastCmd.Parameters.get('Y', None) yVal = params.get('Y', None) zVal = zVal and round(zVal, 8) zVal2 = zVal2 and round(zVal2, 8) if cmd.Name in ['G1', 'G2', 'G3', 'G01', 'G02', 'G03']: if zVal is not None and zVal2 != zVal: if PathGeom.isRoughly(xVal, xVal2) and PathGeom.isRoughly(yVal, yVal2): # this is a straight plunge params['F'] = vertFeed else: # this is a ramp params['F'] = rampFeed else: params['F'] = horizFeed lastCmd = cmd elif cmd.Name in ['G0', 'G00']: if zVal is not None and zVal2 != zVal: params['F'] = vertRapid else: params['F'] = horizRapid lastCmd = cmd outCommands.append(Path.Command(cmd.Name, params)) return Path.Path(outCommands)
def test10(self): """Verify proper geometry objects for G1 and G01 commands are created.""" spt = Vector(1,2,3) self.assertLine(PathGeom.edgeForCmd(Path.Command('G1', {'X': 7, 'Y': 2, 'Z': 3}), spt), spt, Vector(7, 2, 3)) self.assertLine(PathGeom.edgeForCmd(Path.Command('G01', {'X': 1, 'Y': 3, 'Z': 5}), spt), spt, Vector(1, 3, 5))
def RapidMove(self, cmd, curpos): path = PathGeom.edgeForCmd(cmd, curpos) # hack to overcome occ bug if path is None: return curpos return path.valueAt(path.LastParameter)
def updateDepths(self, obj, ignoreErrors=False): '''updateDepths(obj) ... base implementation calculating depths depending on base geometry. Can safely be overwritten.''' def faceZmin(bb, fbb): if fbb.ZMax == fbb.ZMin and fbb.ZMax == bb.ZMax: # top face return bb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMax == bb.ZMax: # vertical face, full cut return fbb.ZMin elif fbb.ZMax > fbb.ZMin and fbb.ZMin > bb.ZMin: # internal vertical wall return fbb.ZMin elif fbb.ZMax == fbb.ZMin and fbb.ZMax > bb.ZMin: # face/shelf return fbb.ZMin return bb.ZMin if not self._setBaseAndStock(obj, ignoreErrors): return False stockBB = self.stock.Shape.BoundBox zmin = stockBB.ZMin zmax = stockBB.ZMax if hasattr(obj, 'Base') and obj.Base: for base, sublist in obj.Base: bb = base.Shape.BoundBox zmax = max(zmax, bb.ZMax) for sub in sublist: fbb = base.Shape.getElement(sub).BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) else: # clearing with stock boundaries pass safeDepths = True if FeatureDepths & self.opFeatures(obj): # first set update final depth, it's value is not negotiable if not PathGeom.isRoughly(obj.FinalDepth.Value, zmin): if not hasattr(obj, 'FinalDepthLock') or not obj.FinalDepthLock: obj.FinalDepth = zmin else: if obj.FinalDepth.Value < zmin: safeDepths = False zmin = obj.FinalDepth.Value def minZmax(z): if hasattr(obj, 'StepDown') and not PathGeom.isRoughly( obj.StepDown.Value, 0): return z + obj.StepDown.Value else: return z + 1 # ensure zmax is higher than zmin if (zmax - 0.0001) <= zmin: zmax = minZmax(zmin) # update start depth if requested and required if not PathGeom.isRoughly(obj.StartDepth.Value, zmax): if not hasattr(obj, 'StartDepthLock') or not obj.StartDepthLock: obj.StartDepth = zmax elif (obj.StartDepth.Value - 0.0001) <= obj.FinalDepth.Value: obj.StartDepth = minZmax(obj.FinalDepth.Value) else: if obj.StartDepth.Value < zmax: safeDepths = False clearance = obj.StartDepth.Value + 5.0 safe = obj.StartDepth.Value + 3 if hasattr(obj, 'ClearanceHeight') and not PathGeom.isRoughly( clearance, obj.ClearanceHeight.Value): obj.ClearanceHeight = clearance if hasattr(obj, 'SafeHeight') and not PathGeom.isRoughly( safe, obj.SafeHeight.Value): obj.SafeHeight = safe return safeDepths
def isDrillable(obj, candidate, tooldiameter=None, includePartials=False): """ Checks candidates to see if they can be drilled. Candidates can be either faces - circular or cylindrical or circular edges. The tooldiameter can be optionally passed. if passed, the check will return False for any holes smaller than the tooldiameter. obj=Shape candidate = Face or Edge tooldiameter=float """ PathLog.track('obj: {} candidate: {} tooldiameter {}'.format(obj, candidate, tooldiameter)) drillable = False try: if candidate.ShapeType == 'Face': face = candidate # eliminate flat faces if (round(face.ParameterRange[0], 8) == 0.0) and (round(face.ParameterRange[1], 8) == round(math.pi * 2, 8)): for edge in face.Edges: # Find seam edge and check if aligned to Z axis. if (isinstance(edge.Curve, Part.Line)): PathLog.debug("candidate is a circle") v0 = edge.Vertexes[0].Point v1 = edge.Vertexes[1].Point #check if the cylinder seam is vertically aligned. Eliminate tilted holes if (numpy.isclose(v1.sub(v0).x, 0, rtol=1e-05, atol=1e-06)) and \ (numpy.isclose(v1.sub(v0).y, 0, rtol=1e-05, atol=1e-06)): drillable = True # vector of top center lsp = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMax) # vector of bottom center lep = Vector(face.BoundBox.Center.x, face.BoundBox.Center.y, face.BoundBox.ZMin) # check if the cylindrical 'lids' are inside the base # object. This eliminates extruded circles but allows # actual holes. if obj.isInside(lsp, 1e-6, False) or obj.isInside(lep, 1e-6, False): PathLog.track("inside check failed. lsp: {} lep: {}".format(lsp,lep)) drillable = False # eliminate elliptical holes elif not hasattr(face.Surface, "Radius"): PathLog.debug("candidate face has no radius attribute") drillable = False else: if tooldiameter is not None: drillable = face.Surface.Radius >= tooldiameter/2 else: drillable = True elif type(face.Surface) == Part.Plane and PathGeom.pointsCoincide(face.Surface.Axis, FreeCAD.Vector(0,0,1)): if len(face.Edges) == 1 and type(face.Edges[0].Curve) == Part.Circle: center = face.Edges[0].Curve.Center if obj.isInside(center, 1e-6, False): if tooldiameter is not None: drillable = face.Edges[0].Curve.Radius >= tooldiameter/2 else: drillable = True else: for edge in candidate.Edges: if isinstance(edge.Curve, Part.Circle) and (includePartials or edge.isClosed()): PathLog.debug("candidate is a circle or ellipse") if not hasattr(edge.Curve, "Radius"): PathLog.debug("No radius. Ellipse.") drillable = False else: PathLog.debug("Has Radius, Circle") if tooldiameter is not None: drillable = edge.Curve.Radius >= tooldiameter/2 if not drillable: FreeCAD.Console.PrintMessage( "Found a drillable hole with diameter: {}: " "too small for the current tool with " "diameter: {}".format(edge.Curve.Radius*2, tooldiameter)) else: drillable = True PathLog.debug("candidate is drillable: {}".format(drillable)) except Exception as ex: PathLog.warning(translate("PathUtils", "Issue determine drillability: {}").format(ex)) return drillable
def createRampMethod1(self, rampedges, p0, projectionlen, rampangle): """ This method generates ramp with following pattern: 1. Start from the original startpoint of the plunge 2. Ramp down along the path that comes after the plunge 3. When reaching the Z level of the original plunge, return back to the beginning by going the path backwards until the original plunge end point is reached 4. Continue with the original path This method causes unecessarily many moves with tool down """ outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge done = False goingForward = True while not done: for i, redge in enumerate(rampedges): if redge.Length >= rampremaining: # will reach end of ramp within this edge, needs to be split p1 = self.getSplitPoint(redge, rampremaining) splitEdge = PathGeom.splitEdgeAt(redge, p1) PathLog.debug("Ramp remaining: {}".format(rampremaining)) PathLog.debug("Got split edge (index: {}) (total len: {}) with lengths: {}, {}".format(i, redge.Length, splitEdge[0].Length, splitEdge[1].Length)) # ramp ends to the last point of first edge p1 = splitEdge[0].valueAt(splitEdge[0].LastParameter) outedges.append(self.createRampEdge(splitEdge[0], curPoint, p1)) # now we have reached the end of the ramp. Go back to plunge position with constant Z # start that by going to the beginning of this splitEdge if goingForward: outedges.append(self.createRampEdge(splitEdge[0], p1, redge.valueAt(redge.FirstParameter))) else: # if we were reversing, we continue to the same direction as the ramp outedges.append(self.createRampEdge(splitEdge[0], p1, redge.valueAt(redge.LastParameter))) done = True break else: deltaZ = redge.Length / math.tan(math.radians(rampangle)) newPoint = FreeCAD.Base.Vector(redge.valueAt(redge.LastParameter).x, redge.valueAt(redge.LastParameter).y, curPoint.z - deltaZ) outedges.append(self.createRampEdge(redge, curPoint, newPoint)) curPoint = newPoint rampremaining = rampremaining - redge.Length if not done: # we did not reach the end of the ramp going this direction, lets reverse. rampedges = self.getreversed(rampedges) PathLog.debug("Reversing") if goingForward: goingForward = False else: goingForward = True # now we need to return to original position. if goingForward: # if the ramp was going forward, the return edges are the edges we already covered in ramping, # except the last one, which was already covered inside for loop. Direction needs to be reversed also returnedges = self.getreversed(rampedges[:i]) else: # if the ramp was already reversing, the edges needed for return are the ones # which were not covered in ramp returnedges = rampedges[(i + 1):] # add the return edges: outedges.extend(returnedges) return outedges
def areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() if obj.Base: PathLog.debug("base items exist. Processing...") self.removalshapes = [] self.horiz = [] vertical = [] for o in obj.Base: PathLog.debug("Base item: {}".format(o)) base = o[0] for sub in o[1]: if "Face" in sub: face = base.Shape.getElement(sub) if type(face.Surface ) == Part.Plane and PathGeom.isVertical( face.Surface.Axis): # it's a flat horizontal face self.horiz.append(face) elif type(face.Surface ) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis): # vertical cylinder wall if any(e.isClosed() for e in face.Edges): # complete cylinder circle = Part.makeCircle( face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) self.horiz.append(disk) else: # partial cylinder wall vertical.append(face) elif type(face.Surface ) == Part.Plane and PathGeom.isHorizontal( face.Surface.Axis): vertical.append(face) else: PathLog.error( translate( 'PathPocket', "Pocket does not support shape %s.%s") % (base.Label, sub)) self.vertical = PathGeom.combineConnectedShapes(vertical) self.vWires = [ TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) for shape in self.vertical ] for wire in self.vWires: w = PathGeom.removeDuplicateEdges(wire) face = Part.Face(w) face.tessellate(0.1) if PathGeom.isRoughly(face.Area, 0): PathLog.error( translate( 'PathPocket', 'Vertical faces do not form a loop - ignoring')) else: self.horiz.append(face) # move all horizontal faces to FinalDepth for f in self.horiz: f.translate( FreeCAD.Vector(0, 0, obj.FinalDepth.Value - f.BoundBox.ZMin)) # check all faces and see if they are touching/overlapping and combine those into a compound self.horizontal = PathGeom.combineConnectedShapes(self.horiz) for shape in self.horizontal: shape.sewShape() shape.tessellate(0.1) # extrude all faces up to StartDepth and those are the removal shapes extent = FreeCAD.Vector( 0, 0, obj.StartDepth.Value - obj.FinalDepth.Value) self.removalshapes = [(face.extrude(extent), False) for face in self.horizontal] else: # process the job base object as a whole PathLog.debug("processing the whole job base object") self.outline = Part.Face( TechDraw.findShapeOutline(self.baseobject.Shape, 1, FreeCAD.Vector(0, 0, 1))) stockBB = self.stock.Shape.BoundBox self.outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) self.body = self.outline.extrude( FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.removalshapes = [(self.stock.Shape.cut(self.body), False)] for (shape, hole) in self.removalshapes: shape.tessellate(0.1) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
def generateRamps(self, allowBounce=True): edges = self.wire.Edges outedges = [] for edge in edges: israpid = False for redge in self.rapids: if PathGeom.edgesMatch(edge, redge): israpid = True if not israpid: bb = edge.BoundBox p0 = edge.Vertexes[0].Point p1 = edge.Vertexes[1].Point rampangle = self.angle if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z: plungelen = abs(p0.z - p1.z) projectionlen = plungelen * math.tan(math.radians(rampangle)) # length of the forthcoming ramp projected to XY plane if self.method == 'RampMethod3': projectionlen = projectionlen / 2 PathLog.debug("Found plunge move at X:{} Y:{} From Z:{} to Z{}, length of ramp: {}".format(p0.x, p0.y, p0.z, p1.z, projectionlen)) # next need to determine how many edges in the path after # plunge are needed to cover the length: covered = False coveredlen = 0 rampedges = [] i = edges.index(edge) + 1 while not covered: candidate = edges[i] cp0 = candidate.Vertexes[0].Point cp1 = candidate.Vertexes[1].Point if abs(cp0.z - cp1.z) > 1e-6: # this edge is not parallel to XY plane, not qualified for ramping. break # PathLog.debug("Next edge length {}".format(candidate.Length)) rampedges.append(candidate) coveredlen = coveredlen + candidate.Length if coveredlen > projectionlen: covered = True i = i + 1 if i >= len(edges): break if len(rampedges) == 0: PathLog.debug("No suitable edges for ramping, plunge will remain as such") outedges.append(edge) else: if not covered: if (not allowBounce) or self.method == 'RampMethod2': l = 0 for redge in rampedges: l = l + redge.Length if self.method == 'RampMethod3': rampangle = math.degrees(math.atan(l / (plungelen / 2))) else: rampangle = math.degrees(math.atan(l / plungelen)) PathLog.warning("Cannot cover with desired angle, tightening angle to: {}".format(rampangle)) # PathLog.debug("Doing ramp to edges: {}".format(rampedges)) if self.method == 'RampMethod1': outedges.extend(self.createRampMethod1(rampedges, p0, projectionlen, rampangle)) elif self.method == 'RampMethod2': outedges.extend(self.createRampMethod2(rampedges, p0, projectionlen, rampangle)) else: # if the ramp cannot be covered with Method3, revert to Method1 # because Method1 support going back-and-forth and thus results in same path as Method3 when # length of the ramp is smaller than needed for single ramp. if (not covered) and allowBounce: projectionlen = projectionlen * 2 outedges.extend(self.createRampMethod1(rampedges, p0, projectionlen, rampangle)) else: outedges.extend(self.createRampMethod3(rampedges, p0, projectionlen, rampangle)) else: outedges.append(edge) else: outedges.append(edge) return outedges
def cmds(pa, pb, pc, flip): return PathGeom.cmdsForEdge(Part.Edge(Part.Arc(pa, pb, pc)), flip)[0]
def tboneVertical(self, bone): angle = bone.angle() boneAngle = math.pi / 2 if PathGeom.isRoughly(angle, math.pi) or angle < 0: boneAngle = -boneAngle return self.inOutBoneCommands(bone, boneAngle, self.toolRadius)
def test03(self): """Verify isVertical/isHorizontal for Edges""" # lines self.assertTrue( PathGeom.isVertical( Part.Edge( Part.LineSegment(Vector(-1, -1, -1), Vector(-1, -1, 8))))) self.assertFalse( PathGeom.isVertical( Part.Edge( Part.LineSegment(Vector(-1, -1, -1), Vector(1, -1, 8))))) self.assertFalse( PathGeom.isVertical( Part.Edge( Part.LineSegment(Vector(-1, -1, -1), Vector(-1, 1, 8))))) self.assertTrue( PathGeom.isHorizontal( Part.Edge( Part.LineSegment(Vector(1, -1, -1), Vector(-1, -1, -1))))) self.assertTrue( PathGeom.isHorizontal( Part.Edge( Part.LineSegment(Vector(-1, 1, -1), Vector(-1, -1, -1))))) self.assertTrue( PathGeom.isHorizontal( Part.Edge( Part.LineSegment(Vector(1, 1, -1), Vector(-1, -1, -1))))) self.assertFalse( PathGeom.isHorizontal( Part.Edge(Part.LineSegment(Vector(1, -1, -1), Vector(1, -1, 8))))) self.assertFalse( PathGeom.isHorizontal( Part.Edge(Part.LineSegment(Vector(-1, 1, -1), Vector(-1, 1, 8))))) # circles self.assertTrue( PathGeom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 1, 0))))) self.assertTrue( PathGeom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 0, 0))))) self.assertTrue( PathGeom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 0))))) self.assertFalse( PathGeom.isVertical( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 1))))) self.assertTrue( PathGeom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 0, 1))))) self.assertFalse( PathGeom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(0, 1, 1))))) self.assertFalse( PathGeom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 0, 1))))) self.assertFalse( PathGeom.isHorizontal( Part.Edge(Part.makeCircle(4, Vector(), Vector(1, 1, 1)))))
def smoothChordCommands(self, bone, inChord, outChord, edge, wire, corner, smooth, color=None): if smooth == 0: PathLog.info(" No smoothing requested") return [bone.lastCommand, outChord.g1Command(bone.F)] d = 'in' refPoint = inChord.Start if smooth == Smooth.Out: d = 'out' refPoint = outChord.End if DraftGeomUtils.areColinear(inChord.asEdge(), outChord.asEdge()): PathLog.info(" straight edge %s" % d) return [outChord.g1Command(bone.F)] pivot = None pivotDistance = 0 PathLog.info("smooth: (%.2f, %.2f)-(%.2f, %.2f)" % (edge.Vertexes[0].Point.x, edge.Vertexes[0].Point.y, edge.Vertexes[1].Point.x, edge.Vertexes[1].Point.y)) for e in wire.Edges: self.dbg.append(e) if type(e.Curve) == Part.LineSegment or type(e.Curve) == Part.Line: PathLog.debug(" (%.2f, %.2f)-(%.2f, %.2f)" % (e.Vertexes[0].Point.x, e.Vertexes[0].Point.y, e.Vertexes[1].Point.x, e.Vertexes[1].Point.y)) else: PathLog.debug( " (%.2f, %.2f)^%.2f" % (e.Curve.Center.x, e.Curve.Center.y, e.Curve.Radius)) for pt in DraftGeomUtils.findIntersection(edge, e, True, findAll=True): if not PathGeom.pointsCoincide( pt, corner) and self.pointIsOnEdge(pt, e): # debugMarker(pt, "candidate-%d-%s" % (self.boneId, d), color, 0.05) PathLog.debug(" -> candidate") distance = (pt - refPoint).Length if not pivot or pivotDistance > distance: pivot = pt pivotDistance = distance else: PathLog.debug(" -> corner intersect") if pivot: # debugCircle(pivot, self.toolRadius, "pivot.%d-%s" % (self.boneId, d), color) pivotEdge = Part.Edge( Part.Circle(pivot, FreeCAD.Vector(0, 0, 1), self.toolRadius)) t1 = self.findPivotIntersection(pivot, pivotEdge, inChord.asEdge(), inChord.End, d, color) t2 = self.findPivotIntersection(pivot, pivotEdge, outChord.asEdge(), inChord.End, d, color) commands = [] if not PathGeom.pointsCoincide(t1, inChord.Start): PathLog.debug(" add lead in") commands.append(Chord(inChord.Start, t1).g1Command(bone.F)) if bone.obj.Side == Side.Left: PathLog.debug(" add g3 command") commands.append(Chord(t1, t2).g3Command(pivot, bone.F)) else: PathLog.debug( " add g2 command center=(%.2f, %.2f) -> from (%2f, %.2f) to (%.2f, %.2f" % (pivot.x, pivot.y, t1.x, t1.y, t2.x, t2.y)) commands.append(Chord(t1, t2).g2Command(pivot, bone.F)) if not PathGeom.pointsCoincide(t2, outChord.End): PathLog.debug(" add lead out") commands.append(Chord(t2, outChord.End).g1Command(bone.F)) # debugMarker(pivot, "pivot.%d-%s" % (self.boneId, d), color, 0.2) # debugMarker(t1, "pivot.%d-%s.in" % (self.boneId, d), color, 0.1) # debugMarker(t2, "pivot.%d-%s.out" % (self.boneId, d), color, 0.1) return commands PathLog.info(" no pivot found - straight command") return [inChord.g1Command(bone.F), outChord.g1Command(bone.F)]
def generateHelix(self): edges = self.wire.Edges minZ = self.findMinZ(edges) outedges = [] i = 0 while i < len(edges): edge = edges[i] israpid = False for redge in self.rapids: if PathGeom.edgesMatch(edge, redge): israpid = True if not israpid: bb = edge.BoundBox p0 = edge.Vertexes[0].Point p1 = edge.Vertexes[1].Point if bb.XLength < 1e-6 and bb.YLength < 1e-6 and bb.ZLength > 0 and p0.z > p1.z: # plungelen = abs(p0.z-p1.z) PathLog.debug( "Found plunge move at X:{} Y:{} From Z:{} to Z{}, Searching for closed loop" .format(p0.x, p0.y, p0.z, p1.z)) # check if above ignoreAbove parameter - do not generate helix if it is newEdge, cont = self.checkIgnoreAbove(edge) if newEdge is not None: outedges.append(newEdge) p0.z = self.ignoreAbove if cont: i = i + 1 continue # next need to determine how many edges in the path after plunge are needed to cover the length: loopFound = False rampedges = [] j = i + 1 while not loopFound: candidate = edges[j] cp0 = candidate.Vertexes[0].Point cp1 = candidate.Vertexes[1].Point if PathGeom.pointsCoincide(p1, cp1): # found closed loop loopFound = True rampedges.append(candidate) break if abs(cp0.z - cp1.z) > 1e-6: # this edge is not parallel to XY plane, not qualified for ramping. break # PathLog.debug("Next edge length {}".format(candidate.Length)) rampedges.append(candidate) j = j + 1 if j >= len(edges): break if len(rampedges) == 0 or not loopFound: PathLog.debug("No suitable helix found") outedges.append(edge) else: outedges.extend(self.createHelix(rampedges, p0, p1)) if not PathGeom.isRoughly(p1.z, minZ): # the edges covered by the helix not handled again, # unless reached the bottom height i = j else: outedges.append(edge) else: outedges.append(edge) i = i + 1 return outedges
def connectsTo(self, chord): return PathGeom.pointsCoincide(self.End, chord.Start)
def minZmax(z): if hasattr(obj, 'StepDown') and not PathGeom.isRoughly( obj.StepDown.Value, 0): return z + obj.StepDown.Value else: return z + 1
def cleanupEdges(self, edges): # want to remove all edges from the wire itself, and all internal struts PathLog.track("+cleanupEdges") PathLog.debug(" edges:") if not edges: return edges for e in edges: debugEdge(e, ' ') PathLog.debug(":") self.edgesCleanup = [copy.copy(edges)] # remove any edge that has a point inside the tag solid # and collect all edges that are connected to the entry and/or exit self.entryEdges = [] self.exitEdges = [] self.edgePoints = [] for e in copy.copy(edges): p1 = e.valueAt(e.FirstParameter) p2 = e.valueAt(e.LastParameter) self.edgePoints.append(p1) self.edgePoints.append(p2) if self.tag.solid.isInside(p1, PathGeom.Tolerance, False) or self.tag.solid.isInside( p2, PathGeom.Tolerance, False): edges.remove(e) debugEdge(e, '......... X0', False) else: if PathGeom.pointsCoincide( p1, self.entry) or PathGeom.pointsCoincide( p2, self.entry): self.entryEdges.append(e) if PathGeom.pointsCoincide( p1, self.exit) or PathGeom.pointsCoincide( p2, self.exit): self.exitEdges.append(e) self.edgesCleanup.append(copy.copy(edges)) # if there are no edges connected to entry/exit, it means the plunge in/out is vertical # we need to add in the missing segment and collect the new entry/exit edges. if not self.entryEdges: print("fill entryEdges ...") self.realEntry = sorted(self.edgePoints, key=lambda p: (p - self.entry).Length)[0] self.entryEdges = filter( lambda e: PathGeom.edgeConnectsTo(e, self.realEntry), edges) edges.append( Part.Edge(Part.LineSegment(self.entry, self.realEntry))) else: self.realEntry = None if not self.exitEdges: print("fill exitEdges ...") self.realExit = sorted(self.edgePoints, key=lambda p: (p - self.exit).Length)[0] self.exitEdges = filter( lambda e: PathGeom.edgeConnectsTo(e, self.realExit), edges) edges.append(Part.Edge(Part.LineSegment(self.realExit, self.exit))) else: self.realExit = None self.edgesCleanup.append(copy.copy(edges)) # if there are 2 edges attached to entry/exit, throw away the one that is "lower" if len(self.entryEdges) > 1: debugEdge(self.entryEdges[0], ' entry[0]', False) debugEdge(self.entryEdges[1], ' entry[1]', False) if self.entryEdges[0].BoundBox.ZMax < self.entryEdges[ 1].BoundBox.ZMax: edges.remove(self.entryEdges[0]) debugEdge(e, '......... X1', False) else: edges.remove(self.entryEdges[1]) debugEdge(e, '......... X2', False) if len(self.exitEdges) > 1: debugEdge(self.exitEdges[0], ' exit[0]', False) debugEdge(self.exitEdges[1], ' exit[1]', False) if self.exitEdges[0].BoundBox.ZMax < self.exitEdges[ 1].BoundBox.ZMax: if self.exitEdges[0] in edges: edges.remove(self.exitEdges[0]) debugEdge(e, '......... X3', False) else: if self.exitEdges[1] in edges: edges.remove(self.exitEdges[1]) debugEdge(e, '......... X4', False) self.edgesCleanup.append(copy.copy(edges)) return edges
def needToFlipEdge(self, edge, p): if PathGeom.pointsCoincide(edge.valueAt(edge.LastParameter), p): return True, edge.valueAt(edge.FirstParameter) return False, edge.valueAt(edge.LastParameter)
def execute(self, obj): PathLog.track() if not obj.Base: PathLog.error(translate('Path_DressupTag', 'No Base object found.')) return if not obj.Base.isDerivedFrom('Path::Feature'): PathLog.error( translate('Path_DressupTag', 'Base is not a Path::Feature object.')) return if not obj.Base.Path: PathLog.error( translate('Path_DressupTag', 'Base doesn\'t have a Path to dress-up.')) return if not obj.Base.Path.Commands: PathLog.error(translate('Path_DressupTag', 'Base Path is empty.')) return self.obj = obj minZ = +sys.maxint minX = minZ minY = minZ maxZ = -sys.maxint maxX = maxZ maxY = maxZ # the assumption is that all helixes are in the xy-plane - in other words there is no # intermittent point of a command that has a lower/higher Z-position than the start and # and end positions of a command. lastPt = FreeCAD.Vector(0, 0, 0) for cmd in obj.Base.Path.Commands: pt = PathGeom.commandEndPoint(cmd, lastPt) if lastPt.x != pt.x: maxX = max(pt.x, maxX) minX = min(pt.x, minX) if lastPt.y != pt.y: maxY = max(pt.y, maxY) minY = min(pt.y, minY) if lastPt.z != pt.z: maxZ = max(pt.z, maxZ) minZ = min(pt.z, minZ) lastPt = pt PathLog.debug("bb = (%.2f, %.2f, %.2f) ... (%.2f, %.2f, %.2f)" % (minX, minY, minZ, maxX, maxY, maxZ)) self.ptMin = FreeCAD.Vector(minX, minY, minZ) self.ptMax = FreeCAD.Vector(maxX, maxY, maxZ) self.masterSolid = TagSolid(self, minZ, self.toolRadius()) self.solids = [ self.masterSolid.cloneAt(pos) for pos in self.obj.Positions ] self.tagSolid = Part.Compound(self.solids) self.wire, rapid = PathGeom.wireForPath(obj.Base.Path) self.edges = self.wire.Edges maxTagZ = minZ + obj.Height.Value # lastX = 0 # lastY = 0 lastZ = 0 commands = [] for cmd in obj.Base.Path.Commands: if cmd in PathGeom.CmdMove: if lastZ <= maxTagZ or cmd.Parameters.get('Z', lastZ) <= maxTagZ: pass else: commands.append(cmd) else: commands.append(cmd) obj.Path = obj.Base.Path PathLog.track()