| | |
| |
|
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | """Provides general functions to work with topological shapes.""" |
| | |
| | |
| | |
| |
|
| | import math |
| | import lazy_loader.lazy_loader as lz |
| |
|
| | import FreeCAD as App |
| | import DraftVecUtils |
| | from draftutils import params |
| |
|
| | |
| | Part = lz.LazyLoader("Part", globals(), "Part") |
| |
|
| | |
| | |
| |
|
| | |
| | NORM = App.Vector(0, 0, 1) |
| |
|
| |
|
| | def precision(): |
| | """Return the Draft precision setting.""" |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | |
| | precisionMax = 10 |
| | precisionInt = params.get_param("precision") |
| | precisionInt = precisionInt if precisionInt <= 10 else precisionMax |
| | return precisionInt |
| |
|
| |
|
| | def vec(edge, use_orientation=False): |
| | """Return a vector from an edge or a Part.LineSegment. |
| | |
| | If use_orientation is True, it takes into account the edges orientation. |
| | If edge is not straight, you'll get strange results! |
| | """ |
| | if isinstance(edge, Part.Shape): |
| | if use_orientation and isinstance(edge, Part.Edge) and edge.Orientation == "Reversed": |
| | return edge.Vertexes[0].Point.sub(edge.Vertexes[-1].Point) |
| | else: |
| | return edge.Vertexes[-1].Point.sub(edge.Vertexes[0].Point) |
| | elif isinstance(edge, Part.LineSegment): |
| | return edge.EndPoint.sub(edge.StartPoint) |
| | else: |
| | return None |
| |
|
| |
|
| | def edg(p1, p2): |
| | """Return an edge from 2 vectors.""" |
| | if isinstance(p1, App.Vector) and isinstance(p2, App.Vector): |
| | if DraftVecUtils.equals(p1, p2): |
| | return None |
| |
|
| | return Part.LineSegment(p1, p2).toShape() |
| |
|
| |
|
| | def getVerts(shape): |
| | """Return a list containing vectors of each vertex of the shape.""" |
| | if not hasattr(shape, "Vertexes"): |
| | return [] |
| |
|
| | p = [] |
| | for v in shape.Vertexes: |
| | p.append(v.Point) |
| | return p |
| |
|
| |
|
| | def v1(edge): |
| | """Return the first point of an edge.""" |
| | return edge.Vertexes[0].Point |
| |
|
| |
|
| | def isNull(something): |
| | """Return True if the given shape, vector, or placement is Null. |
| | |
| | If the vector is (0, 0, 0), it will return True. |
| | """ |
| | if isinstance(something, Part.Shape): |
| | return something.isNull() |
| | elif isinstance(something, App.Vector): |
| | if something == App.Vector(0, 0, 0): |
| | return True |
| | else: |
| | return False |
| | elif isinstance(something, App.Placement): |
| | if something.Base == App.Vector(0, 0, 0) and something.Rotation.Q == (0, 0, 0, 1): |
| | return True |
| | else: |
| | return False |
| |
|
| |
|
| | def isPtOnEdge(pt, edge): |
| | """Test if a point lies on an edge.""" |
| | v = Part.Vertex(pt) |
| | try: |
| | d = v.distToShape(edge) |
| | except Part.OCCError: |
| | return False |
| | else: |
| | if d: |
| | if round(d[0], precision()) == 0: |
| | return True |
| | return False |
| |
|
| |
|
| | def hasCurves(shape): |
| | """Check if the given shape has curves.""" |
| | for e in shape.Edges: |
| | if not isinstance(e.Curve, (Part.LineSegment, Part.Line)): |
| | return True |
| | return False |
| |
|
| |
|
| | def isAligned(edge, axis="x"): |
| | """Check if the given edge or line is aligned to the given axis. |
| | |
| | The axis can be 'x', 'y' or 'z'. |
| | """ |
| |
|
| | def is_same(a, b): |
| | return round(a, precision()) == round(b, precision()) |
| |
|
| | if axis == "x": |
| | if isinstance(edge, Part.Edge): |
| | if len(edge.Vertexes) == 2: |
| | return is_same(edge.Vertexes[0].X, edge.Vertexes[-1].X) |
| | elif isinstance(edge, Part.LineSegment): |
| | return is_same(edge.StartPoint.x, edge.EndPoint.x) |
| |
|
| | elif axis == "y": |
| | if isinstance(edge, Part.Edge): |
| | if len(edge.Vertexes) == 2: |
| | return is_same(edge.Vertexes[0].Y, edge.Vertexes[-1].Y) |
| | elif isinstance(edge, Part.LineSegment): |
| | return is_same(edge.StartPoint.y, edge.EndPoint.y) |
| |
|
| | elif axis == "z": |
| | if isinstance(edge, Part.Edge): |
| | if len(edge.Vertexes) == 2: |
| | return is_same(edge.Vertexes[0].Z, edge.Vertexes[-1].Z) |
| | elif isinstance(edge, Part.LineSegment): |
| | return is_same(edge.StartPoint.z, edge.EndPoint.z) |
| |
|
| | return False |
| |
|
| |
|
| | def getQuad(face): |
| | """Return a list of 3 vectors if the face is a quad, otherwise None. |
| | |
| | Returns |
| | ------- |
| | basepoint, Xdir, Ydir |
| | If the face is a quad. |
| | |
| | None |
| | If the face is not a quad. |
| | """ |
| | if len(face.Edges) != 4: |
| | return None |
| |
|
| | v1 = vec(face.Edges[0]) |
| | v2 = vec(face.Edges[1]) |
| | v3 = vec(face.Edges[2]) |
| | v4 = vec(face.Edges[3]) |
| | angles90 = [round(math.pi * 0.5, precision()), round(math.pi * 1.5, precision())] |
| |
|
| | angles180 = [0, round(math.pi, precision()), round(math.pi * 2, precision())] |
| | for ov in [v2, v3, v4]: |
| | if not (round(v1.getAngle(ov), precision()) in angles90 + angles180): |
| | return None |
| |
|
| | for ov in [v2, v3, v4]: |
| | if round(v1.getAngle(ov), precision()) in angles90: |
| | v1.normalize() |
| | ov.normalize() |
| | return [face.Edges[0].Vertexes[0].Point, v1, ov] |
| |
|
| |
|
| | def areColinear(e1, e2): |
| | """Return True if both edges are colinear.""" |
| | if not isinstance(e1.Curve, (Part.LineSegment, Part.Line)): |
| | return False |
| | if not isinstance(e2.Curve, (Part.LineSegment, Part.Line)): |
| | return False |
| |
|
| | v1 = vec(e1) |
| | v2 = vec(e2) |
| | a = round(v1.getAngle(v2), precision()) |
| | if (a == 0) or (a == round(math.pi, precision())): |
| | v3 = e2.Vertexes[0].Point.sub(e1.Vertexes[0].Point) |
| | if DraftVecUtils.isNull(v3): |
| | return True |
| | else: |
| | a2 = round(v1.getAngle(v3), precision()) |
| | if (a2 == 0) or (a2 == round(math.pi, precision())): |
| | return True |
| | return False |
| |
|
| |
|
| | def hasOnlyWires(shape): |
| | """Return True if all edges are inside a wire.""" |
| | ne = 0 |
| | for w in shape.Wires: |
| | ne += len(w.Edges) |
| | if ne == len(shape.Edges): |
| | return True |
| | return False |
| |
|
| |
|
| | def geomType(edge): |
| | """Return the type of geometry this edge is based on. |
| | |
| | Parameters |
| | ---------- |
| | edge: the edge whose `Curve` attribute is to be checked. |
| | |
| | Returns |
| | ------- |
| | str |
| | Return the type of the edge's Curve attribute or "Unknown", |
| | if the parameter is missing. |
| | """ |
| | try: |
| | if isinstance(edge.Curve, (Part.LineSegment, Part.Line)): |
| | return "Line" |
| | elif isinstance(edge.Curve, Part.Circle): |
| | return "Circle" |
| | elif isinstance(edge.Curve, Part.BSplineCurve): |
| | return "BSplineCurve" |
| | elif isinstance(edge.Curve, Part.BezierCurve): |
| | return "BezierCurve" |
| | elif isinstance(edge.Curve, Part.Ellipse): |
| | return "Ellipse" |
| | else: |
| | return "Unknown" |
| | except Exception: |
| | return "Unknown" |
| |
|
| |
|
| | def isValidPath(shape): |
| | """Return True if the shape can be used as an extrusion path.""" |
| | if shape.isNull(): |
| | return False |
| | if shape.Faces: |
| | return False |
| | if len(shape.Wires) > 1: |
| | return False |
| | if shape.Wires: |
| | if shape.Wires[0].isClosed(): |
| | return False |
| | if shape.isClosed(): |
| | return False |
| | return True |
| |
|
| |
|
| | def findClosest(base_point, point_list): |
| | """Find closest point in a list of points to the base point. |
| | |
| | Returns |
| | ------- |
| | int |
| | An index from the list of points is returned. |
| | |
| | None |
| | If point_list is empty. |
| | """ |
| | npoint = None |
| | if not point_list: |
| | return None |
| |
|
| | smallest = 1000000 |
| | for n in range(len(point_list)): |
| | new = base_point.sub(point_list[n]).Length |
| | if new < smallest: |
| | smallest = new |
| | npoint = n |
| |
|
| | return npoint |
| |
|
| |
|
| | def getBoundaryAngles(angle, alist): |
| | """Return the 2 closest angles that encompass the given angle.""" |
| | negs = True |
| | while negs: |
| | negs = False |
| | for i in range(len(alist)): |
| | if alist[i] < 0: |
| | alist[i] = 2 * math.pi + alist[i] |
| | negs = True |
| | if angle < 0: |
| | angle = 2 * math.pi + angle |
| | negs = True |
| |
|
| | lower = None |
| | for a in alist: |
| | if a < angle: |
| | if lower is None: |
| | lower = a |
| | else: |
| | if a > lower: |
| | lower = a |
| |
|
| | if lower is None: |
| | lower = 0 |
| | for a in alist: |
| | if a > lower: |
| | lower = a |
| |
|
| | higher = None |
| | for a in alist: |
| | if a > angle: |
| | if higher is None: |
| | higher = a |
| | else: |
| | if a < higher: |
| | higher = a |
| |
|
| | if higher is None: |
| | higher = 2 * math.pi |
| | for a in alist: |
| | if a < higher: |
| | higher = a |
| |
|
| | return lower, higher |
| |
|
| |
|
| | |
| |
|