From: <ha...@us...> - 2007-04-14 03:15:44
|
Revision: 7404 http://svn.sourceforge.net/jmol/?rev=7404&view=rev Author: hansonr Date: 2007-04-13 20:15:43 -0700 (Fri, 13 Apr 2007) Log Message: ----------- 11.1.29 adds load TRAJECTORY, an experiment in scaling efficiency for large streams of model data for which only one structure will be shown at any given time. development log out of JmolConstants and into Jmol.properties Modified Paths: -------------- trunk/Jmol/src/org/jmol/Jmol.properties trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollection.java trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollectionReader.java trunk/Jmol/src/org/jmol/adapter/smarter/PdbReader.java trunk/Jmol/src/org/jmol/viewer/Compiler.java trunk/Jmol/src/org/jmol/viewer/Eval.java trunk/Jmol/src/org/jmol/viewer/Frame.java trunk/Jmol/src/org/jmol/viewer/JmolConstants.java trunk/Jmol/src/org/jmol/viewer/ModelManager.java trunk/Jmol/src/org/jmol/viewer/RepaintManager.java trunk/Jmol/src/org/jmol/viewer/Token.java trunk/Jmol/src/org/jmol/viewer/Viewer.java Modified: trunk/Jmol/src/org/jmol/Jmol.properties =================================================================== --- trunk/Jmol/src/org/jmol/Jmol.properties 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/Jmol.properties 2007-04-14 03:15:43 UTC (rev 7404) @@ -1,5 +1,16 @@ version=11.1.29 +# adds load TRAJECTORY -- for a single file with multiple models all with +# the same number of atoms. Atom locations can also be updated on the +# fly using the data statement. +# +# adds script: option for callbacks set from within Jmol. That is, callbacks +# can either be to host page JavaScript functions or to Jmol scripts. This +# will allow interactive sessions without external JavaScript. +# +# adds resizeCallback because certain positioning of echos and sizing of the +# structure may require method intervention after the resizing +# # adds translucency for echo and hover, both text and backgrounds # # adds echo script to defined state Modified: trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollection.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollection.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollection.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -76,6 +76,8 @@ //float wavelength = Float.NaN; boolean coordinatesAreFractional; + boolean isTrajectory; + float[] notionalUnitCell = new float[6]; // expands to 22 for cartesianToFractional matrix as array (PDB) @@ -114,6 +116,13 @@ fileTypeName = type; } + Vector trajectories; + boolean setTrajectory() { + if (!isTrajectory) + trajectories = new Vector(); + return (isTrajectory = true); + } + /** * Appends an AtomSetCollection * @@ -201,6 +210,8 @@ void freeze() { //Logger.debug("AtomSetCollection.freeze; atomCount = " + atomCount); + if (isTrajectory) + finalizeTrajectories(); getAltLocLists(); getInsertionLists(); } @@ -290,12 +301,16 @@ atoms = (Atom[])ArrayUtil.doubleLength(atoms); atom.atomIndex = atomCount; atoms[atomCount++] = atom; - if (atomSetCount == 0) { + if (atomSetCount == 0) + newAtomSet(); + /* + * WAS: { atomSetCount = 1; currentAtomSetIndex = 0; atomSetNumbers[0] = 1; setAtomSetAuxiliaryInfo("modelFileNumber", new Integer(1)); } + */ atom.atomSetIndex = currentAtomSetIndex; atom.atomSite = ++atomSetAtomCounts[currentAtomSetIndex]; } @@ -655,18 +670,50 @@ //////////////////////////////////////////////////////////////// // atomSet stuff //////////////////////////////////////////////////////////////// + + int nTrajectories = 0; + Point3f[] trajectory; + void addTrajectory() { + if (trajectory.length == 0) //done with FIRST atom set + trajectory = new Point3f[atomCount]; + for (int i = 0; i < atomCount; i++) + trajectory[i] = new Point3f(atoms[i]); + trajectories.add(trajectory); + nTrajectories++; + //System.out.println(" nTrajectories:" + nTrajectories + " coord 4: " + trajectory[4]); + } + + void finalizeTrajectories() { + if (trajectory == null || trajectory.length == 0 || nTrajectories == 0) + return; + addTrajectory(); + //reset atom positions to original trajectory + Point3f[] trajectory = (Point3f[])trajectories.get(0); + for (int i = 0; i < atomCount; i++) + atoms[i].set(trajectory[i]); + setAtomSetCollectionAuxiliaryInfo("trajectories", trajectories); + } + void newAtomSet() { + if (isTrajectory) { + if (trajectory == null && atomCount > 0) + trajectory = new Point3f[0]; + if (trajectory != null) { // not BEFORE first atom set + addTrajectory(); + } + trajectory = new Point3f[atomCount]; + discardPreviousAtoms(); + } currentAtomSetIndex = atomSetCount++; if (atomSetCount > atomSetNumbers.length) { atomSetNumbers = ArrayUtil.doubleLength(atomSetNumbers); atomSetNames = ArrayUtil.doubleLength(atomSetNames); - atomSetAtomCounts = - ArrayUtil.doubleLength(atomSetAtomCounts); - atomSetProperties = - (Properties[]) ArrayUtil.doubleLength(atomSetProperties); - atomSetAuxiliaryInfo = - (Hashtable[]) ArrayUtil.doubleLength(atomSetAuxiliaryInfo); + atomSetAtomCounts = ArrayUtil.doubleLength(atomSetAtomCounts); + atomSetProperties = (Properties[]) ArrayUtil + .doubleLength(atomSetProperties); + atomSetAuxiliaryInfo = (Hashtable[]) ArrayUtil + .doubleLength(atomSetAuxiliaryInfo); } atomSetNumbers[currentAtomSetIndex] = atomSetCount; // miguel 2006 03 22 @@ -674,7 +721,8 @@ // seems that it should have been here all along, but apparently // noone else needed it atomSymbolicMap.clear(); - setAtomSetAuxiliaryInfo("modelFileNumber", new Integer(currentAtomSetIndex + 1)); + setAtomSetAuxiliaryInfo("modelFileNumber", new Integer( + currentAtomSetIndex + 1)); } /** Modified: trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollectionReader.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollectionReader.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/adapter/smarter/AtomSetCollectionReader.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -181,6 +181,8 @@ boolean ignoreFileUnitCell; boolean ignoreFileSymmetryOperators; boolean ignoreFileSpaceGroupName; + boolean isTrajectory; + // state variables boolean iHaveUnitCell; @@ -216,6 +218,8 @@ desiredSpaceGroupIndex = -1; + isTrajectory = false; + ignoreFileUnitCell = false; ignoreFileSpaceGroupName = false; ignoreFileSymmetryOperators = false; @@ -242,7 +246,9 @@ // desiredSpaceGroupIndex, // a*10000, b*10000, c*10000, alpha*10000, beta*10000, gamma*10000] - desiredModelNumber = params[0]; + isTrajectory = params[0] == -1; + if (!isTrajectory) + desiredModelNumber = params[0]; latticeCells[0] = params[1]; latticeCells[1] = params[2]; latticeCells[2] = params[3]; @@ -390,6 +396,8 @@ } void applySymmetry() throws Exception { + if (isTrajectory) + atomSetCollection.setTrajectory(); if (!needToApplySymmetry || !iHaveUnitCell) { initializeSymmetry(); return; Modified: trunk/Jmol/src/org/jmol/adapter/smarter/PdbReader.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/smarter/PdbReader.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/adapter/smarter/PdbReader.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -158,7 +158,7 @@ } } serialMap = null; - if (!isNMRdata) + //if (!isNMRdata) applySymmetry(); } catch (Exception e) { return setError(e); @@ -494,7 +494,7 @@ } void applySymmetry() throws Exception { - if (needToApplySymmetry) { + if (needToApplySymmetry && !isNMRdata) { // problem with PDB is that they don't give origins, // so we must force the issue if(spaceGroup.indexOf(":") < 0) Modified: trunk/Jmol/src/org/jmol/viewer/Compiler.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Compiler.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/Compiler.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -246,6 +246,8 @@ strFormat = strFormat.toLowerCase(); if (strFormat.equals("append") || strFormat.equals("files")) addTokenToPrefix(new Token(Token.identifier, strFormat)); + else if (strFormat.equals("trajectory")) + addTokenToPrefix(new Token(Token.trajectory)); else if (strFormat.indexOf("=") == 0) { addTokenToPrefix(new Token(Token.string, strFormat)); } @@ -693,13 +695,13 @@ return -1; } - static String[] loadFormats = { "append", "files", /*ancient:*/ "alchemy", "mol2", "mopac", "nmrpdb", "charmm", - "xyz", "mdl", "pdb" }; + static String[] loadFormats = { "append", "files", "trajectory", + /*ancient:*/ "alchemy", "mol2", "mopac", "nmrpdb", "charmm", "xyz", "mdl", "pdb" }; private boolean lookingAtLoadFormat() { int ichT; String match = script - .substring(ichToken, Math.min(cchScript, ichToken + 7)).toLowerCase(); + .substring(ichToken, Math.min(cchScript, ichToken + 10)).toLowerCase(); for (int i = loadFormats.length; --i >= 0;) { String strFormat = loadFormats[i]; int cchFormat = strFormat.length(); Modified: trunk/Jmol/src/org/jmol/viewer/Eval.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Eval.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/Eval.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -951,8 +951,11 @@ break; case Token.frame: case Token.model: - frame(1); + frame(1, false); break; + case Token.trajectory: + frame(1, true); + break; case Token.font: font(); break; @@ -3508,6 +3511,7 @@ StringBuffer loadScript = new StringBuffer("load"); int[] params = new int[4]; Point3f unitCells = viewer.getDefaultLattice(); + //params[0] will be a designated model number or -1 for a trajectory params[1] = (int) unitCells.x; params[2] = (int) unitCells.y; params[3] = (int) unitCells.z; @@ -3517,7 +3521,10 @@ if (statementLength == 1) { i = 0; } else { - if (getToken(1).tok == Token.identifier) { + if (getToken(1).tok == Token.trajectory) { + params[0] = -1; // + i = 2; + } else if (theTok == Token.identifier) { filename = parameterAsString(1); loadScript.append(" " + filename); isMerge = (filename.equalsIgnoreCase("append")); @@ -5171,8 +5178,11 @@ if (!isSyntaxCheck) viewer.setAnimationOn(animate); break; + case Token.trajectory: + frame(2, true); + break; case Token.frame: - frame(2); + frame(2, false); break; case Token.mode: animationMode(); @@ -5206,11 +5216,11 @@ } viewer.setAnimationOn(false); viewer.setAnimationDirection(1); - viewer.setAnimationRange(modelIndex, modelIndex2); + viewer.setAnimationRange(modelIndex, modelIndex2, false); viewer.setCurrentModelIndex(-1); } - void frame(int offset) throws ScriptException { + void frame(int offset, boolean isTrajectory) throws ScriptException { boolean useModelNumber = true; // for now -- as before -- remove to implement // frame/model difference @@ -5275,13 +5285,15 @@ } } boolean haveFileSet = viewer.haveFileSet(); + if (isRange && nFrames == 0) + isAll = true; + if ((haveFileSet || !useModelNumber || isAll) && isTrajectory) + evalError(GT._("trajectory not applicable in this context")); if (isSyntaxCheck) return; - if (isRange && nFrames == 0) - isAll = true; if (isAll) { viewer.setAnimationOn(false); - viewer.setAnimationRange(-1, -1); + viewer.setAnimationRange(-1, -1, false); if (!isRange) viewer.setCurrentModelIndex(-1); return; @@ -5294,7 +5306,8 @@ for (int i = 0; i < nFrames; i++) if (frameList[i] >= 0) frameList[i] %= 1000000; - int modelIndex = viewer.getModelNumberIndex(frameList[0], useModelNumber); + int modelIndex = (isTrajectory ? frameList[0] - 1 : viewer.getModelNumberIndex( + frameList[0], useModelNumber)); int modelIndex2 = -1; if (haveFileSet && nFrames == 1 && modelIndex < 0 && frameList[0] != 0) { // may have frame 2.0 or frame 2 meaning the range of models in file 2 @@ -5319,16 +5332,23 @@ } if (!isPlay && !isRange || modelIndex >= 0) { - viewer.setCurrentModelIndex(modelIndex); + if (isTrajectory) + viewer.setTrajectory(modelIndex); + else + viewer.setCurrentModelIndex(modelIndex); } if (isPlay && nFrames == 2 || isRange || isHyphen) { if (modelIndex2 < 0) - modelIndex2 = viewer.getModelNumberIndex(frameList[1], useModelNumber); + modelIndex2 = (isTrajectory ? frameList[1] - 1 : + viewer.getModelNumberIndex(frameList[1], useModelNumber)); viewer.setAnimationOn(false); viewer.setAnimationDirection(1); - viewer.setAnimationRange(modelIndex, modelIndex2); - viewer.setCurrentModelIndex(isHyphen && !isRange ? -1 - : modelIndex >= 0 ? modelIndex : 0); + viewer.setAnimationRange(modelIndex, modelIndex2, isTrajectory); + if (isTrajectory) + viewer.setTrajectory(modelIndex); + else + viewer.setCurrentModelIndex(isHyphen && !isRange ? -1 + : modelIndex >= 0 ? modelIndex : 0); } if (isPlay) viewer.resumeAnimation(); Modified: trunk/Jmol/src/org/jmol/viewer/Frame.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Frame.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/Frame.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -153,6 +153,8 @@ g3d = viewer.getGraphics3D(); isMultiFile = mmset.getModelSetAuxiliaryInfoBoolean("isMultiFile"); isPDB = mmset.getModelSetAuxiliaryInfoBoolean("isPDB"); + trajectories = (Vector) mmset.getModelSetAuxiliaryInfo("trajectories"); + isTrajectory = (trajectories != null); someModelsHaveSymmetry = mmset .getModelSetAuxiliaryInfoBoolean("someModelsHaveSymmetry"); someModelsHaveUnitcells = mmset @@ -180,6 +182,8 @@ int baseBondIndex = 0; //int baseGroupIndex = 0; boolean appendNew = true; + boolean isTrajectory = false; + Vector trajectories; void initializeModel(JmolAdapter adapter, Object clientFile) { currentModel = null; @@ -213,7 +217,8 @@ Logger.info("frame: haveSymmetry:" + someModelsHaveSymmetry + " haveUnitcells:" + someModelsHaveUnitcells + " haveFractionalCoord:" + someModelsHaveFractionalCoordinates); - Logger.info(modelCount + " model" + (modelCount == 1 ? "" : "s") + Logger.info(modelCount + " model" + (modelCount == 1 ? "" : "s") + + (isTrajectory ? ", " + trajectories.size() + " trajectories" : "") + " in this collection. Use getProperty \"modelInfo\" or" + " getProperty \"auxiliaryInfo\" to inspect them."); @@ -3103,6 +3108,19 @@ } } + void setTrajectory(int iTraj) { + if (trajectories == null || iTraj < 0 || iTraj >= trajectories.size()) + return; + //System.out.println("setting trajectory " + iTraj); + Point3f[] trajectory = (Point3f[]) trajectories.get(iTraj); + for (int i = atomCount; --i >= 0;) + atoms[i].set(trajectory[i]); + } + + int getTrajectoryCount() { + return (trajectories == null ? 1 : trajectories.size()); + } + void setAtomCoord(int atomIndex, float x, float y, float z) { if (atomIndex < 0 || atomIndex >= atomCount) return; Modified: trunk/Jmol/src/org/jmol/viewer/JmolConstants.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/JmolConstants.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/JmolConstants.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -35,8 +35,6 @@ final public class JmolConstants { - // for now, just update this by hand - // perhaps use ant filter later ... but mth doesn't like it :-( public final static String copyright = "(C) 2007 Jmol Development"; public final static String version; @@ -74,737 +72,7 @@ } version = (tmpVersion != null ? tmpVersion : "(Unknown version)"); } - - /* - * - * - 11.1.29 - - adds script: option for callbacks set from within Jmol. - - adds resizeCallback - - adds translucency for echo and hover, both text and backgrounds - - adds echo script to defined state - - adds hourglass cursor during MO/Isosurface operations - - fixes inoperative set pickingstyle measures on - - 11.1.28 - - adds - - a = script("some script command") - a = javascript("some javascript") - - putting output into a from commands such as "show" or "getProperty", for instance. - - reinstates tempManager properly. - - adds support for CAChe CSF files with MOPAC (AM1, PM3, etc.), - Density Functional, and Extended Huckel Gaussian/Slater-based molecular orbitals. - - CHANGES DEFAULT RENDERING FOR MOLECULAR ORBITALS TO: MESH NOFILL FRONTONLY - - adds MOPAC 2007 graphf output reader (gpt2 files, MOPAC molecular orbitals) - based on the VERY latest version (not released yet), which includes - "MOPAC-Graphical data" on the first line, character index 6. - - adds - - mo HOMO [+/- n] - mo LUMO [+/- n] - fixes bugs found by FindBugs: - - labels: default z setting for labels (set labelFront, set labelGroup, set labelAtom) - was not being recorded properly - move: with slab or zoom was doing integer math - GhemicalMMReader -- was incorrectly assigning aromatic to bond type 4 via fall-through of switch - - adds xodydata reading of "boundary" as unitcell - enhances default axis rendering for axes unitcell - - adds expanded isosurface-related commands: - - draw list - isosurface list - lcaocartoon list - (mo list) -- not particularly useful - pmesh list - - Listing gives id, number of vertices, number of polygons, visibility, - and title (usually the command that was given that created this isosurface) - - CHANGED BEHAVIOR FOR ISOSURFACE COMMAND WITHOUT ID INDICATED: - - Now if no ID is indicated, the previous ID is used for all commands - EXCEPT "isosurface delete", which deletes all isosurfaces. - - This is a change from Jmol 10.2 and 11.0, where if you leave - off the ID, a new isosurface is created. - - This was a needed change to prevent unwanted multiple isosurfaces. - - CHANGED BEHAVIOR FOR ISOSURFACE DEFAULT COLOR - - The default isosurface color no longer changes shade among 5 possible shades. - That was necessary only because it was easy to mistakenly make multiple - isosurfaces that otherwise would look the same. - - - 11.1.27 - fixes two state bugs: - 1) dots/geosurface not being saved properly in state - 2) animation parameters not being saved properly in state - - - 11.1.26 - fixes two nasty bugs relating to isosurfaces and JVXL files. - -- JVXL files created from molecular orbitals will show up with no color - in 11.1.0 - 11.1.25 because of a missing number in the definition line :( - -- JVXL files created from molecular orbitals will show unwanted cross-over - surfaces from + to -. - - 11.1.25 - - --fully dissociates geosurface from dots; - --allows coloring and transparency of geosurface - similarly to the way stars are colored - - 11.1.24 - - refactored Geodesic3D, Dots, DotsRenderer - independent dots/geosurface - - isosurface CAVITY - - - 11.1.23 - - fixes a number of bugs, some critical - - adds isosurface CAVITY x.xx -- a new way to depict the cavities of - a molecule in terms of color. - - - 11.1.21/22 - - adds - - load files ..... # just a cleaner version of loading multiple files. - load append ..... # APPENDS the file(s) or model(s) as new frames onto the current set. - data append ..... # same thing, but inline - - - isosurface MODEL n - pmesh MODEL n - isosurface within x.x (what) - - Introduces "real" color translucency - - color xxxx translucent N - - where N is -1 to 9. - - OR OR - translucent -1 same as Jmol 10.2 - translucent 0.0 opaque - through - translucent 1.0 transparent (invisible) - - translucent 2 0.125 32 1/8 translucency (slightly translucent) - translucent 3 0.25 64 2/8 translucency - translucent 4 0.375 96 3/8 translucency - translucent 5 0.5 128 4/8 translucency (default) - translucent 6 0.625 160 5/8 translucency - translucent 7 0.75 192 6/8 translucency - translucent 8 0.825 224 7/8 translucency (very sheer) - translucent 9 1.00 255 8/8 transparent (invisible) - - - 11.1.20 - - cleans up axes/boundbox/unitcell business - - allows for individually colored axes: - - color axis1 ... - color axis2 ... - color axis3 ... - color axes ... (of course) - - - and these objects are considered more like background -- - colors and sizes persist past file load - - to turn on and off without messing with size, just use - - showAxes = true - showBoundBox = true - - etc. - - - 11.1.19 - - allows comparison of user-defined atom properties in SELECT: - - select property_myprop < 1e-5; - - and - - x = {carbon}[5].property_test - x = {carbon}.property_test.min - x = {carbon}.property_test.max - - etc. - - This is it! :) - - 11.1.18 - - introduces user-definable atom properties that can be used - to color isosurfaces: - - x = load("file.dat"); - isosurface variable x # simple 100% vdw radius mapping - - select 1.3 - data "property_myprop @x" - isosurface property_myprop - - - - allows isosurface mapping of general atom properties: - - isosurface sasurface colorscheme bwr map property temperature - - - - adds "bwr" colorscheme as opposed to "rwb", which I think is backward. - - - isosurface -- now supports APBS ( ) - molecular electrostatic potential output files - - write -- modified to allow unquoted filename in - - write isosurface file.name - - jvxl 1.0 -- adds ANGSTROMS flag on line with # of atoms (line 3) - - 11.1.17 - - deprecation of SET - ------------------ - - The "SET" command is no longer necessary. Anything that could have - been set using "SET x .... " can now be set using - - x = .... - - This allows for a much cleaner interface because we simply make - settings in a normal sort of way: - - axes on - axes = molecular - - measures = angstroms - - It will take a bit more to make it all consistent, but the idea - is that there are then some special reserved variables that - mean something special when set, like "bondmode" - - This build allows for the applet to be "bare-bones" -- only the - essential classes included in the Jar file; others never included - or possibly in accompanying jar files, such as, perhaps, JmolPopupMenu.jar, - JmolNavigation.jar, JmolBio.jar, Jm olSurface.jar, JmolXtal.jar, etc. - - Then a developer can slim down the download. The minimum is 697K, - about 58% of the full package. All that gets you is atoms, bonds, - and measures. - - - - 11.1.16: - - First incompatibility found: - - set echo myecho (atomno=3) or (atomno=5) - 1) adds two new modifiers: - - .min - .max - - as in: - - x = {*}.bonds.length.max #the longest bond length - x = {*}.atoms.max #the last atom - - 2) extends find() to sets of lines. For example: - - longLine={*}.bonds.label("%=, %LENGTH").lines.find({*}.bonds.length.max) - message @longLine - longest = longLine%(longLine.find(",")-1) - b = {*}.bonds[longest] - select b_set;color bonds yellow - - 11.1.15: - - APPLICATION: adds undo/redo to a fixed depth of 50 commands - - - TYPE CONVERSION - - We have eight different variable types now: - - boolean True/False - integer 0, 1, 2, .... - decimal 3.5, 3.25E-3 - string "test" "3.5" - point {2.3 3.4 5.6} {0 1/2 1} - plane {0 1 1 0} - atomset {oxygen} - bondset {oxygen}.bonds - - plane and bondset are new; arithmetic operations are not fully developed. - - These can be mixed and matched to good effect. Certain relatively - intuitive rules apply. Usually the operand on the left sets - the overall type, allowing for easy type conversion depending upon - operand order: - - int + float: - - 0 + 3.6 ==> 3 (int on left rounds float on right) - 3.6 + 0 ==> 3.6 (float on left sets result) - - int/float + string: - - 0.0 + "3.5" ==> 3.5 (string converted to float) - 0 + "3.5" ==> 3 (string converted to float, then int) - "3.5" + 0 ==> "3.50" (integer converted to string) - "3.5" + 0.0 ==> "3.50.0" (float converted to string) - - 1.0 + {carbon}.xyz ==> 1 + distance from {0 0 0} to {carbon} center - {carbon}.xyz + 1 ==> {carbon} center point offset by {1 1 1} - - x = {carbon}.xyz * {1 0 0} ==> (dot product) - - Now x is the average x coordinate of carbon - - Boolean expressions are a bit different in that the operators - AND, OR, XOR, and NOT all require conversion to boolean UNLESS both - operands are atom expressions, in which case these operate directly on the - atom sets and return a new atom set, just like in SELECT. - - 3 and 0.5 ==> TRUE (both are nonzero) - false OR 2.0 ==> true (2.0 is not 0, so it is TRUE) - {oxygen} and {molecule=1} ==> all oxygen atoms in the first molecule - - x = ({oxygen} and {molecule=1}).xyz - - x is now the center point of all oxygen atoms in the first molecule - - In standard math, boolean TRUE evaluates to 1.0; FALSE evaluates to 0.0 - - true + 2.0 ==> 3.0 ("TRUE" evaluates to 1.0 in math operations) - 2 + true ==> 3 ("TRUE" evaluates to 1.0 and is then turned into an integer) - - - - ATOM EXPRESSION AUTOMATIC DEFINE - - When you set a variable to a value, and that value is a point, plane, or atom expression, - then Jmol automatically registers the result as follows: - - points: - x = "{x y z}" - - planes: - x = "{x y z w}" - - atom expressions: - x = n - x_set = "({i j k ...})" - - set x = {oxygen}.xyz - set y = {carbon}.xyz - draw @x - draw @y - draw line1 @x @y - - and - - set x = {carbon}[3][5] - select @x_set - color green - - set x = {carbon or oxygen}.bonds - select BONDS @x_set - color bonds green - - - - DATA() function and variable option for DATA command - - x = data({atomno < 10},"xyz") - x = data({atomno < 10},"mol") - x = data({atomno < 10},"pdb") - - data "model @x" - - write data t.xyz - write data t.mol - write data t.pdb - - - Better BITSET implementation - - CHANGE: default string value for a bitset is now the ({n:m}) - string format, which can be used in numerous commands. - - To get the count within a string context, just use .size: - - x = "number selected is " + {selected}.size - - or force integer math: - - x = "number selected is " + (0 + {selected}) - - merges math functions within(), connected(), substructure() into molecular math - - adds connected() both for finding atoms and for identifying bonds: - - xAtoms = connected(3, {carbon}) - xBonds = connected(1.3,2.5,"single", {carbon} {oxygen}) - - adds - - x.atoms - - to go along with x.bonds - - adds distance({carbon},{oxygen}) - adds angle({carbon}[4],{oxygen}[3], {nitrogen}[2]) - - angle function accepts from three or four - atom expressions or XYZ coordinates and returns a decimal number for - the distance, angle, or dihedral relating these points. - When more than one atom is involved, average positions are used. - - - Note that when more than one atom is involved in a set, - the following are different: - - x1 = {molecule=1}.distance{molecule=2} - x2 = {molecule=1}.xyz - {molecule=2}.xyz - - x1 is a NUMBER that is the "average distance measured - from each molecule 1 atom to the average molecule 2 position" - x2 is a point representing the VECTOR from the "average position of molecule 2" - to the "average position of molecule 1" - - The following are all equivalent: - - x3 = {molecule=1}.xyz.distance{molecule=2} - x4 = 0.0 + ({molecule=1}.xyz - {molecule=2}.xyz) - x5 = ({molecule=1}.xyz - {molecule=2}.xyz).distance{0 0 0} - x6 = distance({molecule=1} {molecule=2}) - - They are all the distance from the center of molecule 1 - to the center of molecule 2 - - - x = load("filename") - - The string data in the file are loaded into the string. - If the file does not exist, then the string contains the error message. - - - - Implements ({i j:k m n}) bitset option across all commands - - RESET varName - - reset varName # clears that variable definition - - - "UNSPECIFIED" and "QUADRUPLE" BOND TYPES - - An additional bond type is now avaiable: "UNSPECIFIED". - This shows up in the MOL2 reader and may be selected for and modified using, for example: - - select connected(unspecified) - color bonds red - - or - - select connected(unspecified) - connect (selected) single modify - - In addition, we now can depict quadruple bonds. - - - 11.1.14: - - - DYNAMIC MEASUREMENTS - - Now that we can move atoms so easily, we don't want those measurements getting stale. - - set dynamicMeasurements - - allows measurements to be recalculated on the fly. - - - MEASUREMENT FORMAT STRINGS - - Measurement format strings can be set using - - measure "format string..." - - where the format string may have the following keys: - - %= 1-based index - %VALUE the value of the measurement - %UNITS the units for the measurement - %x1 atom property "x" for atom 1 - %x2 atom property "x" for atom 2 - %x3 atom property "x" for atom 3 - %x4 atom property "x" for atom 4 - - - for example: - - measure "%a1 -- %VALUE %UNITS --- %a2" - - - MATH OPERATOR PRECEDENCE AND PARENTHESES - - Jmol 11.1.14 supports full standard operator precedence and parentheses - in IF, SET, and %{} expressions - - degUnsat = ({carbon} * 2 + {nitrogen} + 2 - {hydrogen}) / 2 - - - BRACES INDICATE ATOM EXPRESSIONS - - Use {} in IF, SET and %{} for designating atom expressions. - We are still using () for "embedded expressions" in all other commands. - - nOxygen = {oxygen} - xOxygen = {oxygen}.x - ptOxygen = {oxygen.xyz} - - a = {oxygen}.temperature - message %{{carbon}.x} - if {O22}.bondCount > 2;goto ... - - but - - draw line1 (atomno=2) (atomno=3) - - - - ATOM EXPRESSION ITEM SELECTOR [n] - - In SET, IF, and %{ } in MESSAGE and ECHO you can now specify a subset of the - atom expression. - - x = {carbon}[3] # the third carbon atom - x = {carbon}[3][5] # the third through fifth carbon atoms - x = {carbon}[3][0] # the third through last carbon atoms - - - This also works in standard select expressions, but using () instead: - - select (carbon)[3] # the third carbon atom - - and anywhere an embedded expression might be found: - - measure ((_C)[1]) ((_C)[2]) - - - POINTS IN IF, SET, and %{} - - Points in IF, SET, and %{} can be designated using the standard {x y z} - notation WITHOUT commas. This is because we have to distinguish between - atom expressions {1,2,3} and coordinates {x y z}, and this seems to me the - simplest way to do it. (Comma means "or" in atom expressions.) In all other - instances, the commas are fine, including "SET UNITCELL" and "SET DEFAULTLATTICE". - - x = {1 1 0} + {oxygen}.xyz - - - { }.distance ATOM PROPERTY FOR SET, IF, and %{} - - d = {oxygen and * /1}.distance{oxygen and * /2} - set echo top left - echo the O-O distance is %{{oxygen and * /1}.distance{oxygen and * /2}} - - message %{{atomno=3}.distance{atomno=4}} - message %{{atomno=3}.distance{1/2 1/2 1/2}} - - - - { }.label "xxxx" ATOM PROPERTY FOR IF, SET, and %{} - - The .label format provides a convenient means of delivering a wide range of - atom-based data back to the user with whatever formatting is desired. - - x2 = {atomno=3).label("atom %a\t" + (atomno=3).xyz) - - xyzFile = "" + {selected}.size + "\n\n" + {selected}.label("%a %x %y %z") - - - "....".lines - - The .lines operator splits a string into an array based on line termination. - - WRITE VAR "filename" (application only) - - pdbAtomData = {selected and not hetero}.label("ATOM %5i %-4a%1A%3n %1c%4R%1E %8.3x%8.3y%8.3z%6.2Q%6.2b %2e%2C") - pdbHeteroData = {selected and hetero}.label("HETATM%5i %-4a%1A%3n %1c%4R%1E %8.3x%8.3y%8.3z%6.2Q%6.2b %2e%2C") - pdbFile = pdbAtomData + pdbHeteroData - write VAR pdbFile "test.pdb" - - - molFileData = "line1\nline2\nline3\n"+(""+{selected}.size)%-3+(""+{selected}.bonds.size)%-3+" 0 0 0\n"+{selected}.labels("%-10.4x%-10.4y%-10.4z %2e 0 0 0 0 0")+{selected}.bonds.labels("%3D1%3D2%3ORDER 0 0 0") - GETPROPERTY "evaluate" - - You can now use getProperty to get expression information directly: - - getproperty "evaluate" "{*}.xyz" - - or on a web page the following returns a valid XYZ file for molecule 1: - - var info = jmolGetPropertyAsJavaObject("evaluate", '"" + {molecule=1} + "\n\n" + {molecule=1}.label("%a %x %y %z")') - - - - SELECTED ATOMS FROM ATOM EXPRESSIONS - - You can select atoms from an atom expression using [n]. - "[0]" means "and everything after". - - x = {atom expression}[3].ident - x = {atom expression}[3][0].xyz # 3 and after (average position) - x = {atom expression}[3][5].x # 3-5 (average x) - - - SELECTED BONDS FROM EXPRESSIONS - - You can select bonds from an atom expression - - x = {atom expression}.bonds.ident - x = {atom expression}.bonds[3].ident - - BOND INFORMATION - - You can specify how to label a set of bonds using format strings. - Numbers are currently in Angstroms. Keys are - - %# sequential number - %= file 1-based index - %ORDER the bond order - %TYPE the bond type - %LENGTH the bond length - %x1 atom property "x" for atom 1 - %x2 atom property "x" for atom 2 - - The special atom properties %D1 and %D2 give sequential numbers for the - atoms FOR THIS SET OF BONDS. This is so that a proper subfile of type MOL - could be generated. - - x = {atom expression}.bonds[3].label("%# %3ORDER %TYPE %a1 %a2 %6.3LENGTH") - - - EXPANDED MODULUS % OPERATOR IN IF, SET, AND %{} - - Usually modulus is reserved for integer math, so we - extend that here to add some useful "modulus-like" capability: - - string modulus for trimming and padding - - "test" %3 ==> left trim: "tes" - "test" %6 ==> right pad: "test " - "test" %-3 ==> right trim: "est" - "test" %-6 ==> left pad: " test" - - float modulus for rounding and scientific notation - - 3.5456 %3 ==> "3.546" (STRING!) - 3545.6 %-3 ==> "3.55E+3" (STRING!) - - 0.0 + 3.5456 %3 ==> 3.546 (float) - 0.0 + 3545.6 %-3 ==> 3550.0 - - point modulus for getting base unit cell equivalent position - - {3/2 1/2 1/1} % 0 ==> {1/2 1/2 0} - - - - 11.1.13: - - DATA "coord set" - invertSelected POINT .... - invertSelected PLANE .... - invertSelected HKL ...... - rotateSelected .... - rotateSelected spin .... - full state support for "tainting" atom positions using translateSelected or invertSelected - - set allowRotateSelected # then use ALT-LEFT for rotating just the selected molecule - - this all definitely needs some work and discussion in terms of user interface via mouse - - - write coords xxxx.spt - load xxxx.spt # minimal -- just coord. - script xxxx.spt # this is the full state load - - x = (some atom expression).atomProperty -- takes an average if more than one atom - for example: - - x = (* /1).temperature - x = (C5).bondcount - - note that you can even say: - - set echo top left - echo average position= {%{(selected).x},%{(selected).y},%{(selected).z}} - - and it will AUTOMATICALLY update with new values as you select different atoms. - - - 11.1.12: - - app fix for console entry messing up cursor position; - allows for scripting during pause or interrupt of running script using ! as first character of script - new: within(x.x,plane,$plane1) - fix for "draw off" not recorded in save state - fix for within(integer,...) bug using RasMol units - fix for _modelnumber showing up as 2001 - reconfigures _modelNumber as x.y for single models; x.x - y.y for range - adds _currentFileNumber - adds _currentModelNumberInFile - disallows user setting of variables with _ as first character - adds @variableName in any command - adds frame x.x - y.y - adds frame 0.0 - adds frame range x.x - y.y - adds file command - adds select file= - tunes select model= - - */ - - public final static String cvsDate = "$Date$"; public final static String date = cvsDate.substring(7, 23); Modified: trunk/Jmol/src/org/jmol/viewer/ModelManager.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/ModelManager.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/ModelManager.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -1360,7 +1360,15 @@ frame.setZeroBased(); } - public void setAtomCoord(int atomIndex, float x, float y, float z) { + void setTrajectory(int iTraj) { + frame.setTrajectory(iTraj); + } + + int getTrajectoryCount() { + return frame.getTrajectoryCount(); + } + + void setAtomCoord(int atomIndex, float x, float y, float z) { frame.setAtomCoord(atomIndex,x,y,z); } Modified: trunk/Jmol/src/org/jmol/viewer/RepaintManager.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/RepaintManager.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/RepaintManager.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -42,8 +42,19 @@ } int currentModelIndex = 0; + int currentTrajectory = -1; + + boolean isTrajectory; + + void setTrajectory(int iTraj) { + isTrajectory = (iTraj >= 0); + currentTrajectory = iTraj; + } + void setCurrentModelIndex(int modelIndex) { Frame frame = viewer.getFrame(); + if (modelIndex != 0 && isTrajectory) + viewer.setTrajectory(-1); if (frame == null || modelIndex < 0 || modelIndex >= frame.getModelCount()) currentModelIndex = -1; else @@ -57,7 +68,8 @@ } void setStatusFrameChanged() { - viewer.setStatusFrameChanged(animationOn ? -2 - currentModelIndex : currentModelIndex); + int i = (isTrajectory ? currentTrajectory : currentModelIndex); + viewer.setStatusFrameChanged(animationOn ? -2 - i : i); } int backgroundModelIndex = -1; @@ -85,7 +97,7 @@ bsVisibleFrames.set(backgroundModelIndex); return; } - if (frameStep == 0) + if (frameStep == 0 || isTrajectory) return; for (int i = firstModelIndex; i != lastModelIndex; i += frameStep) bsVisibleFrames.set(i); @@ -165,7 +177,8 @@ void initializePointers(int frameStep) { firstModelIndex = 0; - lastModelIndex = (frameStep == 0 ? 0 : viewer.getModelCount()) - 1; + isTrajectory = ((lastModelIndex = viewer.getTrajectoryCount()) > 1); + lastModelIndex = (frameStep == 0 ? 0 : isTrajectory ? lastModelIndex : viewer.getModelCount()) - 1; this.frameStep = frameStep; } @@ -177,6 +190,7 @@ void clearAnimation() { setAnimationOn(false); setCurrentModelIndex(0); + setTrajectory(-1); setAnimationDirection(1); setAnimationFps(10); setAnimationReplayMode(0, 0, 0); @@ -189,6 +203,7 @@ info.put("lastModelIndex", new Integer(lastModelIndex)); info.put("animationDirection", new Integer(animationDirection)); info.put("currentDirection", new Integer(currentDirection)); + info.put("currentTrajectory", new Integer(currentTrajectory)); info.put("displayModelIndex", new Integer(currentModelIndex)); info.put("displayModelNumber", new Integer(currentModelIndex >=0 ? viewer.getModelNumberDotted(currentModelIndex) : "0")); info.put("displayModelName", (currentModelIndex >=0 ? viewer.getModelName(currentModelIndex) : "")); @@ -223,6 +238,8 @@ ";\n"); commands.append("frame " + viewer.getModelNumberDotted(currentModelIndex) + ";\n"); + if (currentTrajectory > -1) + commands.append("trajectory " + currentTrajectory + ";\n"); commands.append( "animation " + (!animationOn ? "OFF" : currentDirection == 1 ? "PLAY" : "PLAYREV")).append(";\n"); @@ -271,8 +288,8 @@ Logger.error("invalid animationReplayMode:" + animationReplayMode); } - void setAnimationRange(int framePointer, int framePointer2) { - int modelCount = viewer.getModelCount(); + void setAnimationRange(int framePointer, int framePointer2, boolean isTrajectory) { + int modelCount = (isTrajectory ? viewer.getTrajectoryCount() : viewer.getModelCount()); if (framePointer < 0) framePointer = 0; if (framePointer2 < 0) framePointer2 = modelCount; if (framePointer >= modelCount) framePointer = modelCount - 1; @@ -291,7 +308,7 @@ return; } viewer.refresh(0, "Viewer:setAnimationOn"); - setAnimationRange(-1, -1); + setAnimationRange(-1, -1, isTrajectory); resumeAnimation(); } @@ -319,8 +336,9 @@ int intAnimThread = 0; void resumeAnimation() { if(currentModelIndex < 0) - setAnimationRange(firstModelIndex, lastModelIndex); - if (viewer.getModelCount() <= 1) { + setAnimationRange(firstModelIndex, lastModelIndex, isTrajectory); + int nModels = (isTrajectory ? viewer.getTrajectoryCount() : viewer.getModelCount()); + if (nModels <= 1) { animationOn = false; return; } @@ -338,11 +356,19 @@ } void setAnimationLast() { - setCurrentModelIndex(animationDirection > 0 ? lastModelIndex : firstModelIndex); + int i = animationDirection > 0 ? lastModelIndex : firstModelIndex; + if (isTrajectory) + viewer.setTrajectory(i);//will call this.setTrajectory() + else + setCurrentModelIndex(i); } void rewindAnimation() { - setCurrentModelIndex(animationDirection > 0 ? firstModelIndex : lastModelIndex); + int i = animationDirection > 0 ? firstModelIndex : lastModelIndex; + if (isTrajectory) + viewer.setTrajectory(i);//will call this.setTrajectory() + else + setCurrentModelIndex(i); currentDirection = 1; } @@ -352,29 +378,33 @@ boolean setAnimationRelative(int direction) { int frameStep = this.frameStep * direction * currentDirection; - int modelIndexNext = currentModelIndex + frameStep; - boolean isDone = (modelIndexNext > firstModelIndex && modelIndexNext > lastModelIndex - || modelIndexNext < firstModelIndex && modelIndexNext < lastModelIndex); - /* - Logger.debug("setAnimationRelative: " + - " firstModelIndex=" + firstModelIndex + - " displayModelIndex=" + displayModelIndex + - " lastModelIndex=" + lastModelIndex + - " currentDirection=" + currentDirection + - " animationDirection=" + animationDirection + - " direction=" + direction + - " isDone="+isDone + - " modelIndexNext=" + modelIndexNext + - " modelCount=" + viewer.getModelCount() + - " animationReplayMode=" + animationReplayMode + - " animationDirection=" + animationDirection); - */ + int modelIndexNext = (isTrajectory ? currentTrajectory : currentModelIndex) + + frameStep; + boolean isDone = (modelIndexNext > firstModelIndex + && modelIndexNext > lastModelIndex || modelIndexNext < firstModelIndex + && modelIndexNext < lastModelIndex); + +/* System.out.println("setAnimationRelative: " + + " firstModelIndex=" + firstModelIndex + + " displayModelIndex=" + currentModelIndex + + " trajectory=" + currentTrajectory + + " lastModelIndex=" + lastModelIndex + + " currentDirection=" + currentDirection + + " animationDirection=" + animationDirection + + " direction=" + direction + + " isDone="+isDone + + " modelIndexNext=" + modelIndexNext + + " modelCount=" + viewer.getModelCount() + + " animationReplayMode=" + animationReplayMode + + " animationDirection=" + animationDirection); +*/ if (isDone) { switch (animationReplayMode) { case ANIMATION_ONCE: return false; case ANIMATION_LOOP: - modelIndexNext = (animationDirection > 0 ? firstModelIndex : lastModelIndex); + modelIndexNext = (animationDirection > 0 ? firstModelIndex + : lastModelIndex); break; case ANIMATION_PALINDROME: currentDirection = -currentDirection; @@ -382,9 +412,15 @@ } } //Logger.debug("next="+modelIndexNext+" dir="+currentDirection+" isDone="+isDone); - if (modelIndexNext < 0 || modelIndexNext >= viewer.getModelCount()) + int nModels = (isTrajectory ? viewer.getTrajectoryCount() : viewer.getModelCount()); + if (modelIndexNext < 0 || modelIndexNext >= nModels) return false; - setCurrentModelIndex(modelIndexNext); + if (isTrajectory) { + viewer.setTrajectory(modelIndexNext);//will call this.setTrajectory() + viewer.setTainted(true); + + } else + setCurrentModelIndex(modelIndexNext); return true; } @@ -427,14 +463,15 @@ if (sleepTime > 0) Thread.sleep(sleepTime); while (!isInterrupted() && animationOn) { - if (currentModelIndex == framePointer) { + int i = (isTrajectory ? currentTrajectory : currentModelIndex); + if (i == framePointer) { targetTime += firstFrameDelayMs; sleepTime = targetTime - (int) (System.currentTimeMillis() - timeBegin); if (sleepTime > 0) Thread.sleep(sleepTime); } - if (currentModelIndex == framePointer2) { + if (i == framePointer2) { targetTime += lastFrameDelayMs; sleepTime = targetTime - (int) (System.currentTimeMillis() - timeBegin); @@ -457,6 +494,7 @@ refresh(); sleepTime = targetTime - (int) (System.currentTimeMillis() - timeBegin); + System.out.println("animation sleeping: " + sleepTime); if (sleepTime > 0) Thread.sleep(sleepTime); /*if (false && autoFps) { Modified: trunk/Jmol/src/org/jmol/viewer/Token.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Token.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/Token.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -168,6 +168,7 @@ final static int animation = command | 67; final static int frame = command | 68; // jmol commands + final static int trajectory = command | 78; final static int hide = command | 79 | expressionCommand; final static int font = command | 80; final static int hover = command | 81 | specialstring; @@ -810,6 +811,8 @@ "spin", new Token(spin, varArgCount), "frame", new Token(frame, varArgCount), "frames", null, + "trajectory", new Token(trajectory, varArgCount), + "trajectories", null, "animation", new Token(animation), "anim", null, Modified: trunk/Jmol/src/org/jmol/viewer/Viewer.java =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Viewer.java 2007-04-13 18:54:32 UTC (rev 7403) +++ trunk/Jmol/src/org/jmol/viewer/Viewer.java 2007-04-14 03:15:43 UTC (rev 7404) @@ -2472,10 +2472,19 @@ refresh(0, "Viewer:pauseAnimation()"); } - void setAnimationRange(int modelIndex1, int modelIndex2) { - repaintManager.setAnimationRange(modelIndex1, modelIndex2); + void setTrajectory(int iTraj) { + modelManager.setTrajectory(iTraj); + repaintManager.setTrajectory(iTraj); } + + int getTrajectoryCount() { + return modelManager.getTrajectoryCount(); + } + void setAnimationRange(int modelIndex1, int modelIndex2, boolean isTrajectory) { + repaintManager.setAnimationRange(modelIndex1, modelIndex2, isTrajectory); + } + BitSet getVisibleFramesBitSet() { return repaintManager.getVisibleFramesBitSet(); } @@ -3426,6 +3435,9 @@ void setStatusFrameChanged(int frameNo) { transformManager.setVibrationPeriod(Float.NaN); + boolean isTrajectory = (getTrajectoryCount() > 1); + if (isTrajectory) + return; //for now int modelIndex = repaintManager.currentModelIndex; int fileNo = getModelFileNumber(modelIndex); int modelNo = fileNo % 1000000; This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |