Improve make_sketch and geometric related functions
This commit is contained in:
committed by
Yorik van Havre
parent
675fdd7c39
commit
aa88f3ded0
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
## @}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
## @}
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user