FreeCAD/src/Mod/Draft/WorkingPlane.py
2012-03-07 10:34:32 -03:00

248 lines
10 KiB
Python

#***************************************************************************
#* *
#* Copyright (c) 2009, 2010 *
#* Ken Cline <cline@frii.com> *
#* *
#* 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 *
#* *
#***************************************************************************
import FreeCAD, FreeCADGui, math
from FreeCAD import Vector
from draftlibs import fcvec
__title__="FreeCAD Working Plane utility"
__author__ = "Ken Cline"
__url__ = "http://free-cad.sourceforge.net"
'''
This module provides a class called plane to assist in selecting and maintaining a working plane.
'''
class plane:
'''A WorkPlane object'''
def __init__(self):
# keep track of active document. Reset view when doc changes.
self.doc = None
# self.weak is true if the plane has been defined by self.setup or has been reset
self.weak = True
# u, v axes and position define plane, perpendicular axis is handy, though redundant.
self.u = Vector(1,0,0)
self.v = Vector(0,1,0)
self.axis = Vector(0,0,1)
self.position = Vector(0,0,0)
# a placeholder for a stored state
self.stored = None
def __repr__(self):
return "Workplane x="+str(fcvec.rounded(self.u))+" y="+str(fcvec.rounded(self.v))+" z="+str(fcvec.rounded(self.axis))
def offsetToPoint(self, p, direction=None):
'''
Return the signed distance from p to the plane, such
that p + offsetToPoint(p)*direction lies on the plane.
direction defaults to -plane.axis
'''
'''
A picture will help explain the computation:
p
//|
/ / |
/ / |
/ / |
/ / |
-------------------- plane -----c-----x-----a--------
Here p is the specified point,
c is a point (in this case plane.position) on the plane
x is the intercept on the plane from p in the specified direction, and
a is the perpendicular intercept on the plane (i.e. along plane.axis)
Using vertival bars to denote the length operator,
|ap| = |cp| * cos(apc) = |xp| * cos(apx)
so
|xp| = |cp| * cos(apc) / cos(apx)
= (cp . axis) / (direction . axis)
'''
if direction == None: direction = self.axis
return direction.dot(self.position.sub(p))
def projectPoint(self, p, direction=None):
'''project point onto plane, default direction is orthogonal'''
if not direction: direction = self.axis
t = Vector(direction)
t.multiply(self.offsetToPoint(p, direction))
return p.add(t)
def alignToPointAndAxis(self, point, axis, offset, upvec=None):
self.doc = FreeCAD.ActiveDocument
self.axis = axis;
self.axis.normalize()
if (fcvec.equals(axis, Vector(1,0,0))):
self.u = Vector(0,1,0)
self.v = Vector(0,0,1)
elif (fcvec.equals(axis, Vector(-1,0,0))):
self.u = Vector(0,-1,0)
self.v = Vector(0,0,1)
elif upvec:
self.v = upvec
self.v.normalize()
self.u = self.v.cross(self.axis)
else:
self.v = axis.cross(Vector(1,0,0))
self.v.normalize()
self.u = fcvec.rotate(self.v, -math.pi/2, self.axis)
offsetVector = Vector(axis); offsetVector.multiply(offset)
self.position = point.add(offsetVector)
self.weak = False
# FreeCAD.Console.PrintMessage("(position = " + str(self.position) + ")\n")
# FreeCAD.Console.PrintMessage("Current workplane: x="+str(fcvec.rounded(self.u))+" y="+str(fcvec.rounded(self.v))+" z="+str(fcvec.rounded(self.axis))+"\n")
def alignToCurve(self, shape, offset):
if shape.ShapeType == 'Edge':
#??? TODO: process curve here. look at shape.edges[0].Curve
return False
elif shape.ShapeType == 'Wire':
#??? TODO: determine if edges define a plane
return False
else:
return False
def alignToFace(self, shape, offset=0):
# Set face to the unique selected face, if found
if shape.ShapeType == 'Face':
#we should really use face.tangentAt to get u and v here, and implement alignToUVPoint
self.alignToPointAndAxis(shape.Faces[0].CenterOfMass, shape.Faces[0].normalAt(0,0), offset)
return True
else:
return False
def alignToSelection(self, offset):
'''If selection uniquely defines a plane, align working plane to it. Return success (bool)'''
sex = FreeCADGui.Selection.getSelectionEx(FreeCAD.ActiveDocument.Name)
if len(sex) == 0:
return False
elif len(sex) == 1:
if not sex[0].Object.isDerivedFrom("Part::Shape"):
return False
return self.alignToCurve(sex[0].Object.Shape, offset) \
or self.alignToFace(sex[0].Object.Shape, offset) \
or (len(sex[0].SubObjects) == 1 and self.alignToFace(sex[0].SubObjects[0], offset))
else:
# len(sex) > 2, look for point and line, three points, etc.
return False
def setup(self, direction, point, upvec=None):
'''If working plane is undefined, define it!'''
if self.weak:
self.alignToPointAndAxis(point, direction, 0, upvec)
self.weak = True
def reset(self):
self.doc = None
self.weak = True
def getRotation(self):
"returns a placement describing the working plane orientation ONLY"
m = fcvec.getPlaneRotation(self.u,self.v,self.axis)
return FreeCAD.Placement(m)
def getPlacement(self):
"returns the placement of the working plane"
m = FreeCAD.Matrix(
self.u.x,self.v.x,self.axis.x,self.position.x,
self.u.y,self.v.y,self.axis.y,self.position.y,
self.u.z,self.v.z,self.axis.z,self.position.z,
0.0,0.0,0.0,1.0)
return FreeCAD.Placement(m)
def save(self):
"stores the current plane state"
self.stored = [self.u,self.v,self.axis,self.position,self.weak]
def restore(self):
"restores a previously saved plane state, if exists"
if self.stored:
self.u = self.stored[0]
self.v = self.stored[1]
self.axis = self.stored[2]
self.position = self.stored[3]
self.weak = self.stored[4]
self.stored = None
def getLocalCoords(self,point):
"returns the coordinates of a given point on the working plane"
xv = fcvec.project(point,self.u)
x = xv.Length
if xv.getAngle(self.u) > 1:
x = -x
yv = fcvec.project(point,self.v)
y = yv.Length
if yv.getAngle(self.v) > 1:
y = -y
zv = fcvec.project(point,self.axis)
z = zv.Length
if zv.getAngle(self.axis) > 1:
z = -z
return Vector(x,y,z)
def getGlobalCoords(self,point):
"returns the global coordinates of the given point, taken relatively to this working plane"
vx = fcvec.scale(self.u,point.x)
vy = fcvec.scale(self.v,point.y)
vz = fcvec.scale(self.axis,point.z)
return (vx.add(vy)).add(vz)
def getClosestAxis(self,point):
"returns which of the workingplane axes is closest from the given vector"
ax = point.getAngle(self.u)
ay = point.getAngle(self.v)
az = point.getAngle(self.axis)
bx = point.getAngle(fcvec.neg(self.u))
by = point.getAngle(fcvec.neg(self.v))
bz = point.getAngle(fcvec.neg(self.axis))
b = min(ax,ay,az,bx,by,bz)
if b in [ax,bx]:
return "x"
elif b in [ay,by]:
return "y"
elif b in [az,bz]:
return "z"
else:
return None
def getPlacementFromPoints(points):
"returns a placement from a list of 3 or 4 vectors"
pl = plane()
try:
pl.position = points[0]
pl.u = (points[1].sub(points[0]).normalize())
pl.v = (points[2].sub(points[0]).normalize())
if len(points) == 4:
pl.axis = (points[3].sub(points[0]).normalize())
else:
pl.axis = ((pl.u).cross(pl.v)).normalize()
except:
pass
p = pl.getPlacement()
del pl
return p