From 1ad7b9788b2ed439d37ff392c8ab93c432014036 Mon Sep 17 00:00:00 2001 From: marioalexis Date: Fri, 9 Oct 2020 23:13:46 -0300 Subject: [PATCH] Improve make_sketch and geometric related functions --- src/Mod/Draft/DraftGeomUtils.py | 7 +- src/Mod/Draft/draftgeoutils/edges.py | 30 +- src/Mod/Draft/draftgeoutils/geometry.py | 306 +++++++++++++----- src/Mod/Draft/draftgeoutils/offsets.py | 7 +- src/Mod/Draft/draftgeoutils/wires.py | 10 +- .../Draft/draftguitools/gui_draft2sketch.py | 15 +- src/Mod/Draft/draftmake/make_sketch.py | 249 ++++++++------ src/Mod/Draft/draftobjects/patharray.py | 5 +- 8 files changed, 435 insertions(+), 194 deletions(-) diff --git a/src/Mod/Draft/DraftGeomUtils.py b/src/Mod/Draft/DraftGeomUtils.py index 4ce4f82520..f7ee879cfe 100644 --- a/src/Mod/Draft/DraftGeomUtils.py +++ b/src/Mod/Draft/DraftGeomUtils.py @@ -68,11 +68,16 @@ from draftgeoutils.general import (precision, from draftgeoutils.geometry import (findPerpendicular, findDistance, getSplineNormal, + get_spline_normal, getNormal, + get_normal, getRotation, isPlanar, + is_planar, calculatePlacement, - mirror) + mirror, + are_coplanar, + is_straight_line) from draftgeoutils.edges import (findEdge, orientEdge, diff --git a/src/Mod/Draft/draftgeoutils/edges.py b/src/Mod/Draft/draftgeoutils/edges.py index 9715080763..fc910c3cab 100644 --- a/src/Mod/Draft/draftgeoutils/edges.py +++ b/src/Mod/Draft/draftgeoutils/edges.py @@ -111,15 +111,27 @@ def isSameLine(e1, e2): return False -def isLine(bspline): +def is_line(bspline): """Return True if the given BSpline curve is a straight line.""" - step = bspline.LastParameter/10 - b = bspline.tangent(0) - for i in range(10): - if bspline.tangent(i * step) != b: - return False - return True +# previous implementation may fail for a multipole striaght spline due +# a second order error in tolerance, wich introduce a difference of 1e-14 +# in the values of the tangents. Also, may fail on a periodic spline. +# step = bspline.LastParameter/10 +# b = bspline.tangent(0) +# +# for i in range(10): +# if bspline.tangent(i * step) != b: +# return False + + start_point = bspline.StartPoint + end_point = bspline.EndPoint + dist_start_end = end_point.distanceToPoint(start_point) + if (dist_start_end == bspline.length()) < 1e-7: + return True + + return False + def invert(shape): @@ -209,4 +221,8 @@ def getTangent(edge, from_point=None): return None +# compatibility layer + +isLine = is_line + ## @} diff --git a/src/Mod/Draft/draftgeoutils/geometry.py b/src/Mod/Draft/draftgeoutils/geometry.py index c98d2a0040..ee64ed5b3d 100644 --- a/src/Mod/Draft/draftgeoutils/geometry.py +++ b/src/Mod/Draft/draftgeoutils/geometry.py @@ -196,70 +196,57 @@ def findDistance(point, edge, strict=False): return None -def getSplineNormal(edge): +def get_spline_normal(edge, tol=-1): """Find the normal of a BSpline edge.""" - startPoint = edge.valueAt(edge.FirstParameter) - endPoint = edge.valueAt(edge.LastParameter) - midParameter = (edge.FirstParameter - + (edge.LastParameter - edge.FirstParameter) / 2) - midPoint = edge.valueAt(midParameter) - v1 = midPoint - startPoint - v2 = midPoint - endPoint - n = v1.cross(v2) - n.normalize() - return n - -def getNormal(shape): - """Find the normal of a shape or list of points, if possible.""" - if isinstance(shape, (list, tuple)): - if len(shape) >= 3: - v1 = shape[1].sub(shape[0]) - v2 = shape[2].sub(shape[0]) - n = v2.cross(v1) - if n.Length: - return n + if is_straight_line(shape, tol): return None - n = App.Vector(0, 0, 1) - if shape.isNull(): - return n - - if (shape.ShapeType == "Face") and hasattr(shape, "normalAt"): - n = shape.copy().normalAt(0.5, 0.5) - elif shape.ShapeType == "Edge": - if geomType(shape.Edges[0]) in ["Circle", "Ellipse"]: - n = shape.Edges[0].Curve.Axis - elif (geomType(shape.Edges[0]) == "BSplineCurve" - or geomType(shape.Edges[0]) == "BezierCurve"): - n = getSplineNormal(shape.Edges[0]) + plane = edge.findPlane(tol) + if plane: + normal = plane.Axis + return normal else: - for e in shape.Edges: - if geomType(e) in ["Circle", "Ellipse"]: - n = e.Curve.Axis - break - elif (geomType(e) == "BSplineCurve" - or geomType(e) == "BezierCurve"): - n = getSplineNormal(e) - break + return None - e1 = vec(shape.Edges[0]) - for i in range(1, len(shape.Edges)): - e2 = vec(shape.Edges[i]) - if 0.1 < abs(e1.getAngle(e2)) < 3.14: - n = e1.cross(e2).normalize() - break + +def get_normal(shape, tol=-1): + """Find the normal of a shape or list of points, if possible.""" + + # for points + if isinstance(shape, (list, tuple)): + if len(shape) <= 2: + return None + else: + poly = Part.makePolygon(shape) + if is_straight_line(poly, tol): + return None + + plane = poly.findPlane(tol) + if plane: + normal = plane.Axis + return normal + else: + return None + + # for shapes + if is_straight_line(shape, tol): + return None + + else: + plane = find_plane(shape, tol) + if plane: + normal = plane.Axis + else: + return None # Check the 3D view to flip the normal if the GUI is available if App.GuiUp: - vdir = gui_utils.get_3d_view().getViewDirection() - if n.getAngle(vdir) < 0.78: - n = n.negative() + v_dir = gui_utils.get_3d_view().getViewDirection() + if normal.getAngle(v_dir) < 0.78: + normal = normal.negative() - if not n.Length: - return None - - return n + return normal def getRotation(v1, v2=App.Vector(0, 0, 1)): @@ -275,34 +262,196 @@ def getRotation(v1, v2=App.Vector(0, 0, 1)): return App.Rotation(axis, angle) -def isPlanar(shape): +def is_planar(shape, tol=-1): """Return True if the given shape or list of points is planar.""" - n = getNormal(shape) - if not n: - return False + # for points if isinstance(shape, list): if len(shape) <= 3: return True else: - for v in shape[3:]: - pv = v.sub(shape[0]) - rv = DraftVecUtils.project(pv, n) - if not DraftVecUtils.isNull(rv): - return False - else: - if len(shape.Vertexes) <= 3: - return True + poly = Part.makePolygon(shape) + if is_straight_line(poly, tol): + return True + plane = poly.findPlane(tol) + if plane: + return True + else: + return False - for p in shape.Vertexes[1:]: - pv = p.Point.sub(shape.Vertexes[0].Point) - rv = DraftVecUtils.project(pv, n) - if not DraftVecUtils.isNull(rv): + # for shapes + # because Part.Shape.findPlane return None for Vertex and straight edges + if shape.ShapeType == "Vertex": + return True + + if is_straight_line(shape, tol): + return True + + plane = find_plane(shape, tol) + if plane: + return True + else: + return False + + +def is_straight_line(shape, tol=-1): + """Return True if shape is a straight line. + function used in other methods because Part.Shape.findPlane assign a + plane and normal to straight wires creating priviliged directions + and to deal with straight wires with overlapped edges.""" + + if len(shape.Faces) != 0: + return False + + if len(shape.Edges) == 0: + return False + + if len(shape.Edges) >= 1: + start_edge = shape.Edges[0] + dir_start_edge = start_edge.tangentAt(start_edge.FirstParameter) + #set tolerance + if tol <=0: + err = shape.globalTolerance(tol) + else: + err = tol + + for edge in shape.Edges: + first_point = edge.firstVertex().Point + last_point = edge.lastVertex().Point + dir_edge = edge.tangentAt(edge.FirstParameter) + # chek if edge is curve or no parallel to start_edge + # because sin(x) = x + O(x**3), for small angular deflection it's + # enough use the cross product of directions (or dot with a normal) + if (abs(edge.Length - first_point.distanceToPoint(last_point)) > err + or dir_start_edge.cross(dir_edge).Length > err): return False return True +def are_coplanar(shape_a, shape_b, tol=-1): + """Return True if exist a plane containing both shapes.""" + + if not is_planar(shape_a, tol) or not is_planar(shape_b, tol): + return False + + if shape_a.isEqual(shape_b): + return True + + plane_a = find_plane(shape_a, tol) + plane_b = find_plane(shape_b, tol) + + #set tolerance + if tol <=0: + err = 1e-7 + else: + err = tol + + if plane_a and plane_b: + normal_a = plane_a.Axis + normal_b = plane_b.Axis + proj = plane_a.projectPoint(plane_b.Position) + if (normal_a.cross(normal_b).Length > err + or plane_b.Position.sub(proj).Length > err): + return False + else: + return True + + elif plane_a and not plane_b: + normal_a = plane_a.Axis + for vertex in shape_b.Vertexes: + dir_ver_b = vertex.Point.sub(plane_a.Position).normalize() + if abs(normal_a.dot(dir_ver_b)) > err: + proj = plane_a.projectPoint(vertex.Point) + if vertex.Point.sub(proj).Length > err: + return False + return True + + elif plane_b and not plane_a: + normal_b = plane_b.Axis + for vertex in shape_a.Vertexes: + dir_ver_a = vertex.Point.sub(plane_b.Position).normalize() + if abs(normal_b.dot(dir_ver_a)) > err: + proj = plane_b.projectPoint(vertex.Point) + if vertex.Point.sub(proj).Length > err: + return False + return True + # not normal_a and not normal_b: + else: + points_a = [vertex.Point for vertex in shape_a.Vertexes] + points_b = [vertex.Point for vertex in shape_b.Vertexes] + poly = Part.makePolygon(points_a + points_b) + if is_planar(poly, tol): + return True + else: + return False + + +def get_spline_surface_normal(shape, tol=-1): + """Check if shape formed by BSpline surfaces is planar and get normal. + If shape is not planar return None.""" + + if len(shape.Faces) == 0: + return None + + #set tolerance + if tol <=0: + err = shape.globalTolerance(tol) + else: + err = tol + + first_surf = shape.Faces[0].Surface + + if not first_surf.isPlanar(tol): + return None + + # find bounds of first_surf + u0, u1, v0, v1 = first_surf.bounds() + u = (u0 + u1)/2 + v = (v0 + v1)/2 + first_normal = first_surf.normal(u, v) + # chek if all faces are planar and parallel + for face in shape.Faces: + surf = face.Surface + if not surf.isPlanar(tol): + return None + u0, u1, v0, v1 = surf.bounds() + u = (u0 + u1)/2 + v = (v0 + v1)/2 + surf_normal = surf.normal(u, v) + if first_normal.cross(surf_normal).Length > err: + return None + + normal = first_normal + + return normal + +def find_plane(shape, tol=-1): + """Find the plane containing the shape if possible. + Use this function as a workaround due Part.Shape.findPlane + fail to find plane on BSpline surfaces.""" + + if shape.ShapeType == "Vertex": + return None + + if is_straight_line(shape, tol): + return None + + plane = shape.findPlane(tol) + if plane: + return plane + elif len(shape.Faces) >= 1: + # in case shape have BSpline surfaces + normal = get_spline_surface_normal(shape, tol) + if normal: + position = shape.CenterOfMass + return Part.Plane(position, normal) + else: + return None + else: + return None + + def calculatePlacement(shape): """Return a placement located in the center of gravity of the shape. @@ -310,11 +459,14 @@ def calculatePlacement(shape): of gravity of the shape, and oriented towards the shape's normal. Otherwise, it returns a null placement. """ - if not isPlanar(shape): + if not is_planar(shape): return App.Placement() pos = shape.BoundBox.Center - norm = getNormal(shape) + norm = get_normal(shape) + # for backward compatibility with previous getNormal implementation + if norm == None: + norm = App.Vector(0, 0, 1) pla = App.Placement() pla.Base = pos r = getRotation(norm) @@ -337,4 +489,14 @@ def mirror(point, edge): else: return None + +#compatibility layer + +getSplineNormal = get_spline_normal + +getNormal = get_normal + +isPlanar = is_planar + + ## @} diff --git a/src/Mod/Draft/draftgeoutils/offsets.py b/src/Mod/Draft/draftgeoutils/offsets.py index 88aca5cf8e..e931e33b9f 100644 --- a/src/Mod/Draft/draftgeoutils/offsets.py +++ b/src/Mod/Draft/draftgeoutils/offsets.py @@ -32,7 +32,7 @@ import FreeCAD as App import DraftVecUtils from draftgeoutils.general import geomType, vec -from draftgeoutils.geometry import getNormal +from draftgeoutils.geometry import get_normal from draftgeoutils.wires import isReallyClosed from draftgeoutils.intersections import wiresIntersect, connect @@ -223,7 +223,10 @@ def offsetWire(wire, dvec, bind=False, occ=False, if normal: norm = normal else: - norm = getNormal(wire) # norm = Vector(0, 0, 1) + norm = get_normal(wire) # norm = Vector(0, 0, 1) + # for backward compatibility with previous getNormal implementation + if norm == None: + norm = App.Vector(0, 0, 1) closed = isReallyClosed(wire) nedges = [] diff --git a/src/Mod/Draft/draftgeoutils/wires.py b/src/Mod/Draft/draftgeoutils/wires.py index 4d6133b8b1..8c6f9dfed9 100644 --- a/src/Mod/Draft/draftgeoutils/wires.py +++ b/src/Mod/Draft/draftgeoutils/wires.py @@ -29,11 +29,12 @@ 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 getNormal +from draftgeoutils.geometry import get_normal from draftgeoutils.edges import findMidpoint, isLine # Delay import of module until first use because it is heavy @@ -157,9 +158,10 @@ def findWiresOld(edges): def flattenWire(wire): """Force a wire to get completely flat along its normal.""" - n = getNormal(wire) - if not n: - return + n = get_normal(wire) + # for backward compatibility with previous getNormal implementation + if n == None: + n = App.Vector(0, 0, 1) o = wire.Vertexes[0].Point plane = WorkingPlane.plane() diff --git a/src/Mod/Draft/draftguitools/gui_draft2sketch.py b/src/Mod/Draft/draftguitools/gui_draft2sketch.py index acfed8c837..a3070ec574 100644 --- a/src/Mod/Draft/draftguitools/gui_draft2sketch.py +++ b/src/Mod/Draft/draftguitools/gui_draft2sketch.py @@ -36,7 +36,6 @@ into several individual Draft objects. ## \addtogroup draftguitools # @{ from PySide.QtCore import QT_TRANSLATE_NOOP - import FreeCADGui as Gui import Draft_rc import draftguitools.gui_base_original as gui_base_original @@ -90,7 +89,8 @@ class Draft2Sketch(gui_base_original.Modifier): for obj in sel: if obj.isDerivedFrom("Sketcher::SketchObject"): allDraft = False - elif obj.isDerivedFrom("Part::Part2DObjectPython"): + elif (obj.isDerivedFrom("Part::Part2DObjectPython") + or obj.isDerivedFrom("Part::Feature")): allSketches = False else: allDraft = False @@ -141,12 +141,13 @@ class Draft2Sketch(gui_base_original.Modifier): if obj.isDerivedFrom("Sketcher::SketchObject"): _cmd_list.append("obj" + str(n) + " = " + _cmd_df) - elif obj.isDerivedFrom("Part::Part2DObjectPython"): - _cmd_list.append("obj" + str(n) + " = " + _cmd_sk) - elif obj.isDerivedFrom("Part::Feature"): - # if (len(obj.Shape.Wires) == 1 - # or len(obj.Shape.Edges) == 1): + elif (obj.isDerivedFrom("Part::Part2DObjectPython") + or obj.isDerivedFrom("Part::Feature")): _cmd_list.append("obj" + str(n) + " = " + _cmd_sk) + #elif obj.isDerivedFrom("Part::Feature"): + # # if (len(obj.Shape.Wires) == 1 + # # or len(obj.Shape.Edges) == 1): + # _cmd_list.append("obj" + str(n) + " = " + _cmd_sk) n += 1 _cmd_list.append('FreeCAD.ActiveDocument.recompute()') self.commit(translate("draft", "Convert Draft/Sketch"), diff --git a/src/Mod/Draft/draftmake/make_sketch.py b/src/Mod/Draft/draftmake/make_sketch.py index c98d3c971d..2785fa0397 100644 --- a/src/Mod/Draft/draftmake/make_sketch.py +++ b/src/Mod/Draft/draftmake/make_sketch.py @@ -38,15 +38,16 @@ import draftutils.gui_utils as gui_utils from draftutils.translate import translate -def make_sketch(objectslist, autoconstraints=False, addTo=None, - delete=False, name="Sketch", radiusPrecision=-1): - """makeSketch(objectslist,[autoconstraints],[addTo],[delete],[name],[radiusPrecision]) +def make_sketch(objects_list, autoconstraints=False, addTo=None, + delete=False, name="Sketch", radiusPrecision=-1, tol=1e-3): + """makeSketch(objects_list,[autoconstraints],[addTo],[delete], + [name],[radiusPrecision],[tol]) - Makes a Sketch objectslist with the given Draft objects. + Makes a Sketch objects_list with the given Draft objects. Parameters ---------- - objectlist: can be single or list of objects of Draft type objects, + objects_list: can be single or list of objects of Draft type objects, Part::Feature, Part.Shape, or mix of them. autoconstraints(False): if True, constraints will be automatically added to @@ -57,14 +58,17 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, delete(False): if True, the original object will be deleted. If set to a string 'all' the object and all its linked object will be - deleted + deleted. - name('Sketch'): the name for the new sketch object + name('Sketch'): the name for the new sketch object. - radiusPrecision(-1): If <0, disable radius constraint. If =0, add indiviaul + radiusPrecision(-1): If <0, disable radius constraint. If =0, add individual radius constraint. If >0, the radius will be rounded according to this precision, and 'Equal' constraint will be added to curve with equal radius within precision. + + tol(1e-3): Tolerance used to check if the shapes are planar and coplanar. + Consider change to tol=-1 for a more accurate analisis. """ if not App.ActiveDocument: @@ -75,24 +79,72 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, from Sketcher import Constraint import Sketcher - StartPoint = 1 - EndPoint = 2 - MiddlePoint = 3 + start_point = 1 + end_point = 2 + middle_point = 3 deletable = None - if not isinstance(objectslist,(list,tuple)): - objectslist = [objectslist] - for obj in objectslist: + if App.GuiUp: + v_dir = gui_utils.get_3d_view().getViewDirection() + else: + v_dir = App.Base.Vector(0,0,-1) + + # lists to accumulate shapes with defined normal and undefined normal + shape_norm_yes = list() + shape_norm_no = list() + + if not isinstance(objects_list,(list,tuple)): + objects_list = [objects_list] + + for obj in objects_list: if isinstance(obj,Part.Shape): shape = obj elif not hasattr(obj,'Shape'): - App.Console.PrintError(translate("draft","not shape found")) + App.Console.PrintError(translate("draft","No shape found\n")) return None else: shape = obj.Shape - if not DraftGeomUtils.isPlanar(shape): - App.Console.PrintError(translate("draft","All Shapes must be co-planar")) + + if not DraftGeomUtils.is_planar(shape, tol): + App.Console.PrintError(translate("draft","All Shapes must be planar\n")) return None + + if DraftGeomUtils.get_normal(shape, tol): + shape_norm_yes.append(shape) + else: + shape_norm_no.append(shape) + + + shapes_list = shape_norm_yes + shape_norm_no + + # test if all shapes are coplanar + if len(shape_norm_yes) >= 1: + for shape in shapes_list[1:]: + if not DraftGeomUtils.are_coplanar(shapes_list[0], shape, tol): + App.Console.PrintError(translate("draft","All Shapes must be coplanar\n")) + return None + # define sketch normal + normal = DraftGeomUtils.get_normal(shapes_list[0], tol) + + else: + # supose all geometries are straight lines or points + points = [vertex.Point for shape in shapes_list for vertex in shape.Vertexes] + if len(points) >= 2: + poly = Part.makePolygon(points) + if not DraftGeomUtils.is_planar(poly, tol): + App.Console.PrintError(translate("draft","All Shapes must be coplanar\n")) + return None + normal = DraftGeomUtils.get_normal(poly, tol) + if not normal: + # all points aligned + poly_dir = poly.Edges[0].Curve.Direction + normal = (v_dir - v_dir.dot(poly_dir)*poly_dir).normalize() + normal = normal.negative() + else: + # only one point + normal = v_dir.negative() + + if addTo: nobj = addTo else: @@ -128,24 +180,23 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, else: return(edge) - rotation = None - for obj in objectslist: + + axis = App.Vector(0,0,1).cross(normal) + angle = DraftVecUtils.angle(normal, App.Vector(0,0,1))*App.Units.Radian + rotation = App.Rotation(axis, angle) + + for obj in objects_list: ok = False tp = utils.get_type(obj) if tp in ["Circle","Ellipse"]: if obj.Shape.Edges: - if rotation is None: - rotation = obj.Placement.Rotation edge = obj.Shape.Edges[0] if len(edge.Vertexes) == 1: - newEdge = DraftGeomUtils.orientEdge(edge) - nobj.addGeometry(newEdge) + newedge = DraftGeomUtils.orientEdge(edge, normal) + nobj.addGeometry(newedge) else: # make new ArcOfCircle - circle = DraftGeomUtils.orientEdge(edge) - angle = edge.Placement.Rotation.Angle - axis = edge.Placement.Rotation.Axis - circle.Center = DraftVecUtils.rotate(edge.Curve.Center, -angle, axis) + circle = DraftGeomUtils.orientEdge(edge, normal) first = math.radians(obj.FirstAngle) last = math.radians(obj.LastAngle) arc = Part.ArcOfCircle(circle, first, last) @@ -153,24 +204,36 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, addRadiusConstraint(edge) ok = True elif tp == "Rectangle": - if rotation is None: - rotation = obj.Placement.Rotation if obj.FilletRadius.Value == 0: for edge in obj.Shape.Edges: - nobj.addGeometry(DraftGeomUtils.orientEdge(edge)) + nobj.addGeometry(DraftGeomUtils.orientEdge(edge, normal)) + # TODO: the previous implementation for autoconstraints fails in front + # and side view. So the autoconstraints for wires is used. This need + # more checking if autoconstraints: - last = nobj.GeometryCount - 1 - segs = [last-3,last-2,last-1,last] - if obj.Placement.Rotation.Q == (0,0,0,1): - constraints.append(Constraint("Coincident",last-3,EndPoint,last-2,StartPoint)) - constraints.append(Constraint("Coincident",last-2,EndPoint,last-1,StartPoint)) - constraints.append(Constraint("Coincident",last-1,EndPoint,last,StartPoint)) - constraints.append(Constraint("Coincident",last,EndPoint,last-3,StartPoint)) - constraints.append(Constraint("Horizontal",last-3)) - constraints.append(Constraint("Vertical",last-2)) - constraints.append(Constraint("Horizontal",last-1)) - constraints.append(Constraint("Vertical",last)) + last = nobj.GeometryCount + segs = list(range(last-len(obj.Shape.Edges),last-1)) + for seg in segs: + constraints.append(Constraint("Coincident",seg,end_point,seg+1,start_point)) + if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"): + constraints.append(Constraint("Vertical",seg)) + elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"): + constraints.append(Constraint("Horizontal",seg)) + constraints.append(Constraint("Coincident",last-1,end_point,segs[0],start_point)) ok = True + # if autoconstraints: + # last = nobj.GeometryCount - 1 + # segs = [last-3,last-2,last-1,last] + # if obj.Placement.Rotation.Q == (0,0,0,1): + # constraints.append(Constraint("Coincident",last-3,end_point,last-2,start_point)) + # constraints.append(Constraint("Coincident",last-2,end_point,last-1,start_point)) + # constraints.append(Constraint("Coincident",last-1,end_point,last,start_point)) + # constraints.append(Constraint("Coincident",last,end_point,last-3,start_point)) + # constraints.append(Constraint("Horizontal",last-3)) + # constraints.append(Constraint("Vertical",last-2)) + # constraints.append(Constraint("Horizontal",last-1)) + # constraints.append(Constraint("Vertical",last)) + # ok = True elif tp in ["Wire","Polygon"]: if obj.FilletRadius.Value == 0: closed = False @@ -180,71 +243,58 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, closed = obj.Closed if obj.Shape.Edges: - if (len(obj.Shape.Vertexes) < 3): - e = obj.Shape.Edges[0] - nobj.addGeometry(Part.LineSegment(e.Curve,e.FirstParameter,e.LastParameter)) - else: - # Use the first three points to make a working plane. We've already - # checked to make sure everything is coplanar - plane = Part.Plane(*[i.Point for i in obj.Shape.Vertexes[:3]]) - normal = plane.Axis - if rotation is None: - axis = App.Vector(0,0,1).cross(normal) - angle = DraftVecUtils.angle(normal, App.Vector(0,0,1)) * App.Units.Radian - rotation = App.Rotation(axis, angle) - for edge in obj.Shape.Edges: - # edge.rotate(App.Vector(0,0,0), rotAxis, rotAngle) - edge = DraftGeomUtils.orientEdge(edge, normal) - nobj.addGeometry(edge) - if autoconstraints: - last = nobj.GeometryCount - segs = list(range(last-len(obj.Shape.Edges),last-1)) - for seg in segs: - constraints.append(Constraint("Coincident",seg,EndPoint,seg+1,StartPoint)) - if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"): - constraints.append(Constraint("Vertical",seg)) - elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"): - constraints.append(Constraint("Horizontal",seg)) - if closed: - constraints.append(Constraint("Coincident",last-1,EndPoint,segs[0],StartPoint)) + for edge in obj.Shape.Edges: + edge = DraftGeomUtils.orientEdge(edge, normal) + nobj.addGeometry(edge) + if autoconstraints: + last = nobj.GeometryCount + segs = list(range(last-len(obj.Shape.Edges),last-1)) + for seg in segs: + constraints.append(Constraint("Coincident",seg,end_point,seg+1,start_point)) + if DraftGeomUtils.isAligned(nobj.Geometry[seg],"x"): + constraints.append(Constraint("Vertical",seg)) + elif DraftGeomUtils.isAligned(nobj.Geometry[seg],"y"): + constraints.append(Constraint("Horizontal",seg)) + if closed: + constraints.append(Constraint("Coincident",last-1,end_point,segs[0],start_point)) ok = True + elif tp == "BSpline": if obj.Shape.Edges: - nobj.addGeometry(obj.Shape.Edges[0].Curve) + edge = DraftGeomUtils.orientEdge(obj.Shape.Edges[0], normal) + nobj.addGeometry(edge) nobj.exposeInternalGeometry(nobj.GeometryCount-1) ok = True + elif tp == "BezCurve": if obj.Shape.Edges: - bez = obj.Shape.Edges[0].Curve - bsp = bez.toBSpline(bez.FirstParameter,bez.LastParameter) - nobj.addGeometry(bsp) - nobj.exposeInternalGeometry(nobj.GeometryCount-1) + for piece in obj.Shape.Edges: + bez = piece.Curve + bsp = bez.toBSpline(bez.FirstParameter,bez.LastParameter).toShape() + edge = DraftGeomUtils.orientEdge(bsp.Edges[0], normal) + nobj.addGeometry(edge) + nobj.exposeInternalGeometry(nobj.GeometryCount-1) ok = True + # TODO: set coincident constraint for vertexes in multi-edge bezier curve + + elif tp == "Point": + shape = obj.Shape.copy() + if angle: + shape.rotate(App.Base.Vector(0,0,0), axis, -1*angle) + point = Part.Point(shape.Point) + nobj.addGeometry(point) + ok = True + elif tp == 'Shape' or hasattr(obj,'Shape'): shape = obj if tp == 'Shape' else obj.Shape - - if not DraftGeomUtils.isPlanar(shape): - App.Console.PrintError(translate("draft","The given object is not planar and cannot be converted into a sketch.")) - return None - if rotation is None: - #rotation = obj.Placement.Rotation - norm = DraftGeomUtils.getNormal(shape) - if norm: - rotation = App.Rotation(App.Vector(0,0,1),norm) - else: - App.Console.PrintWarning(translate("draft","Unable to guess the normal direction of this object")) - rotation = App.Rotation() - norm = obj.Placement.Rotation.Axis if not shape.Wires: for e in shape.Edges: # unconnected edges newedge = convertBezier(e) - nobj.addGeometry(DraftGeomUtils.orientEdge(newedge,norm,make_arc=True)) + nobj.addGeometry(DraftGeomUtils.orientEdge( + newedge, normal, make_arc=True)) addRadiusConstraint(newedge) - # if not addTo: - # nobj.Placement.Rotation = DraftGeomUtils.calculatePlacement(shape).Rotation - if autoconstraints: for wire in shape.Wires: last_count = nobj.GeometryCount @@ -252,7 +302,7 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, for edge in edges: newedge = convertBezier(edge) nobj.addGeometry(DraftGeomUtils.orientEdge( - newedge,norm,make_arc=True)) + newedge, normal, make_arc=True)) addRadiusConstraint(newedge) for i,g in enumerate(nobj.Geometry[last_count:]): if edges[i].Closed: @@ -277,25 +327,25 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, start2 = g2.value(g2.FirstParameter) if DraftVecUtils.equals(end1,start2) : constraints.append(Constraint( - "Coincident",seg,EndPoint,seg2,StartPoint)) + "Coincident",seg,end_point,seg2,start_point)) continue end2 = g2.value(g2.LastParameter) start1 = g.value(g.FirstParameter) if DraftVecUtils.equals(end2,start1): constraints.append(Constraint( - "Coincident",seg,StartPoint,seg2,EndPoint)) + "Coincident",seg,start_point,seg2,end_point)) elif DraftVecUtils.equals(start1,start2): constraints.append(Constraint( - "Coincident",seg,StartPoint,seg2,StartPoint)) + "Coincident",seg,start_point,seg2,start_point)) elif DraftVecUtils.equals(end1,end2): constraints.append(Constraint( - "Coincident",seg,EndPoint,seg2,EndPoint)) + "Coincident",seg,end_point,seg2,end_point)) else: for wire in shape.Wires: for edge in wire.OrderedEdges: newedge = convertBezier(edge) nobj.addGeometry(DraftGeomUtils.orientEdge( - newedge,norm,make_arc=True)) + newedge, normal, make_arc=True)) ok = True gui_utils.format_object(nobj,obj) if ok and delete and hasattr(obj,'Shape'): @@ -318,10 +368,9 @@ def make_sketch(objectslist, autoconstraints=False, addTo=None, except Exception as ex: App.Console.PrintWarning(translate("draft", "Failed to delete object {}: {}".format(obj.Label,ex))+"\n") - if rotation: - nobj.Placement.Rotation = rotation - else: - print("-----error!!! rotation is still None...") + + + nobj.Placement.Rotation = rotation nobj.addConstraint(constraints) return nobj diff --git a/src/Mod/Draft/draftobjects/patharray.py b/src/Mod/Draft/draftobjects/patharray.py index 493d72ae93..58e72e0245 100644 --- a/src/Mod/Draft/draftobjects/patharray.py +++ b/src/Mod/Draft/draftobjects/patharray.py @@ -444,7 +444,10 @@ def placements_on_path(shapeRotation, pathwire, count, xlate, align, Each copy will be distributed evenly. """ closedpath = DraftGeomUtils.isReallyClosed(pathwire) - normal = DraftGeomUtils.getNormal(pathwire) + normal = DraftGeomUtils.get_normal(pathwire) + # for backward compatibility with previous getNormal implementation + if normal == None: + normal = App.Vector(0, 0, 1) if forceNormal and normalOverride: normal = normalOverride