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 on_press(self): point = self.current_event_mousepoint() # the touched point on the visible object (hitpoint) # (this method is defined in the Highlightable which is self.delegate) self.oldpoint = self.startpoint = point # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place. # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].) if self._delegate.altkey: self._this_drag = 'free x-y rotate' #e more options later, and/or more flags like this (maybe some should be booleans) ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance! ##e or should that be renamed DragOperation?? self._screenrect = (ll, lr, ur, ul) = self.screenrect( self.startpoint) # these points should be valid in our delegate's coords == self's coords self._dx = _dx = norm(lr - ll) self._dy = _dy = norm(ur - lr) self._dz = cross(_dx, _dy) # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think) ###k check cross direction sign self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4 # New motion UI suggests that 40% of that distance means 180 degrees of rotation. # We'll draw an axis whose length is chosen so that dragging on a sphere of that size # would have the same effect. (Maybe.) self._objcenter = self._delegate.center self.startrot = + self.rotation else: self._this_drag = 'free x-y translate' if debug070209: self.ndrags = 0 return
def getAtomDistDeltas(self, isAtomDistDeltas, atomDistPrecision, selectedAtom): """ Returns atom distance deltas (delX, delY, delZ) string if the 'Show atom distance delta info' in dynamic tooltip is checked from the user prefs. Otherwise returns None. """ glpane = self.glpane if isAtomDistDeltas: xyz = glpane.selobj.posn() xyzSelAtom = selectedAtom.posn() deltaX = str(round(vlen(xyz[0] - xyzSelAtom[0]), atomDistPrecision)) deltaY = str(round(vlen(xyz[1] - xyzSelAtom[1]), atomDistPrecision)) deltaZ = str(round(vlen(xyz[2] - xyzSelAtom[2]), atomDistPrecision)) atomDistDeltas = "<font color=\"#0000FF\">DeltaX:</font> " \ + deltaX + "<br>" \ + "<font color=\"#0000FF\">DeltaY:</font> " \ + deltaY + "<br>" \ + "<font color=\"#0000FF\">DeltaZ:</font> " \ + deltaZ return atomDistDeltas else: return None
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 on_press(self): point = self.current_event_mousepoint( ) # the touched point on the visible object (hitpoint) # (this method is defined in the Highlightable which is self.delegate) self.oldpoint = self.startpoint = point # decide type of drag now, so it's clearly constant during drag, and so decision code is only in one place. # (but note that some modkey meanings might require that changes to them during the same drag are detected [nim].) if self._delegate.altkey: self._this_drag = 'free x-y rotate' #e more options later, and/or more flags like this (maybe some should be booleans) ###e or better, set up a function or object which turns later points into their effects... hmm, a DragCommand instance! ##e or should that be renamed DragOperation?? self._screenrect = (ll, lr, ur, ul) = self.screenrect(self.startpoint) # these points should be valid in our delegate's coords == self's coords self._dx = _dx = norm(lr - ll) self._dy = _dy = norm(ur - lr) self._dz = cross( _dx, _dy ) # towards the eye (if view is ortho) (but alg is correct whether or not it is, i think) ###k check cross direction sign self._scale = min(vlen(lr - ll), vlen(ur - lr)) * 0.4 # New motion UI suggests that 40% of that distance means 180 degrees of rotation. # We'll draw an axis whose length is chosen so that dragging on a sphere of that size # would have the same effect. (Maybe.) self._objcenter = self._delegate.center self.startrot = +self.rotation else: self._this_drag = 'free x-y translate' if debug070209: self.ndrags = 0 return
def getAxisVector(self, atomAtVectorOrigin = None): """ Returns the unit axis vector of the segment (vector between two axis end points) """ endPoint1, endPoint2 = self.getAxisEndPoints() if endPoint1 is None or endPoint2 is None: return V(0, 0, 0) if atomAtVectorOrigin is not None: #If atomAtVectorOrigin is specified, we will return a vector that #starts at this atom and ends at endPoint1 or endPoint2 . #Which endPoint to choose will be dicided by the distance between #atomAtVectorOrigin and the respective endPoints. (will choose the #frthest endPoint origin = atomAtVectorOrigin.posn() if vlen(endPoint2 - origin ) > vlen(endPoint1 - origin): return norm(endPoint2 - endPoint1) else: return norm(endPoint1 - endPoint2) return norm(endPoint2 - endPoint1)
def recompute_geom_from_quats(self): # non-ring class """ [not a ring; various possible end situations] """ out = self.out up = self.up atoms = self.lista bonds = self.listb # (I wish I could rename these attrs to "atoms" and "bonds", # but self.atoms conflicts with the Jig attr, and self.chain_atoms is too long.) # default values: self.bm1R_pvec = "not needed" # and bug if used, we hope, unless we later change this value self.twist = None # Figure out p vectors for atom[0]-end (left end) of bond[0], and atom[-1]-end (right end) of bond[-1]. # If both are determined from outside this chain (i.e. if the subrs here don't return None), # or if we are a ring (so they are forced to be the same, except for the projection due to bond bending at the ring-joining atom), # then [using self.recompute_geom_both_ends_constrained()] they must be made to match (up to an arbitrary sign), # using a combination of some twist (to be computed here) along each bond, # plus a projection and (in some cases, depending on bond types i think -- see self.twist90) # 90-degree turn at each bond-bond connection; # the projection part for all the bond-bond connections is already accumulated in self.chain_quat_cum. # note: the following p-vec retvals are in abs coords, as they should be #e rename the funcs, since they are not only for sp2, but for any atom that ends our chain of pi bonds pvec1 = p_vector_from_sp2_atom(atoms[0], bonds[0], out=out, up=up) # might be None pvec2 = p_vector_from_sp2_atom( atoms[-1], bonds[-1], out=out, up=up ) # ideally, close to negative or positive of pvec1 ###@@@ handle neg # handle one being None (use other one to determine the twist) or both being None (use arb vectors) if pvec1 is None: if pvec2 is None: # use arbitrary vectors on left end of bonds[0], perp to bond and to out; compute differently if bond axis ~= out axis = self.axes[0] pvec = cross(out, axis) lenpvec = vlen(pvec) if lenpvec < 0.01: # bond axis is approx parallel to out pvec = cross(up, axis) lenpvec = vlen( pvec ) # won't be too small -- bond can't be parallel to both up and out pvec /= lenpvec self.b0L_pvec = pvec else: # pvec2 is defined, pvec1 is not. Need to transport pvec2 back to coords of pvec1 # so our standard code (pvec_i, which wants pvec1, i.e. self.b0L_pvec) can be used. self.b0L_pvec = self.chain_quat_cum.unrot(pvec2) else: if pvec2 is None: self.b0L_pvec = pvec1 else: # both vectors not None -- use recompute_geom_both_ends_constrained self.b0L_pvec = pvec1 self.bm1R_pvec = pvec2 self.recompute_geom_both_ends_constrained() return # from non-ring recompute_geom_from_quats
def constrainedPosition(self): a = self.atoms pos, p0, p1 = self.center() + self.handle_offset, a[0].posn(), a[1].posn() z = p1 - p0 nz = norm(z) dotprod = dot(pos - p0, nz) if dotprod < 0.0: return pos - dotprod * nz elif dotprod > vlen(z): return pos - (dotprod - vlen(z)) * nz else: return pos
def recompute_geom_from_quats(self): # non-ring class """ [not a ring; various possible end situations] """ out = self.out up = self.up atoms = self.lista bonds = self.listb # (I wish I could rename these attrs to "atoms" and "bonds", # but self.atoms conflicts with the Jig attr, and self.chain_atoms is too long.) # default values: self.bm1R_pvec = "not needed" # and bug if used, we hope, unless we later change this value self.twist = None # Figure out p vectors for atom[0]-end (left end) of bond[0], and atom[-1]-end (right end) of bond[-1]. # If both are determined from outside this chain (i.e. if the subrs here don't return None), # or if we are a ring (so they are forced to be the same, except for the projection due to bond bending at the ring-joining atom), # then [using self.recompute_geom_both_ends_constrained()] they must be made to match (up to an arbitrary sign), # using a combination of some twist (to be computed here) along each bond, # plus a projection and (in some cases, depending on bond types i think -- see self.twist90) # 90-degree turn at each bond-bond connection; # the projection part for all the bond-bond connections is already accumulated in self.chain_quat_cum. # note: the following p-vec retvals are in abs coords, as they should be #e rename the funcs, since they are not only for sp2, but for any atom that ends our chain of pi bonds pvec1 = p_vector_from_sp2_atom(atoms[0], bonds[0], out = out, up = up) # might be None pvec2 = p_vector_from_sp2_atom(atoms[-1], bonds[-1], out = out, up = up) # ideally, close to negative or positive of pvec1 ###@@@ handle neg # handle one being None (use other one to determine the twist) or both being None (use arb vectors) if pvec1 is None: if pvec2 is None: # use arbitrary vectors on left end of bonds[0], perp to bond and to out; compute differently if bond axis ~= out axis = self.axes[0] pvec = cross(out, axis) lenpvec = vlen(pvec) if lenpvec < 0.01: # bond axis is approx parallel to out pvec = cross(up, axis) lenpvec = vlen(pvec) # won't be too small -- bond can't be parallel to both up and out pvec /= lenpvec self.b0L_pvec = pvec else: # pvec2 is defined, pvec1 is not. Need to transport pvec2 back to coords of pvec1 # so our standard code (pvec_i, which wants pvec1, i.e. self.b0L_pvec) can be used. self.b0L_pvec = self.chain_quat_cum.unrot(pvec2) else: if pvec2 is None: self.b0L_pvec = pvec1 else: # both vectors not None -- use recompute_geom_both_ends_constrained self.b0L_pvec = pvec1 self.bm1R_pvec = pvec2 self.recompute_geom_both_ends_constrained() return # from non-ring recompute_geom_from_quats
def _determine_how_to_change_length(self): #@ NEEDS WORK """ Returns the difference in length between the original nanotube and the modified nanotube, where: 0 = no change in length > 0 = lengthen < 0 = trim """ nanotubeRise = self.struct.nanotube.getRise() endPoint1, endPoint2 = self.struct.nanotube.getEndPoints() #@ original_nanotube_length = vlen(endPoint1 - endPoint2) new_nanotube_length = vlen(endPoint1 - endPoint2) #@ return new_nanotube_length - original_nanotube_length #@ ALWAYS RETURNS ZERO
def p_vector_from_3_bonds(atom, bond, out = DFLT_OUT, up = DFLT_UP): """ Given an sp2 atom with 3 bonds, and one of those bonds which we assume has pi orbitals in it, return a unit vector from atom along its p orbital, guaranteed perpendicular to bond, for purposes of drawing the pi orbital component of bond. Note that it's arbitrary whether we return a given vector or its opposite. [##e should we fix that, using out and up? I don't think we can in a continuous way, so don't bother.] We don't verify the atom is sp2, since we don't need to for this code to work, though our result would probably not make sense otherwise. """ others = map( lambda bond: bond.other(atom), atom.bonds) assert len(others) == 3 other1 = bond.other(atom) others.remove(other1) other2, other3 = others apos = atom.posn() v1 = other1.posn() - apos # if v1 has 0 length, we should return some default value here; this might sometimes happen so I better handle it. # actually i'm not sure the remaining code would fail in this case! If not, I might revise this. if vlen(v1) < 0.01: # in angstroms return + up v2 = other2.posn() - apos v3 = other3.posn() - apos # projecting along v1, we hope v2 and v3 are opposite, and then we return something perpendicular to them. # if one is zero, just be perp. to the other one alone. # (If both zero? Present code returns near-0. Should never happen, but fix. #e) # otherwise if they are not opposite, use perps to each one, "averaged", # which means (for normalized vectors), normalize the larger of the sum or difference # (equivalent to clustering them in the way (of choice of sign for each) that spans the smallest angle). # Optim: no need to project them before taking cross products to get the perps to use. ## v2 -= v1 * dot(v2,v1) ## v3 -= v1 * dot(v3,v1) v2p = cross(v2,v1) v3p = cross(v3,v1) lenv2p = vlen(v2p) if lenv2p < 0.01: return norm(v3p) v2p /= lenv2p lenv3p = vlen(v3p) if lenv3p < 0.01: return v2p # normalized above v3p /= lenv3p r1 = v2p + v3p r2 = v2p - v3p lenr1 = vlen(r1) lenr2 = vlen(r2) if lenr1 > lenr2: return r1/lenr1 else: return r2/lenr2 pass
def p_vector_from_3_bonds(atom, bond, out=DFLT_OUT, up=DFLT_UP): """ Given an sp2 atom with 3 bonds, and one of those bonds which we assume has pi orbitals in it, return a unit vector from atom along its p orbital, guaranteed perpendicular to bond, for purposes of drawing the pi orbital component of bond. Note that it's arbitrary whether we return a given vector or its opposite. [##e should we fix that, using out and up? I don't think we can in a continuous way, so don't bother.] We don't verify the atom is sp2, since we don't need to for this code to work, though our result would probably not make sense otherwise. """ others = map(lambda bond: bond.other(atom), atom.bonds) assert len(others) == 3 other1 = bond.other(atom) others.remove(other1) other2, other3 = others apos = atom.posn() v1 = other1.posn() - apos # if v1 has 0 length, we should return some default value here; this might sometimes happen so I better handle it. # actually i'm not sure the remaining code would fail in this case! If not, I might revise this. if vlen(v1) < 0.01: # in angstroms return +up v2 = other2.posn() - apos v3 = other3.posn() - apos # projecting along v1, we hope v2 and v3 are opposite, and then we return something perpendicular to them. # if one is zero, just be perp. to the other one alone. # (If both zero? Present code returns near-0. Should never happen, but fix. #e) # otherwise if they are not opposite, use perps to each one, "averaged", # which means (for normalized vectors), normalize the larger of the sum or difference # (equivalent to clustering them in the way (of choice of sign for each) that spans the smallest angle). # Optim: no need to project them before taking cross products to get the perps to use. ## v2 -= v1 * dot(v2,v1) ## v3 -= v1 * dot(v3,v1) v2p = cross(v2, v1) v3p = cross(v3, v1) lenv2p = vlen(v2p) if lenv2p < 0.01: return norm(v3p) v2p /= lenv2p lenv3p = vlen(v3p) if lenv3p < 0.01: return v2p # normalized above v3p /= lenv3p r1 = v2p + v3p r2 = v2p - v3p lenr1 = vlen(r1) lenr2 = vlen(r2) if lenr1 > lenr2: return r1 / lenr1 else: return r2 / lenr2 pass
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 inferBonds(mol): # [probably by Will; TODO: needs docstring] # bruce 071030 moved this from bonds.py to bonds_from_atoms.py # not sure how big a margin we should have for "coincident" maxBondLength = 2.0 # first remove any coincident singlets singlets = filter(lambda a: a.is_singlet(), mol.atoms.values()) removable = {} sngen = NeighborhoodGenerator(singlets, maxBondLength) for sing1 in singlets: key1 = sing1.key pos1 = sing1.posn() for sing2 in sngen.region(pos1): key2 = sing2.key dist = vlen(pos1 - sing2.posn()) if key1 != key2: removable[key1] = sing1 removable[key2] = sing2 for badGuy in removable.values(): badGuy.kill() from operations.bonds_from_atoms import make_bonds make_bonds(mol.atoms.values()) return
def carbons(self): if self._carbons == None: edges = self.edges() lst = [] carbonIndex = 0 for tri in self.triangles(): a, b, c = tri ab = edges.edgeFor(a, b) ab.add_atom(carbonIndex) ac = edges.edgeFor(a, c) ac.add_atom(carbonIndex) bc = edges.edgeFor(b, c) bc.add_atom(carbonIndex) v0, v1, v2 = self.lst[a], self.lst[b], self.lst[c] v = (v0 + v1 + v2) / 3.0 lst.append(v) carbonIndex += 1 # get a bond length a1, a2 = edges.anyOldBond() bondlen = vlen(lst[a1] - lst[a2]) # scale to get the correct bond length factor = self.bondlength / bondlen for i in range(len(lst)): lst[i] = factor * lst[i] self._carbons = lst # invalidate _bonds self._bonds = None return self._carbons
def _getCursorText_length(self, vec): """ Subclasses may override this method. @see: self._drawCursorText() for details. """ dist = vlen(vec) return "%5.2A" % (dist)
def indexVerts(verts, close): """ Compress a vertex array into an array of unique vertices, and an array of index values into the unique vertices. This is good for converting input for glDrawArrays into input for glDrawElements. The second arg is 'close', the distance between vertices which are close enough to be considered a single vertex. The return value is a pair of arrays (index, verts). """ unique = [] index = [] for v in verts: for i in range(len(unique)): if vlen(unique[i] - v) < close: index += [i] break pass else: index += [len(unique)] unique += [v] pass continue return (index, unique)
def __init__(self, shp, ptlist, origin, selSense, opts): """ ptlist is a list of 3d points describing a selection (in a subclass-specific manner). origin is the center of view, and shp.normal gives the direction of the line of light. """ # store orthonormal screen-coordinates from shp self.right = shp.right self.up = shp.up self.normal = shp.normal # store other args self.ptlist = ptlist self.org = origin + 0.0 self.selSense = selSense self.slab = opts.get('slab', None) # how thick in what direction self.eyeball = opts.get('eye', None) # for projecting if not in ortho mode if self.eyeball: self.eye2Pov = vlen(self.org - self.eyeball) # project the (3d) path onto the plane. Warning: arbitrary 2d origin! # Note: original code used project_2d_noeyeball, and I think this worked # since the points were all in the same screen-parallel plane as # self.org (this is a guess), but it seems better to not require this # but just to use project_2d here (taking eyeball into account). self._computeBBox()
def getCursorText(self, endPoint1, endPoint2): """ This is used as a callback method in CntLine mode @see: NanotubeLineMode.setParams, NanotubeLineMode_GM.Draw """ if endPoint1 is None or endPoint2 is None: return if not env.prefs[insertNanotubeEditCommand_showCursorTextCheckBox_prefs_key]: return '', black textColor = black vec = endPoint2 - endPoint1 ntLength = vlen(vec) lengthString = self._getCursorText_length(ntLength) thetaString = '' if env.prefs[insertNanotubeEditCommand_cursorTextCheckBox_angle_prefs_key]: theta = self.glpane.get_angle_made_with_screen_right(vec) thetaString = '%5.2f deg'%theta commaString = ", " text = lengthString if text and thetaString: text += commaString text += thetaString return text , textColor
def writepov(self, file, dispdef): if self.hidden: return if self.is_disabled(): return #bruce 050421 c = self.posn() a = self.axen() xrot = -atan2(a[1], sqrt(1 - a[1] * a[1])) * 180 / pi yrot = atan2(a[0], sqrt(1 - a[0] * a[0])) * 180 / pi file.write("lmotor(" \ + povpoint([self.width * 0.5, self.width * 0.5, self.length * 0.5]) + "," \ + povpoint([self.width * -0.5, self.width * -0.5, self.length * -0.5]) + "," \ + "<0.0, " + str(yrot) + ", 0.0>," \ + "<" + str(xrot) + ", 0.0, 0.0>," \ + povpoint(c) + "," \ + "<" + str(self.color[0]) + "," + str(self.color[1]) + "," + str(self.color[2]) + ">)\n") for a in self.atoms: if vlen( c - a.posn() ) > 0.001: #bruce 060808 add condition to see if this fixes bug 719 (two places in this file) file.write("spoke(" + povpoint(c) + "," + povpoint(a.posn()) + "," + str(self.sradius) + ",<" + str(self.color[0]) + "," + str(self.color[1]) + "," + str(self.color[2]) + ">)\n")
def _subtriangulate(self, n): # don't put new vectors right on top of old ones # this is similar to the neighborhood generator gap = 0.001 occupied = { } def quantize(v): return (int(floor(v[0] / gap)), int(floor(v[1] / gap)), int(floor(v[2] / gap))) def overlap(v): x0, y0, z0 = quantize(v) for x in range(x0 - 1, x0 + 2): for y in range(y0 - 1, y0 + 2): for z in range(z0 - 1, z0 + 2): key = (x, y, z) if occupied.has_key(key): return True return False def occupy(v): key = quantize(v) occupied[key] = 1 def add_if_ok(v): if not overlap(v): occupy(v) self.lst.append(v) for v in self.lst: occupy(v) for tri in self.triangles(): v0, v1, v2 = self.lst[tri[0]], self.lst[tri[1]], self.lst[tri[2]] v10, v21 = v1 - v0, v2 - v1 for i in range(n + 1): for j in range(i + 1): v = v21 * (1. * j / n) + v10 * (1. * i / n) + v0 add_if_ok(v / vlen(v)) self.order *= n
def drawcylinder(color, pos1, pos2, radius, capped = 0, opacity = 1.0): """ Schedule a cylinder for rendering whenever ColorSorter thinks is appropriate. """ if 1: #bruce 060304 optimization: don't draw zero-length or almost-zero-length # cylinders. (This happens a lot, apparently for both long-bond # indicators and for open bonds. The callers hitting this should be # fixed directly! That might provide a further optim by making a lot # more single bonds draw as single cylinders.) The reason the # threshhold depends on capped is in case someone draws a very thin # cylinder as a way of drawing a disk. But they have to use some # positive length (or the direction would be undefined), so we still # won't permit zero-length then. cyllen = vlen(pos1 - pos2) if cyllen < (capped and 0.000000000001 or 0.0001): # Uncomment this to find the callers that ought to be optimized. #e optim or remove this test; until then it's commented out. ## if env.debug(): ## print ("skipping drawcylinder since length is only %5g" % ## (cyllen,)), \ ## (" (color is (%0.2f, %0.2f, %0.2f))" % ## (color[0], color[1], color[2])) return pass ColorSorter.schedule_cylinder(color, pos1, pos2, radius, capped = capped, opacity = opacity)
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 getCursorText(self): """ This is used as a callback method in NanotubeLine mode @see: NanotubeLineMode.setParams, NanotubeLineMode_GM.Draw """ if self.grabbedHandle is None: return if not env.prefs[nanotubeSegmentEditCommand_showCursorTextCheckBox_prefs_key]: return '', black text = "" textColor = black currentPosition = self.grabbedHandle.currentPosition fixedEndOfStructure = self.grabbedHandle.fixedEndOfStructure nanotubeLength = vlen( currentPosition - fixedEndOfStructure ) nanotubeLengthString = self._getCursorText_length(nanotubeLength) text = nanotubeLengthString #@TODO: The following updates the PM as the cursor moves. #Need to rename this method so that you that it also does more things #than just to return a textString -- Ninad 2007-12-20 self.propMgr.ntLengthLineEdit.setText(nanotubeLengthString) return text, textColor
def _getCursorText_length(self, vec): """ Subclasses may override this method. @see: self._drawCursorText() for details. """ dist = vlen(vec) return "%5.2A"%(dist)
def getCursorText(self, endPoint1, endPoint2): """ This is used as a callback method in CntLine mode @see: NanotubeLineMode.setParams, NanotubeLineMode_GM.Draw """ if endPoint1 is None or endPoint2 is None: return if not env.prefs[ insertNanotubeEditCommand_showCursorTextCheckBox_prefs_key]: return '', black textColor = black vec = endPoint2 - endPoint1 ntLength = vlen(vec) lengthString = self._getCursorText_length(ntLength) thetaString = '' if env.prefs[ insertNanotubeEditCommand_cursorTextCheckBox_angle_prefs_key]: theta = self.glpane.get_angle_made_with_screen_right(vec) thetaString = '%5.2f deg' % theta commaString = ", " text = lengthString if text and thetaString: text += commaString text += thetaString return text, textColor
def getCursorText(self, endPoint1, endPoint2): """ This is used as a callback method in PeptideLineLine mode @see: PeptideLine_GraphicsMode.setParams, PeptideLine_GraphicsMode.Draw """ text = '' textColor = env.prefs[cursorTextColor_prefs_key] if endPoint1 is None or endPoint2 is None: return text, textColor vec = endPoint2 - endPoint1 from geometry.VQT import vlen peptideLength = vlen(vec) ss_idx, phi, psi, aa_type = self._gatherParameters() peptideLength = self.structGenerator.get_number_of_res(endPoint1, endPoint2, phi, psi) lengthString = self._getCursorText_length(peptideLength) thetaString = '' #Urmi 20080804: not sure if angle will be required later theta = self.glpane.get_angle_made_with_screen_right(vec) thetaString = '%5.2f deg'%theta commaString = ", " text = lengthString if text and thetaString: text += commaString text += thetaString return text, textColor
def carbons(self): if self._carbons == None: edges = self.edges() lst = [ ] carbonIndex = 0 for tri in self.triangles(): a, b, c = tri ab = edges.edgeFor(a, b) ab.add_atom(carbonIndex) ac = edges.edgeFor(a, c) ac.add_atom(carbonIndex) bc = edges.edgeFor(b, c) bc.add_atom(carbonIndex) v0, v1, v2 = self.lst[a], self.lst[b], self.lst[c] v = (v0 + v1 + v2) / 3.0 lst.append(v) carbonIndex += 1 # get a bond length a1, a2 = edges.anyOldBond() bondlen = vlen(lst[a1] - lst[a2]) # scale to get the correct bond length factor = self.bondlength / bondlen for i in range(len(lst)): lst[i] = factor * lst[i] self._carbons = lst # invalidate _bonds self._bonds = None return self._carbons
def list_potential_bonds(atmlist0): """ Given a list of atoms, return a list of triples (cost, atm1, atm2) for all bondable pairs of atoms in the list. Each pair of atoms is considered separately, as if only it would be bonded, in addition to all existing bonds. In other words, the returned bonds can't necessarily all be made (due to atom valence), but any one alone can be made, in addition to whatever bonds the atoms currently have. Warning: the current implementation takes quadratic time in len(atmlist0). The return value will have reasonable size for physically realistic atmlists, but could be quadratic in size for unrealistic ones (e.g. if all atom positions were compressed into a small region of space). """ atmlist = filter( bondable_atm, atmlist0 ) lst = [] maxBondLength = 2.0 ngen = NeighborhoodGenerator(atmlist, maxBondLength) for atm1 in atmlist: key1 = atm1.key pos1 = atm1.posn() for atm2 in ngen.region(pos1): bondLen = vlen(pos1 - atm2.posn()) idealBondLen = idealBondLength(atm1, atm2) if atm2.key < key1 and bondLen < max_dist_ratio(atm1, atm2) * idealBondLen: # i.e. for each pair (atm1, atm2) of bondable atoms cost = bond_cost(atm1, atm2) if cost is not None: lst.append((cost, atm1, atm2)) lst.sort() # least cost first return lst
def baseframe_from_pam3_data(ss1, ax, ss2): """ Given the positions of the Ss3-Ax3-Ss3 atoms in a PAM3 basepair, return the first Ss3's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). """ yprime_vector = ss2 - ss1 yprime_length = vlen(yprime_vector) y_direction = yprime_vector / yprime_length # optimization of norm z_direction = norm(cross(ax - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) # still need origin, easy since we know SPRIME_D_SDFRAME -- but we do have to rotate that, using the quat # rel_to_abs_quat.rot( SPRIME_D_SDFRAME ) # this is Ss5 to Ss3 vector, abs coords Ss3_d_abspos = ss1 Ss5_d_abspos = Ss3_d_abspos - rel_to_abs_quat.rot( SPRIME_D_SDFRAME ) origin = Ss5_d_abspos # y_m = (|S'_u - S'_d| / 2) + y_s' y_m = yprime_length / 2.0 + Y_SPRIME return ( origin, rel_to_abs_quat, y_m )
def sameAsCurrentView(self, view = None): """ Tests if self is the same as I{view}, or the current view if I{view} is None (the default). @param view: A named view to compare with self. If None (the default), self is compared to the current view (i.e. the 3D graphics area). @type view: L{NamedView} @return: True if they are the same. Otherwise, returns False. @rtype: boolean """ # Note: I'm guessing this could be rewritten to be more # efficient/concise. For example, it seems possible to implement # this using a simple conditional like this: # # if self == view: # return True # else: # return False # # It occurs to me that the GPLane class should use a NamedView attr # along with (or in place of) quat, scale, pov and zoomFactor attrs. # That would make this method (and possibly other code) easier to # write and understand. # # Ask Bruce about all this. # # BTW, this code was originally copied/borrowed from # GLPane.animateToView(). Mark 2008-02-03. # Make copies of self parameters. q1 = Q(self.quat) s1 = self.scale p1 = V(self.pov[0], self.pov[1], self.pov[2]) z1 = self.zoomFactor if view is None: # use the graphics area in which self is displayed # (usually the main 3D graphics area; code in this class # has not been reviewed for working in other GLPane_minimal instances) view = self.assy.glpane # Copy the parameters of view for comparison q2 = Q(view.quat) s2 = view.scale p2 = V(view.pov[0], view.pov[1], view.pov[2]) z2 = view.zoomFactor # Compute the deltas deltaq = q2 - q1 deltap = vlen(p2 - p1) deltas = abs(s2 - s1) deltaz = abs(z2 - z1) if deltaq.angle + deltap + deltas + deltaz == 0: return True else: return False
def is_lozenge_visible(self, pos1, pos2, radius): # piotr 080402 """ Perform a simple frustum culling test against a "lozenge" object in absolute model space coordinates. The lozenge is a cylinder with two hemispherical caps. Assume that the frustum planes are allocated, i.e. glpane._compute_frustum_planes was already called. (If it wasn't, the test will always succeed.) Currently, this is a loose (but correct) approximation which calls glpane.is_sphere_visible on the lozenge's bounding sphere. @warning: this will give incorrect results unless the current GL matrices are in the same state as when _compute_frustum_planes was last called (i.e. in absolute model space coordinates). """ if self._frustum_planes_available: center = 0.5 * (pos1 + pos2) sphere_radius = 0.5 * vlen(pos2 - pos1) + radius res = self.is_sphere_visible(center, sphere_radius) # Read Bruce's comment in glpane.is_sphere_visible # It applies here, as well. return res return True
def draw(self, glpane, offset=V(0, 0, 0), color=None, info={}): # modified copy of superclass draw method "draw our spheres (in practice we'll need to extend this for different sets...)" ## self.radius_multiplier = 1.0 # this might be changed by certain subclass's process_optional_info method ## self.process_optional_info(info) # might reset instvars that affect following code... (kluge?) color = color or self.color ##detailLevel = 0 # just an icosahedron detailLevel = 1 # easier to click on this way ##radius = 0.33 # the one we store might be too large? no, i guess it's ok. #e (i might prefer an octahedron, or a miniature-convex-hull-of-extrude-unit) offset = offset + self.origin radius_multiplier = self.radius_multiplier special_pos = self.special_pos # patched in ###nim? special_pos = special_pos + offset #k?? or just self.origin?? special_color = self.special_color # default is used ## count = 0 for (pos, radius, info) in self.handles: radius *= radius_multiplier pos = pos + offset dist = vlen(special_pos - pos) if dist <= radius: color2 = ave_colors(1.0 - dist / radius, special_color, color) ## count += 1 else: color2 = color ## experiment 050218: add alpha factor to color color2 = tuple(color2) + (0.25, ) drawsphere(color2, pos, radius, detailLevel) ## self.color2_count = count # kluge, probably not used since should equal nbonds return
def baseframe_from_pam5_data(ss1, gv, ss2): """ Given the positions of the Ss5-Gv5-Ss5 atoms in a PAM5 basepair, return the first Ss5's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). @note: this is correct even if gv is actually an Ax5 position. """ # y axis is parallel to inter-sugar line # base plane orientation comes from the other atom, Gv # so get x and z axis around that line origin = ss1 y_vector = ss2 - ss1 y_length = vlen(y_vector) ## y_direction = norm(ss2 - ss1) y_direction = y_vector / y_length # optimization z_direction = norm(cross(gv - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) y_m = y_length / 2.0 return (origin, rel_to_abs_quat, y_m)
def baseframe_from_pam3_data(ss1, ax, ss2): """ Given the positions of the Ss3-Ax3-Ss3 atoms in a PAM3 basepair, return the first Ss3's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). """ yprime_vector = ss2 - ss1 yprime_length = vlen(yprime_vector) y_direction = yprime_vector / yprime_length # optimization of norm z_direction = norm(cross(ax - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) # still need origin, easy since we know SPRIME_D_SDFRAME -- but we do have to rotate that, using the quat # rel_to_abs_quat.rot( SPRIME_D_SDFRAME ) # this is Ss5 to Ss3 vector, abs coords Ss3_d_abspos = ss1 Ss5_d_abspos = Ss3_d_abspos - rel_to_abs_quat.rot(SPRIME_D_SDFRAME) origin = Ss5_d_abspos # y_m = (|S'_u - S'_d| / 2) + y_s' y_m = yprime_length / 2.0 + Y_SPRIME return (origin, rel_to_abs_quat, y_m)
def on_drag(self): # Note: we can assume this is a "real drag", since the caller (ultimately a selectMode method in testmode, as of 070209) # is tracking mouse motion and not calling this until it becomes large enough, as the debug070209 prints show. oldpoint = self.oldpoint # was saved by prior on_drag or by on_press point = self.current_event_mousepoint(plane=self.startpoint) if debug070209: self.ndrags += 1 ## if (self.ndrags == 1) or 1: ## print "drag event %d, model distance = %r, pixel dist not computed" % (self.ndrags, vlen(oldpoint - point),) if self._this_drag == 'free x-y rotate': # rotate using New motion UI # [probably works for this specific kind of rotation, one of 4 that UI proposes; # doesn't yet have fancy cursors or during-rotation graphics; add those only after it's a DragCommand] # two implem choices: # 1. know the eye direction and the screen dims in plane of startpoint, in model coords; compute in model coords # 2. get the mouse positions (startpoint and point) and screen dims in window x,y coords, compute rotation in eye coords, # but remember to reorient it to correspond with model if model coords are rotated already. # Not sure which one is better. # In general, placing user into model coords (or more precisely, into object local coords) seems more general -- # for example, what if there were several interacting users, each visible to the others? # We'd want each user's eye & screen to be visible! (Maybe even an image of their face & screen, properly scaled and aligned?) # And we'd want their posns to be used in the computations here, all in model coords. # (Even if zoom had occurred, which means, even the user's *size* is quite variable!) # I need "user in model coords" for other reasons too, so ok, I'll do it that way. # # [Hey, I might as well fix the bug in current_event_mousepoint which fakes the center of view, at the same time. # (I can't remember its details right now, but I think it assumed the local origin was the cov, which is obviously wrong.) # (But I didn't look at that code or fix that bug now.)] vec = point - self.startpoint uvec = norm(vec) #k needed?? axisvec = cross( self._dz, uvec ) # unit length (suitable for glRotate -- but we need to use it to make a quat!) axisvec = norm( axisvec) # just to be sure (or to reduce numerical errors) scale = self._scale draw_axisvec = axisvec * scale #e times some other length constant too? center = self._objcenter self.axisends = (center - axisvec, center + axisvec ) # draw a rotation axis here ###e self.degrees = degrees = vlen( vec ) / scale * 180.0 # draw a textual indicator with degrees (and axisvec angle too) ###e ###e or print that info into sbar? or somewhere fixed in glpane? or in glpane near mouse? # now set self.rotation to a quat made from axisvec and degrees theta = degrees / 360.0 * 2 * pi # print "axisvec %r, degrees %r, theta %r" % (axisvec ,degrees,theta) rot = Q(axisvec, theta) self.rotation = self.startrot + rot # note use of self.startrot rather than self.rotation on rhs # avoid += to make sure it gets changed-tracked -- and since it would be the wrong op! elif self._this_drag == 'free x-y translate': self._cmd_drag_from_to( oldpoint, point) # use Draggable interface cmd on self else: assert 0 self.oldpoint = point return
def _drawCursorText(self): """" """ if self.endPoint1 is None or self.endPoint2 is None: return self.text = '' textColor = black #Draw the text next to the cursor that gives info about #number of base pairs etc. So this class and its command class needs #cleanup. e.g. callbackMethodForCursorTextString should be simply #self.command.getCursorText() and like that. -- Ninad2008-04-17 if self.command and hasattr(self.command, 'callbackMethodForCursorTextString'): self.text, textColor = self.command.callbackMethodForCursorTextString( self.endPoint1, self.endPoint2) else: vec = self.endPoint2 - self.endPoint1 theta = self.glpane.get_angle_made_with_screen_right(vec) dist = vlen(vec) self.text = "%5.2fA, %5.2f deg"%(dist, theta) self.glpane.renderTextNearCursor(self.text, color = textColor)
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 draw(self, glpane, offset = V(0,0,0), color = None, info = {}): # modified copy of superclass draw method "draw our spheres (in practice we'll need to extend this for different sets...)" ## self.radius_multiplier = 1.0 # this might be changed by certain subclass's process_optional_info method ## self.process_optional_info(info) # might reset instvars that affect following code... (kluge?) color = color or self.color ##detailLevel = 0 # just an icosahedron detailLevel = 1 # easier to click on this way ##radius = 0.33 # the one we store might be too large? no, i guess it's ok. #e (i might prefer an octahedron, or a miniature-convex-hull-of-extrude-unit) offset = offset + self.origin radius_multiplier = self.radius_multiplier special_pos = self.special_pos # patched in ###nim? special_pos = special_pos + offset #k?? or just self.origin?? special_color = self.special_color # default is used ## count = 0 for (pos,radius,info) in self.handles: radius *= radius_multiplier pos = pos + offset dist = vlen(special_pos - pos) if dist <= radius: color2 = ave_colors( 1.0 - dist/radius, special_color, color ) ## count += 1 else: color2 = color ## experiment 050218: add alpha factor to color color2 = tuple(color2) + (0.25,) drawsphere(color2, pos, radius, detailLevel) ## self.color2_count = count # kluge, probably not used since should equal nbonds return
def getCursorText(self, endPoint1, endPoint2): """ This is used as a callback method in PeptideLineLine mode @see: PeptideLine_GraphicsMode.setParams, PeptideLine_GraphicsMode.Draw """ text = '' textColor = env.prefs[cursorTextColor_prefs_key] if endPoint1 is None or endPoint2 is None: return text, textColor vec = endPoint2 - endPoint1 from geometry.VQT import vlen peptideLength = vlen(vec) ss_idx, phi, psi, aa_type = self._gatherParameters() peptideLength = self.structGenerator.get_number_of_res( endPoint1, endPoint2, phi, psi) lengthString = self._getCursorText_length(peptideLength) thetaString = '' #Urmi 20080804: not sure if angle will be required later theta = self.glpane.get_angle_made_with_screen_right(vec) thetaString = '%5.2f deg' % theta commaString = ", " text = lengthString if text and thetaString: text += commaString text += thetaString return text, textColor
def baseframe_from_pam5_data(ss1, gv, ss2): """ Given the positions of the Ss5-Gv5-Ss5 atoms in a PAM5 basepair, return the first Ss5's baseframe (and y_m) as a tuple of (origin, rel_to_abs_quat, y_m). @note: this is correct even if gv is actually an Ax5 position. """ # y axis is parallel to inter-sugar line # base plane orientation comes from the other atom, Gv # so get x and z axis around that line origin = ss1 y_vector = ss2 - ss1 y_length = vlen(y_vector) ## y_direction = norm(ss2 - ss1) y_direction = y_vector / y_length # optimization z_direction = norm(cross(gv - ss1, y_direction)) # BUG: nothing checks for cross product being too small x_direction = norm(cross(y_direction, z_direction)) # this norm is redundant, but might help with numerical stability rel_to_abs_quat = Q(x_direction, y_direction, z_direction) y_m = y_length / 2.0 return ( origin, rel_to_abs_quat, y_m )
def _snapEndPointHorizontalOrVertical(self): """ Snap the second endpoint of the line (and thus the whole line) to the screen horizontal or vertical vectors. @return: The new endPoint2 i.e. the moving endpoint of the rubberband line . This value may be same as previous or snapped so that line is horizontal or vertical depending upon the angle it makes with the horizontal and vertical. @rtype: B{A} """ up = self.glpane.up down = self.glpane.down left = self.glpane.left right = self.glpane.right endPoint2 = self.endPoint2 snapVector = V(0, 0, 0) currentLineVector = norm(self.endPoint2 - self.endPoint1) theta_horizontal = angleBetween(right, currentLineVector) theta_vertical = angleBetween(up, currentLineVector) theta_horizontal_old = theta_horizontal theta_vertical_old = theta_vertical if theta_horizontal != 90.0: theta_horizontal = min(theta_horizontal, (180.0 - theta_horizontal)) if theta_vertical != 90.0: theta_vertical = min(theta_vertical, 180.0 - theta_vertical) theta = min(theta_horizontal, theta_vertical) if theta <= 2.0 and theta != 0.0: self._snapOn = True if theta == theta_horizontal: self._snapType = 'HORIZONTAL' if theta_horizontal == theta_horizontal_old: snapVector = right else: snapVector = left elif theta == theta_vertical: self._snapType = 'VERTICAL' if theta_vertical == theta_vertical_old: snapVector = up else: snapVector = down endPoint2 = self.endPoint1 + \ vlen(self.endPoint1 - self.endPoint2)*snapVector else: self._snapOn = False return endPoint2
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 updateLength( self ): """ Update the nanotube Length lineEdit widget. """ nanotubeLength = vlen(self.endPoint1 - self.endPoint2) text = "%-7.4f Angstroms" % (nanotubeLength) self.ntLengthLineEdit.setText(text) return
def updateLength(self): """ Update the nanotube Length lineEdit widget. """ nanotubeLength = vlen(self.endPoint1 - self.endPoint2) text = "%-7.4f Angstroms" % (nanotubeLength) self.ntLengthLineEdit.setText(text) return
def _draw(self, flipDirection = False, highlighted = False): """ Main drawing code. @param flipDirection: This flag decides the direction in which the arrow is drawn. This value is set in the leftClick method. The default value is 'False' @type flipDirection: bool @param highlighted: Decids the color of the arrow based on whether it is highlighted. The default value is 'False' @type highlighted: bool """ if highlighted: color = orange else: color = gray if flipDirection: #@NOTE: Remember we are drawing the arrow inside of the _draw_geometry #so its drawing it in the translated coordinate system (translated #at the center of the Plane. So we should do the following. #(i.e. use V(0,0,1)). This will change if we decide to draw the #direction arrow outside of the parent object #requesting this drawing.--ninad 20070612 #Using headPoint = self.tailPoint + V(0,0,1) * 2.0 etc along with #the transformation matrix in self.draw_in_abs_coordinate() #fixes bug 2702 and 2703 -- Ninad 2008-06-11 headPoint = self.tailPoint + V(0,0,1) * 2.0 ##headPoint = self.tailPoint + 2.0 * norm(self.parent.getaxis()) else: headPoint = self.tailPoint - V(0,0,1) * 2.0 ##headPoint = self.tailPoint - 2.0 * self.parent.getaxis() vec = vlen(headPoint - self.tailPoint) vec = self.glpane.scale*0.07*vec tailRadius = vlen(vec)*0.16 drawDirectionArrow(color, self.tailPoint, headPoint, tailRadius, self.glpane.scale, flipDirection = flipDirection)