Revision: 20146 http://sourceforge.net/p/jmol/code/20146 Author: hansonr Date: 2014-11-28 20:29:45 +0000 (Fri, 28 Nov 2014) Log Message: ----------- Jmol.___JmolVersion="14.3.11_2014.11.28"
new feature: readers for ESS input types: CFILE, VFILE, MOPAC, Gaussian, GAMESS, Orca, PQS -- CFILE and VFILE require CFI:: and VFI:: (or C:: and V::) Modified Paths: -------------- trunk/Jmol/src/org/jmol/adapter/readers/simple/ZMatrixReader.java trunk/Jmol/src/org/jmol/adapter/smarter/Resolver.java trunk/Jmol/src/org/jmol/io/JmolUtil.java trunk/Jmol/src/org/jmol/script/T.java trunk/Jmol/src/org/jmol/viewer/Jmol.properties Modified: trunk/Jmol/src/org/jmol/adapter/readers/simple/ZMatrixReader.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/readers/simple/ZMatrixReader.java 2014-11-27 05:38:51 UTC (rev 20145) +++ trunk/Jmol/src/org/jmol/adapter/readers/simple/ZMatrixReader.java 2014-11-28 20:29:45 UTC (rev 20146) @@ -29,7 +29,6 @@ import java.util.Map; - import org.jmol.adapter.smarter.AtomSetCollectionReader; import org.jmol.adapter.smarter.Bond; import org.jmol.adapter.smarter.Atom; @@ -46,151 +45,156 @@ public class ZMatrixReader extends AtomSetCollectionReader { /* - * a simple Z-matrix reader, also serves as simple - * input file reader for Q-Chem, Jaguar, MolPro, and ADF, as produced by NBO6Pro + * A simple Z-matrix reader, also serves as simple input file reader for + * CFILE, VFILE, PQS, Orca, GAMESS, Gaussian, MOPAC, Q-Chem, Jaguar, MolPro, + * and ADF, as produced by NBO6Pro * * Can be invoked using ZMATRIX:: or with file starting with #ZMATRIX + * + * CFILE and VFILE require CFI:: and VFI::, respectively, or just C:: and V:: + * + * Other invocations include: ADF::, G::, GMS::, JAG::, MND::, MP::, ORC::, PQS::, QC::, + * but those are optional. + * * # are comments; can include jmolscript: xxxx * - * a positive dihedral is defined as being - * - * (back) - * +120 / - * (front)---O * * Any invalid element symbol such as X or XX indicates a dummy * atom that will not be included in the model but is needed * to create the structure * + * * Bob Hanson hans...@stolaf.edu 11/19/2011 */ - /* SYNTAX + // a positive dihedral is defined as being + // + // (back) + // +120 / + // (front)---O -Anything after # on a line is considered a comment. -A first line starting with #ZMATRIX defines the file type: - Jmol z-format type (just #ZMATRIX) - Gaussian (#ZMATRIX GAUSSIAN) - Mopac (#ZMATRIX MOPAC) -Lines starting with # may contain jmolscript -Blank lines are ignored: + // Anything after # on a line is considered a comment. + // A first line starting with #ZMATRIX defines the file type: + // Jmol z-format type (just #ZMATRIX) + // Gaussian (#ZMATRIX GAUSSIAN) + // Mopac (#ZMATRIX MOPAC) + // Lines starting with # may contain jmolscript + // Blank lines are ignored: + // + // #ZMATRIX -- methane + // #jmolscript: spin on + // + // C + // H 1 1.089000 + // H 1 1.089000 2 109.4710 + // H 1 1.089000 2 109.4710 3 120.0000 + // H 1 1.089000 2 109.4710 3 -120.0000 + // + // Bonds will not generally be added, leaving Jmol to do its autoBonding. + // To add bond orders, just add them as one more integer on any line + // other than the first-atom line: + // + // #ZMATRIX -- CO2 + // C + // O 1 1.3000 2 + // O 1 1.3000 2 180 2 + // + // Any position number may be replaced by a unique atom name, with number: + // + // #ZMATRIX -- CO2 + // C1 + // O1 C1 1.3000 2 + // O2 C1 1.3000 O1 180 2 + // + // Ignored dummy atoms are any atoms starting with "X" and a number, + // allowing for positioning: + // + // #ZMATRIX -- CO2 + // X1 + // X2 X1 1.0 + // C1 X1 1.0 X2 90 + // O1 C1 1.3000 X2 90 X1 0 2 + // O2 C1 1.3000 O1 180 X2 0 2 + // + // Negative distance indicates that the second angle is a normal angle, not a dihedral: + // + // #ZMATRIX -- NH3 (using simple angles only) + // N1 + // H1 N1 1.0 + // H2 N1 1.0 H1 107 + // H3 N1 -1.0 H1 107 H2 107 + // + // Negative distance and one negative angle reverses the chirality: + // + // #ZMATRIX -- NH3 (using simple angles only; reversed chirality): + // N1 + // H1 N1 1.0 + // H2 N1 1.0 H1 107 + // H3 N1 -1.0 H1 -107 H2 107 + // + // Symbolics may be used -- they may be listed first or last: + // + // #ZMATRIX + // + // dist 1.0 + // angle 107 + // + // N1 + // H1 N1 dist + // H2 N1 dist H1 angle + // H3 N1 -dist H1 angle H2 angle + // + // All atoms will end up with numbers in their names, + // but you do not need to include those in the z-matrix file + // if atoms have unique elements or you are referring + // to the last atom of that type. Still, numbering is recommended. + // + // #ZMATRIX + // + // dist 1.0 + // angle 107 + // + // N # will be N1 + // H N dist # will be H2 + // H N dist H angle # will be H3 + // H N -dist H2 angle H angle # H here refers to H3 + // + // MOPAC format will have an initial comment section. Isotopes are in the form of "C13". + // If isotopes are used, the file type MUST be identified as #ZMATRIX MOPAC. + // Lines prior to the first line with less than 2 characters will be considered to be comments and ignored. + // + // AM1 + // Ethane + // + // C + // C 1 r21 + // H 2 r32 1 a321 + // H 2 r32 1 a321 3 d4213 + // H 2 r32 1 a321 3 -d4213 + // H 1 r32 2 a321 3 60. + // H 1 r32 2 a321 3 180. + // H 1 r32 2 a321 3 d300 + // + // r21 1.5 + // r32 1.1 + // a321 109.5 + // d4213 120.0 + // d300 300.0 + // + // Gaussian will not have the third line blank and has a slightly + // different format for showing the alternative two-angle format + // involving the eighth field having the flag 1 + // (see http://www.gaussian.com/g_tech/g_ur/c_zmat.htm): + // + // C5 O1 1.0 C2 110.4 C4 105.4 1 + // C6 O1 R C2 A1 C3 A2 1 + // + // Note that Gaussian cartesian format is allowed -- simply + // set the first atom index to be 0. + // + // + // No distinction between "Variable:" and "Constant:" is made by Jmol. -#ZMATRIX -- methane -#jmolscript: spin on - -C -H 1 1.089000 -H 1 1.089000 2 109.4710 -H 1 1.089000 2 109.4710 3 120.0000 -H 1 1.089000 2 109.4710 3 -120.0000 - -Bonds will not generally be added, leaving Jmol to do its autoBonding. -To add bond orders, just add them as one more integer on any line -other than the first-atom line: - -#ZMATRIX -- CO2 -C -O 1 1.3000 2 -O 1 1.3000 2 180 2 - -Any position number may be replaced by a unique atom name, with number: - -#ZMATRIX -- CO2 -C1 -O1 C1 1.3000 2 -O2 C1 1.3000 O1 180 2 - -Ignored dummy atoms are any atoms starting with "X" and a number, -allowing for positioning: - -#ZMATRIX -- CO2 -X1 -X2 X1 1.0 -C1 X1 1.0 X2 90 -O1 C1 1.3000 X2 90 X1 0 2 -O2 C1 1.3000 O1 180 X2 0 2 - -Negative distance indicates that the second angle is a normal angle, not a dihedral: - -#ZMATRIX -- NH3 (using simple angles only) -N1 -H1 N1 1.0 -H2 N1 1.0 H1 107 -H3 N1 -1.0 H1 107 H2 107 - -Negative distance and one negative angle reverses the chirality: - -#ZMATRIX -- NH3 (using simple angles only; reversed chirality): -N1 -H1 N1 1.0 -H2 N1 1.0 H1 107 -H3 N1 -1.0 H1 -107 H2 107 - -Symbolics may be used -- they may be listed first or last: - -#ZMATRIX - -dist 1.0 -angle 107 - -N1 -H1 N1 dist -H2 N1 dist H1 angle -H3 N1 -dist H1 angle H2 angle - -All atoms will end up with numbers in their names, -but you do not need to include those in the z-matrix file -if atoms have unique elements or you are referring -to the last atom of that type. Still, numbering is recommended. - -#ZMATRIX - -dist 1.0 -angle 107 - -N # will be N1 -H N dist # will be H2 -H N dist H angle # will be H3 -H N -dist H2 angle H angle # H here refers to H3 - -MOPAC format will have an initial comment section. Isotopes are in the form of "C13". -If isotopes are used, the file type MUST be identified as #ZMATRIX MOPAC. -Lines prior to the first line with less than 2 characters will be considered to be comments and ignored. - - AM1 -Ethane - -C -C 1 r21 -H 2 r32 1 a321 -H 2 r32 1 a321 3 d4213 -H 2 r32 1 a321 3 -d4213 -H 1 r32 2 a321 3 60. -H 1 r32 2 a321 3 180. -H 1 r32 2 a321 3 d300 - -r21 1.5 -r32 1.1 -a321 109.5 -d4213 120.0 -d300 300.0 - -Gaussian will not have the third line blank and has a slightly -different format for showing the alternative two-angle format -involving the eighth field having the flag 1 -(see http://www.gaussian.com/g_tech/g_ur/c_zmat.htm): - -C5 O1 1.0 C2 110.4 C4 105.4 1 -C6 O1 R C2 A1 C3 A2 1 - -Note that Gaussian cartesian format is allowed -- simply -set the first atom index to be 0: - - -No distinction between "Variable:" and "Constant:" is made by Jmol. - - */ - protected int ac; protected Lst<Atom> vAtoms = new Lst<Atom>(); private Map<String, Integer> atomMap = new Hashtable<String, Integer>(); @@ -200,14 +204,14 @@ private Map<String, Float> symbolicMap = new Hashtable<String, Float>(); private boolean isMopac; private boolean isHeader = true; - + @Override protected boolean checkLine() throws Exception { // easiest just to grab all lines that are comments or symbolic first, then do the processing of atoms. cleanLine(); if (line.length() <= 2) // for Mopac, could be blank or an atom symbol, but not an atom name isHeader = false; - if (line.startsWith("#") || isMopac && isHeader) { + if (line.startsWith("#") || line.startsWith("*") || isMopac && isHeader) { if (line.startsWith("#ZMATRIX")) isJmolZformat = line.toUpperCase().indexOf("GAUSSIAN") < 0 && !(isMopac = (line.toUpperCase().indexOf("MOPAC") >= 0)); @@ -222,7 +226,7 @@ // Q-Chem input rd(); // spin return readBlock("$end"); - } + } if (line.startsWith("$")) return true; // $NBO if (line.contains("%mem")) { @@ -240,25 +244,111 @@ // Jaguar input return readBlock("&"); } - + if (line.contains("%coords")) { + // ORCA input + discardLinesUntilContains("coords"); + return readBlock("end"); + } + if (line.contains("GEOM=PQS")) { + // PQS input + return readBlock("BASIS"); + } if (line.contains("geometry={")) { // MolPRO via NBO6 readLines(2); return readBlock("}"); } - + tokens = getTokensStr(line); - if (tokens.length == 2) { + + if (tokens.length > 10) + return readVFI(); + + switch (tokens.length) { + case 1: + lineBuffer.clear(); + break; + case 2: + if (parseIntStr(line) > 0 && parseInt() >= 0) { + // int int --> Gaussian CFILE (via NBO) + readCFI(); + return (continuing = false); + } getSymbolic(); return true; + case 10: + // MOPAC archive format + if (tokens[0].equals("0")) + return (continuing = false); + if (tokens[1].indexOf(".") < 0) + return true; + if (lineBuffer.size() > 0 + && lineBuffer.get(lineBuffer.size() - 1).length < 8) + lineBuffer.clear(); + break; } lineBuffer.addLast(tokens); return true; } + private void readCFI() throws Exception { + // from NBO + tokens = getTokens(); + int nAtoms = (int) getValue(0); + int nBonds = (int) getValue(1); + Map<String, Atom> map = new Hashtable<String, Atom>(); + for (int i = 0; i < nAtoms; i++) { + tokens = getTokensStr(rd()); + if (tokens[1].equals("0")) + continue; + Atom a = addAtomXYZSymName(tokens, 2, null, null); + a.elementNumber = (short) getValue(1); + map.put(tokens[0], a); + } + float[] bonds = fillFloatArray(null, 0, new float[nBonds * 2]); + float[] orders = fillFloatArray(null, 0, new float[nBonds]); + for (int i = 0, pt = 0; i < nBonds; i++) + asc.addBond(new Bond(map.get("" + (int) bonds[pt++]).index, map.get("" + + (int) bonds[pt++]).index, (int) orders[i])); + } + + private boolean readVFI() throws Exception { + // VFI format -- more than 10 tokens + // 0 0 0 1 1 0.0000 0 0.00 0 0.00 0 2 + // 0 0 1 2 74 1.6040 1 0.00 0 0.00 0 1 3 4 5 6 7 + // 0 1 2 3 1 1.6040 2 116.57 3 0.00 0 2 + // 3 1 2 4 1 1.6040 4 116.57 5 216.00 6 2 + // 3 1 2 5 1 1.6040 7 63.43 8 108.00 9 2 + // 1 4 2 7 1 1.6040 10 63.43 11 108.00 12 2 + // 4 3 2 6 1 1.6040 13 63.43 14 108.00 15 2 + // 6 2 3 8 0 1.0000 16 120.00 17 72.00 18 + + Map<String, Atom> map = new Hashtable<String, Atom>(); + Lst<String[]> bonds = new Lst<String[]>(); + while (tokens != null && tokens.length > 0) { + for (int i = tokens.length; --i >= 11;) + bonds.addLast(new String[] { tokens[3], tokens[i] }); + String id = tokens[3]; + tokens = (tokens[2].equals("0") ? new String[] { tokens[4] } : tokens[1] + .equals("0") ? new String[] { tokens[4], tokens[2], tokens[5] } + : tokens[0].equals("0") ? new String[] { tokens[4], tokens[2], + tokens[5], tokens[1], tokens[7] } + : new String[] { tokens[4], tokens[2], tokens[5], tokens[1], + tokens[7], tokens[0], tokens[9] }); + Atom atom = getAtom(); + map.put(id, atom); + tokens = getTokensStr(rd()); + } + for (int i = bonds.size(); --i >= 0;) { + String[] b = bonds.get(i); + asc.addBond(new Bond(map.get(b[0]).index, map.get(b[1]).index, 1)); + } + return (continuing = false); + } + private boolean readBlock(String strEnd) throws Exception { lineBuffer.clear(); - while (rd() != null && cleanLine() != null + while (rd() != null && cleanLine() != null && (strEnd == null ? line.length() > 0 : line.indexOf(strEnd) < 0)) lineBuffer.addLast(getTokens()); return (continuing = false); @@ -268,7 +358,8 @@ // remove commas for Gaussian and parenthetical expressions for MOPAC line = line.replace(',', ' '); int pt1, pt2; - while ((pt1 = line.indexOf('(')) >= 0 && (pt2 = line.indexOf('(', pt1)) >= 0) + while ((pt1 = line.indexOf('(')) >= 0 + && (pt2 = line.indexOf('(', pt1)) >= 0) line = line.substring(0, pt1) + " " + line.substring(pt2 + 1); return (line = line.trim()); } @@ -290,8 +381,7 @@ Logger.info("symbolic " + tokens[0] + " = " + f); } - private void getAtom() throws Exception { - float f; + private Atom getAtom() throws Exception { Atom atom = new Atom(); String element = tokens[0]; int i = element.length(); @@ -299,8 +389,8 @@ //continue; } if (++i == 0) - throw new Exception("Bad Z-matrix atom name"); - if (i == element.length()) { + element = JmolAdapter.getElementSymbol(parseIntStr(element)); + if (i == 0 || i == element.length()) { // no number -- append ac atom.atomName = element + (ac + 1); } else { @@ -308,64 +398,76 @@ atom.atomName = element; element = element.substring(0, i); } - if (isMopac && i != tokens[0].length()) // C13 == 13C + if (isMopac && i != tokens[0].length()) // C13 == 13C element = tokens[0].substring(i) + element; + parseAtomTokens(atom, element); + return atom; + } + + private void parseAtomTokens(Atom atom, String element) throws Exception { setElementAndIsotope(atom, element); - + + if (tokens.length > 1 && tokens[1].indexOf(".") >= 0) { + // C + // O 1.200000 1 1 0 0 + // H 1.080000 1 120.000000 1 1 2 0 + // H 1.080000 0 120.000000 0 180.000000 0 1 2 3 + // 0 0.000000 0 0.000000 0 0.000000 0 0 0 0 + String[] t = tokens; + int l = t.length; + tokens = (t[l - 3].equals("0") ? new String[] { t[0] } + : t[l - 2].equals("0") ? new String[] { t[0], t[l - 3], t[1] } + : t[l - 1].equals("0") ? new String[] { t[0], t[l - 3], t[1], t[l - 2], t[3] } + : new String[] { t[0], t[l - 3], t[1], t[l - 2], t[3], t[l - 1], t[5] }); + } int ia = getAtomIndex(1); int bondOrder = 0; switch (tokens.length) { case 8: - // angle + dihedral + bond order - // angle + angle + bond order + // distance + angle + dihedral + bond order (not 1) + // O2 C1 1.3000 O1 180 X2 0 2 + // distance + angle + angle + "1" + // O2 C1 1.3000 O1 180 X2 120 1 case 6: - // angle + bond order - bondOrder = (int) getValue(tokens.length - 1); - //$FALL-THROUGH$ + // distance + angle + bond order + // O 1 1.3000 2 180 2 + atom = getAtomGeneral(atom, ia, + bondOrder = (int) getValue(tokens.length - 1)); + break; case 5: - // angle - // Gaussian Sym 0 x y z - if (tokens.length == 5 && tokens[1].equals("0")) { + if (tokens[1].equals("0")) { + // Gaussian Sym 0 x y z atom.set(getValue(2), getValue(3), getValue(4)); - bondOrder = 0; break; } + // distance + angle + // H 1 1.089000 2 109.4710 //$FALL-THROUGH$ case 7: - // angle + dihedral or angle + angle - int ib, ic; - if (tokens.length < 7 && ac != 2 - || (ib = getAtomIndex(3)) < 0 - || (ic = (tokens.length < 7 ? -2 : getAtomIndex(5))) == -1 - ) { - atom = null; - } else { - float d = getValue(2); - float theta1 = getValue(4); - float theta2 = (tokens.length < 7 ? Float.MAX_VALUE : getValue(6)); - if (tokens.length == 8 && !isJmolZformat && !isMopac && bondOrder == 1) - // Gaussian indicator of alternative angle representation - d = -Math.abs(d); - atom = setAtom(atom, ia, ib, ic, d, theta1, theta2); - } + // distance + angle + dihedral + // H 1 1.089000 2 109.4710 3 120.0000 + // distance + two angles + // H3 N1 1.0 H1 107 H2 107 1 + atom = getAtomGeneral(atom, ia, 0); break; case 4: - // angle + bond order - // Gaussian cartesian if (getAtomIndex(1) < 0) { + // Gaussian cartesian + // N1 0.0 0.0 0.0 atom.set(getValue(1), getValue(2), getValue(3)); break; } + // distance + bond order + // O 1 1.3000 2 bondOrder = (int) getValue(3); //$FALL-THROUGH$ case 3: - // angle - f = getValue(2); - if (ac != 1 - || (ia = getAtomIndex(1)) != 0) { + // distance only + // H 1 1.089000 + if (ac != 1 || (ia = getAtomIndex(1)) != 0) { atom = null; } else { - atom.set(f, 0, 0); + atom.set(getValue(2), 0, 0); } break; case 1: @@ -380,21 +482,35 @@ if (atom == null) throw new Exception("bad Z-Matrix line"); vAtoms.addLast(atom); - atomMap.put(atom.atomName, Integer.valueOf(ac)); - ac++; + atomMap.put(atom.atomName, Integer.valueOf(ac++)); if (element.startsWith("X") && JmolAdapter.getElementNumber(element) < 1) { - Logger.info("#dummy atom ignored: atom " + ac + " - " - + atom.atomName); + Logger.info("#dummy atom ignored: atom " + ac + " - " + atom.atomName); } else { asc.addAtom(atom); setAtomCoord(atom); Logger.info(atom.atomName + " " + atom.x + " " + atom.y + " " + atom.z); - if (isJmolZformat && bondOrder > 0) - asc.addBond(new Bond(atom.index, - vAtoms.get(ia).index, bondOrder)); + if (bondOrder < 0 || isJmolZformat && bondOrder > 0) + asc.addBond(new Bond(atom.index, vAtoms.get(ia).index, Math + .abs(bondOrder))); } } + private Atom getAtomGeneral(Atom atom, int ia, int bondOrder) + throws Exception { + int ib, ic; + if (tokens.length < 7 && ac != 2 || (ib = getAtomIndex(3)) < 0 + || (ic = (tokens.length < 7 ? -2 : getAtomIndex(5))) == -1) { + return null; + } + float d = getValue(2); + float theta1 = getValue(4); + float theta2 = (tokens.length < 7 ? Float.MAX_VALUE : getValue(6)); + if (tokens.length == 8 && !isJmolZformat && !isMopac && bondOrder == 1) + // Gaussian indicator of alternative angle representation + d = -Math.abs(d); + return atom = setAtom(atom, ia, ib, ic, d, theta1, theta2); + } + private float getSymbolic(String key) { boolean isNeg = key.startsWith("-"); Float F = symbolicMap.get(isNeg ? key.substring(1) : key); @@ -403,20 +519,20 @@ float f = F.floatValue(); return (isNeg ? -f : f); } - + private float getValue(int i) throws Exception { float f = getSymbolic(tokens[i]); - if (Float.isNaN(f)) + if (Float.isNaN(f)) { f = parseFloatStr(tokens[i]); - if (Float.isNaN(f)) - throw new Exception("Bad Z-matrix value: " + tokens[i]); + if (Float.isNaN(f)) + throw new Exception("Bad Z-matrix value: " + tokens[i]); + } return f; } private int getAtomIndex(int i) { String name; - if (i >= tokens.length - || (name = tokens[i]).indexOf(".") >= 0 + if (i >= tokens.length || (name = tokens[i]).indexOf(".") >= 0 || !PT.isLetterOrDigit(name.charAt(0))) return -1; int ia = parseIntStr(name); @@ -426,16 +542,16 @@ if (I == null) { // We allow just an element name without a number if it is unique. // The most recent atom match will be used - for (i = vAtoms.size(); --i >= 0; ) { + for (i = vAtoms.size(); --i >= 0;) { Atom atom = vAtoms.get(i); - if (atom.atomName.startsWith(name) + if (atom.atomName.startsWith(name) && atom.atomName.length() > name.length() && PT.isDigit(atom.atomName.charAt(name.length()))) { I = atomMap.get(atom.atomName); break; - } + } } - } + } if (I == null) ia = -1; else @@ -451,9 +567,9 @@ private final V3 v2 = new V3(); private final P4 plane1 = new P4(); private final P4 plane2 = new P4(); - + protected Atom setAtom(Atom atom, int ia, int ib, int ic, float d, - float theta1, float theta2) { + float theta1, float theta2) { if (Float.isNaN(theta1) || Float.isNaN(theta2)) return null; pt0.setT(vAtoms.get(ia)); Modified: trunk/Jmol/src/org/jmol/adapter/smarter/Resolver.java =================================================================== --- trunk/Jmol/src/org/jmol/adapter/smarter/Resolver.java 2014-11-27 05:38:51 UTC (rev 20145) +++ trunk/Jmol/src/org/jmol/adapter/smarter/Resolver.java 2014-11-28 20:29:45 UTC (rev 20146) @@ -156,6 +156,9 @@ private final static String getReaderFromType(String type) { type = ";" + type.toLowerCase() + ";"; + if (";cfi;vfi;mnd;jag;adf;gms;g;c;mp;nw;orc;pqs;qc;v;".indexOf(type) >= 0) + return "ZMatrix"; + String set; int pt; for (int i = readerSets.length; --i >= 0;) @@ -212,8 +215,6 @@ * This is only necessary for a few file types, where only numbers are involved -- * molecular dynamics coordinate files, for instance (mdcrd). * - * We must do this in a very specific order. DON'T MESS WITH THIS! - * * @param readerOrDocument * @param returnLines * @return readerName or a few lines, if requested, or null @@ -221,24 +222,22 @@ */ private static String determineAtomSetCollectionReader(Object readerOrDocument, boolean returnLines) throws Exception { + + + // We must do this in a very specific order. DON'T MESS WITH THIS! + if (readerOrDocument instanceof GenericBinaryDocument) { // only binary type to date; return "PyMOL"; } + + String readerName; + LimitedLineReader llr = new LimitedLineReader((BufferedReader) readerOrDocument, 16384); - // first check just the first 64 bytes - String leader = llr.getHeader(LEADER_CHAR_MAX).trim(); - for (int i = 0; i < fileStartsWithRecords.length; ++i) { - String[] recordTags = fileStartsWithRecords[i]; - for (int j = 1; j < recordTags.length; ++j) { - String recordTag = recordTags[j]; - if (leader.startsWith(recordTag)) - return recordTags[0]; - } - } + // Test 1. check magic number for embedded Jmol script or PNGJ // PNG or BCD-encoded JPG or JPEG if (leader.indexOf("PNG") == 1 && leader.indexOf("PNGJ") >= 0) @@ -246,9 +245,12 @@ if (leader.indexOf("PNG") == 1 || leader.indexOf("JPG") == 1 || leader.indexOf("JFIF") == 6) return "spt"; // presume embedded script --- allows dragging into Jmol - if (leader.startsWith("##TITLE")) - return "Jcampdx"; + // Test 2. check starting 64 bytes of file + + if ((readerName = checkFileStart(leader)) != null) + return readerName; + // now allow identification in first 16 lines // excluding those starting with "#" @@ -260,149 +262,137 @@ nLines++; } - String readerName; - if ((readerName = checkSpecial(nLines, lines, false)) != null) + // Test 3. check special file formats (pass 1) + + if ((readerName = checkSpecial1(nLines, lines)) != null) return readerName; + // Test 4. check line starts + if ((readerName = checkLineStarts(lines)) != null) return readerName; + // Test 5. check content of initial 16K bytes of file + if ((readerName = checkHeaderContains(llr.getHeader(0))) != null) return readerName; - if ((readerName = checkSpecial(nLines, lines, true)) != null) + // Test 6. check special file formats (pass 2) + + if ((readerName = checkSpecial2(lines)) != null) return readerName; + // Failed to identify file type + return (returnLines ? "\n" + lines[0] + "\n" + lines[1] + "\n" + lines[2] + "\n" : null); } - private static String checkHeaderContains(String header) throws Exception { - for (int i = 0; i < headerContainsRecords.length; ++i) { - String[] recordTags = headerContainsRecords[i]; - for (int j = 1; j < recordTags.length; ++j) { - String recordTag = recordTags[j]; - if (header.indexOf(recordTag) < 0) - continue; - String type = recordTags[0]; - // for XML check for an error message from a server -- certainly not XML - // but new CML format includes xmlns:xhtml="http://www.w3.org/1999/xhtml" in <cml> tag. - return (!type.equals("Xml") ? type - : header.indexOf("<!DOCTYPE HTML PUBLIC") < 0 - && header.indexOf("XHTML") < 0 - && (header.indexOf("xhtml") < 0 || header.indexOf("<cml") >= 0) - ? getXmlType(header) - : null); - } - } - return null; - } + //////////////////////////////////////////////////////////////// + // Test 2. check to see if first few bytes (trimmed) start with any of these strings + //////////////////////////////////////////////////////////////// - private static String checkLineStarts(String[] lines) { - for (int i = 0; i < lineStartsWithRecords.length; ++i) { - String[] recordTags = lineStartsWithRecords[i]; + private static String checkFileStart(String leader) { + for (int i = 0; i < fileStartsWithRecords.length; ++i) { + String[] recordTags = fileStartsWithRecords[i]; for (int j = 1; j < recordTags.length; ++j) { String recordTag = recordTags[j]; - for (int k = 0; k < lines.length; k++) { - if (lines[k].startsWith(recordTag)) - return recordTags[0]; - } + if (leader.startsWith(recordTag)) + return recordTags[0]; } } return null; } - private static String getXmlType(String header) throws Exception { - if (header.indexOf("http://www.molpro.net/") >= 0) { - return "XmlMolpro"; - } - if (header.indexOf("odyssey") >= 0) { - return "XmlOdyssey"; - } - if (header.indexOf("C3XML") >= 0) { - return "XmlChem3d"; - } - if (header.indexOf("arguslab") >= 0) { - return "XmlArgus"; - } - if (header.indexOf("jvxl") >= 0 - || header.indexOf(CML_NAMESPACE_URI) >= 0 - || header.indexOf("cml:") >= 0) { - return "XmlCml"; - } - if (header.indexOf("XSD") >= 0) { - return "XmlXsd"; - } - if (header.indexOf(">vasp") >= 0) { - return "XmlVasp"; - } - if (header.indexOf("<GEOMETRY_INFO>") >= 0) { - return "XmlQE"; - } - - return "XmlCml(unidentified)"; - } + private final static int LEADER_CHAR_MAX = 64; + + private final static String[] sptRecords = + { "spt", "# Jmol state", "# Jmol script", "JmolManifest" }; + + private final static String[] m3dStartRecords = + { "Alchemy", "STRUCTURE 1.00 1" }; // M3D reader is very similar to Alchemy + + private final static String[] cubeFileStartRecords = + {"Cube", "JVXL", "#JVXL"}; + private final static String[] mol2Records = + {"Mol2", "mol2", "@<TRIPOS>"}; -// -// private final static String checkType(String[][] typeTags, String type) { -// for (int i = 0; i < typeTags.length; ++i) -// if (typeTags[i][0].toLowerCase().equals(type)) -// return typeTags[i][0]; -// return null; -// } -// + private final static String[] webmoFileStartRecords = + {"WebMO", "[HEADER]"}; + private final static String[] moldenFileStartRecords = + {"Molden", "[Molden"}; + + private final static String[] dcdFileStartRecords = + {"BinaryDcd", "T\0\0\0CORD", "\0\0\0TCORD"}; + + private final static String[] tlsDataOnlyFileStartRecords = + {"TlsDataOnly", "REFMAC\n\nTL", "REFMAC\r\n\r\n", "REFMAC\r\rTL"}; - private final static String checkSpecial(int nLines, String[] lines, - boolean isEnd) { + private final static String[] zMatrixFileStartRecords = + {"ZMatrix", "#ZMATRIX", "%mem=", "AM1"}; + + private final static String[] magresFileStartRecords = + {"Magres", "#$magres", "# magres"}; + + private final static String[] pymolStartRecords = + {"PyMOL", "}q" }; + + private final static String[] janaStartRecords = + { "Jana", "Version Jana" }; + + private final static String[] jsonStartRecords = + { "JSON", "{\"mol\":" }; + + private final static String[] jcampdxStartRecords = + { "Jcampdx", "##TITLE" }; + + private final static String[][] fileStartsWithRecords = + { sptRecords, m3dStartRecords, cubeFileStartRecords, + mol2Records, webmoFileStartRecords, + moldenFileStartRecords, dcdFileStartRecords, tlsDataOnlyFileStartRecords, + zMatrixFileStartRecords, magresFileStartRecords, pymolStartRecords, + janaStartRecords, jsonStartRecords, jcampdxStartRecords }; + + //////////////////////////////////////////////////////////////// + // Test 3. check first time for special file types + //////////////////////////////////////////////////////////////// + + private final static String checkSpecial1(int nLines, String[] lines) { // the order here is CRITICAL - - if (isEnd) { - if (checkGromacs(lines)) - return "Gromacs"; - if (checkCrystal(lines)) - return "Crystal"; - if (checkCastep(lines)) - return "Castep"; - if (checkVasp(lines, true)) - return "VaspPoscar"; - if (checkVasp(lines, false)) - return "VaspChgcar"; - } else { - if (nLines == 1 && lines[0].length() > 0 - && PT.isDigit(lines[0].charAt(0))) - return "Jme"; //only one line, and that line starts with a number - - if (checkMopacGraphf(lines)) - return "MopacGraphf"; //must be prior to checkFoldingXyz and checkMol - if (checkOdyssey(lines)) - return "Odyssey"; - switch (checkMol(lines)) { - case 1: - case 3: - case 2000: - case 3000: - return "Mol"; - } - switch(checkXyz(lines)) { - case 1: - return "Xyz"; - case 2: - return "Bilbao"; - } - if (checkAlchemy(lines[0])) - return "Alchemy"; - if (checkFoldingXyz(lines)) - return "FoldingXyz"; - if (checkCube(lines)) - return "Cube"; - if (checkWien2k(lines)) - return "Wien2k"; - if (checkAims(lines)) - return "Aims"; - if (checkGenNBO(lines)) - return "GenNBO"; + + if (nLines == 1 && lines[0].length() > 0 && PT.isDigit(lines[0].charAt(0))) + return "Jme"; //only one line, and that line starts with a number + + if (checkMopacGraphf(lines)) + return "MopacGraphf"; //must be prior to checkFoldingXyz and checkMol + if (checkOdyssey(lines)) + return "Odyssey"; + switch (checkMol(lines)) { + case 1: + case 3: + case 2000: + case 3000: + return "Mol"; } + switch (checkXyz(lines)) { + case 1: + return "Xyz"; + case 2: + return "Bilbao"; + } + if (checkAlchemy(lines[0])) + return "Alchemy"; + if (checkFoldingXyz(lines)) + return "FoldingXyz"; + if (checkCube(lines)) + return "Cube"; + if (checkWien2k(lines)) + return "Wien2k"; + if (checkAims(lines)) + return "Aims"; + if (checkGenNBO(lines)) + return "GenNBO"; return null; } @@ -445,47 +435,6 @@ return false; } - private static boolean checkCastep(String[] lines) { - for ( int i = 0; i<lines.length; i++ ) { - if (lines[i].indexOf("Frequencies in cm-1") == 1 - || lines[i].contains("CASTEP") - || lines[i].toUpperCase().startsWith("%BLOCK LATTICE_ABC") - || lines[i].toUpperCase().startsWith("%BLOCK LATTICE_CART") - || lines[i].toUpperCase().startsWith("%BLOCK POSITIONS_FRAC") - || lines[i].toUpperCase().startsWith("%BLOCK POSITIONS_ABS") - || lines[i].contains("<-- E")) return true; - } - return false; - } - - private static boolean checkVasp(String[] lines, boolean isPoscar) { - String line = lines[isPoscar ? 7 : 6].toLowerCase(); - if (isPoscar && line.contains("selective")) - line = lines[7].toLowerCase(); - return (line.contains("direct") || line.contains("cartesian")); - } - - private static boolean checkCrystal(String[] lines) { - String s = lines[1].trim(); - if (s.equals("SLAB") ||s.equals("MOLECULE") - || s.equals("CRYSTAL") - || s.equals("POLYMER") || (s = lines[3]).equals("SLAB") - || s.equals("MOLECULE") || s.equals("POLYMER")) - return true; - for (int i = 0; i < lines.length; i++) { - if (lines[i].trim().equals("OPTGEOM") || lines[i].trim().equals("FREQCALC") || - lines[i].contains("DOVESI") - || lines[i].contains("TORINO") - || lines[i].contains("http://www.crystal.unito.it") - //new lenghty scripts for CRYSTAL14 - || lines[i].contains("Pcrystal") - || lines[i].contains("MPPcrystal") - || lines[i].contains("crystal executable")) - return true; - } - return false; - } - private static boolean checkCube(String[] lines) { try { for (int j = 2; j <= 5; j++) { @@ -543,16 +492,6 @@ || lines[2].indexOf(" N A T U R A L A T O M I C O R B I T A L") >= 0); } - private static boolean checkGromacs(String[] lines) { - if (PT.parseInt(lines[1]) == Integer.MIN_VALUE) - return false; - int len = -1; - for (int i = 2; i < 16 && len != 0; i++) - if ((len = lines[i].length()) != 69 && len != 45 && len != 0) - return false; - return true; - } - private static int checkMol(String[] lines) { String line4trimmed = ("X" + lines[3]).trim().toUpperCase(); if (line4trimmed.length() < 7 || line4trimmed.indexOf(".") >= 0) @@ -609,11 +548,11 @@ return false; } - private static boolean checkWien2k(String[] lines) { - return (lines[2].startsWith("MODE OF CALC=") - || lines[2].startsWith(" RELA") - || lines[2].startsWith(" NREL")); - } + private static boolean checkWien2k(String[] lines) { + return (lines[2].startsWith("MODE OF CALC=") + || lines[2].startsWith(" RELA") || lines[2] + .startsWith(" NREL")); + } private static int checkXyz(String[] lines) { // first and third lines numerical --> Bilbao format @@ -631,76 +570,25 @@ } return 0; } - - -/* - private void dumpLines(String[] lines) { - for (int i = 0; i < lines.length; i++) { - Logger.info("\nLine "+i + " len " + lines[i].length()); - for (int j = 0; j < lines[i].length(); j++) - Logger.info("\t"+(int)lines[i].charAt(j)); - } - Logger.info(""); - } - -*/ //////////////////////////////////////////////////////////////// - // these test files that startWith one of these strings + // Test 4. One of the first 16 lines starts with one of these strings //////////////////////////////////////////////////////////////// - private final static int LEADER_CHAR_MAX = 64; - - private final static String[] sptRecords = - { "spt", "# Jmol state", "# Jmol script", "JmolManifest" }; - - private final static String[] m3dStartRecords = - { "Alchemy", "STRUCTURE 1.00 1" }; // M3D reader is very similar to Alchemy - - private final static String[] cubeFileStartRecords = - {"Cube", "JVXL", "#JVXL"}; + private static String checkLineStarts(String[] lines) { + for (int i = 0; i < lineStartsWithRecords.length; ++i) { + String[] recordTags = lineStartsWithRecords[i]; + for (int j = 1; j < recordTags.length; ++j) { + String recordTag = recordTags[j]; + for (int k = 0; k < lines.length; k++) { + if (lines[k].startsWith(recordTag)) + return recordTags[0]; + } + } + } + return null; + } - private final static String[] mol2Records = - {"Mol2", "mol2", "@<TRIPOS>"}; - - private final static String[] webmoFileStartRecords = - {"WebMO", "[HEADER]"}; - - private final static String[] moldenFileStartRecords = - {"Molden", "[Molden"}; - - private final static String[] dcdFileStartRecords = - {"BinaryDcd", "T\0\0\0CORD", "\0\0\0TCORD"}; - - private final static String[] tlsDataOnlyFileStartRecords = - {"TlsDataOnly", "REFMAC\n\nTL", "REFMAC\r\n\r\n", "REFMAC\r\rTL"}; - - private final static String[] zMatrixFileStartRecords = - {"ZMatrix", "#ZMATRIX", "%mem="}; - - private final static String[] magresFileStartRecords = - {"Magres", "#$magres", "# magres"}; - - private final static String[] pymolStartRecords = - {"PyMOL", "}q" }; - - private final static String[] janaStartRecords = - { "Jana", "Version Jana" }; - - private final static String[] jsonStartRecords = - { "JSON", "{\"mol\":" }; - - private final static String[][] fileStartsWithRecords = - { sptRecords, m3dStartRecords, cubeFileStartRecords, - mol2Records, webmoFileStartRecords, - moldenFileStartRecords, dcdFileStartRecords, tlsDataOnlyFileStartRecords, - zMatrixFileStartRecords, magresFileStartRecords, pymolStartRecords, - janaStartRecords, jsonStartRecords }; - - //////////////////////////////////////////////////////////////// - // these test if one of the first 16 lines starts with one of these strings - //////////////////////////////////////////////////////////////// - private final static String[] mmcifLineStartRecords = { "MMCif", "_entry.id", "_database_PDB_", "_pdbx_", "_chem_comp.pdbx_type", "_audit_author.name" }; @@ -763,9 +651,61 @@ //////////////////////////////////////////////////////////////// - // contains formats + // Test 5. contents of first 16384 bytes //////////////////////////////////////////////////////////////// + private static String checkHeaderContains(String header) throws Exception { + for (int i = 0; i < headerContainsRecords.length; ++i) { + String[] recordTags = headerContainsRecords[i]; + for (int j = 1; j < recordTags.length; ++j) { + String recordTag = recordTags[j]; + if (header.indexOf(recordTag) < 0) + continue; + String type = recordTags[0]; + // for XML check for an error message from a server -- certainly not XML + // but new CML format includes xmlns:xhtml="http://www.w3.org/1999/xhtml" in <cml> tag. + return (!type.equals("Xml") ? type + : header.indexOf("<!DOCTYPE HTML PUBLIC") < 0 + && header.indexOf("XHTML") < 0 + && (header.indexOf("xhtml") < 0 || header.indexOf("<cml") >= 0) + ? getXmlType(header) + : null); + } + } + return null; + } + + private static String getXmlType(String header) throws Exception { + if (header.indexOf("http://www.molpro.net/") >= 0) { + return "XmlMolpro"; + } + if (header.indexOf("odyssey") >= 0) { + return "XmlOdyssey"; + } + if (header.indexOf("C3XML") >= 0) { + return "XmlChem3d"; + } + if (header.indexOf("arguslab") >= 0) { + return "XmlArgus"; + } + if (header.indexOf("jvxl") >= 0 + || header.indexOf(CML_NAMESPACE_URI) >= 0 + || header.indexOf("cml:") >= 0) { + return "XmlCml"; + } + if (header.indexOf("XSD") >= 0) { + return "XmlXsd"; + } + if (header.indexOf(">vasp") >= 0) { + return "XmlVasp"; + } + if (header.indexOf("<GEOMETRY_INFO>") >= 0) { + return "XmlQE"; + } + + return "XmlCml(unidentified)"; + } + private final static String[] bilbaoContainsRecords = { "Bilbao", ">Bilbao Crystallographic Server<" }; @@ -849,7 +789,7 @@ { "GaussianFchk", "Number of point charges in /Mol/" }; private final static String[] zmatrixContainsRecords = - { "ZMatrix", " ATOMS cartesian", "$molecule", "&zmat", "geometry={", "$DATA" }; + { "ZMatrix", " ATOMS cartesian", "$molecule", "&zmat", "geometry={", "$DATA", "%coords", "GEOM=PQS" }; private final static String[][] headerContainsRecords = @@ -866,5 +806,77 @@ zmatrixContainsRecords }; + + //////////////////////////////////////////////////////////////// + // Test 6. check second time for special file types + //////////////////////////////////////////////////////////////// + + private final static String checkSpecial2(String[] lines) { + // the order here is CRITICAL + if (checkGromacs(lines)) + return "Gromacs"; + if (checkCrystal(lines)) + return "Crystal"; + if (checkCastep(lines)) + return "Castep"; + if (checkVasp(lines, true)) + return "VaspPoscar"; + if (checkVasp(lines, false)) + return "VaspChgcar"; + return null; + } + + private static boolean checkCrystal(String[] lines) { + String s = lines[1].trim(); + if (s.equals("SLAB") ||s.equals("MOLECULE") + || s.equals("CRYSTAL") + || s.equals("POLYMER") || (s = lines[3]).equals("SLAB") + || s.equals("MOLECULE") || s.equals("POLYMER")) + return true; + for (int i = 0; i < lines.length; i++) { + if (lines[i].trim().equals("OPTGEOM") || lines[i].trim().equals("FREQCALC") || + lines[i].contains("DOVESI") + || lines[i].contains("TORINO") + || lines[i].contains("http://www.crystal.unito.it") + //new lenghty scripts for CRYSTAL14 + || lines[i].contains("Pcrystal") + || lines[i].contains("MPPcrystal") + || lines[i].contains("crystal executable")) + return true; + } + return false; + } + + private static boolean checkGromacs(String[] lines) { + if (PT.parseInt(lines[1]) == Integer.MIN_VALUE) + return false; + int len = -1; + for (int i = 2; i < 16 && len != 0; i++) + if ((len = lines[i].length()) != 69 && len != 45 && len != 0) + return false; + return true; + } + + private static boolean checkCastep(String[] lines) { + for ( int i = 0; i<lines.length; i++ ) { + if (lines[i].indexOf("Frequencies in cm-1") == 1 + || lines[i].contains("CASTEP") + || lines[i].toUpperCase().startsWith("%BLOCK LATTICE_ABC") + || lines[i].toUpperCase().startsWith("%BLOCK LATTICE_CART") + || lines[i].toUpperCase().startsWith("%BLOCK POSITIONS_FRAC") + || lines[i].toUpperCase().startsWith("%BLOCK POSITIONS_ABS") + || lines[i].contains("<-- E")) return true; + } + return false; + } + + private static boolean checkVasp(String[] lines, boolean isPoscar) { + String line = lines[isPoscar ? 7 : 6].toLowerCase(); + if (isPoscar && line.contains("selective")) + line = lines[7].toLowerCase(); + return (line.contains("direct") || line.contains("cartesian")); + } + + } Modified: trunk/Jmol/src/org/jmol/io/JmolUtil.java =================================================================== --- trunk/Jmol/src/org/jmol/io/JmolUtil.java 2014-11-27 05:38:51 UTC (rev 20145) +++ trunk/Jmol/src/org/jmol/io/JmolUtil.java 2014-11-28 20:29:45 UTC (rev 20146) @@ -370,7 +370,7 @@ if (tokens[0].indexOf(".") > 0) return (line3.length() >= 60 || tokens.length != 3 ? null : "VaspChgcar"); // M40 files are > 60 char if (nAtoms >= 0) - return "Cube"; //Can't be a Jvxl file; + return (tokens.length == 4 ? "Cube" : null); //Can't be a Jvxl file; nAtoms = -nAtoms; for (int i = 4 + nAtoms; --i >= 0;) if ((line = br.readLineWithNewline()) == null) Modified: trunk/Jmol/src/org/jmol/script/T.java =================================================================== --- trunk/Jmol/src/org/jmol/script/T.java 2014-11-27 05:38:51 UTC (rev 20145) +++ trunk/Jmol/src/org/jmol/script/T.java 2014-11-28 20:29:45 UTC (rev 20146) @@ -1000,6 +1000,7 @@ public final static int drawhover = booleanparam | 56; public final static int drawpicking = booleanparam | 57; public final static int dsspcalchydrogen = booleanparam | 58; + public final static int dynamicmeasurements = booleanparam | 59; //DEPRECATED; not implemented; leave here to avoid SET error public final static int ellipsoidarcs = booleanparam | 60; public final static int ellipsoidarrows = booleanparam | 61; public final static int ellipsoidaxes = booleanparam | 62; @@ -1176,7 +1177,7 @@ public final static int dotted = misc | 102; public final static int downsample = misc | 104; public final static int drawing = misc | 105; - public final static int eccentricity = misc | 106; + public final static int eccentricity = misc | 107; public final static int ed = misc | 108 | expression; public final static int edges = misc | 109; public final static int energy = misc | 110; @@ -2195,6 +2196,7 @@ "distanceFactor", "downsample", "drawing", + "dynamicMeasurements", "eccentricity", "ed", "edges", @@ -3216,6 +3218,7 @@ distancefactor, // "distanceFactor" downsample, // "downsample" drawing, // "drawing" + dynamicmeasurements, // "dynamicMeasurements" eccentricity, // "eccentricity" ed, // "ed" edges, // "edges" Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties =================================================================== --- trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2014-11-27 05:38:51 UTC (rev 20145) +++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties 2014-11-28 20:29:45 UTC (rev 20146) @@ -15,8 +15,16 @@ TODO: design and implement sidechain mutation -- MUTATE command ? TODO: remove HTML5 dependency on synchronous file loading (check SCRIPT command for problems) -Jmol.___JmolVersion="14.3.11_2014.11.27" +Jmol.___JmolVersion="14.3.11_2014.11.28" +new feature: readers for ESS input types: CFILE, VFILE, MOPAC, Gaussian, GAMESS, Orca, PQS + -- CFILE and VFILE require CFI:: and VFI:: (or C:: and V::) + +bug fix: dynamicMeasurements deprecated in 14.1.1 but + not left in as a valid token to allow use + +JmolVersion="14.3.11_2014.11.27" + new feature: GAMESS input reader new feature: NBO .37 archive file coordinate reader (not MOs) new feature: ZMatrixReader also serves as simple input file reader This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. ------------------------------------------------------------------------------ Download BIRT iHub F-Type - The Free Enterprise-Grade BIRT Server from Actuate! Instantly Supercharge Your Business Reports and Dashboards with Interactivity, Sharing, Native Excel Exports, App Integration & more Get technology previously reserved for billion-dollar corporations, FREE http://pubads.g.doubleclick.net/gampad/clk?id=157005751&iu=/4140/ostg.clktrk _______________________________________________ Jmol-commits mailing list Jmol-commits@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/jmol-commits