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 obj.OpStockZMin = zmin obj.OpStockZMax = 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: try: fbb = base.Shape.getElement(sub).BoundBox zmin = max(zmin, faceZmin(bb, fbb)) zmax = max(zmax, fbb.ZMax) except Part.OCCError as e: PathLog.error(e) else: # clearing with stock boundaries job = PathUtils.findParentJob(obj) zmax = stockBB.ZMax zmin = job.Proxy.modelBoundBox(job).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 else: # every obj has a StartDepth if obj.StartDepth.Value != zmax: obj.StartDepth = zmax self.opUpdateDepths(obj)
def test45(self): '''Check offsetting a single inside edge not forward.''' obj = doc.getObjectsByLabel('offset-edge')[0] w = getWireInside(obj) length = 20 * math.cos(math.pi/6) for e in w.Edges: self.assertRoughly(length, e.Length) # let's offset the horizontal edge for starters hEdges = [e for e in w.Edges if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)] x = length / 2 y = -5 self.assertEqual(1, len(hEdges)) edge = hEdges[0] self.assertCoincide(Vector(-x, y, 0), edge.Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), edge.Vertexes[1].Point) wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) self.assertEqual(1, len(wire.Edges)) y = y + 2 self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point) # make sure we get the same result even if the edge is oriented the other way edge = PathGeom.flipEdge(edge) wire = PathOpTools.offsetWire(Part.Wire([edge]), obj.Shape, 2, False) self.assertEqual(1, len(wire.Edges)) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[1].Point)
def commandsForEdges(self): global failures 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, False, 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])) #traceback.print_exc(e) self.tag.enabled = False commands = [] for e in self.edges: commands.extend(PathGeom.cmdsForEdge(e)) failures.append(self) return commands return []
def test72(self): '''Flip a circle''' edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, 1)) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) edge = Part.makeCircle(3, Vector(1, 3, 2), Vector(0, 0, -1)) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
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 test32(self): '''Check offsetting a box.''' obj = doc.getObjectsByLabel('square-cut')[0] wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, True) self.assertEqual(8, len(wire.Edges)) self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual(4, len([e for e in wire.Edges if Part.Circle == type(e.Curve)])) for e in wire.Edges: if Part.Line == type(e.Curve): if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertEqual(40, e.Length) if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertEqual(60, e.Length) if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) self.assertCoincide(Vector(0, 0, -1), e.Curve.Axis) self.assertTrue(PathOpTools.isWireClockwise(wire)) # change offset orientation wire = PathOpTools.offsetWire(getWire(obj.Tool), getPositiveShape(obj), 3, False) self.assertEqual(8, len(wire.Edges)) self.assertEqual(4, len([e for e in wire.Edges if Part.Line == type(e.Curve)])) self.assertEqual(4, len([e for e in wire.Edges if Part.Circle == type(e.Curve)])) for e in wire.Edges: if Part.Line == type(e.Curve): if PathGeom.isRoughly(e.Vertexes[0].Point.x, e.Vertexes[1].Point.x): self.assertEqual(40, e.Length) if PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y): self.assertEqual(60, e.Length) if Part.Circle == type(e.Curve): self.assertRoughly(3, e.Curve.Radius) self.assertCoincide(Vector(0, 0, +1), e.Curve.Axis) self.assertFalse(PathOpTools.isWireClockwise(wire))
def test47(self): '''Check offsetting multiple backwards inside edges.''' # This is exactly the same as test36 except that the wire is flipped to make # sure it's orientation doesn't matter obj = doc.getObjectsByLabel('offset-edge')[0] w = getWireInside(obj) length = 20 * math.cos(math.pi/6) # let's offset the other two legs lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)] self.assertEqual(2, len(lEdges)) w = PathGeom.flipWire(Part.Wire(lEdges)) wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) x = length/2 - 2 * math.cos(math.pi/6) y = -5 - 2 * math.sin(math.pi/6) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point) rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)] self.assertEqual(0, len(rEdges)) #offset the other way wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point) rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)] self.assertEqual(0, len(rEdges))
def orderAndFlipEdges(self, inputEdges): 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 edges = copy.copy(inputEdges) while edges: # print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in copy.copy(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): flipped = PathGeom.flipEdge(e) if not flipped is None: outputEdges.append((flipped, True)) else: p0 = None cnt = 0 for p in reversed(e.discretize(Deflection=0.01)): if not p0 is None: outputEdges.append((Part.Edge(Part.LineSegment(p0, p)), True)) cnt = cnt + 1 p0 = p PathLog.info("replaced edge with %d straight segments" % cnt) 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('input edges:') for e in inputEdges: debugEdge(e, ' ', False) 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 __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 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 isInside(edge): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide(p, p1, 0.01): return True return 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 test75(self): '''Flip a B-spline''' spline = Part.BSplineCurve() spline.interpolate([Vector(1,2,3), Vector(-3,0,7), Vector(-3,1,9), Vector(1, 3, 5)]) edge = Part.Edge(spline) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) edge = Part.Edge(Part.BSplineCurve([Vector(-8,4,0), Vector(1,-5,0), Vector(5,11,0), Vector(12,-5,0)], weights=[2,3,5,7])) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
def test71(self): '''Flip a line segment.''' edge = Part.Edge(Part.LineSegment(Vector(0,0,0), Vector(3, 2, 1))) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) edge = Part.Edge(Part.LineSegment(Vector(4,2,1), Vector(-3, -7, 9))) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge)) edge = Part.makeLine(Vector(1,0,3), Vector(3, 2, 1)) self.assertEdgeShapesMatch(edge, PathGeom.flipEdge(edge))
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 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 buildpathocc(self, obj, wires, zValues, rel=False): '''buildpathocc(obj, wires, zValues, rel=False) ... internal helper function to generate engraving commands.''' PathLog.track(obj.Label, len(wires), zValues) for wire in wires: offset = wire # reorder the wire if hasattr(obj, 'StartVertex'): offset = DraftGeomUtils.rebaseWire(offset, obj.StartVertex) edges = copy.copy(offset.Edges) last = None for z in zValues: if last: if rel: self.commandlist.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z - z, 'F': self.vertFeed})) else: self.commandlist.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': z, 'F': self.vertFeed})) for edge in edges: if not last: # we set the first move to our first point last = edge.Vertexes[0].Point if len(offset.Edges) > 1: ve = edge.Vertexes[-1] e2 = offset.Edges[1] if not PathGeom.pointsCoincide(ve.Point, e2.Vertexes[0].Point) and not PathGeom.pointsCoincide(ve.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})) if rel: self.commandlist.append(Path.Command('G1', {'X': last.x, 'Y': last.y, 'Z': last.z - z, 'F': self.vertFeed})) else: self.commandlist.append(Path.Command('G1', {'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): self.appendCommand(cmd, z, rel) last = edge.Vertexes[-1].Point else: for cmd in PathGeom.cmdsForEdge(edge, True): self.appendCommand(cmd, z, rel) 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 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 createSolidsAt(self, z, R): self.z = z self.toolRadius = R r1 = self.fullWidth() / 2 self.r1 = r1 self.r2 = r1 height = self.height * 1.01 radius = 0 if PathGeom.isRoughly(90, self.angle) and height > 0: # cylinder self.isSquare = True self.solid = Part.makeCylinder(r1, height) radius = min(min(self.radius, r1), self.height) PathLog.debug("Part.makeCone(%f, %f)" % (r1, height)) elif self.angle > 0.0 and height > 0.0: # cone rad = math.radians(self.angle) tangens = math.tan(rad) dr = height / tangens if dr < r1: # with top r2 = r1 - dr s = height / math.sin(rad) radius = min(r2, s) * math.tan((math.pi - rad)/2) * 0.95 else: # triangular r2 = 0 height = r1 * tangens * 1.01 self.actualHeight = height self.r2 = r2 PathLog.debug("Part.makeCone(%f, %f, %f)" % (r1, r2, height)) self.solid = Part.makeCone(r1, r2, height) else: # degenerated case - no tag PathLog.debug("Part.makeSphere(%f / 10000)" % (r1)) self.solid = Part.makeSphere(r1 / 10000) if not PathGeom.isRoughly(0, R): # testing is easier if the solid is not rotated angle = -PathGeom.getAngle(self.originAt(0)) * 180 / math.pi PathLog.debug("solid.rotate(%f)" % angle) self.solid.rotate(FreeCAD.Vector(0, 0, 0), FreeCAD.Vector(0, 0, 1), angle) orig = self.originAt(z - 0.01 * self.actualHeight) PathLog.debug("solid.translate(%s)" % orig) self.solid.translate(orig) radius = min(self.radius, radius) self.realRadius = radius if not PathGeom.isRoughly(0, radius.Value): PathLog.debug("makeFillet(%.4f)" % radius) self.solid = self.solid.makeFillet(radius, [self.solid.Edges[0]])
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 test46(self): '''Check offsetting multiple inside edges.''' obj = doc.getObjectsByLabel('offset-edge')[0] w = getWireInside(obj) length = 20 * math.cos(math.pi/6) # let's offset the other two legs lEdges = [e for e in w.Edges if not PathGeom.isRoughly(e.Vertexes[0].Point.y, e.Vertexes[1].Point.y)] self.assertEqual(2, len(lEdges)) wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, True) x = length/2 - 2 * math.cos(math.pi/6) y = -5 - 2 * math.sin(math.pi/6) self.assertCoincide(Vector(+x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(-x, y, 0), wire.Edges[-1].Vertexes[1].Point) rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)] self.assertEqual(0, len(rEdges)) #offset the other way wire = PathOpTools.offsetWire(Part.Wire(lEdges), obj.Shape, 2, False) self.assertCoincide(Vector(-x, y, 0), wire.Edges[0].Vertexes[0].Point) self.assertCoincide(Vector(+x, y, 0), wire.Edges[-1].Vertexes[1].Point) rEdges = [e for e in wire.Edges if Part.Circle == type(e.Curve)] self.assertEqual(0, len(rEdges))
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 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 test15(self): '''Check offsetting a cylinder with Placement.''' obj = doc.getObjectsByLabel('offset-placement')[0] wires = [w for w in obj.Shape.Wires if 1 == len(w.Edges) and PathGeom.isRoughly(0, w.Edges[0].Vertexes[0].Point.z)] self.assertEqual(2, len(wires)) w = wires[0] if wires[0].BoundBox.isInside(wires[1].BoundBox) else wires[1] self.assertRoughly(20, w.Edges[0].Curve.Radius) # make sure there is a placement and I didn't mess up the model self.assertFalse(PathGeom.pointsCoincide(Vector(), w.Edges[0].Placement.Base)) wire = PathOpTools.offsetWire(w, obj.Shape, 2, True) self.assertIsNotNone(wire) self.assertEqual(1, len(wire.Edges)) self.assertRoughly(22, wire.Edges[0].Curve.Radius) self.assertCoincide(Vector(0, 0, 0), wire.Edges[0].Curve.Center) self.assertCoincide(Vector(0, 0, -1), wire.Edges[0].Curve.Axis)
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 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 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 clasifySub(self, bs, sub): '''clasifySub(bs, sub)... Given a base and a sub-feature name, returns True if the sub-feature is a horizontally oriented flat face. ''' face = bs.Shape.getElement(sub) if type(face.Surface) == Part.Plane: PathLog.debug('type() == Part.Plane') if PathGeom.isVertical(face.Surface.Axis): PathLog.debug(' -isVertical()') # it's a flat horizontal face self.horiz.append(face) return True elif PathGeom.isHorizontal(face.Surface.Axis): PathLog.debug(' -isHorizontal()') self.vert.append(face) return True else: return False elif type(face.Surface) == Part.Cylinder and PathGeom.isVertical( face.Surface.Axis): PathLog.debug('type() == Part.Cylinder') # vertical cylinder wall if any(e.isClosed() for e in face.Edges): PathLog.debug(' -e.isClosed()') # complete cylinder circle = Part.makeCircle(face.Surface.Radius, face.Surface.Center) disk = Part.Face(Part.Wire(circle)) disk.translate( FreeCAD.Vector(0, 0, face.BoundBox.ZMin - disk.BoundBox.ZMin)) self.horiz.append(disk) return True else: PathLog.debug(' -none isClosed()') # partial cylinder wall self.vert.append(face) return True elif type(face.Surface) == Part.SurfaceOfExtrusion: # extrusion wall PathLog.debug('type() == Part.SurfaceOfExtrusion') # Save face to self.horiz for processing or display error if self.isVerticalExtrusionFace(face): self.vert.append(face) return True else: PathLog.error( translate( "Path", "Failed to identify vertical face from {}.".format( sub))) else: PathLog.debug(' -type(face.Surface): {}'.format(type( face.Surface))) return False
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 = [] tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value if obj.RampFeedRate == "Horizontal Feed Rate": rampFeed = tc.HorizFeed.Value elif obj.RampFeedRate == "Vertical Feed Rate": rampFeed = tc.VertFeed.Value elif obj.RampFeedRate == "Ramp Feed Rate": rampFeed = math.sqrt(pow(tc.VertFeed.Value, 2) + pow(tc.HorizFeed.Value, 2)) else: rampFeed = obj.CustomFeedRate.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) 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 isCircleAt(edge, center): '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.''' if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type( edge.Curve): return PathGeom.pointsCoincide(edge.Curve.Center, center) return False
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 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 connectsTo(self, chord): return PathGeom.pointsCoincide(self.End, chord.Start)
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 isAPlungeMove(self): return not PathGeom.isRoughly(self.End.z, self.Start.z)
def hasDefaultToolRapids(self): return PathGeom.isRoughly(self.obj.VertRapid.Value, 0) and PathGeom.isRoughly( self.obj.HorizRapid.Value, 0)
def _get_working_edges(op, obj): """_get_working_edges(op, obj)... Compile all working edges from the Base Geometry selection (obj.Base) for the current operation. Additional modifications to selected region(face), such as extensions, should be placed within this function. """ all_regions = list() edge_list = list() avoidFeatures = list() rawEdges = list() # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) for e in extensions: if e.avoid: avoidFeatures.append(e.feature) # Get faces selected by user for base, subs in obj.Base: for sub in subs: if sub.startswith("Face"): if sub not in avoidFeatures: if obj.UseOutline: face = base.Shape.getElement(sub) # get outline with wire_A method used in PocketShape, but it does not play nicely later # wire_A = TechDraw.findShapeOutline(face, 1, FreeCAD.Vector(0.0, 0.0, 1.0)) wire_B = face.Wires[0] shape = Part.Face(wire_B) else: shape = base.Shape.getElement(sub) all_regions.append(shape) elif sub.startswith("Edge"): # Save edges for later processing rawEdges.append(base.Shape.getElement(sub)) # Efor # Process selected edges if rawEdges: edgeWires = DraftGeomUtils.findWires(rawEdges) if edgeWires: for w in edgeWires: for e in w.Edges: edge_list.append([discretize(e)]) # Apply regular Extensions op.exts = [] # pylint: disable=attribute-defined-outside-init for ext in extensions: if not ext.avoid: wire = ext.getWire() if wire: for f in ext.getExtensionFaces(wire): op.exts.append(f) all_regions.append(f) # Second face-combining method attempted horizontal = PathGeom.combineHorizontalFaces(all_regions) if horizontal: obj.removalshape = Part.makeCompound(horizontal) for f in horizontal: for w in f.Wires: for e in w.Edges: edge_list.append([discretize(e)]) return edge_list
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 tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value horizRapid = tc.HorizRapid.Value vertRapid = tc.VertRapid.Value 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, hSpeed=horizFeed, vSpeed=vertFeed) 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, 'F': horizRapid })) else: commands.append( Path.Command('G0', { 'X': v.X, 'Y': v.Y, 'Z': v.Z, 'F': vertRapid })) else: commands.extend( PathGeom.cmdsForEdge(edge, segm=segm, hSpeed=horizFeed, vSpeed=vertFeed)) edge = None t = 0 return Path.Path(commands)
def generateTags(self, obj, count, width=None, height=None, angle=None, radius=None, spacing=None): # pylint: disable=unused-argument PathLog.track(count, width, height, angle, spacing) # for e in self.baseWire.Edges: # debugMarker(e.Vertexes[0].Point, 'base', (0.0, 1.0, 1.0), 0.2) if spacing: tagDistance = spacing else: tagDistance = self.baseWire.Length / (count if count else 4) W = width if width else self.defaultTagWidth() H = height if height else self.defaultTagHeight() A = angle if angle else self.defaultTagAngle() R = radius if radius else self.defaultTagRadius() # start assigning tags on the longest segment (shortestEdge, longestEdge) = self.shortestAndLongestPathEdge() startIndex = 0 for i in range(0, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] PathLog.debug(' %d: %.2f' % (i, edge.Length)) if PathGeom.isRoughly(edge.Length, longestEdge.Length): startIndex = i break startEdge = self.baseWire.Edges[startIndex] startCount = int(startEdge.Length / tagDistance) if (longestEdge.Length - shortestEdge.Length) > shortestEdge.Length: startCount = int(startEdge.Length / tagDistance) + 1 lastTagLength = (startEdge.Length + (startCount - 1) * tagDistance) / 2 currentLength = startEdge.Length minLength = min(2. * W, longestEdge.Length) PathLog.debug( "length=%.2f shortestEdge=%.2f(%.2f) longestEdge=%.2f(%.2f) minLength=%.2f" % (self.baseWire.Length, shortestEdge.Length, shortestEdge.Length / self.baseWire.Length, longestEdge.Length, longestEdge.Length / self.baseWire.Length, minLength)) PathLog.debug( " start: index=%-2d count=%d (length=%.2f, distance=%.2f)" % (startIndex, startCount, startEdge.Length, tagDistance)) PathLog.debug(" -> lastTagLength=%.2f)" % lastTagLength) PathLog.debug(" -> currentLength=%.2f)" % currentLength) edgeDict = {startIndex: startCount} for i in range(startIndex + 1, len(self.baseWire.Edges)): edge = self.baseWire.Edges[i] (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) for i in range(0, startIndex): edge = self.baseWire.Edges[i] (currentLength, lastTagLength) = self.processEdge(i, edge, currentLength, lastTagLength, tagDistance, minLength, edgeDict) tags = [] for (i, count) in PathUtil.keyValueIter(edgeDict): edge = self.baseWire.Edges[i] PathLog.debug(" %d: %d" % (i, count)) # debugMarker(edge.Vertexes[0].Point, 'base', (1.0, 0.0, 0.0), 0.2) # debugMarker(edge.Vertexes[1].Point, 'base', (0.0, 1.0, 0.0), 0.2) if 0 != count: distance = (edge.LastParameter - edge.FirstParameter) / count for j in range(0, count): tag = edge.Curve.value((j + 0.5) * distance) tags.append(Tag(j, tag.x, tag.y, W, H, A, R, True)) return tags
def orderAndFlipEdges(self, inputEdges): 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 edges = copy.copy(inputEdges) while edges: # print("(%.2f, %.2f, %.2f) %d %d" % (p0.x, p0.y, p0.z)) for e in copy.copy(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): flipped = PathGeom.flipEdge(e) if not flipped is None: outputEdges.append((flipped, True)) else: p0 = None cnt = 0 for p in reversed(e.discretize(Deflection=0.01)): if not p0 is None: outputEdges.append( (Part.Edge(Part.LineSegment(p0, p)), True)) cnt = cnt + 1 p0 = p PathLog.info( "replaced edge with %d straight segments" % cnt) 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) PathLog.debug('input edges:') for e in inputEdges: debugEdge(e, ' ', False) PathLog.debug('ordered edges:') for e, flip in outputEdges: debugEdge(e, ' %c ' % ('<' if flip else '>'), False) PathLog.debug('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 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: PathLog.debug("fill entryEdges ...") self.realEntry = sorted(self.edgePoints, key=lambda p: (p - self.entry).Length)[0] self.entryEdges = list([ e for e in edges if PathGeom.edgeConnectsTo(e, self.realEntry) ]) edges.append( Part.Edge(Part.LineSegment(self.entry, self.realEntry))) else: self.realEntry = None if not self.exitEdges: PathLog.debug("fill exitEdges ...") self.realExit = sorted(self.edgePoints, key=lambda p: (p - self.exit).Length)[0] self.exitEdges = list([ e for e in edges if PathGeom.edgeConnectsTo(e, self.realExit) ]) 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 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: # check if above ignoreAbove parameter - do not generate ramp if it is newEdge, cont = self.checkIgnoreAbove(edge) if newEdge is not None: outedges.append(newEdge) p0.z = self.ignoreAbove if cont: continue plungelen = abs(p0.z - p1.z) projectionlen = plungelen * math.tan( math.radians(rampangle) ) # length of the forthcoming ramp projected to XY plane 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)) if self.method == 'RampMethod3': projectionlen = projectionlen / 2 # 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 isDefinitelySmaller(z, zRef): # Eliminate false positives of edges that just brush along the top of the tag return z < zRef and not PathGeom.isRoughly(z, zRef, 0.01)
def generateHelix(self): edges = self.wire.Edges minZ = self.findMinZ(edges) PathLog.debug("Minimum Z in this path is {}".format(minZ)) 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 offsetWire(wire, base, offset, forward): '''offsetWire(wire, base, offset, forward) ... offsets the wire away from base and orients the wire accordingly. The function tries to avoid most of the pitfalls of Part.makeOffset2D which is possible because all offsetting happens in the XY plane. ''' PathLog.track('offsetWire') if 1 == len(wire.Edges): edge = wire.Edges[0] curve = edge.Curve if Part.Circle == type(curve) and wire.isClosed(): # it's a full circle and there are some problems with that, see # http://www.freecadweb.org/wiki/Part%20Offset2D # it's easy to construct them manually though z = -1 if forward else 1 edge = Part.makeCircle(curve.Radius + offset, curve.Center, FreeCAD.Vector(0, 0, z)) if base.isInside(edge.Vertexes[0].Point, offset / 2, True): if offset > curve.Radius or PathGeom.isRoughly( offset, curve.Radius): # offsetting a hole by its own radius (or more) makes the hole vanish return None edge = Part.makeCircle(curve.Radius - offset, curve.Center, FreeCAD.Vector(0, 0, -z)) w = Part.Wire([edge]) return w if Part.Line == type(curve) or Part.LineSegment == type(curve): # offsetting a single edge doesn't work because there is an infinite # possible planes into which the edge could be offset # luckily, the plane here must be the XY-plane ... p0 = edge.Vertexes[0].Point v0 = edge.Vertexes[1].Point - p0 n = v0.cross(FreeCAD.Vector(0, 0, 1)) o = n.normalize() * offset edge.translate(o) # offset edde the other way if the result is inside if base.isInside( edge.valueAt( (edge.FirstParameter + edge.LastParameter) / 2), offset / 2, True): edge.translate(-2 * o) # flip the edge if it's not on the right side of the original edge if forward is not None: v1 = edge.Vertexes[1].Point - p0 left = PathGeom.Side.Left == PathGeom.Side.of(v0, v1) if left != forward: edge = PathGeom.flipEdge(edge) return Part.Wire([edge]) # if we get to this point the assumption is that makeOffset2D can deal with the edge pass # pylint: disable=unnecessary-pass owire = orientWire(wire.makeOffset2D(offset), True) debugWire('makeOffset2D_%d' % len(wire.Edges), owire) if wire.isClosed(): if not base.isInside(owire.Edges[0].Vertexes[0].Point, offset / 2, True): PathLog.track('closed - outside') return orientWire(owire, forward) PathLog.track('closed - inside') try: owire = wire.makeOffset2D(-offset) except Exception: # pylint: disable=broad-except # most likely offsetting didn't work because the wire is a hole # and the offset is too big - making the hole vanish return None # For negative offsets (holes) 'forward' is the other way if forward is None: return orientWire(owire, None) return orientWire(owire, not forward) # An edge is considered to be inside of shape if the mid point is inside # Of the remaining edges we take the longest wire to be the engraving side # Looking for a circle with the start vertex as center marks and end # starting from there follow the edges until a circle with the end vertex as center is found # if the traversed edges include any of the remaining from above, all those edges are remaining # this is to also include edges which might partially be inside shape # if they need to be discarded, split, that should happen in a post process # Depending on the Axis of the circle, and which side remains we know if the wire needs to be flipped # first, let's make sure all edges are oriented the proper way edges = _orientEdges(wire.Edges) # determine the start and end point start = edges[0].firstVertex().Point end = edges[-1].lastVertex().Point debugWire('wire', wire) debugWire('wedges', Part.Wire(edges)) # find edges that are not inside the shape common = base.common(owire) insideEndpoints = [e.lastVertex().Point for e in common.Edges] insideEndpoints.append(common.Edges[0].firstVertex().Point) def isInside(edge): p0 = edge.firstVertex().Point p1 = edge.lastVertex().Point for p in insideEndpoints: if PathGeom.pointsCoincide(p, p0, 0.01) or PathGeom.pointsCoincide( p, p1, 0.01): return True return False outside = [e for e in owire.Edges if not isInside(e)] # discard all edges that are not part of the longest wire longestWire = None for w in [Part.Wire(el) for el in Part.sortEdges(outside)]: if not longestWire or longestWire.Length < w.Length: longestWire = w debugWire('outside', Part.Wire(outside)) debugWire('longest', longestWire) def isCircleAt(edge, center): '''isCircleAt(edge, center) ... helper function returns True if edge is a circle at the given center.''' if Part.Circle == type(edge.Curve) or Part.ArcOfCircle == type( edge.Curve): return PathGeom.pointsCoincide(edge.Curve.Center, center) return False # split offset wire into edges to the left side and edges to the right side collectLeft = False collectRight = False leftSideEdges = [] rightSideEdges = [] # traverse through all edges in order and start collecting them when we encounter # an end point (circle centered at one of the end points of the original wire). # should we come to an end point and determine that we've already collected the # next side, we're done for e in (owire.Edges + owire.Edges): if isCircleAt(e, start): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectLeft and leftSideEdges: break collectLeft = True collectRight = False else: if not collectRight and rightSideEdges: break collectLeft = False collectRight = True elif isCircleAt(e, end): if PathGeom.pointsCoincide(e.Curve.Axis, FreeCAD.Vector(0, 0, 1)): if not collectRight and rightSideEdges: break collectLeft = False collectRight = True else: if not collectLeft and leftSideEdges: break collectLeft = True collectRight = False elif collectLeft: leftSideEdges.append(e) elif collectRight: rightSideEdges.append(e) debugWire('left', Part.Wire(leftSideEdges)) debugWire('right', Part.Wire(rightSideEdges)) # figure out if all the left sided edges or the right sided edges are the ones # that are 'outside'. However, we return the full side. edges = leftSideEdges for e in longestWire.Edges: for e0 in rightSideEdges: if PathGeom.edgesMatch(e, e0): edges = rightSideEdges PathLog.debug("#use right side edges") if not forward: PathLog.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None) # at this point we have the correct edges and they are in the order for forward # traversal (climb milling). If that's not what we want just reverse the order, # orientWire takes care of orienting the edges appropriately. PathLog.debug("#use left side edges") if not forward: PathLog.debug("#reverse") edges.reverse() return orientWire(Part.Wire(edges), None)
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 many unnecessary moves with tool down. """ outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge done = False goingForward = True i = 0 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 generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start): """generate(center, cmd, zStart, zFinal, pitch, radius, leadInOut, elevator, start) ... returns the g-code to mill the given internal thread""" thread = _Thread(cmd, zStart, zFinal, pitch, radius > elevator) yMin = center.y - radius yMax = center.y + radius path = [] # at this point the tool is at a safe heiht (depending on the previous thread), so we can move # into position first, and then drop to the start height. If there is any material in the way this # op hasn't been setup properly. if leadInOut: _comment(path, "lead-in") if start is None: path.append( Path.Command("G0", { "X": center.x, "Y": center.y + elevator })) path.append(Path.Command("G0", {"Z": thread.zStart})) else: path.append( Path.Command( thread.g4Start2Elevator(), { "X": center.x, "Y": center.y + elevator, "Z": thread.zStart, "I": (center.x - start.x) / 2, "J": (center.y + elevator - start.y) / 2, "K": (thread.zStart - start.z) / 2, }, )) path.append( Path.Command( thread.g4LeadInOut(), { "Y": yMax, "J": (yMax - (center.y + elevator)) / 2 }, )) _comment(path, "lead-in") else: if start is None: path.append( Path.Command("G0", { "X": center.x, "Y": center.y + elevator })) path.append(Path.Command("G0", {"Z": thread.zStart})) else: path.append( Path.Command("G0", { "X": center.x, "Y": center.y + elevator, "Z": thread.zStart })) path.append(Path.Command("G1", {"Y": yMax})) z = thread.zStart r = -radius i = 0 while not PathGeom.isRoughly(z, thread.zFinal): if thread.overshoots(z): break if 0 == (i & 0x01): y = yMin else: y = yMax path.append( Path.Command(thread.cmd, { "Y": y, "Z": z + thread.hPitch, "J": r })) r = -r i = i + 1 z = z + thread.hPitch if PathGeom.isRoughly(z, thread.zFinal): x = center.x y = yMin if (i & 0x01) else yMax else: n = math.fabs(thread.zFinal - thread.zStart) / thread.hPitch k = n - int(n) dy = math.cos(k * math.pi) dx = math.sin(k * math.pi) y = thread.adjustY(center.y, r * dy) x = thread.adjustX(center.x, r * dx) _comment(path, "finish-thread") path.append( Path.Command(thread.cmd, { "X": x, "Y": y, "Z": thread.zFinal, "J": r })) _comment(path, "finish-thread") a = math.atan2(y - center.y, x - center.x) dx = math.cos(a) * (radius - elevator) dy = math.sin(a) * (radius - elevator) PathLog.debug("") PathLog.debug("a={}: dx={:.2f}, dy={:.2f}".format(a / math.pi * 180, dx, dy)) elevatorX = x - dx elevatorY = y - dy PathLog.debug("({:.2f}, {:.2f}) -> ({:.2f}, {:.2f})".format( x, y, elevatorX, elevatorY)) if leadInOut: _comment(path, "lead-out") path.append( Path.Command( thread.g4LeadInOut(), { "X": elevatorX, "Y": elevatorY, "I": -dx / 2, "J": -dy / 2 }, )) _comment(path, "lead-out") else: path.append(Path.Command("G1", {"X": elevatorX, "Y": elevatorY})) return (path, FreeCAD.Vector(elevatorX, elevatorY, thread.zFinal))
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 original plunge end point 4. Continue with the original path This method causes many unnecessary moves with tool down. """ outedges = [] rampremaining = projectionlen curPoint = p0 # start from the upper point of plunge done = False i = 0 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 areaOpShapes(self, obj): '''areaOpShapes(obj) ... return shapes representing the solids to be removed.''' PathLog.track() # self.isDebug = True if PathLog.getLevel(PathLog.thisModule()) == 4 else False self.removalshapes = [] avoidFeatures = list() # Get extensions and identify faces to avoid extensions = FeatureExtensions.getExtensions(obj) for e in extensions: if e.avoid: avoidFeatures.append(e.feature) if obj.Base: PathLog.debug('base items exist. Processing...') self.horiz = [] self.vert = [] for (base, subList) in obj.Base: for sub in subList: if 'Face' in sub: if sub not in avoidFeatures and not self.clasifySub( base, sub): PathLog.error( translate( 'PathPocket', 'Pocket does not support shape %s.%s') % (base.Label, sub)) # Convert horizontal faces to use outline only if requested if obj.UseOutline and self.horiz: horiz = [Part.Face(f.Wire1) for f in self.horiz] self.horiz = horiz # Check if selected vertical faces form a loop if len(self.vert) > 0: self.vertical = PathGeom.combineConnectedShapes(self.vert) 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) # Add faces for extensions self.exts = [] # pylint: disable=attribute-defined-outside-init for ext in extensions: if not ext.avoid: wire = ext.getWire() if wire: faces = ext.getExtensionFaces(wire) for f in faces: self.horiz.append(f) self.exts.append(f) # check all faces and see if they are touching/overlapping and combine and simplify self.horizontal = PathGeom.combineHorizontalFaces(self.horiz) # Move all faces to final depth before extrusion for h in self.horizontal: h.translate( FreeCAD.Vector(0.0, 0.0, obj.FinalDepth.Value - h.BoundBox.ZMin)) # 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.removeSplitter().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.outlines = [ Part.Face( TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model ] stockBB = self.stock.Shape.BoundBox self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude( FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False)) # Tessellate all working faces # for (shape, hole) in self.removalshapes: # shape.tessellate(0.05) # originally 0.1 if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes
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 traveling 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 traveling 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 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 isStrut(self, edge): p1 = PathGeom.xy(edge.valueAt(edge.FirstParameter)) p2 = PathGeom.xy(edge.valueAt(edge.LastParameter)) return PathGeom.pointsCoincide(p1, p2)
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 = [] tc = PathDressup.toolController(obj.Base) horizFeed = tc.HorizFeed.Value vertFeed = tc.VertFeed.Value if obj.RampFeedRate == "Horizontal Feed Rate": rampFeed = tc.HorizFeed.Value elif obj.RampFeedRate == "Vertical Feed Rate": rampFeed = tc.VertFeed.Value else: rampFeed = obj.CustomFeedRate.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) 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 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 = [] for shape in PathGeom.combineConnectedShapes(self.horiz): shape.sewShape() shape.tessellate(0.1) if obj.UseOutline: wire = TechDraw.findShapeOutline(shape, 1, FreeCAD.Vector(0, 0, 1)) wire.translate(FreeCAD.Vector(0, 0, obj.FinalDepth.Value - wire.BoundBox.ZMin)) self.horizontal.append(Part.Face(wire)) else: self.horizontal.append(shape) # 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.outlines = [Part.Face(TechDraw.findShapeOutline(base.Shape, 1, FreeCAD.Vector(0, 0, 1))) for base in self.model] stockBB = self.stock.Shape.BoundBox self.removalshapes = [] self.bodies = [] for outline in self.outlines: outline.translate(FreeCAD.Vector(0, 0, stockBB.ZMin - 1)) body = outline.extrude(FreeCAD.Vector(0, 0, stockBB.ZLength + 2)) self.bodies.append(body) self.removalshapes.append((self.stock.Shape.cut(body), False)) for (shape,hole) in self.removalshapes: shape.tessellate(0.1) if self.removalshapes: obj.removalshape = self.removalshapes[0][0] return self.removalshapes