def __call__(self, input, error=False): X = self.scale_input(input) if not error: Y = dot(self.w, X) else: Y = dot(self.w, X), zeros(self.num_outputs) return self.scale_output(Y)
def _leftDown_preparation_for_dragging(self, objectUnderMouse, event): """ Handle left down event. Preparation for rotation and/or selection This method is called inside of self.leftDown. @param event: The mouse left down event. @type event: QMouseEvent instance @see: self.leftDown @see: self.leftDragRotation Overrides _superclass._leftDown_preparation_for_dragging """ _superclass._leftDown_preparation_for_dragging(self, objectUnderMouse, event) self.o.SaveMouse(event) self.picking = True self.dragdist = 0.0 # delta for constrained rotations. self.rotDelta = 0 if self.rotateOption == "ROTATEDEFAULT": self.o.trackball.start(self.o.MousePos[0], self.o.MousePos[1]) else: if self.rotateOption == "ROTATEX": ma = V(1, 0, 0) # X Axis self.axis = "X" elif self.rotateOption == "ROTATEY": ma = V(0, 1, 0) # Y Axis self.axis = "Y" elif self.rotateOption == "ROTATEZ": ma = V(0, 0, 1) # Z Axis self.axis = "Z" elif self.rotateOption == "ROT_TRANS_ALONG_AXIS": # The method 'self._leftDown_preparation_for_dragging should # never be reached if self.rotateOption is 'ROT_TRANS_ALONG_AXIS' # If this code is reached, it indicates a bug. So fail gracefully # by calling self.leftADown() if debug_flags.atom_debug: print_compact_stack( "bug: _leftDown_preparation_for_dragging" " called for rotate option" "'ROT_TRANS_ALONG_AXIS'" ) self.leftADown(objectUnderMouse, event) return else: print "Move_Command: Error - unknown rotateOption value =", self.rotateOption return ma = norm(V(dot(ma, self.o.right), dot(ma, self.o.up))) # When in the front view, right = 1,0,0 and up = 0,1,0, so ma will # be computed as 0,0.This creates a special case problem when the # user wants to constrain rotation around the Z axis because Zmat # will be zero. So we have to test for this case (ma = 0,0) and # fix ma to -1,0. This was needed to fix bug 537. Mark 050420 if ma[0] == 0.0 and ma[1] == 0.0: ma = [-1.0, 0.0] self.Zmat = A([ma, [-ma[1], ma[0]]]) self.leftDownType = "ROTATE" return
def __call__(self,input,error=False): X = self.scale_input(input) if not error: Y = dot(self.w,X) else: Y = dot(self.w,X), zeros(self.num_outputs) return self.scale_output(Y)
def __init__(self, point1=None, point2=None, slab=None): # Huaicai 4/23/05: added some comments below to help understand the code. if slab: # convert from 2d (x, y) coordinates into its 3d world (x, y, 0) #coordinates(the lower-left and upper-right corner). #In another word, the 3d coordinates minus the z offset of the plane x = dot(A(point1), A(point2)) # Get the vector from upper-right point to the lower-left point dx = subtract.reduce(x) # Get the upper-left and lower right corner points oc = x[1] + V(point2[0] * dot(dx, point2[0]), point2[1] * dot(dx, point2[1])) # Get the four 3d cooridinates on the bottom cookie-cutting plane sq1 = cat(x, oc) + slab.normal * dot(slab.point, slab.normal) # transfer the above 4 3d coordinates in parallel to get that on #the top plane, put them together sq1 = cat(sq1, sq1 + slab.thickness * slab.normal) self.data = V(maximum.reduce(sq1), minimum.reduce(sq1)) elif point2: # just 2 3d points self.data = V(maximum(point1, point2), minimum(point1, point2)) elif point1: # list of points: could be 2d or 3d? +/- 1.8 to make the bounding #box enclose the vDw ball of an atom? self.data = V( maximum.reduce(point1) + BBOX_MARGIN, minimum.reduce(point1) - BBOX_MARGIN) else: # a null bbox self.data = None
def __init__(self, point1 = None, point2 = None, slab = None): # Huaicai 4/23/05: added some comments below to help understand the code. if slab: # convert from 2d (x, y) coordinates into its 3d world (x, y, 0) #coordinates(the lower-left and upper-right corner). #In another word, the 3d coordinates minus the z offset of the plane x = dot(A(point1), A(point2)) # Get the vector from upper-right point to the lower-left point dx = subtract.reduce(x) # Get the upper-left and lower right corner points oc = x[1] + V(point2[0]*dot(dx,point2[0]), point2[1]*dot(dx,point2[1])) # Get the four 3d cooridinates on the bottom cookie-cutting plane sq1 = cat(x,oc) + slab.normal*dot(slab.point, slab.normal) # transfer the above 4 3d coordinates in parallel to get that on #the top plane, put them together sq1 = cat(sq1, sq1+slab.thickness*slab.normal) self.data = V(maximum.reduce(sq1), minimum.reduce(sq1)) elif point2: # just 2 3d points self.data = V(maximum(point1, point2), minimum(point1, point2)) elif point1: # list of points: could be 2d or 3d? +/- 1.8 to make the bounding #box enclose the vDw ball of an atom? self.data = V(maximum.reduce(point1) + BBOX_MARGIN, minimum.reduce(point1) - BBOX_MARGIN) else: # a null bbox self.data = None
def rtz(self, pt): d = pt - self.p0 z = dot(d, self.zn) d = d - z * self.zn r = vlen(d) theta = Numeric.arctan2(dot(d, self.v), dot(d, self.u)) return Numeric.array((r, theta, z), 'd')
def rtz(self, pt): d = pt - self.p0 z = dot(d, self.zn) d = d - z * self.zn r = vlen(d) theta = Numeric.arctan2(dot(d, self.v), dot(d, self.u)) return Numeric.array((r, theta, z), "d")
def drawLinearDimension(color, # what color are we drawing this in right, up, # screen directions mapped to xyz coords bpos, # position of the handle for moving the text p0, p1, # positions of the ends of the dimension text, highlighted=False): outOfScreen = cross(right, up) bdiff = bpos - 0.5 * (p0 + p1) csys = CylindricalCoordinates(p0, p1 - p0, bdiff, right) # This works OK until we want to keep the text right side up, then the # criterion for right-side-up-ness changes because we've changed 'up'. br, bt, bz = csys.rtz(bpos) e0 = csys.xyz((br + 0.5, 0, 0)) e1 = csys.xyz((br + 0.5, 0, 1)) drawline(color, p0, e0) drawline(color, p1, e1) v0 = csys.xyz((br, 0, 0)) v1 = csys.xyz((br, 0, 1)) if highlighted: drawline(color, v0, v1, width=THICKLINEWIDTH) else: drawline(color, v0, v1) # draw arrowheads at the ends a1, a2 = 0.25, 1.0 * csys.zinv arrow00 = csys.xyz((br + a1, 0, a2)) arrow01 = csys.xyz((br - a1, 0, a2)) drawline(color, v0, arrow00) drawline(color, v0, arrow01) arrow10 = csys.xyz((br + a1, 0, 1-a2)) arrow11 = csys.xyz((br - a1, 0, 1-a2)) drawline(color, v1, arrow10) drawline(color, v1, arrow11) # draw the text for the numerical measurement, make # sure it goes from left to right xflip = dot(csys.z, right) < 0 # then make sure it's right side up theoreticalRight = (xflip and -csys.z) or csys.z theoreticalOutOfScreen = cross(theoreticalRight, bdiff) yflip = dot(theoreticalOutOfScreen, outOfScreen) < 0 if debug_flags.atom_debug: print "DEBUG INFO FROM drawLinearDimension" print csys print theoreticalRight, theoreticalOutOfScreen print xflip, yflip if yflip: def fx(y): return br + 1.5 - y / (1. * HEIGHT) else: def fx(y): return br + 0.5 + y / (1. * HEIGHT) if xflip: def fz(x): return 0.9 - csys.zinv * x / (1. * WIDTH) else: def fz(x): return 0.1 + csys.zinv * x / (1. * WIDTH) def tfm(x, y, fx=fx, fz=fz): return csys.xyz((fx(y), 0, fz(x))) f3d = Font3D() f3d.drawString(text, tfm=tfm, color=color)
def Q(self,state,action=None): """ Compute Q(s,a) from W*s. """ if action is None: return dot(self.w, state) else: return dot(self.w[action],state)
def Q(self, state, action=None): """ Compute Q(s,a) from W*s. """ if action is None: return dot(self.w, state) else: return dot(self.w[action], state)
def go(self, xn): if (self.x == None): self.x = ones(len(self.b)) * xn * 1.0 self.y = ones(len(self.a)) * xn * 1.0 * sum(self.b) / (1+sum(self.a)) self.x = concatenate([[xn], self.x[:-1]]) yn = dot(self.b, self.x) - dot(self.a, self.y) self.y = concatenate([[yn], self.y[:-1]]) return yn
def constrainedPosition(self): a = self.atoms p0, p1, p2, p3 = a[0].posn(), a[1].posn(), a[2].posn(), a[3].posn() axis = norm(p2 - p1) midpoint = 0.5 * (p1 + p2) return _constrainHandleToAngle(self.center() + self.handle_offset, p0 - dot(p0 - midpoint, axis) * axis, midpoint, p3 - dot(p3 - midpoint, axis) * axis)
def rotateAboutPoint(self): """ Rotates the selected entities along the specified vector, about the specified pivot point (pivot point it the starting point of the drawn vector. """ if len(self.mouseClickPoints) != self.mouseClickLimit: print_compact_stack("Rotate about point bug: mouseclick points != mouseclicklimit: ") return pivotPoint = self.mouseClickPoints[0] ref_vec_endPoint = self.mouseClickPoints[1] rot_vec_endPoint = self.mouseClickPoints[2] reference_vec = norm(ref_vec_endPoint - pivotPoint) lineVector = norm(rot_vec_endPoint - pivotPoint) #lineVector = endPoint - startPoint quat1 = Q(lineVector, reference_vec) #DEBUG Disabled temporarily . will not be used if dot(lineVector, reference_vec) < 0: theta = math.pi - quat1.angle else: theta = quat1.angle #TEST_DEBUG-- Works fine theta = quat1.angle rot_axis = cross(lineVector, reference_vec) if dot(lineVector, reference_vec) < 0: rot_axis = - rot_axis cross_prod_1 = norm(cross(reference_vec, rot_axis)) cross_prod_2 = norm(cross(lineVector, rot_axis)) if dot(cross_prod_1, cross_prod_2) < 0: quat2 = Q(rot_axis, theta) else: quat2 = Q(rot_axis, - theta) movables = self.graphicsMode.getMovablesForLeftDragging() self.assy.rotateSpecifiedMovables( quat2, movables = movables, commonCenter = pivotPoint) self.glpane.gl_update() return
def rotateAboutPoint(self): """ Rotates the selected entities along the specified vector, about the specified pivot point (pivot point it the starting point of the drawn vector. """ startPoint = self.mouseClickPoints[0] endPoint = self.mouseClickPoints[1] pivotAtom = self.graphicsMode.pivotAtom #initial assignment of reference_vec. The selected movables will be #rotated by the angle between this vector and the lineVector reference_vec = self.glpane.right if isinstance(pivotAtom, Atom) and not pivotAtom.molecule.isNullChunk(): mol = pivotAtom.molecule reference_vec, node_junk = mol.getAxis_of_self_or_eligible_parent_node( atomAtVectorOrigin = pivotAtom) del node_junk else: reference_vec = self.glpane.right lineVector = endPoint - startPoint quat1 = Q(lineVector, reference_vec) #DEBUG Disabled temporarily . will not be used ##if dot(lineVector, reference_vec) < 0: ##theta = math.pi - quat1.angle ##else: ##theta = quat1.angle #TEST_DEBUG-- Works fine theta = quat1.angle rot_axis = cross(lineVector, reference_vec) if dot(lineVector, reference_vec) < 0: rot_axis = - rot_axis cross_prod_1 = norm(cross(reference_vec, rot_axis)) cross_prod_2 = norm(cross(lineVector, rot_axis)) if dot(cross_prod_1, cross_prod_2) < 0: quat2 = Q(rot_axis, theta) else: quat2 = Q(rot_axis, - theta) movables = self.graphicsMode.getMovablesForLeftDragging() self.assy.rotateSpecifiedMovables( quat2, movables = movables, commonCenter = startPoint) self.glpane.gl_update()
def project_2d_noeyeball(self, pt): """ Bruce: Project a point into our plane (ignoring eyeball). Warning: arbitrary origin! Huaicai 4/20/05: This is just to project pt into a 2d coordinate system (self.right, self.up) on a plane through pt and parallel to the screen plane. For perspective projection, (x, y) on this plane is different than that on the plane through pov. """ x, y = self.right, self.up return V(dot(pt, x), dot(pt, y))
def calc_torsion_angle(atom_list): """ Calculates torsional angle defined by four atoms, A1-A2-A3-A4, Return torsional angle value between atoms A2 and A3. @param atom_list: list of four atoms describing the torsion bond @type atom_list: list @return: value of the torsional angle (float) """ # Note: this appears to be very general and perhaps ought to be moved to a more # general place (someday), perhaps VQT.py or nearby. [bruce 080828 comment] from Numeric import dot from math import atan2, pi, sqrt from geometry.VQT import cross if len(atom_list) != 4: # The list has to have four members. return 0.0 # Calculate pairwise distances v12 = atom_list[0].posn() - atom_list[1].posn() v43 = atom_list[3].posn() - atom_list[2].posn() v23 = atom_list[1].posn() - atom_list[2].posn() # p is a vector perpendicular to the plane defined by atoms 1,2,3 # p is perpendicular to v23_v12 plane p = cross(v23, v12) # x is a vector perpendicular to the plane defined by atoms 2,3,4. # x is perpendicular to v23_v43 plane x = cross(v23, v43) # y is perpendicular to v23_x plane y = cross(v23, x) # Calculate lengths of the x, y vectors. u1 = dot(x, x) v1 = dot(y, y) if u1 < 0.0 or \ v1 < 0.0: return 360.0 u2 = dot(p, x) / sqrt(u1) v2 = dot(p, y) / sqrt(v1) if u2 != 0.0 and \ v2 != 0.0: # calculate the angle return atan2(v2, u2) * (180.0 / pi) else: return 360.0
def rotateAboutPoint(self): """ Rotates the selected entities along the specified vector, about the specified pivot point (pivot point it the starting point of the drawn vector. """ if len(self.mouseClickPoints) != self.mouseClickLimit: print_compact_stack( "Rotate about point bug: mouseclick points != mouseclicklimit: " ) return pivotPoint = self.mouseClickPoints[0] ref_vec_endPoint = self.mouseClickPoints[1] rot_vec_endPoint = self.mouseClickPoints[2] reference_vec = norm(ref_vec_endPoint - pivotPoint) lineVector = norm(rot_vec_endPoint - pivotPoint) #lineVector = endPoint - startPoint quat1 = Q(lineVector, reference_vec) #DEBUG Disabled temporarily . will not be used if dot(lineVector, reference_vec) < 0: theta = math.pi - quat1.angle else: theta = quat1.angle #TEST_DEBUG-- Works fine theta = quat1.angle rot_axis = cross(lineVector, reference_vec) if dot(lineVector, reference_vec) < 0: rot_axis = -rot_axis cross_prod_1 = norm(cross(reference_vec, rot_axis)) cross_prod_2 = norm(cross(lineVector, rot_axis)) if dot(cross_prod_1, cross_prod_2) < 0: quat2 = Q(rot_axis, theta) else: quat2 = Q(rot_axis, -theta) movables = self.graphicsMode.getMovablesForLeftDragging() self.assy.rotateSpecifiedMovables(quat2, movables=movables, commonCenter=pivotPoint) self.glpane.gl_update() return
def kfupdate(self, dt, rs): self.ab = array((rs.ax, -rs.ay, -rs.az)) ph = self.phi th = self.theta P = self.pmat A = array(((-rs.q*cos(ph)*tan(th)+rs.r*sin(ph)*tan(th), (-rs.q*sin(ph)+rs.r*cos(ph))/(cos(th)*cos(th))), (rs.q*sin(ph)-rs.r*cos(ph) , 0))) dph = rs.p - rs.q*sin(ph)*tan(th) - rs.r*cos(ph)*tan(th) dth = - rs.q*cos(ph) - rs.r*sin(ph) dP = dot(A, P) + dot(P, transpose(A)) + self.Q ph = ph + dph * dt th = th + dth * dt P = P + dP * dt Cx = array((0 , cos(th))) Cy = array((-cos(th)*cos(ph), sin(th)*sin(ph))) Cz = array((cos(th)*sin(ph) , sin(th)*cos(ph))) C = array((Cx, Cy, Cz)) L = dot(dot(P, transpose(C)), inverse(self.R + dot(dot(C, P), transpose(C)))) h = array((sin(th), -cos(th)*sin(ph), -cos(th)*cos(ph))) P = dot(identity(2) - dot(L, C), P) ph = ph + dot(L[0], self.ab - h) th = th + dot(L[1], self.ab - h) ph = ((ph+pi) % (2*pi)) - pi; th = ((th+pi) % (2*pi)) - pi; self.pmat = P self.phi = ph self.theta = th psidot = rs.q * sin(ph) / cos(th) + rs.r * cos(ph) / cos(th); self.psi += psidot * dt; self.quat = eul2quat(ph,th,self.psi) # self.dcmb2e = quat2dcm(quatinv(self.quat)) # self.ae = dot(self.dcmb2e, self.ab) self.ae = quatrotate(quatinv(self.quat), self.ab) self.ae[2] = -self.ae[2]-1 self.ae[1] = -self.ae[1] self.ve += self.ae * dt * 9.81 self.xe += self.ve * dt
def selection_polyhedron(basepos, borderwidth=1.8): """ Given basepos (a Numeric array of 3d positions), compute and return (as a list of vertices, each pair being an edge to draw) a simple bounding polyhedron, convenient for designating the approximate extent of this set of points. (This is used on the array of atom and open bond positions in a chunk to designate a selected chunk.) Make every face farther out than it needs to be to enclose all points in basepos, by borderwidth (default 1.8). Negative values of borderwidth might sometimes work, but are likely to fail drastically if the polyhedron is too small. The orientation of the faces matches the coordinate axes used in basepos (in typical calls, these are chunk-relative). [#e Someday we might permit caller-specified orientation axes. The axes of inertia would be interesting to try.] """ #bruce 060226/060605 made borderwidth an argument (non-default values untested); documented retval format #bruce 060119 split this out of shakedown_poly_evals_evecs_axis() in chunk.py. # Note, it has always had some bad bugs for certain cases, like long diagonal rods. if not len(basepos): return [] # a guess # find extrema in many directions xtab = dot(basepos, polyXmat) mins = minimum.reduce(xtab) - borderwidth maxs = maximum.reduce(xtab) + borderwidth polyhedron = makePolyList(cat(maxs, mins)) # apparently polyhedron is just a long list of vertices [v0,v1,v2,v3,...] to be passed to GL_LINES # (via drawlinelist), which will divide them into pairs and draw lines v0-v1, v2-v3, etc. # Maybe we should generalize the format, so some callers could also draw faces. # [bruce 060226/060605 comment] return polyhedron
def inertia_eigenvectors(basepos, already_centered=False): """ Given basepos (an array of positions), compute and return (as a 2-tuple) the lists of eigenvalues and eigenvectors of the inertia tensor (computed as if all points had the same mass). These lists are always length 3, even for len(basepos) of 0,1, or 2, overlapping or colinear points, etc, though some evals will be 0 in these cases. Optional small speedup: if caller knows basepos is centered at the origin, it can say so. """ #bruce 060119 split this out of shakedown_poly_evals_evecs_axis() in chunk.py basepos = A(basepos) # make sure it's a Numeric array if not already_centered and len(basepos): center = add.reduce(basepos) / len(basepos) basepos = basepos - center # compute inertia tensor tensor = zeros((3, 3), Float) for p in basepos: rsq = dot(p, p) m = -multiply.outer(p, p) m[0, 0] += rsq m[1, 1] += rsq m[2, 2] += rsq tensor += m evals, evecs = eigenvectors(tensor) assert len(evals) == len(evecs) == 3 return evals, evecs
def recompute_geom_both_ends_constrained(self): """ Using self.b0L_pvec and self.chain_quat_cum and self.bm1R_pvec, figure out self.twist... [used for rings, and for linear chains with both ends constrained] """ bonds = self.listb # what pvec would we have on the right end of the chain, if there were no per-bond twist? bm1R_pvec_no_twist = self.chain_quat_cum.rot( self.b0L_pvec ) # note, this is a vector perp to bond[-1] axism1 = self.axes[-1] # what twist is needed to put that vector over the actual one (or its neg), perhaps with 90-deg offset? i = len(bonds) - 1 ## if self.twist90 and i % 2 == 1: ## bm1R_pvec_no_twist = norm(cross(axism1, bm1R_pvec_no_twist)) # use negative if that fits better if dot(bm1R_pvec_no_twist, self.bm1R_pvec) < 0: bm1R_pvec_no_twist = - bm1R_pvec_no_twist # total twist is shared over every bond # note: we care which direction we rotate around axism1 total_twist = twistor_angle( axism1, bm1R_pvec_no_twist, self.bm1R_pvec) # in radians; angular range is undefined, for now # [it's actually +- 2pi, twice what it would need to be in general], # so we need to coerce it into the range we want, +- pi/2 (90 degrees); here too we use the fact # that in this case, a twist of pi (180 degrees) is the same as 0 twist. while total_twist > math.pi/2: total_twist -= math.pi while total_twist <= - math.pi/2: total_twist += math.pi self.twist = total_twist / len(bonds) return
def setup_quat_center(self, atomList = None): """ Setup the plane's quat using a list of atoms. If no atom list is supplied, the plane is centered in the glpane and parallel to the screen. @param atomList: A list of atoms. @type atomList: list """ if atomList: self.atomPos = [] for a in atomList: self.atomPos += [a.posn()] planeNorm = self._getPlaneOrientation(self.atomPos) if dot(planeNorm, self.glpane.lineOfSight) < 0: planeNorm = -planeNorm self.center = add.reduce(self.atomPos) / len(self.atomPos) self.quat = Q(V(0.0, 0.0, 1.0), planeNorm) else: self.center = V(0.0, 0.0, 0.0) # Following makes sure that Plane edges are parallel to # the 3D workspace borders. Fixes bug 2448 x, y ,z = self.glpane.right, self.glpane.up, self.glpane.out self.quat = Q(x, y, z) self.quat += Q(self.glpane.right, pi)
def InnerProduct(self, vector, other): """Innerproduct for vectors""" if vector.GetSpace() == other.GetSpace(): return dot(vector.GetCartesianCoordinates(), other.GetCartesianCoordinates()) else: raise ValueError, 'Innerproduct of two vectors in different vector space not implemented'
def project_2d(self, pt): """ like project_2d_noeyeball, but take into account self.eyeball; return None for a point that is too close to eyeball to be projected [in the future this might include anything too close to be drawn #e] """ p = self.project_2d_noeyeball(pt) if self.eyeball: # bruce 041214: use "pfix" to fix bug 30 comment #3 pfix = self.project_2d_noeyeball(self.org) p -= pfix try: ###e we recompute this a lot; should cache it in self or self.shp--Bruce ## Huaicai 04/23/05: made the change as suggested by Bruce above. p = p / (dot(pt - self.eyeball, self.normal) / self.eye2Pov) except: # bruce 041214 fix of unreported bug: # point is too close to eyeball for in-ness to be determined! # [More generally, do we want to include points which are # projectable without error, but too close to the eyeball # to be drawn? I think not, but I did not fix this yet # (or report the bug). ###e] if debug_flags.atom_debug: print_compact_traceback("atom_debug: ignoring math error for point near eyeball: ") return None p += pfix return p
def selection_polyhedron(basepos, borderwidth = 1.8): """ Given basepos (a Numeric array of 3d positions), compute and return (as a list of vertices, each pair being an edge to draw) a simple bounding polyhedron, convenient for designating the approximate extent of this set of points. (This is used on the array of atom and open bond positions in a chunk to designate a selected chunk.) Make every face farther out than it needs to be to enclose all points in basepos, by borderwidth (default 1.8). Negative values of borderwidth might sometimes work, but are likely to fail drastically if the polyhedron is too small. The orientation of the faces matches the coordinate axes used in basepos (in typical calls, these are chunk-relative). [#e Someday we might permit caller-specified orientation axes. The axes of inertia would be interesting to try.] """ #bruce 060226/060605 made borderwidth an argument (non-default values untested); documented retval format #bruce 060119 split this out of shakedown_poly_evals_evecs_axis() in chunk.py. # Note, it has always had some bad bugs for certain cases, like long diagonal rods. if not len(basepos): return [] # a guess # find extrema in many directions xtab = dot(basepos, polyXmat) mins = minimum.reduce(xtab) - borderwidth maxs = maximum.reduce(xtab) + borderwidth polyhedron = makePolyList(cat(maxs,mins)) # apparently polyhedron is just a long list of vertices [v0,v1,v2,v3,...] to be passed to GL_LINES # (via drawlinelist), which will divide them into pairs and draw lines v0-v1, v2-v3, etc. # Maybe we should generalize the format, so some callers could also draw faces. # [bruce 060226/060605 comment] return polyhedron
def inertia_eigenvectors(basepos, already_centered = False): """ Given basepos (an array of positions), compute and return (as a 2-tuple) the lists of eigenvalues and eigenvectors of the inertia tensor (computed as if all points had the same mass). These lists are always length 3, even for len(basepos) of 0,1, or 2, overlapping or colinear points, etc, though some evals will be 0 in these cases. Optional small speedup: if caller knows basepos is centered at the origin, it can say so. """ #bruce 060119 split this out of shakedown_poly_evals_evecs_axis() in chunk.py basepos = A(basepos) # make sure it's a Numeric array if not already_centered and len(basepos): center = add.reduce(basepos)/len(basepos) basepos = basepos - center # compute inertia tensor tensor = zeros((3,3),Float) for p in basepos: rsq = dot(p, p) m= - multiply.outer(p, p) m[0,0] += rsq m[1,1] += rsq m[2,2] += rsq tensor += m evals, evecs = eigenvectors(tensor) assert len(evals) == len(evecs) == 3 return evals, evecs
def makePolyList(v): xlines = [[],[],[],[],[],[],[],[],[],[],[],[]] segs = [] for corner, edges, planes in polyTab: linx = [] for i in range(3): l,s,r = planes[2*i:2*i+3] e = remainder(i+1,3) p1 = planepoint(v,corner,l,r) if abs(dot(p1,polyMat[s])) <= abs(v[s]): p2 = planepoint(v,l,s,r) linx += [p1] xlines[edges[i]] += [p2] xlines[edges[e]] += [p2] segs += [p1,p2] else: p1 = planepoint(v,corner,l,s) p2 = planepoint(v,corner,r,s) linx += [p1,p2] xlines[edges[i]] += [p1] xlines[edges[e]] += [p2] e=edges[0] xlines[e] = xlines[e][:-2] + [xlines[e][-1],xlines[e][-2]] for p1,p2 in zip(linx, linx[1:]+[linx[0]]): segs += [p1,p2] ctl = 12 for lis in xlines[:ctl]: segs += [lis[0],lis[3],lis[1],lis[2]] assert type(segs) == type([]) #bruce 041119 return segs
def closest_pt_params_to_ray(self, ray): "" p2, v2 = ray.params # note: at first I wrote self.params() (using method not attr) p1, v1 = self.params # do some math, solve for k in p1 + k * v1 = that point (remember that the vecs can be of any length): # way 1: express p2-p1 as a weighted sum of v1, v2, cross(v1,v2), then take the v1 term in that sum and add it to p1. # way 2: There must be a NumPy function that would just do this in about one step... # way 3: or maybe we can mess around with dot(v1,v2) sort of like in corner_analyzer in demo_polygon... # way 4: or we could google for "closest points on two lines" or so... # way 5: or we could call it the intersection of self with the plane containing p2, and directions v2 and the cross prod, # and use a formula in VQT. Yes, that may not be self-contained but it's fastest to code! v1n = norm(v1) v2n = norm(v2) perp0 = cross(v1n, v2n) if vlen(perp0) < 0.01: ##k btw what if the lines are parallel, in what way should we fail? # and for that matter what if they are *almost* parallel so that we're too sensitive -- do we use an env param # to decide whether to fail in that case too? If we're an Instance we could do that from self.env... #e print "closest_pt_params_to_ray: too sensitive, returning None" ###### teach caller to handle this; let 0.01 be option return None perpn = norm(perp0) perpperp = cross(perpn,v2n) inter = planeXline(p2, perpperp, p1, v1n) # intersect plane (as plane point and normal) with line (as point and vector) if inter is None: print "inter is None (unexpected); data:",p1,v1,p2,v2,perp0 return None # inter is the retval for a variant which just wants the closest point itself, i.e. closest_pt_to_ray return dot(inter - p1, v1n) / vlen(v1)
def recompute_geom_both_ends_constrained(self): """ Using self.b0L_pvec and self.chain_quat_cum and self.bm1R_pvec, figure out self.twist... [used for rings, and for linear chains with both ends constrained] """ bonds = self.listb # what pvec would we have on the right end of the chain, if there were no per-bond twist? bm1R_pvec_no_twist = self.chain_quat_cum.rot( self.b0L_pvec) # note, this is a vector perp to bond[-1] axism1 = self.axes[-1] # what twist is needed to put that vector over the actual one (or its neg), perhaps with 90-deg offset? i = len(bonds) - 1 ## if self.twist90 and i % 2 == 1: ## bm1R_pvec_no_twist = norm(cross(axism1, bm1R_pvec_no_twist)) # use negative if that fits better if dot(bm1R_pvec_no_twist, self.bm1R_pvec) < 0: bm1R_pvec_no_twist = -bm1R_pvec_no_twist # total twist is shared over every bond # note: we care which direction we rotate around axism1 total_twist = twistor_angle(axism1, bm1R_pvec_no_twist, self.bm1R_pvec) # in radians; angular range is undefined, for now # [it's actually +- 2pi, twice what it would need to be in general], # so we need to coerce it into the range we want, +- pi/2 (90 degrees); here too we use the fact # that in this case, a twist of pi (180 degrees) is the same as 0 twist. while total_twist > math.pi / 2: total_twist -= math.pi while total_twist <= -math.pi / 2: total_twist += math.pi self.twist = total_twist / len(bonds) return
def update_numberOfBases(self): """ Updates the numberOfBases in the PM while a resize handle is being dragged. @see: self.getCursorText() where it is called. """ #@Note: originally (before 2008-04-05, it was called in #DnaStrand_ResizeHandle.on_drag() but that 'may' have some bugs #(not verified) also see self.getCursorText() to know why it is #called there (basically a workaround for bug 2729 if self.grabbedHandle is None: return currentPosition = self.grabbedHandle.currentPosition resize_end = self.grabbedHandle.origin new_duplexLength = vlen( currentPosition - resize_end ) numberOfBasePairs_to_change = getNumberOfBasePairsFromDuplexLength('B-DNA', new_duplexLength) original_numberOfBases = self.struct.getNumberOfBases() #If the dot product of handle direction and the direction in which it #is dragged is negative, this means we need to subtract bases direction_of_drag = norm(currentPosition - resize_end) if dot(self.grabbedHandle.direction, direction_of_drag ) < 0: total_number_of_bases = original_numberOfBases - numberOfBasePairs_to_change self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases) else: total_number_of_bases = original_numberOfBases + numberOfBasePairs_to_change self.propMgr.numberOfBasesSpinBox.setValue(total_number_of_bases - 1)
def closest_pt_params_to_ray(self, ray): "" p2, v2 = ray.params # note: at first I wrote self.params() (using method not attr) p1, v1 = self.params # do some math, solve for k in p1 + k * v1 = that point (remember that the vecs can be of any length): # way 1: express p2-p1 as a weighted sum of v1, v2, cross(v1,v2), then take the v1 term in that sum and add it to p1. # way 2: There must be a NumPy function that would just do this in about one step... # way 3: or maybe we can mess around with dot(v1,v2) sort of like in corner_analyzer in demo_polygon... # way 4: or we could google for "closest points on two lines" or so... # way 5: or we could call it the intersection of self with the plane containing p2, and directions v2 and the cross prod, # and use a formula in VQT. Yes, that may not be self-contained but it's fastest to code! v1n = norm(v1) v2n = norm(v2) perp0 = cross(v1n, v2n) if vlen(perp0) < 0.01: ##k btw what if the lines are parallel, in what way should we fail? # and for that matter what if they are *almost* parallel so that we're too sensitive -- do we use an env param # to decide whether to fail in that case too? If we're an Instance we could do that from self.env... #e print "closest_pt_params_to_ray: too sensitive, returning None" ###### teach caller to handle this; let 0.01 be option return None perpn = norm(perp0) perpperp = cross(perpn, v2n) inter = planeXline( p2, perpperp, p1, v1n ) # intersect plane (as plane point and normal) with line (as point and vector) if inter is None: print "inter is None (unexpected); data:", p1, v1, p2, v2, perp0 return None # inter is the retval for a variant which just wants the closest point itself, i.e. closest_pt_to_ray return dot(inter - p1, v1n) / vlen(v1)
def __CM_Align_to_chunk(self): """ Rotary or Linear Motor context menu command: "Align to chunk" This uses the chunk connected to the first atom of the motor. """ # I needed this when attempting to simulate the rotation of a long, skinny # chunk. The axis computed from the attached atoms was not close to the axis # of the chunk. I figured this would be a common feature that was easy to add. # ##e it might be nice to dim this menu item if the chunk's axis hasn't moved since this motor was made or recentered; # first we'd need to extend the __CM_ API to make that possible. [mark 050717] cmd = greenmsg("Align to Chunk: ") chunk = self.atoms[0].molecule # Get the chunk attached to the motor's first atom. # wware 060116 bug 1330 # The chunk's axis could have its direction exactly reversed and be equally valid. # We should choose between those two options for which one has the positive dot # product with the old axis, to avoid reversals of motor direction when going # between "align to chunk" and "recenter on atoms". #bruce 060116 modified this fix to avoid setting axis to V(0,0,0) if it's perpendicular to old axis. newAxis = chunk.getaxis() if dot(self.axis,newAxis) < 0: newAxis = - newAxis self.axis = newAxis self.assy.changed() # wware 060116 bug 1331 - assembly changed when axis changed info = "Aligned motor [%s] on chunk [%s]" % (self.name, chunk.name) env.history.message( cmd + info ) self.assy.w.win_update() return
def viewParallelTo(self): """ Set view parallel to the vector defined by 2 selected atoms. """ cmd = greenmsg("Set View Parallel To: ") atoms = self.assy.selatoms_list() if len(atoms) != 2: msg = redmsg("You must select 2 atoms.") env.history.message(cmd + msg) return v = norm(atoms[0].posn()-atoms[1].posn()) if vlen(v) < 0.0001: # Atoms are on top of each other. info = 'The selected atoms are on top of each other. No change in view.' env.history.message(cmd + info) return # If vec is pointing into the screen, negate (reverse) vec. if dot(v, self.glpane.lineOfSight) > 0: v = -v # Compute the destination quat (q2). q2 = Q(V(0,0,1), v) q2 = q2.conj() self.glpane.rotateView(q2) info = 'View set parallel to the vector defined by the 2 selected atoms.' env.history.message(cmd + info)
def makePolyList(v): xlines = [[], [], [], [], [], [], [], [], [], [], [], []] segs = [] for corner, edges, planes in polyTab: linx = [] for i in range(3): l, s, r = planes[2 * i:2 * i + 3] e = remainder(i + 1, 3) p1 = planepoint(v, corner, l, r) if abs(dot(p1, polyMat[s])) <= abs(v[s]): p2 = planepoint(v, l, s, r) linx += [p1] xlines[edges[i]] += [p2] xlines[edges[e]] += [p2] segs += [p1, p2] else: p1 = planepoint(v, corner, l, s) p2 = planepoint(v, corner, r, s) linx += [p1, p2] xlines[edges[i]] += [p1] xlines[edges[e]] += [p2] e = edges[0] xlines[e] = xlines[e][:-2] + [xlines[e][-1], xlines[e][-2]] for p1, p2 in zip(linx, linx[1:] + [linx[0]]): segs += [p1, p2] ctl = 12 for lis in xlines[:ctl]: segs += [lis[0], lis[3], lis[1], lis[2]] assert type(segs) == type([]) #bruce 041119 return segs
def getDnaRibbonParams(self): """ Returns parameters for drawing the dna ribbon. If the dna rubberband line should NOT be drawn (example when you are removing bases from the strand or if its unable to get dnaSegment) , it retuns None. So the caller should check if the method return value is not None. @see: DnaStrand_GraphicsMode._draw_handles() """ if self.grabbedHandle is None: return None if self.grabbedHandle.origin is None: return None direction_of_drag = norm(self.grabbedHandle.currentPosition - \ self.grabbedHandle.origin) #If the strand bases are being removed (determined by checking the #direction of drag) , no need to draw the rubberband line. if dot(self.grabbedHandle.direction, direction_of_drag) < 0: return None strandEndAtom, axisEndAtom = self.get_strand_and_axis_endAtoms_at_resize_end() #DnaStrand.get_DnaSegment_with_content_atom saely handles the case where #strandEndAtom is None. dnaSegment = self.struct.get_DnaSegment_with_content_atom(strandEndAtom) ribbon1_direction = None if dnaSegment: basesPerTurn = dnaSegment.getBasesPerTurn() duplexRise = dnaSegment.getDuplexRise() ribbon1_start_point = strandEndAtom.posn() if strandEndAtom: ribbon1_start_point = strandEndAtom.posn() for bond_direction, neighbor in strandEndAtom.bond_directions_to_neighbors(): if neighbor and neighbor.is_singlet(): ribbon1_direction = bond_direction break ribbon1Color = strandEndAtom.molecule.color if not ribbon1Color: ribbon1Color = strandEndAtom.element.color return (self.grabbedHandle.origin, self.grabbedHandle.currentPosition, basesPerTurn, duplexRise, ribbon1_start_point, ribbon1_direction, ribbon1Color ) return None
def __init__(self, point0, z, uhint, uhint2): # u and v and zn are unit vectors # z is NOT a unit vector self.p0 = point0 self.p1 = point1 = point0 + z self.z = z zlen = vlen(z) if zlen < 1.0e-6: raise ZeroLengthCylinder() self.zinv = 1.0 / zlen self.zn = zn = norm(z) u = norm(uhint - (dot(uhint, z) / zlen**2) * z) if vlen(u) < 1.0e-4: u = norm(uhint2 - (dot(uhint2, z) / zlen**2) * z) v = cross(zn, u) self.u = u self.v = v
def __init__(self, point0, z, uhint, uhint2): # u and v and zn are unit vectors # z is NOT a unit vector self.p0 = point0 self.p1 = point1 = point0 + z self.z = z zlen = vlen(z) if zlen < 1.0e-6: raise ZeroLengthCylinder() self.zinv = 1.0 / zlen self.zn = zn = norm(z) u = norm(uhint - (dot(uhint, z) / zlen ** 2) * z) if vlen(u) < 1.0e-4: u = norm(uhint2 - (dot(uhint2, z) / zlen ** 2) * z) v = cross(zn, u) self.u = u self.v = v
def mymousepoints(glpane, x, y): #bruce 071017 moved this here from testdraw.py ### TODO: rename, docstring # modified from GLPane.mousepoints; x and y are window coords (except y is 0 at bottom, positive as you go up [guess 070124]) self = glpane just_beyond = 0.0 p1 = A(gluUnProject(x, y, just_beyond)) p2 = A(gluUnProject(x, y, 1.0)) los = self.lineOfSight # isn't this just norm(p2 - p1)?? Probably not, if we're in perspective mode! [bruce Q 061206] # note: this might be in abs coords (not sure!) even though p1 and p2 would be in local coords. # I need to review that in GLPane.__getattr__. ###k k = dot(los, -self.pov - p1) / dot(los, p2 - p1) p2 = p1 + k*(p2-p1) return (p1, p2)
def mvmul(A, x): """Multiply matrix A onto vector x """ from Numeric import dot, reshape x = reshape(x, (A.shape[1], 1)) #Make x a column vector return dot(A, x)
def computeEndPointsFromChunk(self, chunk, update = True): """ Derives and returns the endpoints and radius of a Peptide chunk. @param chunk: a Peptide chunk @type chunk: Chunk @return: endPoint1, endPoint2 and radius @rtype: Point, Point and float @note: computing the endpoints works fine when n=m or m=0. Otherwise, the endpoints can be slightly off the central axis, especially if the Peptide is short. @attention: endPoint1 and endPoint2 may not be the original endpoints, and they may be flipped (opposites of) the original endpoints. """ # Since chunk.axis is not always one of the vectors chunk.evecs # (actually chunk.poly_evals_evecs_axis[2]), it's best to just use # the axis and center, then recompute a bounding cylinder. if not chunk.atoms: return None axis = chunk.axis axis = norm(axis) # needed center = chunk._get_center() points = chunk.atpos - center # not sure if basepos points are already centered # compare following Numeric Python code to findAtomUnderMouse and its caller matrix = matrix_putting_axis_at_z(axis) v = dot( points, matrix) # compute xy distances-squared between axis line and atom centers r_xy_2 = v[:,0]**2 + v[:,1]**2 # to get radius, take maximum -- not sure if max(r_xy_2) would use Numeric code, but this will for sure: i = argmax(r_xy_2) max_xy_2 = r_xy_2[i] radius = sqrt(max_xy_2) # to get limits along axis (since we won't assume center is centered between them), use min/max z: z = v[:,2] min_z = z[argmin(z)] max_z = z[argmax(z)] # Adjust the endpoints such that the ladder rungs (rings) will fall # on the ring segments. # TO DO: Fix drawPeptideLadder() to offset the first ring, then I can # remove this adjustment. --Mark 2008-04-12 z_adjust = self.getEndPointZOffset() min_z += z_adjust max_z -= z_adjust endpoint1 = center + min_z * axis endpoint2 = center + max_z * axis if update: #print "Original endpoints:", self.getEndPoints() self.setEndPoints(endpoint1, endpoint2) #print "New endpoints:", self.getEndPoints() return (endpoint1, endpoint2, radius)
def projectPointOntoPlane(point, planeNormal, planePoint): planeNormal = normalise(planeNormal) relVector = vectorDifference(planePoint, point) dist = dot(relVector,planeNormal) oldpoint =point point = vectorAdd(point, (scalarMultiply(dist, planeNormal))) return point
def InnerProduct(self,vector,other): """Innerproduct for vectors""" if vector.GetSpace()==other.GetSpace(): from Numeric import matrixmultiply,transpose # (v,o)=(v_coor^T*basis^T*basis*o_coor) metric=matrixmultiply(self.GetBasis(),transpose(self.GetBasis())) return dot(vector.GetCoordinates(),matrixmultiply(metric,other.GetCoordinates())) else: raise ValueError, 'Innerproduct of two vectors in different vector space not implemented'
def best_vector_in_plane( axes, goodvecs, numeric_threshhold ): """ axes is a list of two orthonormal vectors defining a plane, and goodvecs is a list of unit vectors or (ignored) zero vectors or Nones; return whichever unit vector in the plane defined by axes is closest in direction to the first goodvec which helps determine this (i.e. which is not zero or None, and is not perpendicular to the plane, using numeric_threshhold to determine what's too close to call). If none of the goodvecs help, return None. """ x,y = axes for good in goodvecs: if good is not None: dx = dot(x,good) # will be 0 for good = V(0,0,0); not sensible for vlen(good) other than 0 or 1 dy = dot(y,good) if abs(dx) < numeric_threshhold and abs(dy) < numeric_threshhold: continue # good is perpendicular to the plane (or is zero) return norm(dx * x + dy * y) return None
def _constrainHandleToAngle(pos, p0, p1, p2): """ This works in two steps. (1) Project pos onto the plane defined by (p0, p1, p2). (2) Confine the projected point to lie within the angular arc. """ u = pos - p1 z0 = norm(p0 - p1) z2 = norm(p2 - p1) oop = norm(cross(z0, z2)) u = u - dot(oop, u) * oop # clip the point so it lies within the angle if dot(cross(z0, u), oop) < 0: # Clip on the z0 side of the angle. u = vlen(u) * z0 elif dot(cross(u, z2), oop) < 0: # Clip on the z2 side of the angle. u = vlen(u) * z2 return p1 + u
def pca(M): "Perform PCA on M, return eigenvectors and eigenvalues, sorted." T, N = shape(M) # if there are fewer rows T than columns N, use snapshot method if T < N: C = dot(M, t(M)) evals, evecsC = eigenvectors(C) # HACK: make sure evals are all positive evals = where(evals < 0, 0, evals) evecs = 1. / sqrt(evals) * dot(t(M), t(evecsC)) else: # calculate covariance matrix K = 1. / T * dot(t(M), M) evals, evecs = eigenvectors(K) # sort the eigenvalues and eigenvectors, descending order order = (argsort(evals)[::-1]) evecs = take(evecs, order, 1) evals = take(evals, order) return evals, t(evecs)
def pi_vectors( bond, out=DFLT_OUT, up=DFLT_UP, abs_coords=False): # rename -- pi_info for pi bond in degen sp chain # see also PiBondSpChain.get_pi_info """ Given a bond involving some pi orbitals, return the 4-tuple ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z), where a1py and a1pz are orthogonal vectors from atom1 giving the direction of its p orbitals for use in drawing this bond (for twisted bonds, the details of these might be determined more by graphic design issues than by the shapes of the real pi orbitals of the bond, though the goal is to approximate those); where a2py and a2pz are the same for atom2 (note that a2py might not be parallel to a1py for twisted bonds); and where ord_pi_y and ord_pi_z are order estimates (between 0.0 and 1.0 inclusive) for use in drawing the bond, for the pi_y and pi_z orbitals respectively. Note that the choice of how to name the two pi orbitals (pi_y, pi_z) is arbitrary and up to this function. All returned vectors are in the coordinate system of bond, unless abs_coords is true. This should not be called for pi bonds which are part of an "sp chain", but it should be equivalent to the code that would be used for a 1-bond sp-chain (i.e. it's an optimization of that code, PiBondSpChain.get_pi_info, for that case). """ atom1 = bond.atom1 atom2 = bond.atom2 bond_axis = atom2.posn() - atom1.posn() #k not always needed i think # following subrs should be renamed, since we routinely call them for sp atoms, # e.g. in -C#C- where outer bonds are not potential pi bonds pvec1 = p_vector_from_sp2_atom(atom1, bond, out=out, up=up) # might be None pvec2 = p_vector_from_sp2_atom( atom2, bond, out=out, up=up) # ideally, close to negative or positive of pvec1 # handle one being None (use other one in place of it) or both being None (use arb vectors) if pvec1 is None: if pvec2 is None: # use arbitrary vectors perp to the bond; compute differently if bond_axis ~= out [###@@@ make this a subr? dup code?] pvec = cross(out, bond_axis) lenpvec = vlen(pvec) if lenpvec < 0.01: pvec = up else: pvec /= lenpvec pvec1 = pvec2 = pvec else: pvec1 = pvec2 else: if pvec2 is None: pvec2 = pvec1 else: # both vectors not None -- use them, but negate pvec2 if this makes them more aligned if dot(pvec1, pvec2) < 0: pvec2 = -pvec2 return pi_info_from_abs_pvecs(bond, bond_axis, pvec1, pvec2, abs_coords=abs_coords)
def viewNormalTo(self): # """ Set view to the normal vector of the plane defined by 3 or more selected atoms or a jig's (Motor or RectGadget) axis. """ cmd = greenmsg("Set View Normal To: ") chunks = self.assy.selmols jigs = self.assy.getSelectedJigs() atoms = self.assy.selatoms_list() #following fixes bug 1748 ninad 061003. if len(chunks) > 0 and len(atoms) == 0: # Even though chunks have an axis, it is not necessarily the same # axis attr stored in the chunk. Get the chunks atoms and let # compute_heuristic_axis() recompute them. for c in range(len(chunks)): atoms += chunks[c].atoms.values() elif len(jigs) == 1 and len(atoms) == 0: # Warning: RectGadgets have no atoms. We handle this special case below. atoms = jigs[0].atoms elif len(atoms) < 3: # There is a problem when allowing only 2 selected atoms. # Changing requirement to 3 atoms fixes bug 1418. mark 060322 msg = redmsg("Please select some atoms, jigs, and/or chunks, covering at least 3 atoms") print "ops_view.py len(atoms) = ", len(atoms) env.history.message(cmd + msg) return # This check is needed for jigs that have no atoms. Currently, this # is the case for RectGadgets (ESP Image and Grid Plane) only. if len(atoms): pos = A( map( lambda a: a.posn(), atoms ) ) nears = [ self.glpane.out, self.glpane.up ] axis = compute_heuristic_axis( pos, 'normal', already_centered = False, nears = nears, dflt = None ) else: # We have a jig with no atoms. axis = jigs[0].getaxis() # Get the jig's axis. # If axis is pointing into the screen, negate (reverse) axis. if dot(axis, self.glpane.lineOfSight) > 0: axis = -axis if not axis: msg = orangemsg( "Warning: Normal axis could not be determined. No change in view." ) env.history.message(cmd + msg) return # Compute the destination quat (q2). q2 = Q(V(0,0,1), axis) q2 = q2.conj() self.glpane.rotateView(q2) info = 'View set to normal vector of the plane defined by the selected atoms.' env.history.message(cmd + info)
def best_vector_in_plane(axes, goodvecs, numeric_threshhold): """ axes is a list of two orthonormal vectors defining a plane, and goodvecs is a list of unit vectors or (ignored) zero vectors or Nones; return whichever unit vector in the plane defined by axes is closest in direction to the first goodvec which helps determine this (i.e. which is not zero or None, and is not perpendicular to the plane, using numeric_threshhold to determine what's too close to call). If none of the goodvecs help, return None. """ x, y = axes for good in goodvecs: if good is not None: dx = dot( x, good ) # will be 0 for good = V(0,0,0); not sensible for vlen(good) other than 0 or 1 dy = dot(y, good) if abs(dx) < numeric_threshhold and abs(dy) < numeric_threshhold: continue # good is perpendicular to the plane (or is zero) return norm(dx * x + dy * y) return None