Draft: move functions to draftgeoutils.geometry

This commit is contained in:
vocx-fc
2020-05-22 01:43:20 -05:00
committed by Yorik van Havre
parent 339be2f4bb
commit 035a63c68b
3 changed files with 333 additions and 218 deletions

View File

@@ -35,6 +35,7 @@ SET (Draft_geoutils
draftgeoutils/intersections.py
draftgeoutils/sort_edges.py
draftgeoutils/faces.py
draftgeoutils/geometry.py
)
SET(Draft_tests

View File

@@ -402,36 +402,7 @@ def superWire(edgeslist, closed=False):
from draftgeoutils.edges import findMidpoint
def findPerpendicular(point, edgeslist, force=None):
"""
findPerpendicular(vector,wire,[force]):
finds the shortest perpendicular distance between a point and an edgeslist.
If force is specified, only the edge[force] will be considered, and it will be
considered infinite.
The function will return a list [vector_from_point_to_closest_edge,edge_index]
or None if no perpendicular vector could be found.
"""
if not isinstance(edgeslist,list):
try:
edgeslist = edgeslist.Edges
except:
return None
if (force is None):
valid = None
for edge in edgeslist:
dist = findDistance(point,edge,strict=True)
if dist:
if not valid: valid = [dist,edgeslist.index(edge)]
else:
if (dist.Length < valid[0].Length):
valid = [dist,edgeslist.index(edge)]
return valid
else:
edge = edgeslist[force]
dist = findDistance(point,edge)
if dist: return [dist,force]
else: return None
return None
from draftgeoutils.geometry import findPerpendicular
def offset(edge, vector, trim=False):
@@ -484,89 +455,17 @@ def isReallyClosed(wire):
if DraftVecUtils.equals(v1,v2): return True
return False
def getSplineNormal(edge):
"""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
return None
n = 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])
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
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
if FreeCAD.GuiUp:
import Draft
vdir = Draft.get3DView().getViewDirection()
if n.getAngle(vdir) < 0.78:
n = n.negative()
if not n.Length:
return None
return n
def getRotation(v1, v2=FreeCAD.Vector(0, 0, 1)):
"""Get the rotation Quaternion between 2 vectors."""
if (v1.dot(v2) > 0.999999) or (v1.dot(v2) < -0.999999):
# vectors are opposite
return None
axis = v1.cross(v2)
axis.normalize()
#angle = math.degrees(math.sqrt((v1.Length ^ 2) * (v2.Length ^ 2)) + v1.dot(v2))
angle = math.degrees(DraftVecUtils.angle(v1,v2,axis))
return FreeCAD.Rotation(axis,angle)
from draftgeoutils.geometry import getSplineNormal
def calculatePlacement(shape):
"""calculatePlacement(shape): if the given shape is planar, this function
returns a placement located at the center of gravity of the shape, and oriented
towards the shape's normal. Otherwise, it returns a null placement."""
if not isPlanar(shape):
return FreeCAD.Placement()
pos = shape.BoundBox.Center
norm = getNormal(shape)
pla = FreeCAD.Placement()
pla.Base = pos
r = getRotation(norm)
if r:
pla.Rotation = r
return pla
from draftgeoutils.geometry import getNormal
from draftgeoutils.geometry import getRotation
from draftgeoutils.geometry import calculatePlacement
def offsetWire(wire, dvec, bind=False, occ=False,
@@ -823,91 +722,7 @@ def offsetWire(wire, dvec, bind=False, occ=False,
from draftgeoutils.intersections import connect
def findDistance(point, edge, strict=False):
"""
findDistance(vector,edge,[strict]) - Returns a vector from the point to its
closest point on the edge. If strict is True, the vector will be returned
only if its endpoint lies on the edge. Edge can also be a list of 2 points.
"""
if isinstance(point, FreeCAD.Vector):
if isinstance(edge,list):
segment = edge[1].sub(edge[0])
chord = edge[0].sub(point)
norm = segment.cross(chord)
perp = segment.cross(norm)
dist = DraftVecUtils.project(chord,perp)
if not dist: return None
newpoint = point.add(dist)
if (dist.Length == 0):
return None
if strict:
s1 = newpoint.sub(edge[0])
s2 = newpoint.sub(edge[1])
if (s1.Length <= segment.Length) and (s2.Length <= segment.Length):
return dist
else:
return None
else: return dist
elif geomType(edge) == "Line":
segment = vec(edge)
chord = edge.Vertexes[0].Point.sub(point)
norm = segment.cross(chord)
perp = segment.cross(norm)
dist = DraftVecUtils.project(chord,perp)
if not dist: return None
newpoint = point.add(dist)
if (dist.Length == 0):
return None
if strict:
s1 = newpoint.sub(edge.Vertexes[0].Point)
s2 = newpoint.sub(edge.Vertexes[-1].Point)
if (s1.Length <= segment.Length) and (s2.Length <= segment.Length):
return dist
else:
return None
else: return dist
elif geomType(edge) == "Circle":
ve1 = edge.Vertexes[0].Point
if (len(edge.Vertexes) > 1):
ve2 = edge.Vertexes[-1].Point
else:
ve2 = None
center = edge.Curve.Center
segment = center.sub(point)
if segment.Length == 0:
return None
ratio = (segment.Length - edge.Curve.Radius) / segment.Length
dist = segment.multiply(ratio)
newpoint = Vector.add(point, dist)
if (dist.Length == 0):
return None
if strict and ve2:
ang1 = DraftVecUtils.angle(ve1.sub(center))
ang2 = DraftVecUtils.angle(ve2.sub(center))
angpt = DraftVecUtils.angle(newpoint.sub(center))
if ((angpt <= ang2 and angpt >= ang1) or (angpt <= ang1 and angpt >= ang2)):
return dist
else:
return None
else:
return dist
elif geomType(edge) == "BSplineCurve" or \
geomType(edge) == "BezierCurve":
try:
pr = edge.Curve.parameter(point)
np = edge.Curve.value(pr)
dist = np.sub(point)
except:
print("DraftGeomUtils: Unable to get curve parameter for point ",point)
return None
else:
return dist
else:
print("DraftGeomUtils: Couldn't project point")
return None
else:
print("DraftGeomUtils: Couldn't project point")
return None
from draftgeoutils.geometry import findDistance
def angleBisection(edge1, edge2):
@@ -948,29 +763,7 @@ def findClosestCircle(point, circles):
from draftgeoutils.faces import isCoplanar
def isPlanar(shape):
"""Check if the given shape or list of points is planar."""
n = getNormal(shape)
if not n:
return False
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
for p in shape.Vertexes[1:]:
pv = p.Point.sub(shape.Vertexes[0].Point)
rv = DraftVecUtils.project(pv,n)
if not DraftVecUtils.isNull(rv):
return False
return True
from draftgeoutils.geometry import isPlanar
def findWiresOld(edges):

View File

@@ -0,0 +1,321 @@
# ***************************************************************************
# * Copyright (c) 2009, 2010 Yorik van Havre <yorik@uncreated.net> *
# * Copyright (c) 2009, 2010 Ken Cline <cline@frii.com> *
# * *
# * This file is part of the FreeCAD CAx development system. *
# * *
# * This program is free software; you can redistribute it and/or modify *
# * it under the terms of the GNU Lesser General Public License (LGPL) *
# * as published by the Free Software Foundation; either version 2 of *
# * the License, or (at your option) any later version. *
# * for detail see the LICENCE text file. *
# * *
# * FreeCAD is distributed in the hope that it will be useful, *
# * but WITHOUT ANY WARRANTY; without even the implied warranty of *
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
# * GNU Library General Public License for more details. *
# * *
# * You should have received a copy of the GNU Library General Public *
# * License along with FreeCAD; if not, write to the Free Software *
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
# * USA *
# * *
# ***************************************************************************
"""Provides various functions for working with geometrical elements."""
## @package geometry
# \ingroup DRAFTGEOUTILS
# \brief Provides various functions for working with geometry.
import lazy_loader.lazy_loader as lz
import math
import FreeCAD
import DraftVecUtils
import draftutils.gui_utils as gui_utils
from draftgeoutils.general import geomType, vec
# Delay import of module until first use because it is heavy
Part = lz.LazyLoader("Part", globals(), "Part")
def findPerpendicular(point, edgeslist, force=None):
"""Find the perpendicular distance between a point and a list of edges.
If force is specified, only the edge[force] will be considered,
and it will be considered infinite.
Returns
-------
[vector_from_point_to_closest_edge, edge_index]
The vector and the index in the list.
None
If no perpendicular vector could be found.
"""
if not isinstance(edgeslist, list):
try:
edgeslist = edgeslist.Edges
except AttributeError:
print("Doesn't have 'Edges'")
return None
if force is None:
valid = None
for edge in edgeslist:
dist = findDistance(point, edge, strict=True)
if dist:
if not valid:
valid = [dist, edgeslist.index(edge)]
else:
if dist.Length < valid[0].Length:
valid = [dist, edgeslist.index(edge)]
return valid
else:
edge = edgeslist[force]
dist = findDistance(point, edge)
if dist:
return [dist, force]
else:
return None
return None
def findDistance(point, edge, strict=False):
"""Return a vector from the point to its closest point on the edge.
If `strict` is `True`, the vector will be returned
only if its endpoint lies on the `edge`.
Edge can also be a list of 2 points.
"""
if isinstance(point, FreeCAD.Vector):
if isinstance(edge, list):
segment = edge[1].sub(edge[0])
chord = edge[0].sub(point)
norm = segment.cross(chord)
perp = segment.cross(norm)
dist = DraftVecUtils.project(chord, perp)
if not dist:
return None
newpoint = point.add(dist)
if dist.Length == 0:
return None
if strict:
s1 = newpoint.sub(edge[0])
s2 = newpoint.sub(edge[1])
if (s1.Length <= segment.Length
and s2.Length <= segment.Length):
return dist
else:
return None
else:
return dist
elif geomType(edge) == "Line":
segment = vec(edge)
chord = edge.Vertexes[0].Point.sub(point)
norm = segment.cross(chord)
perp = segment.cross(norm)
dist = DraftVecUtils.project(chord, perp)
if not dist:
return None
newpoint = point.add(dist)
if (dist.Length == 0):
return None
if strict:
s1 = newpoint.sub(edge.Vertexes[0].Point)
s2 = newpoint.sub(edge.Vertexes[-1].Point)
if (s1.Length <= segment.Length
and s2.Length <= segment.Length):
return dist
else:
return None
else:
return dist
elif geomType(edge) == "Circle":
ve1 = edge.Vertexes[0].Point
if len(edge.Vertexes) > 1:
ve2 = edge.Vertexes[-1].Point
else:
ve2 = None
center = edge.Curve.Center
segment = center.sub(point)
if segment.Length == 0:
return None
ratio = (segment.Length - edge.Curve.Radius) / segment.Length
dist = segment.multiply(ratio)
newpoint = FreeCAD.Vector.add(point, dist)
if dist.Length == 0:
return None
if strict and ve2:
ang1 = DraftVecUtils.angle(ve1.sub(center))
ang2 = DraftVecUtils.angle(ve2.sub(center))
angpt = DraftVecUtils.angle(newpoint.sub(center))
if ((angpt <= ang2 and angpt >= ang1)
or (angpt <= ang1 and angpt >= ang2)):
return dist
else:
return None
else:
return dist
elif (geomType(edge) == "BSplineCurve"
or geomType(edge) == "BezierCurve"):
try:
pr = edge.Curve.parameter(point)
np = edge.Curve.value(pr)
dist = np.sub(point)
except Part.OCCError:
print("DraftGeomUtils: Unable to get curve parameter "
"for point ", point)
return None
else:
return dist
else:
print("DraftGeomUtils: Couldn't project point")
return None
else:
print("DraftGeomUtils: Couldn't project point")
return None
def getSplineNormal(edge):
"""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
return None
n = FreeCAD.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])
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
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
# Check the 3D view to flip the normal if the GUI is available
if FreeCAD.GuiUp:
vdir = gui_utils.get_3d_view().getViewDirection()
if n.getAngle(vdir) < 0.78:
n = n.negative()
if not n.Length:
return None
return n
def getRotation(v1, v2=FreeCAD.Vector(0, 0, 1)):
"""Get the rotation Quaternion between 2 vectors."""
if (v1.dot(v2) > 0.999999) or (v1.dot(v2) < -0.999999):
# vectors are opposite
return None
axis = v1.cross(v2)
axis.normalize()
# angle = math.degrees(math.sqrt(v1.Length^2 * v2.Length^2) + v1.dot(v2))
angle = math.degrees(DraftVecUtils.angle(v1, v2, axis))
return FreeCAD.Rotation(axis, angle)
def isPlanar(shape):
"""Return True if the given shape or list of points is planar."""
n = getNormal(shape)
if not n:
return False
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
for p in shape.Vertexes[1:]:
pv = p.Point.sub(shape.Vertexes[0].Point)
rv = DraftVecUtils.project(pv, n)
if not DraftVecUtils.isNull(rv):
return False
return True
def calculatePlacement(shape):
"""Return a placement located in the center of gravity of the shape.
If the given shape is planar, return a placement located at the center
of gravity of the shape, and oriented towards the shape's normal.
Otherwise, it returns a null placement.
"""
if not isPlanar(shape):
return FreeCAD.Placement()
pos = shape.BoundBox.Center
norm = getNormal(shape)
pla = FreeCAD.Placement()
pla.Base = pos
r = getRotation(norm)
if r:
pla.Rotation = r
return pla