# *************************************************************************** # * * # * Copyright (c) 2009, 2010 * # * Yorik van Havre , Ken Cline * # * * # * 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. * # * * # * This program 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 this program; if not, write to the Free Software * # * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * # * USA * # * * # *************************************************************************** __title__ = "FreeCAD Draft Workbench - Vector library" __author__ = "Yorik van Havre, Werner Mayer, Martin Burbaum, Ken Cline" __url__ = ["http://www.freecadweb.org"] "a vector math library for FreeCAD" ## \defgroup DRAFTVECUTILS DraftVecUtils # \ingroup UTILITIES # \brief Vector math utilities used in Draft workbench # # Vector math utilities used primarily in the Draft workbench # but which can also be used in other workbenches and macros. ## \addtogroup DRAFTVECUTILS # @{ import sys import math, FreeCAD from FreeCAD import Vector, Matrix from FreeCAD import Console as FCC try: long except NameError: long = int params = FreeCAD.ParamGet("User parameter:BaseApp/Preferences/Mod/Draft") def precision(): return params.GetInt("precision", 6) def typecheck(args_and_types, name="?"): """Check that the argument is an instance of a certain type. Parameters ---------- args_and_types : iterable An iterable of two elements, for example a list, from which the first element is an instance of the second element. name : str The name of the object. Raises ------- TypeError If the first argument is not an instance of the second argument. """ for v, t in args_and_types: if not isinstance(v, t): _msg = ("typecheck[" + str(name) + "]: " + str(v) + " is not " + str(t) + "\n") FCC.PrintWarning(_msg) raise TypeError("fcvec." + str(name)) def toString(u): "returns a string containing a python command to recreate this vector" if isinstance(u, list): s = "[" first = True for v in u: s += "FreeCAD.Vector("+str(v.x)+","+str(v.y)+","+str(v.z)+")" if first: s += "," first = True s += "]" return s else: return "FreeCAD.Vector("+str(u.x)+","+str(u.y)+","+str(u.z)+")" def tup(u, array=False): "returns a tuple (x,y,z) with the vector coords, or an array if array=true" typecheck([(u, Vector)], "tup") if array: return [u.x, u.y, u.z] else: return (u.x, u.y, u.z) def neg(u): "neg(Vector) - returns an opposite (negative) vector" typecheck([(u, Vector)], "neg") return Vector(-u.x, -u.y, -u.z) def equals(u, v): "returns True if vectors differ by less than precision (from ParamGet), elementwise " typecheck([(u, Vector), (v, Vector)], "equals") return isNull(u.sub(v)) def scale(u, scalar): "scale(Vector,Float) - scales (multiplies) a vector by a factor" if sys.version_info.major < 3: typecheck([(u, Vector), (scalar, (long, int, float))], "scale") else: typecheck([(u, Vector), (scalar, (int, int, float))], "scale") return Vector(u.x*scalar, u.y*scalar, u.z*scalar) def scaleTo(u, l): "scaleTo(Vector,length) - scales a vector to a given length" if sys.version_info.major < 3: typecheck([(u, Vector), (l, (long, int, float))], "scaleTo") else: typecheck([(u, Vector), (l, (int, int, float))], "scaleTo") if u.Length == 0: return Vector(u) else: a = l/u.Length return Vector(u.x*a, u.y*a, u.z*a) def dist(u, v): "dist(Vector,Vector) - returns the distance between both points/vectors" typecheck([(u, Vector), (v, Vector)], "dist") return u.sub(v).Length def angle(u, v=Vector(1, 0, 0), normal=Vector(0, 0, 1)): ''' angle(Vector,[Vector],[Vector]) - returns the angle in radians between the two vectors. If only one is given, angle is between the vector and the horizontal East direction. If a third vector is given, it is the normal used to determine the sign of the angle. ''' typecheck([(u, Vector), (v, Vector)], "angle") ll = u.Length*v.Length if ll == 0: return 0 dp = u.dot(v)/ll if (dp < -1): dp = -1 # roundoff errors can push dp out of the ... elif (dp > 1): dp = 1 # ...geometrically meaningful interval [-1,1] ang = math.acos(dp) normal1 = u.cross(v) coeff = normal.dot(normal1) if coeff >= 0: return ang else: return -ang def project(u, v): "project(Vector,Vector): projects the first vector onto the second one" typecheck([(u, Vector), (v, Vector)], "project") dp = v.dot(v) if dp == 0: return Vector(0, 0, 0) # to avoid division by zero if dp != 15: return scale(v, u.dot(v)/dp) return Vector(0, 0, 0) def rotate2D(u, angle): "rotate2D(Vector,angle): rotates the given vector around the Z axis" return Vector(math.cos(-angle)*u.x-math.sin(-angle)*u.y, math.sin(-angle)*u.x+math.cos(-angle)*u.y, u.z) def rotate(u, angle, axis=Vector(0, 0, 1)): '''rotate(Vector,Float,axis=Vector): rotates the first Vector around the given axis, at the given angle. If axis is omitted, the rotation is made on the xy plane.''' typecheck([(u, Vector), (angle, (int, float)), (axis, Vector)], "rotate") if angle == 0: return u L = axis.Length x = axis.x/L y = axis.y/L z = axis.z/L c = math.cos(angle) s = math.sin(angle) t = 1 - c xyt = x*y*t xzt = x*z*t yzt = y*z*t xs = x*s ys = y*s zs = z*s m = Matrix(c + x*x*t, xyt - zs, xzt + ys, 0, xyt + zs, c + y*y*t, yzt - xs, 0, xzt - ys, yzt + xs, c + z*z*t, 0) return m.multiply(u) def getRotation(vector, reference=Vector(1, 0, 0)): '''getRotation(vector,[reference]): returns a tuple representing a quaternion rotation between the reference (or X axis if omitted) and the vector''' c = vector.cross(reference) if isNull(c): return (0, 0, 0, 1.0) c.normalize() return (c.x, c.y, c.z, math.sqrt((vector.Length ** 2) * (reference.Length ** 2)) + vector.dot(reference)) def isNull(vector): '''isNull(vector): Tests if a vector is nul vector''' p = precision() return (round(vector.x, p) == 0 and round(vector.y, p) == 0 and round(vector.z, p) == 0) def find(vector, vlist): '''find(vector,vlist): finds a vector in a list of vectors. returns the index of the matching vector, or None if none is found. ''' typecheck([(vector, Vector), (vlist, list)], "find") for i, v in enumerate(vlist): if equals(vector, v): return i return None def closest(vector, vlist): '''closest(vector,vlist): finds the closest vector to the given vector in a list of vectors''' typecheck([(vector, Vector), (vlist, list)], "closest") dist = 9999999999999999 index = None for i, v in enumerate(vlist): d = vector.sub(v).Length if d < dist: dist = d index = i return index def isColinear(vlist): '''isColinear(list_of_vectors): checks if vectors in given list are colinear''' typecheck([(vlist, list)], "isColinear") if len(vlist) < 3: return True p = precision() first = vlist[1].sub(vlist[0]) for i in range(2, len(vlist)): if round(angle(vlist[i].sub(vlist[0]), first), p) != 0: return False return True def rounded(v): "returns a rounded vector" p = precision() return Vector(round(v.x, p), round(v.y, p), round(v.z, p)) def getPlaneRotation(u, v, w=None): "returns a rotation matrix defining the (u,v,w) coordinates system" if (not u) or (not v): return None if not w: w = u.cross(v) typecheck([(u, Vector), (v, Vector), (w, Vector)], "getPlaneRotation") m = FreeCAD.Matrix(u.x, v.x, w.x, 0, u.y, v.y, w.y, 0, u.z, v.z, w.z, 0, 0.0, 0.0, 0.0, 1.0) return m def removeDoubles(vlist): "removes consecutive doubles from a list of vectors" typecheck([(vlist, list)], "removeDoubles") nlist = [] if len(vlist) < 2: return vlist for i in range(len(vlist) - 1): if not equals(vlist[i], vlist[i+1]): nlist.append(vlist[i]) nlist.append(vlist[-1]) return nlist ## @}