| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides various functions to work with wires.""" |
| | |
| | |
| | |
| |
|
| | import math |
| | import lazy_loader.lazy_loader as lz |
| |
|
| | import FreeCAD as App |
| | import DraftVecUtils |
| | import WorkingPlane |
| |
|
| | from draftgeoutils.general import geomType, vec, precision |
| | from draftgeoutils.geometry import get_normal |
| | from draftgeoutils.geometry import project_point_on_plane |
| | from draftgeoutils.edges import findMidpoint, isLine |
| |
|
| | |
| | Part = lz.LazyLoader("Part", globals(), "Part") |
| |
|
| | |
| | |
| |
|
| |
|
| | def findWires(edgeslist): |
| | """Find wires in a list of edges.""" |
| | return [Part.Wire(e) for e in Part.sortEdges(edgeslist)] |
| |
|
| |
|
| | def findWiresOld2(edgeslist): |
| | """Find connected wires in the given list of edges.""" |
| |
|
| | def touches(e1, e2): |
| | """Return True if two edges connect at the edges.""" |
| | if len(e1.Vertexes) < 2: |
| | return False |
| | if len(e2.Vertexes) < 2: |
| | return False |
| | if DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[0].Point): |
| | return True |
| | if DraftVecUtils.equals(e1.Vertexes[0].Point, e2.Vertexes[-1].Point): |
| | return True |
| | if DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[0].Point): |
| | return True |
| | if DraftVecUtils.equals(e1.Vertexes[-1].Point, e2.Vertexes[-1].Point): |
| | return True |
| | return False |
| |
|
| | edges = edgeslist[:] |
| | wires = [] |
| | lost = [] |
| | while edges: |
| | e = edges[0] |
| | if not wires: |
| | |
| | edges.remove(e) |
| | wires.append([e]) |
| | else: |
| | found = False |
| | for w in wires: |
| | if not found: |
| | for we in w: |
| | if touches(e, we): |
| | edges.remove(e) |
| | w.append(e) |
| | found = True |
| | break |
| | if not found: |
| | if e in lost: |
| | |
| | edges.remove(e) |
| | wires.append([e]) |
| | lost = [] |
| | else: |
| | |
| | edges.remove(e) |
| | edges.append(e) |
| | lost.append(e) |
| | nwires = [] |
| | for w in wires: |
| | try: |
| | wi = Part.Wire(w) |
| | except Part.OCCError: |
| | print("couldn't join some edges") |
| | else: |
| | nwires.append(wi) |
| | return nwires |
| |
|
| |
|
| | def findWiresOld(edges): |
| | """Return a list of lists containing edges that can be connected. |
| | |
| | Find connected edges in the list. |
| | """ |
| | raise DeprecationWarning( |
| | "This function shouldn't be called anymore. " "Use findWires() instead" |
| | ) |
| |
|
| | def verts(shape): |
| | return [shape.Vertexes[0].Point, shape.Vertexes[-1].Point] |
| |
|
| | def group(shapes): |
| | shapesIn = shapes[:] |
| | shapesOut = [shapesIn.pop()] |
| | changed = False |
| | for s in shapesIn: |
| | if len(s.Vertexes) < 2: |
| | continue |
| | else: |
| | clean = True |
| | for v in verts(s): |
| | for i in range(len(shapesOut)): |
| | if clean and (v in verts(shapesOut[i])): |
| | shapesOut[i] = Part.Wire(shapesOut[i].Edges + s.Edges) |
| | changed = True |
| | clean = False |
| | if clean: |
| | shapesOut.append(s) |
| | return changed, shapesOut |
| |
|
| | working = True |
| | edgeSet = edges |
| |
|
| | while working: |
| | result = group(edgeSet) |
| | working = result[0] |
| | edgeSet = result[1] |
| |
|
| | return result[1] |
| |
|
| |
|
| | def flattenWire(wire, origin=None, normal=None): |
| | """Force a wire to be flat on a plane defined by an origin and a normal. |
| | |
| | If origin or normal are None they are derived from the wire. |
| | """ |
| | if normal is None: |
| | normal = get_normal(wire) |
| | |
| | if normal is None: |
| | normal = App.Vector(0, 0, 1) |
| | if origin is None: |
| | origin = wire.Vertexes[0].Point |
| |
|
| | points = [project_point_on_plane(vert.Point, origin, normal) for vert in wire.Vertexes] |
| | if wire.isClosed(): |
| | points.append(points[0]) |
| | new_wire = Part.makePolygon(points) |
| |
|
| | return new_wire |
| |
|
| |
|
| | def superWire(edgeslist, closed=False): |
| | """Force a wire between edges that don't have coincident endpoints. |
| | |
| | Forces a wire between edges that don't necessarily |
| | have coincident endpoints. If closed=True, the wire will always be closed. |
| | """ |
| |
|
| | def median(v1, v2): |
| | vd = v2.sub(v1) |
| | vd.scale(0.5, 0.5, 0.5) |
| | return v1.add(vd) |
| |
|
| | edges = Part.__sortEdges__(edgeslist) |
| | print(edges) |
| | newedges = [] |
| |
|
| | for i in range(len(edges)): |
| | curr = edges[i] |
| | if i == 0: |
| | if closed: |
| | prev = edges[-1] |
| | else: |
| | prev = None |
| | else: |
| | prev = edges[i - 1] |
| |
|
| | if i == (len(edges) - 1): |
| | if closed: |
| | _next = edges[0] |
| | else: |
| | _next = None |
| | else: |
| | _next = edges[i + 1] |
| |
|
| | print(i, prev, curr, _next) |
| |
|
| | if prev: |
| | if curr.Vertexes[0].Point == prev.Vertexes[-1].Point: |
| | p1 = curr.Vertexes[0].Point |
| | else: |
| | p1 = median(curr.Vertexes[0].Point, prev.Vertexes[-1].Point) |
| | else: |
| | p1 = curr.Vertexes[0].Point |
| |
|
| | if _next: |
| | if curr.Vertexes[-1].Point == _next.Vertexes[0].Point: |
| | p2 = _next.Vertexes[0].Point |
| | else: |
| | p2 = median(curr.Vertexes[-1].Point, _next.Vertexes[0].Point) |
| | else: |
| | p2 = curr.Vertexes[-1].Point |
| |
|
| | if geomType(curr) == "Line": |
| | print("line", p1, p2) |
| | newedges.append(Part.LineSegment(p1, p2).toShape()) |
| | elif geomType(curr) == "Circle": |
| | p3 = findMidpoint(curr) |
| | print("arc", p1, p3, p2) |
| | newedges.append(Part.Arc(p1, p3, p2).toShape()) |
| | else: |
| | print("Cannot superWire edges that are not lines or arcs") |
| | return None |
| |
|
| | print(newedges) |
| | return Part.Wire(newedges) |
| |
|
| |
|
| | def isReallyClosed(wire): |
| | if isinstance(wire, (Part.Wire, Part.Edge)): |
| | return wire.isClosed() |
| | return isinstance(wire, Part.Face) |
| |
|
| |
|
| | def curvetowire(obj, steps): |
| | """Discretize the object and return a list of edges.""" |
| | points = obj.copy().discretize(steps) |
| | p0 = points[0] |
| | edgelist = [] |
| | for p in points[1:]: |
| | edge = Part.makeLine((p0.x, p0.y, p0.z), (p.x, p.y, p.z)) |
| | edgelist.append(edge) |
| | p0 = p |
| | return edgelist |
| |
|
| |
|
| | def curvetosegment(curve, seglen): |
| | """Discretize the curve and return a list of edges.""" |
| | points = curve.discretize(seglen) |
| | p0 = points[0] |
| | edgelist = [] |
| | for p in points[1:]: |
| | edge = Part.makeLine((p0.x, p0.y, p0.z), (p.x, p.y, p.z)) |
| | edgelist.append(edge) |
| | p0 = p |
| | return edgelist |
| |
|
| |
|
| | def rebaseWire(wire, vidx=0): |
| | """Return a copy of the wire with the first vertex indicated by the index. |
| | |
| | Return a new wire which is a copy of the current wire, |
| | but where the first vertex is the vertex indicated by the given |
| | index vidx, starting from 1. |
| | 0 will return an exact copy of the wire. |
| | """ |
| | if vidx < 1: |
| | return wire |
| |
|
| | if vidx > len(wire.Vertexes): |
| | |
| | return wire |
| |
|
| | |
| | return Part.Wire(wire.Edges[vidx - 1 :] + wire.Edges[: vidx - 1]) |
| |
|
| |
|
| | def removeInterVertices(wire): |
| | """Remove middle vertices from a straight wire and return a new wire. |
| | |
| | Remove unneeded vertices, those that are in the middle of a straight line, |
| | from a wire, return a new wire. |
| | """ |
| | _pre = precision() |
| | edges = Part.__sortEdges__(wire.Edges) |
| | nverts = [] |
| |
|
| | def getvec(v1, v2): |
| | if not abs(round(v1.getAngle(v2), _pre) in [0, round(math.pi, _pre)]): |
| | nverts.append(edges[i].Vertexes[-1].Point) |
| |
|
| | for i in range(len(edges) - 1): |
| | vA = vec(edges[i]) |
| | vB = vec(edges[i + 1]) |
| | getvec(vA, vB) |
| |
|
| | vA = vec(edges[-1]) |
| | vB = vec(edges[0]) |
| | getvec(vA, vB) |
| |
|
| | if nverts: |
| | if wire.isClosed(): |
| | nverts.append(nverts[0]) |
| | w = Part.makePolygon(nverts) |
| | return w |
| | else: |
| | return wire |
| |
|
| |
|
| | def cleanProjection(shape, tessellate=True, seglength=0.05): |
| | """Return a compound of edges, optionally tessellate ellipses, splines |
| | and bezcurves. |
| | |
| | The function was formerly used to workaround bugs in the projection |
| | algorithm. These bugs have since been fixed. Now the function is only |
| | used when tessellation of ellipses, splines and bezcurves is required |
| | (DXF output and Draft_Shape2DView). |
| | """ |
| | oldedges = shape.Edges |
| | newedges = [] |
| | for e in oldedges: |
| | typ = geomType(e) |
| | try: |
| | if typ in ["Line", "Circle"]: |
| | newedges.append(e) |
| | elif typ == "Ellipse": |
| | if tessellate: |
| | newedges.append(Part.Wire(curvetowire(e, seglength))) |
| | else: |
| | newedges.append(e) |
| | elif typ in ["BSplineCurve", "BezierCurve"]: |
| | if isLine(e.Curve): |
| | line = Part.LineSegment(e.Vertexes[0].Point, e.Vertexes[-1].Point) |
| | newedges.append(line) |
| | elif tessellate: |
| | newedges.append(Part.Wire(curvetowire(e, seglength))) |
| | else: |
| | newedges.append(e) |
| | else: |
| | newedges.append(e) |
| | except Part.OCCError: |
| | print("Debug: error cleaning edge ", e) |
| |
|
| | return Part.makeCompound(newedges) |
| |
|
| |
|
| | def tessellateProjection(shape, seglen): |
| | """Return projection with BSplines and Ellipses broken into line segments. |
| | |
| | Useful for exporting projected views to DXF files. |
| | """ |
| | oldedges = shape.Edges |
| | newedges = [] |
| | for e in oldedges: |
| | try: |
| | if geomType(e) == "Line": |
| | newedges.append(e.Curve.toShape()) |
| | elif geomType(e) == "Circle": |
| | newedges.append(e.Curve.toShape()) |
| | elif geomType(e) == "Ellipse": |
| | newedges.append(Part.Wire(curvetosegment(e, seglen))) |
| | elif geomType(e) == "BSplineCurve": |
| | newedges.append(Part.Wire(curvetosegment(e, seglen))) |
| | else: |
| | newedges.append(e) |
| | except Part.OCCError: |
| | print("Debug: error cleaning edge ", e) |
| |
|
| | return Part.makeCompound(newedges) |
| |
|
| |
|
| | def get_placement_perpendicular_to_wire(wire): |
| | """Return the placement whose base is the wire's first vertex and it's z axis aligned to the wire's tangent.""" |
| | pl = App.Placement() |
| | if wire.Length > 0.0: |
| | pl.Base = wire.OrderedVertexes[0].Point |
| | first_edge = wire.OrderedEdges[0] |
| | if first_edge.Orientation == "Forward": |
| | zaxis = -first_edge.tangentAt(first_edge.FirstParameter) |
| | else: |
| | zaxis = first_edge.tangentAt(first_edge.LastParameter) |
| | pl.Rotation = App.Rotation(App.Vector(1, 0, 0), App.Vector(0, 0, 1), zaxis, "ZYX") |
| | else: |
| | App.Console.PrintError( |
| | "debug: get_placement_perpendicular_to_wire called with a zero-length wire.\n" |
| | ) |
| | return pl |
| |
|
| |
|
| | def get_extended_wire(wire, offset_start, offset_end): |
| | """Return a wire trimmed (negative offset) or extended (positive offset) at its first vertex, last vertex or both ends. |
| | |
| | get_extended_wire(wire, -100.0, 0.0) -> returns a copy of the wire with its first 100 mm removed |
| | get_extended_wire(wire, 0.0, 100.0) -> returns a copy of the wire extended by 100 mm after it's last vertex |
| | """ |
| | if min(offset_start, offset_end, offset_start + offset_end) <= -wire.Length: |
| | App.Console.PrintError( |
| | "debug: get_extended_wire error, wire's length insufficient for trimming.\n" |
| | ) |
| | return wire |
| | if offset_start < 0: |
| | offset_start = -offset_start |
| | out_edges = [] |
| | for edge in wire.OrderedEdges: |
| | if offset_start >= edge.Length: |
| | offset_start -= edge.Length |
| | elif round(offset_start, precision()) > 0: |
| | if edge.Orientation == "Forward": |
| | new_edge = edge.split(edge.getParameterByLength(offset_start)).OrderedEdges[1] |
| | else: |
| | new_edge = edge.split( |
| | edge.getParameterByLength(edge.Length - offset_start) |
| | ).OrderedEdges[0] |
| | new_edge.Placement = ( |
| | edge.Placement |
| | ) |
| | new_edge.Orientation = edge.Orientation |
| | out_edges.append(new_edge) |
| | offset_start = 0 |
| | else: |
| | out_edges.append(edge) |
| | wire = Part.Wire(out_edges) |
| | elif offset_start > 0: |
| | first_edge = wire.OrderedEdges[0] |
| | if first_edge.Orientation == "Forward": |
| | start, end = first_edge.FirstParameter, first_edge.LastParameter |
| | vec = first_edge.tangentAt(start).multiply(offset_start) |
| | else: |
| | start, end = first_edge.LastParameter, first_edge.FirstParameter |
| | vec = -first_edge.tangentAt(start).multiply(offset_start) |
| | if geomType(first_edge) == "Line": |
| | new_edge = Part.LineSegment( |
| | first_edge.valueAt(start).sub(vec), first_edge.valueAt(end) |
| | ).toShape() |
| | wire = Part.Wire([new_edge] + wire.OrderedEdges[1:]) |
| | else: |
| | new_edge = Part.LineSegment( |
| | first_edge.valueAt(start).sub(vec), first_edge.valueAt(start) |
| | ).toShape() |
| | wire = Part.Wire([new_edge] + wire.OrderedEdges) |
| | if offset_end < 0: |
| | offset_end = -offset_end |
| | out_edges = [] |
| | for edge in reversed(wire.OrderedEdges): |
| | if offset_end >= edge.Length: |
| | offset_end -= edge.Length |
| | elif round(offset_end, precision()) > 0: |
| | if edge.Orientation == "Forward": |
| | new_edge = edge.split( |
| | edge.getParameterByLength(edge.Length - offset_end) |
| | ).OrderedEdges[0] |
| | else: |
| | new_edge = edge.split(edge.getParameterByLength(offset_end)).OrderedEdges[1] |
| | new_edge.Placement = ( |
| | edge.Placement |
| | ) |
| | new_edge.Orientation = edge.Orientation |
| | out_edges.insert(0, new_edge) |
| | offset_end = 0 |
| | else: |
| | out_edges.insert(0, edge) |
| | wire = Part.Wire(out_edges) |
| | elif offset_end > 0: |
| | last_edge = wire.OrderedEdges[-1] |
| | if last_edge.Orientation == "Forward": |
| | start, end = last_edge.FirstParameter, last_edge.LastParameter |
| | vec = last_edge.tangentAt(end).multiply(offset_end) |
| | else: |
| | start, end = last_edge.LastParameter, last_edge.FirstParameter |
| | vec = -last_edge.tangentAt(end).multiply(offset_end) |
| | if geomType(last_edge) == "Line": |
| | new_edge = Part.LineSegment( |
| | last_edge.valueAt(start), last_edge.valueAt(end).add(vec) |
| | ).toShape() |
| | wire = Part.Wire(wire.OrderedEdges[:-1] + [new_edge]) |
| | else: |
| | new_edge = Part.LineSegment( |
| | last_edge.valueAt(end), last_edge.valueAt(end).add(vec) |
| | ).toShape() |
| | wire = Part.Wire(wire.OrderedEdges + [new_edge]) |
| | return wire |
| |
|
| |
|
| | |
| |
|