Meri Atom -- logic manual Atom is arranged as a set of separate windows, each containing an assembly. The intent is to be able to have multiple windows open onto the same assembly if desired, but this isn't quite implemented yet. [Note: when I write foo.bar in this document, foo can be a module (i.e. there's a file foo.py), in which case bar is a class, variable, or function in that module; or foo can be a class, in which case bar is a member function or variable.] Overview: form1.windowList, variable, a list of form1.Form1, a class, containing Form1.glpane, member variable (m.v.) containing one GLPane.GLPane, a class, containing GLPane.assy, m.v. containing one assembly.assembly, a class, containing assembly.molecules, m.v., a list of chem.molecule, class molecule.atoms, m.v., dictionary of chem.atom, class atom.element, m.v.: atom.elem, class atom.bonds, m.v., list of chem.bond, class molecule.center, m.v. xyz position molecule.quat VQT.Q (quaternion) assembly.motors, m.v., list of assembly.motor, class assembly.selatoms, m.v., dictionary of chem.atom, class assembly.selmols, m.v., list of chem.molecule, class There is a global variable windowList, in module form1, that contains a list of window objects. The window object is class Form1, which subclasses QMainWindow. A Form1 contains the controls (menus and buttons, etc) found on the window and a GLPane object (Form1.glpane), which is the graphics subwindow. The Form1 has methods that fire off the various actions from the controls, and also a pointer to the assembly object (Form1.assy). The GLPane object contains the assembly (GLPane.assy) and a number of variables that control the display. GLPane is where the mouse interactions to the pane are handled. The OpenGL setup and refresh in GLPane are tangled and suboptimal because of the quick port from Tkinter to Qt. Sorry about that! Qt has a nicer interface, and we should rewrite GLPane to take advantage. In particular, GLPane subclasses QGLWidget, which has virtual methods initializeGL, mousePressEvent, mouseReleaseEvent, mouseMoveEvent, paintGL, and resizeGL which are called automatically when appropriate. There are three major modes of mouse operation. With no modifiers, the mouse changes the point of view. The assembly/molecule appears to move on the screen, but actually the "camera" moves. The left button works a virtual trackball (implemented as a trackball object in module VQT), the right button moves the object (apparently, actually the camera) in a plane orthogonal to the line from the camera to the center of view (which is represented on-screen by a short set of axes). Using the middle button, horizontal mouse motion moves the camera in and out, leaving the center (and clipping planes) in place; moving the mouse vertically moves the center as well. The major member variables in GLPane that control the view are: width, height (of the screen in pixels) scale = 10.0, near = 0.66, far = 2.0 (initial values) scale (*2) tells you how high the screen is in space units near and far are the distances to the near and far clipping planes as fractions of the distance from point of view to center of view pov = V(0,0,0) "point of view" actually the center of view quat = Q(1,0,0,0) the quaternion telling how the view is rotated Mouse motion is dispatched to mousePressEvent, mouseReleaseEvent, and mouseMoveEvent, which check the event object for which buttons and shifts were used. (mouseMoveEvent is not called when all mouse buttons are up.) For example, the virtual trackball works as follows: mousePressEvent detects left button and no shift, and calls StartRotate. StartRotate calls SaveMouse, which sets MousePos to a vector of the x and y positions on the screen. It then calls the start method of the trackball object, which basically just saves the start position there as well. Then mouseMoveEvent dispatches a motion event to Rotate. Then SaveMouse is called to set MousePos, and the trackball's update method is called with the new position. The trackball maps the screen to an imaginary sphere around the center of view, and calculates the quaternion that would rotate the previous mouse position to the new one. That quaternion is added to the quaternion representing the current camera angle. Then Rotate calls display. This is the general entry point for paintGL, and currently doesn't have any extra housekeeping to do (but will when we go to multiple screens on the same object). paintGL recalculates the viewport (which should really be done in resizeGL), sets the color (which should really be done in initializeGL), and then does what it's supposed to. First the projection matrix, which depends on whether the view is orthographic or perspective (the flag is GLPane.ortho). Then the camera is placed using the quaternion from the trackball, and moved to match the center of view (GLPane.pov). Then several drawing methods are called: The grid, if any (GLPane.griddraw), the blue or red shape for freehand selection, if any (GLPane.pickdraw), the shape being cut out if in cookie-cutter mode (the draw method of the shape object, if any), and finally the assembly's draw method which draws all the molecules and motors, etc. Most of the mouse operations don't need any action done on button release, except for the freehand selection / cookie-cutter. The other buttons in move point of view mode start with SaveMouse and then send mouse motion events to Pan and Zoom respectively. The second major mode of mouse control is moving the selected object. This is invoked by pressing the control key while moving the mouse, and using the corresponding mouse buttons. These are similar to the above except for using StartSelRot instead of StartRotate to begin, and SelRotate, SelVert, and SelHoriz instead of Rotate, Pan, and Zoom. For the rotates, the difference is updating the trackball of each selected object instead of the main screen one. For the other two, the actual motion in space is calculated and the movesel method of the assembly called with the offset. The third major mode is selection. The member functions Startpick, ContinPick, and EndPick are called for mouse left button down, motion, and release to draw the blue or red line (its color depends on the total curvature, blue for clockwise and red for counterclockwise). SelAtom and SelPart select the individual atom or part pointed to for right and middle clicks respectively. Startpick, ContinPick, and EndPick use the non-member functions startpiectpick, piecepick, and pieceendpick respectively. I don't remember why I separated these out, and it isn't necessary to keep them that way. Essentially they map out (using pppoint) a sequence of points in 3-space (kept in GLPane.sellist) defining the curve. If GLPane.mode is 0, the curve is used to select. If 1, you were in cookie-cutter mode and the curve is used to create new diamond. In either case, the logic is in module shape. shape.curve is a class that represents a single flat closed curve in 3-space, with enough information to tell efficiently whether a point on its plane is inside or outside. This is done with an array where the curve is drawn raster-fashion and then filled. Member function isin projects a 3-d point onto this plane and returns 1 for in, 0 for out. It optionally checks whether the point is within a distance of the plane given by curve.thick. shape.shape maintains a list of curves and thus allows you to build up a more complicated shape. The isin function of shape calls that of each curve sequentially. Member functions of shape also display the chunk of diamond as it is being built, or select atoms that fall in the shape. assembly.assembly is the main class for everything you manipulate in the window, namely molecules, motors, grounds, and so forth. Motors are as yet incompletely implemented -- they need a properties box that can be used to set speed and torque, for example. Motors and grounds don't do anything in Atom -- they just get written as records for the simulator to simulate. The file-reading and -writing functions are methods of assembly. In particular, mmp files can contain an entire assembly, not just one molecule. Besides the molecules and atoms, assemblies contain lists of which atoms or molecules are selected. The molecules are in an actual Python list, assembly.selmols, while the atoms are in a dictionary (assembly.selatoms), for efficiency in removing them when they're unselected. Selection of atoms and molecules is mutually exclusive -- you can't do both at the same time. Note that "part" and "molecule" are used interchangeably. It would be better to settle on one, but I haven't yet. Molecules (chem.molecule) are the main data structure for building "real" objects. The molecule has its own quaternion and all the atoms in it are represented in space relative to its center position and the angle of the quaternion, so the whole molecule can be moved and rotated without changing anything in every atom. molecule.shakedown is the function that puts the atoms into this relative form (and sets the molecule's bounding box) after all the atoms have been added one at a time (atom initialization includes a molecule it's part of). All the atoms of a molecule are in a dictionary (molecule.atoms), again for ease of removal and membership checks. Once a molecule is drawn, its appearance is saved as a GL call list so the python code doesn't have to put it together again every video frame. This can be moved or rotated by changing the center or quat members. If something is done that will change the appearance of the molecule, changeapp must be called (or, internally, havelist set to 0). Motors are attached to, and drawn by, molecules. This is a kludge that should be fixed when we go to arbitrary structure nesting. Each atom has a unique identifier, atom.key, to be used as a key in the molecule and selected dictionaries. Each atom also has a back-link (atom.molecule) to the molecule it's a part of. Molecules don't have back links to assemblies, because I originally intended to be able to have different assemblies contain the same molecule. Whether this is doable or valuable remains to be seen, but its effects can be seen in various calls to atom and molecule methods that have to supply the assembly explicitly. Atoms that are selected have atom.picked set non-zero as well as being To draw an assembly on the screen, call its draw method, which in turn calls the draw method of everything in it. Each atom (and molecule) has a local indication of whether it's selected, and its own draw routine varies its appearance based on that. If selected, the bounding box is drawn as well as the atom or molecule, and if an atom, it's drawn in wireframe as well (the idea was to be able to see thru atoms to pick ones underneath. There is a default display code for a GLPane, and a default for each molecule which can override the GLPane one, and a code for each atom which can override each of the above. These are the constants diXXX which can be found in module "constants". The draw method for a molecule calls the draw method for each atom in the molecule and each bond attached to each atom (using the dictionary "drawn" to keep from drawing a bond twice). Bonds are basically structures that point to two atoms. Each atom keeps a list of its bonds but there is no other list of bonds. Bonds draw themselves based on the display codes of the atoms they are attached to. A bond can exist between atoms belonging to different molecules. Oddnesses: to delete an atom, call the killatom method of its molecule. To move an atom from one molecule to another, call the hopmol method of the atom. (This is done by the "separate" function of the Modify menu.)