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 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 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 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 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 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 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 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 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 numpy.oldnumeric 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. """ 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 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 walk_great_circle(P, Q, D, R=radius): """Given two points P and Q on or near the surface of the sphere, use P and Q to define a great circle. Then walk along that great circle starting at P and going in the direction of Q, and traveling far enough the chord is of length D. P and Q are not required to lie exactly on the sphere's surface. """ dP, dQ = P - sphere_center, Q - sphere_center dPs = cross(cross(dP, dQ), dP) cpart, spart = norm(dP), norm(dPs) theta = 2 * asin(0.5 * D / R) return sphere_center + R * (cpart * cos(theta) + spart * sin(theta))
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 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 get_dihedral(self): """ Returns the dihedral between two atoms (nuclei) """ wx = self.atoms[0].posn() - self.atoms[1].posn() yx = self.atoms[2].posn() - self.atoms[1].posn() xy = -yx zy = self.atoms[3].posn() - self.atoms[2].posn() u = cross(wx, yx) v = cross(xy, zy) if dot(zy, u) < 0: # angles go from -180 to 180, wware 051101 return -angleBetween(u, v) # use new acos(dot) func, wware 051103 else: return angleBetween(u, v)
def get_dihedral(self): """ Returns the dihedral between two atoms (nuclei) """ wx = self.atoms[0].posn()-self.atoms[1].posn() yx = self.atoms[2].posn()-self.atoms[1].posn() xy = -yx zy = self.atoms[3].posn()-self.atoms[2].posn() u = cross(wx, yx) v = cross(xy, zy) if dot(zy, u) < 0: # angles go from -180 to 180, wware 051101 return -angleBetween(u, v) # use new acos(dot) func, wware 051103 else: return angleBetween(u, v)
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 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 arb_perp_unit_vector(vec): """ Given a nonzero vector, return an arbitrary unit vector perpendicular to it. """ vec2 = arb_non_parallel_vector(vec) return norm(cross(vec, vec2))
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 _getPlaneOrientation(self, atomPos): """ """ assert len(atomPos) >= 3 v1 = atomPos[-2] - atomPos[-1] v2 = atomPos[-3] - atomPos[-1] return cross(v1, v2)
def arb_ortho_pair(vec): #k not presently used # see also some related code in pi_vectors() """ Given a nonzero vector, return an arbitrary pair of unit vectors perpendicular to it and to each other. """ res1 = arb_perp_unit_vector(vec) res2 = norm(cross(vec, res1)) return res1, res2
def _C_width_direction(self): """ compute self.width_direction """ # Note: to do this with a formula expr instead # would require cross_Expr to be defined, # and glpane.lineOfSight to be tracked. return cross( self.direction, self.env.glpane.lineOfSight )
def pi_info_from_abs_pvecs( bond, bond_axis, pvec1, pvec2, abs_coords = False): """ #doc; bond_axis (passed only as an optim) and pvecs are in abs coords; retval is in bond coords unless abs_coords is true """ a1py = pvec1 a2py = pvec2 a1pz = norm(cross(bond_axis, pvec1)) a2pz = norm(cross(bond_axis, pvec2)) ord_pi_y, ord_pi_z = pi_orders(bond) if not abs_coords: # put into bond coords (which might be the same as abs coords) quat = bond.bond_to_abs_coords_quat() a1py = quat.unrot(a1py) a2py = quat.unrot(a2py) a1pz = quat.unrot(a1pz) a2pz = quat.unrot(a2pz) return ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z)
def pi_info_from_abs_pvecs(bond, bond_axis, pvec1, pvec2, abs_coords=False): """ #doc; bond_axis (passed only as an optim) and pvecs are in abs coords; retval is in bond coords unless abs_coords is true """ a1py = pvec1 a2py = pvec2 a1pz = norm(cross(bond_axis, pvec1)) a2pz = norm(cross(bond_axis, pvec2)) ord_pi_y, ord_pi_z = pi_orders(bond) if not abs_coords: # put into bond coords (which might be the same as abs coords) quat = bond.bond_to_abs_coords_quat() a1py = quat.unrot(a1py) a2py = quat.unrot(a2py) a1pz = quat.unrot(a1pz) a2pz = quat.unrot(a2pz) return ((a1py, a1pz), (a2py, a2pz), ord_pi_y, ord_pi_z)
def __init__(self, xpos=0, ypos=0, right=None, up=None, rot90=False, glBegin=False): self.glBegin = glBegin if right is not None and up is not None: # The out-of-screen direction for text should always agree with # the "real" out-of-screen direction. self.outOfScreen = cross(right, up) if rot90: self.xflip = xflip = right[1] < 0.0 else: self.xflip = xflip = right[0] < 0.0 xgap = WIDTH halfheight = 0.5 * HEIGHT if xflip: xgap *= -SCALE def fx(x): return SCALE * (WIDTH - 1 - x) else: xgap *= SCALE def fx(x): return SCALE * x if rot90: ypos += xgap xpos -= halfheight * SCALE def tfm(x, y, yoff1, yflip): if yflip: y1 = SCALE * (HEIGHT - 1 - y) else: y1 = SCALE * y return Numeric.array( (xpos + yoff1 + y1, ypos + fx(x), 0.0)) else: xpos += xgap ypos -= halfheight * SCALE def tfm(x, y, yoff1, yflip): if yflip: y1 = SCALE * (HEIGHT - 1 - y) else: y1 = SCALE * y return Numeric.array( (xpos + fx(x), ypos + yoff1 + y1, 0.0)) self.tfm = tfm
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 _orient(self, cntChunk, pt1, pt2): """ Orients the CNT I{cntChunk} based on two points. I{pt1} is the first endpoint (origin) of the nanotube. The vector I{pt1}, I{pt2} defines the direction and central axis of the nanotube. @param pt1: The starting endpoint (origin) of the nanotube. @type pt1: L{V} @param pt2: The second point of a vector defining the direction and central axis of the nanotube. @type pt2: L{V} """ a = V(0.0, 0.0, -1.0) # <a> is the unit vector pointing down the center axis of the default # DNA structure which is aligned along the Z axis. bLine = pt2 - pt1 bLength = vlen(bLine) b = bLine / bLength # <b> is the unit vector parallel to the line (i.e. pt1, pt2). axis = cross(a, b) # <axis> is the axis of rotation. theta = angleBetween(a, b) # <theta> is the angle (in degress) to rotate about <axis>. scalar = bLength * 0.5 rawOffset = b * scalar if 0: # Debugging code. print "" print "uVector a = ", a print "uVector b = ", b print "cross(a,b) =", axis print "theta =", theta print "cntRise =", self.getCntRise() print "# of cells =", self.getNumberOfCells() print "scalar =", scalar print "rawOffset =", rawOffset if theta == 0.0 or theta == 180.0: axis = V(0, 1, 0) # print "Now cross(a,b) =", axis rot = (pi / 180.0) * theta # Convert to radians qrot = Q(axis, rot) # Quat for rotation delta. # Move and rotate the nanotube into final orientation. cntChunk.move( qrot.rot(cntChunk.center) - cntChunk.center + rawOffset + pt1) cntChunk.rot(qrot) # Bruce suggested I add this. It works here, but not if its # before move() and rot() above. Mark 2008-04-11 cntChunk.full_inval_and_update() return
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 _orient(self, cntChunk, pt1, pt2): """ Orients the CNT I{cntChunk} based on two points. I{pt1} is the first endpoint (origin) of the nanotube. The vector I{pt1}, I{pt2} defines the direction and central axis of the nanotube. @param pt1: The starting endpoint (origin) of the nanotube. @type pt1: L{V} @param pt2: The second point of a vector defining the direction and central axis of the nanotube. @type pt2: L{V} """ a = V(0.0, 0.0, -1.0) # <a> is the unit vector pointing down the center axis of the default # DNA structure which is aligned along the Z axis. bLine = pt2 - pt1 bLength = vlen(bLine) b = bLine/bLength # <b> is the unit vector parallel to the line (i.e. pt1, pt2). axis = cross(a, b) # <axis> is the axis of rotation. theta = angleBetween(a, b) # <theta> is the angle (in degress) to rotate about <axis>. scalar = bLength * 0.5 rawOffset = b * scalar if 0: # Debugging code. print "" print "uVector a = ", a print "uVector b = ", b print "cross(a,b) =", axis print "theta =", theta print "cntRise =", self.getCntRise() print "# of cells =", self.getNumberOfCells() print "scalar =", scalar print "rawOffset =", rawOffset if theta == 0.0 or theta == 180.0: axis = V(0, 1, 0) # print "Now cross(a,b) =", axis rot = (pi / 180.0) * theta # Convert to radians qrot = Q(axis, rot) # Quat for rotation delta. # Move and rotate the nanotube into final orientation. cntChunk.move(qrot.rot(cntChunk.center) - cntChunk.center + rawOffset + pt1) cntChunk.rot(qrot) # Bruce suggested I add this. It works here, but not if its # before move() and rot() above. Mark 2008-04-11 cntChunk.full_inval_and_update() return
def drawString(self, str, yoff=1.0, color=None, tfm=None, _font_X=_font['X']): n = len(str) if not self.glBegin: assert color is not None if hasattr(self, 'tfm'): assert tfm is None if self.xflip: def fi(i): return i - (n + 1) else: def fi(i): return i # figure out what the yflip should be p0 = self.tfm(0, 0, yoff, False) textOutOfScreen = cross(self.tfm(1, 0, yoff, False) - p0, self.tfm(0, 1, yoff, False) - p0) yflip = dot(textOutOfScreen, self.outOfScreen) < 0.0 def tfmgen(i): def tfm2(x, y): return self.tfm(x + (WIDTH+1) * fi(i), y, yoff, yflip) return tfm2 else: assert tfm is not None def tfmgen(i): def tfm2(x, y): return tfm(x + i * (WIDTH+1), y) return tfm2 for i in range(n): # A pen-stroke is a tuple of 2D vectors with integer # coordinates. Each character is represented as a stroke, # or a tuple of strokes e.g. '+' or 'X' or '#'. def drawSequence(seq, tfm=tfmgen(i)): if len(seq) == 0: return # a space character has an empty sequence if type(seq[0][0]) is not types.IntType: # handle multi-stroke characters for x in seq: drawSequence(x) return seq = map(lambda tpl: apply(tfm,tpl), seq) for i in range(len(seq) - 1): pos1, pos2 = seq[i], seq[i+1] if self.glBegin: # This is what we do for grid planes, where "somebody" # is drawGPGrid in drawers.py. # Somebody has already taken care of glBegin(GL_LINES). # TODO: explain this in docstring. glVertex(pos1[0], pos1[1], pos1[2]) glVertex(pos2[0], pos2[1], pos2[2]) # Somebody has already taken care of glEnd(). else: # This is what we do for dimensions. drawline(color, seq[i], seq[i+1]) drawSequence(_font.get(str[i], _font_X)) return
def matrix_putting_axis_at_z(axis): #bruce 060608 """ Return an orthonormal matrix which can be used (via dot(points, matrix) for a Numeric array of 3d points) to transform points so that axis transforms to the z axis. (Not sure if it has determinant 1 or -1. If you're sure, change it to always be determinant 1.) """ # this code was modified from ops_select.py's findAtomUnderMouse, as it prepares to call Chunk.findAtomUnderMouse z = norm(axis) x = arbitrary_perpendicular(axis) y = cross(z,x) matrix = transpose(V(x,y,z)) return matrix
def matrix_putting_axis_at_z(axis): #bruce 060608 """ Return an orthonormal matrix which can be used (via dot(points, matrix) for a Numeric array of 3d points) to transform points so that axis transforms to the z axis. (Not sure if it has determinant 1 or -1. If you're sure, change it to always be determinant 1.) """ # this code was modified from ops_select.py's findAtomUnderMouse, as it prepares to call Chunk.findAtomUnderMouse z = norm(axis) x = arbitrary_perpendicular(axis) y = cross(z, x) matrix = transpose(V(x, y, z)) return matrix
def _orient_to_position_first_strandA_base_in_axis_plane( self, baseList, end1, end2): """ The self._orient method orients the DNA duplex parallel to the screen (lengthwise) but it doesn't ensure align the vector through the strand end atom on StrandA and the corresponding axis end atom (at end1) , parallel to the screen. This function does that ( it has some rare bugs which trigger where it doesn't do its job but overall works okay ) What it does: After self._orient() is done orienting, it finds a Quat that rotates between the 'desired vector' between strand and axis ends at end1(aligned to the screen) and the actual vector based on the current positions of these atoms. Using this quat we rotate all the chunks (as a unit) around a common center. @BUG: The last part 'rotating as a unit' uses a readymade method in ops_motion.py -- 'rotateSpecifiedMovables' . This method itself may have some bugs because the axis of the dna duplex is slightly offset to the original axis. @see: self._determine_axis_and_strandA_endAtoms_at_end_1() @see: self.make() """ #the vector between the two end points. these are more likely #points clicked by the user while creating dna duplex using endpoints #of a line. In genral, end1 and end2 are obtained from self.make() b = norm(end2 - end1) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = cross(-self.assy.o.lineOfSight, b) unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep expected_vec = self.final_pos_strand_end_atom - self.axis_atom_end1.posn( ) q_new = Q(axis_strand_vector, expected_vec) if dot(axis_strand_vector, self.assy.o.lineOfSight) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, baseList, end1)
def _orientRawDnaGroup(self, rawDnaGroup, pt1, pt2): """ Orients the raw DNA group based on two endpoints. @param rawDnaGroup: The raw DNA group created by make(). @type rawDnaGroup: L{Group} @param pt1: The first endpoint of the DNA strand. @type pt1: L{V} @param pt2: The second endpoint of the DNA strand. @type pt2: L{V} @attention: Only works for PAM5 models. """ a = V(0.0, 0.0, -1.0) # <a> is the unit vector pointing down the center axis of the default # rawDnaGroup structure which is aligned along the Z axis. bLine = pt2 - pt1 bLength = vlen(bLine) b = bLine/bLength # <b> is the unit vector parallel to the line (i.e. pt1, pt2). axis = cross(a, b) # <axis> is the axis of rotation. theta = angleBetween(a, b) # <theta> is the angle (in degress) to rotate about <axis>. scalar = self.dna.getBaseRise() * self.getSequenceLength() * 0.5 rawOffset = b * scalar if 0: # Debugging code. print "" print "uVector a = ", a print "uVector b = ", b print "cross(a,b) =", axis print "theta =", theta print "baserise =", self.dna.getBaseRise() print "seqLength =", self.getSequenceLength() print "scalar =", scalar print "rawOffset =", rawOffset if theta == 0.0 or theta == 180.0: axis = V(0, 1, 0) # print "Now cross(a,b) =", axis rot = (pi / 180.0) * theta # Convert to radians qrot = Q(axis, rot) # Quat for rotation delta. # Move and rotate the base chunks into final orientation. for m in rawDnaGroup.members: m.move(qrot.rot(m.center) - m.center + rawOffset + pt1) m.rot(qrot)
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 _orient_to_position_first_strandA_base_in_axis_plane(self, baseList, end1, end2): """ The self._orient method orients the DNA duplex parallel to the screen (lengthwise) but it doesn't ensure align the vector through the strand end atom on StrandA and the corresponding axis end atom (at end1) , parallel to the screen. This function does that ( it has some rare bugs which trigger where it doesn't do its job but overall works okay ) What it does: After self._orient() is done orienting, it finds a Quat that rotates between the 'desired vector' between strand and axis ends at end1(aligned to the screen) and the actual vector based on the current positions of these atoms. Using this quat we rotate all the chunks (as a unit) around a common center. @BUG: The last part 'rotating as a unit' uses a readymade method in ops_motion.py -- 'rotateSpecifiedMovables' . This method itself may have some bugs because the axis of the dna duplex is slightly offset to the original axis. @see: self._determine_axis_and_strandA_endAtoms_at_end_1() @see: self.make() """ #the vector between the two end points. these are more likely #points clicked by the user while creating dna duplex using endpoints #of a line. In genral, end1 and end2 are obtained from self.make() b = norm(end2 - end1) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = cross(-self.assy.o.lineOfSight, b) unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep expected_vec = self.final_pos_strand_end_atom - self.axis_atom_end1.posn() q_new = Q(axis_strand_vector, expected_vec) if dot(axis_strand_vector, self.assy.o.lineOfSight) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, baseList, end1)
def _orient(self, chunk, pt1, pt2): """ Orients the Peptide I{chunk} based on two points. I{pt1} is the first endpoint (origin) of the Peptide. The vector I{pt1}, I{pt2} defines the direction and central axis of the Peptide. piotr 080801: I copied this method from Nanotube Builder. @param pt1: The starting endpoint (origin) of the Peptide. @type pt1: L{V} @param pt2: The second point of a vector defining the direction and central axis of the Peptide. @type pt2: L{V} """ a = V(0.0, 0.0, -1.0) # <a> is the unit vector pointing down the center axis of the default # structure which is aligned along the Z axis. bLine = pt2 - pt1 bLength = vlen(bLine) if bLength == 0: return b = bLine / bLength # <b> is the unit vector parallel to the line (i.e. pt1, pt2). axis = cross(a, b) # <axis> is the axis of rotation. theta = angleBetween(a, b) # <theta> is the angle (in degress) to rotate about <axis>. scalar = bLength * 0.5 rawOffset = b * scalar if theta == 0.0 or theta == 180.0: axis = V(0, 1, 0) # print "Now cross(a,b) =", axis rot = (pi / 180.0) * theta # Convert to radians qrot = Q(axis, rot) # Quat for rotation delta. # Move and rotate the Peptide into final orientation. chunk.move(-chunk.center) chunk.rot(qrot) # Bruce suggested I add this. It works here, but not if its # before move() and rot() above. Mark 2008-04-11 chunk.full_inval_and_update() return
def updateHandlePositions(self): """ Update handle positions and also update the resize handle radii and their 'stopper' lengths. @see: self._update_resizeHandle_radius() @see: self._update_resizeHandle_stopper_length() @see: EditNanotube_GraphicsMode._drawHandles() """ self.handlePoint1 = None # Needed! self.handlePoint2 = None #TODO: Call this method less often by implementing model_changed #see bug 2729 for a planned optimization self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE self._update_resizeHandle_radius() handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints() if 0: # Debug prints print "updateHandlePositions(): handlePoint1=", handlePoint1 print "updateHandlePositions(): handlePoint2=", handlePoint2 if handlePoint1 is not None and handlePoint2 is not None: # (that condition is bugfix for deleted axis segment, bruce 080213) self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2 #Update the 'stopper' length where the resize handle being dragged #should stop. See self._update_resizeHandle_stopper_length() #for more details self._update_resizeHandle_stopper_length() if DEBUG_ROTATION_HANDLES: self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE #Following computes the base points for rotation handles. #to be revised -- Ninad 2008-02-13 unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2) v = cross(self.glpane.lineOfSight, unitVectorAlongAxis) self.rotationHandleBasePoint1 = self.handlePoint1 + norm( v) * 4.0 self.rotationHandleBasePoint2 = self.handlePoint2 + norm( v) * 4.0 return
def _orient_for_modify(self, end1, end2): """ Do the final orientation of the newly generated dna, around its own axis so that the axis and strand atoms on this new dna are fusable with the resize end on the original dna. @param end1: end1 of new dna (segment) generated @param end2: end2 of new dna (segment) generated """ b = norm(end2 - end1) new_ladder = self.axis_atom_end1.molecule.ladder chunkListForRotation = new_ladder.all_chunks() endBaseAtomList = new_ladder.get_endBaseAtoms_containing_atom( self.axis_atom_end1) endStrandbaseAtoms = (endBaseAtomList[0], endBaseAtomList[2]) self.strandA_atom_end1 = None #TODO: REVIEW / REFACTOR THIS and DOC for atm in endStrandbaseAtoms: if atm is not None: self.strandA_atom_end1 = atm self._set_bond_direction_on_new_strand(atm) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = self._resizeEndStrand1Atom.posn() - \ self._resizeEndAxisAtom.posn() unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep q_new = Q(axis_strand_vector, vectorAlongLadderStep) if dot(axis_strand_vector, cross(vectorAlongLadderStep, b)) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, chunkListForRotation, end1)
def _orient_for_modify(self, end1, end2): """ Do the final orientation of the newly generated dna, around its own axis so that the axis and strand atoms on this new dna are fusable with the resize end on the original dna. @param end1: end1 of new dna (segment) generated @param end2: end2 of new dna (segment) generated """ b = norm(end2 - end1) new_ladder = self.axis_atom_end1.molecule.ladder chunkListForRotation = new_ladder.all_chunks() endBaseAtomList = new_ladder.get_endBaseAtoms_containing_atom(self.axis_atom_end1) endStrandbaseAtoms = (endBaseAtomList[0], endBaseAtomList[2]) self.strandA_atom_end1 = None #TODO: REVIEW / REFACTOR THIS and DOC for atm in endStrandbaseAtoms: if atm is not None: self.strandA_atom_end1 = atm self._set_bond_direction_on_new_strand(atm) axis_strand_vector = (self.strandA_atom_end1.posn() - \ self.axis_atom_end1.posn()) vectorAlongLadderStep = self._resizeEndStrand1Atom.posn() - \ self._resizeEndAxisAtom.posn() unitVectorAlongLadderStep = norm(vectorAlongLadderStep) self.final_pos_strand_end_atom = \ self.axis_atom_end1.posn() + \ vlen(axis_strand_vector)*unitVectorAlongLadderStep q_new = Q(axis_strand_vector, vectorAlongLadderStep) if dot(axis_strand_vector, cross(vectorAlongLadderStep, b)) < 0: q_new2 = Q(b, -q_new.angle) else: q_new2 = Q(b, q_new.angle) self.assy.rotateSpecifiedMovables(q_new2, chunkListForRotation, end1)
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 updateHandlePositions(self): """ Update handle positions and also update the resize handle radii and their 'stopper' lengths. @see: self._update_resizeHandle_radius() @see: self._update_resizeHandle_stopper_length() @see: NanotubeSegment_GraphicsMode._drawHandles() """ self.handlePoint1 = None # Needed! self.handlePoint2 = None #TODO: Call this method less often by implementing model_changed #see bug 2729 for a planned optimization self.cylinderWidth = CYLINDER_WIDTH_DEFAULT_VALUE self.cylinderWidth2 = CYLINDER_WIDTH_DEFAULT_VALUE self._update_resizeHandle_radius() handlePoint1, handlePoint2 = self.struct.nanotube.getEndPoints() if 0: # Debug prints print "updateHandlePositions(): handlePoint1=", handlePoint1 print "updateHandlePositions(): handlePoint2=", handlePoint2 if handlePoint1 is not None and handlePoint2 is not None: # (that condition is bugfix for deleted axis segment, bruce 080213) self.handlePoint1, self.handlePoint2 = handlePoint1, handlePoint2 #Update the 'stopper' length where the resize handle being dragged #should stop. See self._update_resizeHandle_stopper_length() #for more details self._update_resizeHandle_stopper_length() if DEBUG_ROTATION_HANDLES: self.rotation_distance1 = CYLINDER_WIDTH_DEFAULT_VALUE self.rotation_distance2 = CYLINDER_WIDTH_DEFAULT_VALUE #Following computes the base points for rotation handles. #to be revised -- Ninad 2008-02-13 unitVectorAlongAxis = norm(self.handlePoint1 - self.handlePoint2) v = cross(self.glpane.lineOfSight, unitVectorAlongAxis) self.rotationHandleBasePoint1 = self.handlePoint1 + norm(v) * 4.0 self.rotationHandleBasePoint2 = self.handlePoint2 + norm(v) * 4.0 return
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 __init__(self, xpos=0, ypos=0, right=None, up=None, rot90=False, glBegin=False): self.glBegin = glBegin if right is not None and up is not None: # The out-of-screen direction for text should always agree with # the "real" out-of-screen direction. self.outOfScreen = cross(right, up) if rot90: self.xflip = xflip = right[1] < 0.0 else: self.xflip = xflip = right[0] < 0.0 xgap = WIDTH halfheight = 0.5 * HEIGHT if xflip: xgap *= -SCALE def fx(x): return SCALE * (WIDTH - 1 - x) else: xgap *= SCALE def fx(x): return SCALE * x if rot90: ypos += xgap xpos -= halfheight * SCALE def tfm(x, y, yoff1, yflip): if yflip: y1 = SCALE * (HEIGHT - 1 - y) else: y1 = SCALE * y return Numeric.array((xpos + yoff1 + y1, ypos + fx(x), 0.0)) else: xpos += xgap ypos -= halfheight * SCALE def tfm(x, y, yoff1, yflip): if yflip: y1 = SCALE * (HEIGHT - 1 - y) else: y1 = SCALE * y return Numeric.array((xpos + fx(x), ypos + yoff1 + y1, 0.0)) self.tfm = tfm
def SurfaceNormals(self): """calculate surface normals for all points""" normals = [] for i in range(len(self.points)): normals.append(V(0.0, 0.0, 0.0)) for i in range(len(self.trias)): t = self.trias[i] p0 = self.points[t[0]] p1 = self.points[t[1]] p2 = self.points[t[2]] v0 = V(p1[0] - p0[0], p1[1] - p0[1], p1[2] - p0[2]) v1 = V(p2[0] - p0[0], p2[1] - p0[1], p2[2] - p0[2]) n = cross(v0, v1) normals[t[0]] += n normals[t[1]] += n normals[t[2]] += n self.normals = [] for n in normals: self.normals.append((n[0], n[1], n[2])) return self.normals