Revision: 21636
          http://sourceforge.net/p/jmol/code/21636
Author:   hansonr
Date:     2017-06-17 12:04:50 +0000 (Sat, 17 Jun 2017)
Log Message:
-----------
Jmol.___JmolVersion="14.19.1"

new feature: WRITE "SDF" implements atom value V  nnn ... information
 -- requires assigning the data property name "atom_values" in the model's 
molData property
 -- for example:
 
  $ load $2,3-dichlorobutane
  $ molData = {"atom_values":"chirality"}
  $ model properties "molData" molData
  $ write diol.sdf

 -- note that if the model already has a molData property, then use the 
following to append to it:
 
  $ molData = (_M.molData ? _M.molData : {})
  $ molData.atom_values = "chirality"
  $ model properties "molData" molData
   
   
   
new feature: x = load("filename","JSON")
 -- loads JSON data into variable
bug fix: CIP gives wrong alkene root distance for cyclopropene
bug fix: WRITE of structure file does not report number of atoms and warn if 
selected is different from this molecule
bug fix: WRITE MOL using format 10f.5 instead of 10f.4 for x, y, z coordinates
bug fix: MOL/SDF files should truncate lines at 80 characters.

Modified Paths:
--------------
    trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java
    trunk/Jmol/src/org/jmol/modelset/Atom.java
    trunk/Jmol/src/org/jmol/scriptext/CmdExt.java
    trunk/Jmol/src/org/jmol/scriptext/MathExt.java
    trunk/Jmol/src/org/jmol/smiles/SmilesGenerator.java
    trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java
    trunk/Jmol/src/org/jmol/viewer/Jmol.properties
    trunk/Jmol/src/org/jmol/viewer/PropertyManager.java

Modified: trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java
===================================================================
--- trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java       
2017-06-14 18:41:46 UTC (rev 21635)
+++ trunk/Jmol/src/org/jmol/adapter/readers/molxyz/MolReader.java       
2017-06-17 12:04:50 UTC (rev 21636)
@@ -83,6 +83,8 @@
   protected boolean allow2D = true;
   private int iatom0;
   private V3000Rdr vr;
+  private int atomCount;
+  private String[] atomData;
 
   @Override
   public void initializeReader() throws Exception {
@@ -100,8 +102,9 @@
         continuing = false;
         return false;
       }
-    } else if (line.equals("M  END"))
+    } else if (line.equals("M  END")) {
       return true;
+    }
     if (doGetModel(++modelNumber, null)) {
       iatom0 = asc.ac;
       processMolSdHeader();
@@ -200,6 +203,7 @@
   // xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee
 
   private void readAtomsAndBonds(int ac, int bc) throws Exception {
+    atomCount  = ac;
     for (int i = 0; i < ac; ++i) {
       rd();
       int len = line.length();
@@ -247,10 +251,16 @@
       addMolAtom(iAtom, isotope, elementSymbol, charge, x, y, z);
     }
 
+    rd();
+    if (line.startsWith("V  ")) {
+      readAtomValues();
+    }      
+
     // read bonds
 
     for (int i = 0; i < bc; ++i) {
-      rd();
+      if (i > 0)
+        rd();
       String iAtom1, iAtom2;
       int stereo = 0;
       iAtom1 = line.substring(0, 3).trim();
@@ -268,7 +278,7 @@
 
     // read V2000 user data
 
-    Map<String, String> molData = new Hashtable<String, String>();
+    Map<String, Object> molData = new Hashtable<String, Object>();
     rd();
     while (line != null && line.indexOf("$$$$") != 0) {
       if (line.indexOf(">") == 0) {
@@ -281,10 +291,40 @@
       }
       rd();
     }
+    if (atomData != null) {
+      Object atomValueName = molData.get("atom_value_name");
+      molData.put(atomValueName == null ? "atom_values" : 
atomValueName.toString(), atomData);
+    }
     if (!molData.isEmpty())
       asc.setModelInfoForSet("molData", molData, asc.iSet);
   }
 
+  /**
+   * Read all V  nnn lines as string data; user can adapt as needed.
+   * 
+   * @throws Exception
+   */
+  private void readAtomValues() throws Exception {
+    atomData = new String[atomCount];
+    for (int i = atomData.length; --i >= 0;)
+      atomData[i] = "";
+    while (line.indexOf("V  ") == 0) {
+     int iAtom = parseIntAt(line, 3);
+     if (iAtom < 1 || iAtom > atomCount) {
+       Logger.error("V  nnn does not evalute to a valid atom number: " + 
iAtom);
+       return;
+     }
+     String s = line.substring(6).trim();
+     atomData[iAtom - 1] = s;
+     rd(); 
+    }
+  }
+
+  /**
+   * Read all M  ISO  lines. These are absolute isotope numbers.
+   * 
+   * @throws Exception
+   */
   private void readIsotopes() throws Exception {
     int n = parseIntAt(line, 6);
     try {
@@ -308,7 +348,7 @@
    * @param molData
    * @throws Exception
    */
-  private void readMolData(Map<String, String> molData) throws Exception {
+  private void readMolData(Map<String, Object> molData) throws Exception {
     Atom[] atoms = asc.atoms;
     // "> <xxx>" becomes "xxx"
     // "> yyy <xxx> zzz" becomes "yyy <xxx> zzz"

Modified: trunk/Jmol/src/org/jmol/modelset/Atom.java
===================================================================
--- trunk/Jmol/src/org/jmol/modelset/Atom.java  2017-06-14 18:41:46 UTC (rev 
21635)
+++ trunk/Jmol/src/org/jmol/modelset/Atom.java  2017-06-17 12:04:50 UTC (rev 
21636)
@@ -497,7 +497,8 @@
   int getTargetValence() {
     switch (getElementNumber()) {
     case 6: //C
-    case 14: //Si      
+    case 14: //Si
+    case 32: // Ge
       return 4;
     case 5:  // B
     case 7:  // N

Modified: trunk/Jmol/src/org/jmol/scriptext/CmdExt.java
===================================================================
--- trunk/Jmol/src/org/jmol/scriptext/CmdExt.java       2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/scriptext/CmdExt.java       2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -3967,7 +3967,11 @@
       } else if (data == "CIF" || data == "SDF" || data == "MOL" || data == 
"MOL67" || data == "V2000"
           || data == "V3000" || data == "CD" || data == "JSON" || data == "XYZ"
           || data == "XYZRN" || data == "XYZVIB" || data == "CML") {
-        data = vwr.getModelExtract("selected", isCoord, false, data);
+        BS selected = vwr.bsA(), bsModel;
+        msg = " (" + selected.cardinality() + " atoms)";
+        if (vwr.am.cmi >= 0 && !selected.equals(bsModel = 
vwr.getModelUndeletedAtomsBitSet(vwr.am.cmi)))
+          msg += "\nNote! Selected atom set " + selected + " is not the same 
as the current model " + bsModel;
+        data = vwr.getModelExtract(selected, isCoord, false, data);
         if (data.startsWith("ERROR:"))
           bytes = data;
       } else if (data == "CFI") {
@@ -4113,14 +4117,14 @@
     params.put("width", Integer.valueOf(width));
     params.put("height", Integer.valueOf(height));
     params.put("nVibes", Integer.valueOf(nVibes));
-    msg = vwr.processWriteOrCapture(params);
-    if (msg == null)
-      msg = "canceled";
-    if (isImage && msg.startsWith("OK"))
-      msg += "; width=" + width + "; height=" + height;
+    String ret = vwr.processWriteOrCapture(params);
+    if (ret == null)
+      ret = "canceled";
+    if (isImage && ret.startsWith("OK"))
+      ret += "; width=" + width + "; height=" + height;
     if (timeMsg)
       showString(Logger.getTimerMsg("write", 0));
-    return writeMsg(msg);
+    return writeMsg(ret + (msg == null ? "" : msg));
   }
 
   public Lst<Object> prepareBinaryOutput(SV tvar) {

Modified: trunk/Jmol/src/org/jmol/scriptext/MathExt.java
===================================================================
--- trunk/Jmol/src/org/jmol/scriptext/MathExt.java      2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/scriptext/MathExt.java      2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -2114,26 +2114,17 @@
     // file("myfile.xyz")
     // load("myfile.png",true)
     // load("myfile.txt",1000)
-    // load("myfil.xyz",0,true)
+    // load("myfile.xyz",0,true)
+    // load("myfile.json","JSON")
+    // load("myfile.json","JSON", true)
 
-    String file;
-    int nBytesMax = -1;
-    boolean asBytes = false;
-    boolean async = vwr.async;
-    switch (args.length) {
-    case 3:
-      async = SV.bValue(args[2]);
-      //$FALL-THROUGH$
-    case 2:
-      nBytesMax = (args[1].tok == T.integer ? args[1].asInt() : -1);
-      asBytes = args[1].tok == T.on;
-      //$FALL-THROUGH$
-    case 1:
-      file = FileManager.fixDOSName(SV.sValue(args[0]));
-      break;
-    default:
+    if (args.length < 1 || args.length > 3)
       return false;
-    }
+    String file = FileManager.fixDOSName(SV.sValue(args[0]));
+    boolean asBytes = (args.length > 1 && args[1].tok == T.on);
+    boolean async = (vwr.async || args.length > 2 && args[args.length - 1].tok 
== T.on);
+    int nBytesMax = (args.length > 1 && args[1].tok == T.integer ? 
args[1].asInt() : -1);
+    boolean asJSON = (args.length > 1 && 
args[1].asString().equalsIgnoreCase("JSON"));
     if (asBytes)
       return mp.addXMap(vwr.fm.getFileAsMap(file));
     boolean isQues = file.startsWith("?");
@@ -2148,8 +2139,9 @@
       // The evaluation will be repeated up to this point, so for example,
       // x = (i++) + load("?") would increment i twice.
     }
-    return mp.addXStr(isFile ? vwr.fm.getFilePath(file, false, false) : vwr
-        .getFileAsString4(file, nBytesMax, false, false, true, "script"));
+    String str = isFile ? vwr.fm.getFilePath(file, false, false) : vwr
+        .getFileAsString4(file, nBytesMax, false, false, true, "script");
+    return (asJSON ? mp.addXObj(vwr.parseJSON(str)) : mp.addXStr(str));
   }
 
   private boolean evaluateMath(ScriptMathProcessor mp, SV[] args, int tok) {

Modified: trunk/Jmol/src/org/jmol/smiles/SmilesGenerator.java
===================================================================
--- trunk/Jmol/src/org/jmol/smiles/SmilesGenerator.java 2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/smiles/SmilesGenerator.java 2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -467,7 +467,9 @@
           SimpleNode atomA = atom12[j];
           Edge[] bb = ((Node) atomA).getEdges();
           for (int b = 0; b < bb.length; b++) {
-            if (bb[b].getCovalentOrder() != 1 || 
bb[b].getOtherNode(atomA).getElementNumber() == 1)
+            SimpleNode other;
+            if (bb[b].getCovalentOrder() != 1 || (other = 
bb[b].getOtherNode(atomA)).getElementNumber() == 1
+                && other.getIsotopeNumber() == 1)
               continue;
             edges[j][edgeCount++] = bb[b];
             if (getBondStereochemistry(bb[b], atomA) != '\0') {

Modified: trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java
===================================================================
--- trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java  2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/symmetry/CIPChirality.java  2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -108,6 +108,8 @@
  * 
  * code history:
  * 
+ * 6/12/2017 Jmo 14.18.2 tested for Rule 1b sphere (AY236.53, 163, 173, 192); 
957 lines
+ * 
  * 6/8/2017 Jmol 14.18.2 removed unnecessary presort for Rule 1b
  * 
  * 5/27/17 Jmol 14.17.2 fully interfaced using SimpleNode and SimpleEdge
@@ -320,6 +322,14 @@
   // }
   //
   // Of course, the actual code is a bit more complex than that.
+  //
+  // approximate line count calculated using MSDOS batch script:
+  //
+  //  type CIPChirality.java | find /V "}"|find /V "//" |find /V "import" | 
find /V "*" |find ";"|find /V "Logger"|find /V "System.out"  > t
+  //  type CIPChirality.java | find /V "}"|find /V "//" |find /V "import" | 
find /V "*" |find "if"|find /V "Logger"|find /V "System.out"  >> t
+  //  type CIPChirality.java | find "COUNT_LINE"  >> t
+  //  type t |find " " /C
+  
 
   static final int NO_CHIRALITY = 0;
   static final int TIED = NO_CHIRALITY;
@@ -427,6 +437,7 @@
     // for reflection
     //System.out.println("TESTING ALLOWRULE1B");
     //allowRule1bAlkenes = false;
+    System.out.println("TESTING SPHERE+1");
   }
 
   /**
@@ -470,7 +481,7 @@
 
     BS bs = BSUtil.copy(bsAtoms);
     lstSmallRings = new Lst<BS>();
-    while (!bs.isEmpty())
+    while (!bs.isEmpty())  // COUNT_LINE
       getSmallRings(atoms[bs.nextSetBit(0)], bs);
     bsKekuleAmbiguous = getKekule(atoms);
     bsAzacyclic = getAzacyclic(atoms, bsAtoms);
@@ -918,7 +929,7 @@
     if (nSP2 != null)
       nSP2[0] = 2;
     int ppt = 0;
-    while (true) {
+    while (true) {  // COUNT_LINE
       if (atom2.getCovalentBondCount() != 2)
         return atom2;
       SimpleEdge[] edges = atom2.getEdges();
@@ -968,7 +979,7 @@
     boolean isChiral = false, isAlkene = false;
     try {
       if (cipAtom == null) {
-        cipAtom = new CIPAtom().create(atom, null, false, isAlkene, false);
+        cipAtom = new CIPAtom().create(atom, null, isAlkene, false, false);
         int nSubs = atom.getCovalentBondCount(), elemNo = 
atom.getElementNumber();
         isAlkene = (nSubs == 3 && elemNo <= 10 && 
!cipAtom.isTrigonalPyramidal); // (IUPAC 2013.P-93.2.4)
         if (nSubs != (parent == null ? 4 : 3)
@@ -1014,7 +1025,7 @@
             }
             if (Logger.debugging)
               Logger.info(atom + " " + JC.getCIPChiralityName(rs) + " by Rule "
-                  + getRuleName() + "\n----------------------------------");
+                  + getRuleName() + "\n----------------------------------"); 
// Logger
             break;
           }
         }
@@ -1071,10 +1082,10 @@
    */
   private int setBondChirality(SimpleNode a, SimpleNode pa, SimpleNode pb, 
SimpleNode b,
                                boolean isAxial, int ruleMax) {
-    CIPAtom a1 = new CIPAtom().create(a, null, false, true, false);
+    CIPAtom a1 = new CIPAtom().create(a, null, true, false, false);
     int atop = getAlkeneEndTopPriority(a1, pa, isAxial, ruleMax);
     int ruleA = currentRule;
-    CIPAtom b2 = new CIPAtom().create(b, null, false, true, false);
+    CIPAtom b2 = new CIPAtom().create(b, null, true, false, false);
     int btop = getAlkeneEndTopPriority(b2, pb, isAxial, ruleMax);
     int ruleB = currentRule;
     int c = (atop >= 0 && btop >= 0 ? getEneChirality(b2.atoms[btop], b2, a1,
@@ -1127,7 +1138,7 @@
                                       int ruleMax) {
     a.canBePseudo = a.isAxialRoot = isAxial;
     return getAtomChiralityLimited(a.atom, a,
-        new CIPAtom().create(pa, null, false, true, false), ruleMax) - 1;
+        new CIPAtom().create(pa, null, true, false, false), ruleMax) - 1;
   }
 
   /**
@@ -1427,13 +1438,13 @@
      * @param atom
      *        or null to indicate a null placeholder
      * @param parent
+     * @param isAlkene
      * @param isDuplicate
-     * @param isAlkene
      * @param isParentBond TODO
      * @return this
      */
-    CIPAtom create(SimpleNode atom, CIPAtom parent, boolean isDuplicate,
-                   boolean isAlkene, boolean isParentBond) {
+    CIPAtom create(SimpleNode atom, CIPAtom parent, boolean isAlkene,
+                   boolean isDuplicate, boolean isParentBond) {
       this.id = ++ptID;
       this.parent = parent;
       if (atom == null)
@@ -1532,7 +1543,7 @@
       BS bsRing = BSUtil.newAndSetBit(atomIndex);
       CIPAtom p = this;
       int index = -1;
-      while ((p = p.parent) != null && index != atomIndex)
+      while ((p = p.parent) != null && index != atomIndex)  // COUNT_LINE
         bsRing.set(index = p.atomIndex);
       if (bsRing.cardinality() <= SMALL_RING_MAX) {
         for (int i = lstSmallRings.size(); --i >= 0;)
@@ -1549,12 +1560,9 @@
      * 
      */
     boolean setNode() {
-      if (isSet)
+      // notice we are setting isSet TRUE here, not just testing it.
+      if (isSet || (isSet = true) && isDuplicate)
         return true;
-      isSet = true;
-      if (isDuplicate)
-        return true;
-
       SimpleEdge[] bonds = atom.getEdges();
       int nBonds = bonds.length;
       if (Logger.debuggingHigh)
@@ -1576,34 +1584,25 @@
               setEne();
           }
         }
-        if (nBonds == 1 && order == 1 && isParentBond) {
-          isTerminal = true;
-          return true;
-        }
+        if (nBonds == 1 && order == 1 && isParentBond)
+          return isTerminal = true;
         // from here on, isTerminal indicates an error condition
         switch (order) {
         case 3:
-          if (addAtom(pt++, other, isParentBond, false, isParentBond) == null) 
{
-            isTerminal = true;
-            return false;
-          }
+          if (addAtom(pt++, other, isParentBond, false, isParentBond) == null)
+            return !(isTerminal = true);
           //$FALL-THROUGH$
         case 2:
-          if (addAtom(pt++, other, order != 2 || isParentBond, order == 2, 
isParentBond) == null) {
-            isTerminal = true;
-            return false;
-          }
+          if (addAtom(pt++, other, order != 2 || isParentBond, order == 2, 
isParentBond) == null)
+            return !(isTerminal = true);
           //$FALL-THROUGH$
         case 1:
-          if (!isParentBond
-              && addAtom(pt++, other, order != 1 && elemNo <= 10, false, 
false) == null) {
-            isTerminal = true;
-            return false;
-          }
-          break;
+          if (isParentBond
+              || addAtom(pt++, other, order != 1 && elemNo <= 10, false, 
false) != null)
+            break;
+          //$FALL-THROUGH$
         default:
-          isTerminal = true;
-          return false;
+          return !(isTerminal = true);
         }
       }
       // don't think this can happen
@@ -1610,7 +1609,7 @@
       isTerminal = (pt == 0);
       nAtoms = pt;
       for (; pt < atoms.length; pt++)
-        atoms[pt] = new CIPAtom().create(null, this, true, false, false);
+        atoms[pt] = new CIPAtom().create(null, this, false, true, false);
 
       // Do an initial very shallow atom-only Rule 1 sort using a.compareTo(b)
 
@@ -1644,6 +1643,7 @@
      * @param other
      * @param isDuplicate
      * @param isAlkene
+     * @param isParentBond 
      * @return new atom or null
      */
     CIPAtom addAtom(int i, SimpleNode other, boolean isDuplicate, boolean 
isAlkene, boolean isParentBond) {
@@ -1654,8 +1654,7 @@
       }
       if (parent == null) {
         // For top level, we do not allow two 1H atoms.
-        int atomIsotope = other.getIsotopeNumber();
-        if (atomIsotope == 1) {
+        if (other.getIsotopeNumber() == 1) {
           if (++h1Count > 1) {
             if (Logger.debuggingHigh)
               Logger.info(" second H atom found on " + atom);
@@ -1664,7 +1663,7 @@
         }
       }
       return atoms[i] = new CIPAtom()
-          .create(other, this, isDuplicate, isAlkene, isParentBond);
+          .create(other, this, isAlkene, isDuplicate, isParentBond);
     }
 
     /**
@@ -1679,8 +1678,7 @@
      */
     boolean sortSubstituents(int sphere) {
 
-      int[] indices = new int[4];
-      int[] prevPrior = new int[4];
+      int[] indices = new int[4], prevPrior = new int[4];
       ties = null;
       int nTies = 0;
       for (int i = 0; i < 4; i++) {
@@ -1703,8 +1701,7 @@
         CIPAtom a = atoms[i];
         for (int j = i + 1; j < 4; j++) {
           CIPAtom b = atoms[j];
-          boolean logger_debugHigh = Logger.debuggingHigh && b.isHeavy()
-              && a.isHeavy();
+          boolean logger_debugHigh = Logger.debuggingHigh && b.isHeavy() && 
a.isHeavy();
           int score = (a.atom == null ? B_WINS : b.atom == null ? A_WINS
               : prevPrior[i] == prevPrior[j] ? TIED
                   : prevPrior[j] < prevPrior[i] ? B_WINS : A_WINS);
@@ -1854,25 +1851,22 @@
      * @return [0 (TIED), -1 (A_WINS), or 1 (B_WINS)] * sphere where broken
      */
     private int breakTie(CIPAtom b, int sphere) {
-      if (atomIndex == 1 && b.atomIndex == 3)
-        if (Logger.debugging && isHeavy() && b.isHeavy())
+      if (Logger.debugging && isHeavy() && b.isHeavy())
           Logger.info(dots() + "tie for " + this + " and " + b + " at sphere " 
+ sphere);
 
-      // Two duplicates of the same atom are always tied.
-      if (isDuplicate && b.isDuplicate && atom == b.atom
-          && rootDistance == b.rootDistance)
+      // Two duplicates of the same atom are always tied, if they have the 
same root distance.
+      if (isDuplicate && b.isDuplicate && atom == b.atom && rootDistance == 
b.rootDistance)
         return TIED;
 
       // Do a duplicate check -- if that is not a TIE we do not have to go any 
further.
       // NOTE THAT THIS IS NOT EXPLICIT IN THE RULES
       // BECAUSE DUPLICATES LOSE IN THE NEXT SPHERE, NOT THIS.
-
-      // THE NEED FOR (sphere+1) HERE SHOWS THAT 
+      // THE NEED FOR (sphere+1) in AY236.53, 163, 173, 192 SHOWS THAT 
       // SUBRULE 1a MUST BE COMPLETED EXHAUSTIVELY PRIOR TO SUBRULE 1b.
 
       int score = checkIsDuplicate(b);
       if (score != TIED)
-        return score * (sphere + 1);
+        return score * (sphere + 1);  // COUNT_LINE
 
       // return NO_CHIRALITY/TIED if:
       //  a) one or the other can't be set (because it has too many 
connections)
@@ -1885,26 +1879,8 @@
       // We are done -- again, for the next sphere, actually -- if one of these
       // is terminal.
       if (isTerminal != b.isTerminal)
-        return (isTerminal ? B_WINS : A_WINS) * (sphere + 1);
+        return (isTerminal ? B_WINS : A_WINS) * (sphere + 1); // COUNT_LINE
 
-      // Rule 1b requires a special presort to ensure that all duplicate atoms 
-      // are in the right order -- first by element number and then by root 
distance.
-      // Otherwise we are liable to have an a-b alignment such as the following
-      // when we have SMARTS C1...C=C1 
-      //
-      //     a         b
-      //   C        C
-      //   (C-alk)  (C-ring)
-      //   (C-ring) (C-alk)
-      //   H        H
-      //
-      // see bh64.65
-
-      if (currentRule == RULE_1b) {
-        preSortRule1b();
-        b.preSortRule1b();
-      }
-
       // Phase I -- shallow check only
       //
       // Check to see if any of the three connections to a and b are different.
@@ -1931,30 +1907,6 @@
     }
 
     /**
-     * Sort duplicate nodes of the same element by root distance, from closest
-     * to root to furthest.
-     * 
-     */
-    private void preSortRule1b() {
-      CIPAtom a1, a2;
-      for (int i = 0; i < 3; i++) {
-        if (!(a1 = atoms[i]).isDuplicate)
-          continue;
-        if (Logger.debuggingHigh)
-          Logger.info("presort1b: " + i + " " + a1.myPath);
-        for (int j = i + 1; j < 4; j++) {
-          if (!(a2 = atoms[j]).isDuplicate || a1.elemNo != a2.elemNo
-              || a1.rootDistance <= a2.rootDistance)
-            continue;
-          atoms[i] = a2;
-          atoms[j] = a1;
-          j = 4;
-          i = -1;
-        }
-      }
-    }
-
-    /**
      * Just checking for hydrogen.
      * 
      * @return true if not hydrogen.
@@ -1985,7 +1937,7 @@
           if (Logger.debugging && ai.isHeavy() && bi.isHeavy())
             Logger.info(ai.dots() + "compareShallow " + i + " " + this + "."
                 + ai + "/" + b + "." + bi + ": " + score * (sphere + 1));  // 
Logger
-          return score * (sphere + 1);
+          return score * (sphere + 1);  // COUNT_LINE
         }
       }
       return TIED;
@@ -2036,7 +1988,8 @@
       return (b == null ? A_WINS
           : (atom == null) != (b.atom == null) ? (atom == null ? B_WINS
               : A_WINS) : (score = checkRule1a(b)) != TIED ? score
-              : checkIsDuplicate(b));
+              : (score = checkIsDuplicate(b)) != TIED ? score : 
+                !isDuplicate ? TIED : checkRule1b(b));
     }
 
     /**
@@ -2064,8 +2017,7 @@
      * @return 0 (TIED), -1 (A_WINS), or 1 (B_WINS)
      */
     private int checkIsDuplicate(CIPAtom b) {
-      return b.isDuplicate == isDuplicate ? TIED : b.isDuplicate ? A_WINS
-          : B_WINS;
+      return b.isDuplicate == isDuplicate ? TIED : b.isDuplicate ? A_WINS : 
B_WINS;
     }
 
     /**
@@ -2246,7 +2198,7 @@
       Lst<CIPAtom> path = new Lst<CIPAtom>();
       CIPAtom thisAtom = this, newSub, oldParent = fromAtom, oldSub = 
newParent;
       // create path back to root
-      while (oldParent.parent != null && oldParent.parent.atoms[0] != null) {
+      while (oldParent.parent != null && oldParent.parent.atoms[0] != null) { 
// COUNT_LINE
         if (Logger.debuggingHigh)
           Logger.info("path:" + oldParent.parent + "->" + oldParent);
         path.addLast(oldParent = oldParent.parent);
@@ -2254,8 +2206,8 @@
       path.addLast(null);
       for (int i = 0, n = path.size(); i < n; i++) {
         oldParent = path.get(i);
-        newSub = (oldParent == null ? new CIPAtom().create(null, this, true,
-            isAlkene, false) : (CIPAtom) oldParent.clone());
+        newSub = (oldParent == null ? new CIPAtom().create(null, this, 
isAlkene,
+            true, false) : (CIPAtom) oldParent.clone());
         newSub.sphere = thisAtom.sphere + 1;
         thisAtom.replaceParentSubstituent(oldSub, newParent, newSub);
         if (i > 0 && thisAtom.isAlkene && !thisAtom.isAlkeneAtom2) {
@@ -2532,7 +2484,7 @@
       for (int c = 1; c < n; c++) {
         boolean alike = (aref == aStr.charAt(c));
         if (alike != (bref == bStr.charAt(c)))
-          return (isRSTest ? c : 1) * (alike ? A_WINS : B_WINS);
+          return (isRSTest ? c : 1) * (alike ? A_WINS : B_WINS);  // COUNT_LINE
       }
       if (isRSTest)
         return TIED;
@@ -2563,8 +2515,7 @@
       for (int i = 0; i < n; i++)
         sorted[i] = PT.rep(sorted[i], "A", aref);
       if (Logger.debuggingHigh)
-        for (int i = 0; i < n; i++)
-          // Logger
+        for (int i = 0; i < n; i++) // Logger
           Logger.info("Sorted Mata list " + i + " " + aref + ": " + sorted[i]);
       return sorted;
     }
@@ -2826,10 +2777,9 @@
      * @param rs
      */
     private void addMataRef(int sphere, int priority, int rs) {
-      if (rule4Count == null) {
+      if (rule4Count == null)
         rule4Count = new int[] { Integer.MAX_VALUE, 0, 0 };
-      }
-      int n = sphere * 10 + priority;
+      int n = sphere * 10 + priority;  // COUNT_LINE
       if (n <= rule4Count[0]) {
         if (n < rule4Count[0]) {
           rule4Count[0] = n;
@@ -2859,7 +2809,7 @@
         return NOT_RELEVANT;
       int n = rs1.length();
       if (n != rs2.length())
-        return NOT_RELEVANT; // TODO: ?? this may not be true -- paths with 
and without O, N, C for example, that still have stereochemistry
+        return NOT_RELEVANT;
       if (rs1.equals(rs2))
         return TIED;
       boolean haveRS = (rs1.indexOf("R") >= 0 || rs1.indexOf("S") >= 0);
@@ -2903,9 +2853,8 @@
         int score = i1 + rs.indexOf(rs2.charAt(i));
         if (score == 0)
           continue;
-        if (score != STEREO_BOTH_RS) {
+        if (score != STEREO_BOTH_RS)
           return DIASTEREOMERIC;
-        }
         if (finalScore == TIED)
           finalScore = (i1 == STEREO_R ? A_WINS : B_WINS);
       }
@@ -2939,7 +2888,7 @@
       atom1.atoms[bondCount - 1] = atoms[Math.max(ia, ib)];
       int rs = atom1.checkHandedness();
       if (Logger.debugging) {
-        for (int i = 0; i < 4; i++)
+        for (int i = 0; i < 4; i++) // Logger
           Logger.info("pseudo " + rs + " " + priorities[i] + " " + 
atoms[i].myPath);
       }
       return rs;
@@ -3018,19 +2967,11 @@
       a.htPathPoints = htPathPoints;
       a.doCheckPseudo = false;
       a.alkeneParent = null;
-      //a.rule4Count = null;
-      //a.rule4List = null;
-      for (int i = 0; i < 4; i++) {
-        //        a.priorities[i] = priorities[i];
-        if (atoms[i] != null) {
+      for (int i = 0; i < 4; i++)
+        if (atoms[i] != null)
           a.atoms[i] = atoms[i];
-        }
-      }
       a.ties = null;
-//      if (Logger.debugging)
-//        Logger.info("cloning " + this + " as " + a);
       return a;
-
     }
 
     @Override

Modified: trunk/Jmol/src/org/jmol/viewer/Jmol.properties
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/viewer/Jmol.properties      2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -56,9 +56,31 @@
 
 # TODO: fix UNDO
 
-Jmol.___JmolVersion="14.18.2"
+Jmol.___JmolVersion="14.19.1"
 
+new feature: WRITE "SDF" implements atom value V  nnn ... information
+ -- requires assigning the data property name "atom_values" in the model's 
molData property
+ -- for example:
+ 
+  $ load $2,3-dichlorobutane
+  $ molData = {"atom_values":"chirality"}
+  $ model properties "molData" molData
+  $ write diol.sdf
+
+ -- note that if the model already has a molData property, then use the 
following to append to it:
+ 
+  $ molData = (_M.molData ? _M.molData : {})
+  $ molData.atom_values = "chirality"
+  $ model properties "molData" molData
+   
+   
+   
+new feature: x = load("filename","JSON")
+ -- loads JSON data into variable
 bug fix: CIP gives wrong alkene root distance for cyclopropene
+bug fix: WRITE of structure file does not report number of atoms and warn if 
selected is different from this molecule
+bug fix: WRITE MOL using format 10f.5 instead of 10f.4 for x, y, z coordinates
+bug fix: MOL/SDF files should truncate lines at 80 characters.
 
 JmolVersion="14.18.1"  // 2017.06.06
 

Modified: trunk/Jmol/src/org/jmol/viewer/PropertyManager.java
===================================================================
--- trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2017-06-14 18:41:46 UTC 
(rev 21635)
+++ trunk/Jmol/src/org/jmol/viewer/PropertyManager.java 2017-06-17 12:04:50 UTC 
(rev 21636)
@@ -23,8 +23,16 @@
  */
 package org.jmol.viewer;
 
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Properties;
+
 import javajs.J2SIgnoreImport;
-
 import javajs.util.AU;
 import javajs.util.BArray;
 import javajs.util.Base64;
@@ -39,15 +47,6 @@
 import javajs.util.V3;
 import javajs.util.XmlUtil;
 
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Enumeration;
-import java.util.Hashtable;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Properties;
-
 import org.jmol.api.Interface;
 import org.jmol.api.JmolDataManager;
 import org.jmol.api.JmolPropertyManager;
@@ -58,18 +57,18 @@
 import org.jmol.modelset.BondSet;
 import org.jmol.modelset.Chain;
 import org.jmol.modelset.Group;
-import org.jmol.modelsetbio.BioModel;
 import org.jmol.modelset.LabelToken;
 import org.jmol.modelset.Model;
 import org.jmol.modelset.ModelSet;
+import org.jmol.modelsetbio.BioModel;
 import org.jmol.script.SV;
 import org.jmol.script.T;
 import org.jmol.shape.Shape;
 import org.jmol.util.BSUtil;
 import org.jmol.util.C;
+import org.jmol.util.Edge;
 import org.jmol.util.Elements;
 import org.jmol.util.Escape;
-import org.jmol.util.Edge;
 import org.jmol.util.JmolMolecule;
 import org.jmol.util.Logger;
 import org.jmol.viewer.binding.Binding;
@@ -1148,10 +1147,8 @@
       return getModelCif(bs);
     if (type.equalsIgnoreCase("CML"))
       return getModelCml(bs, Integer.MAX_VALUE, true, doTransform, 
allTrajectories);
-    
     if (type.equals("PDB") || type.equals("PQR"))
       return getPdbAtomData(bs, null, type.equals("PQR"), doTransform, 
allTrajectories);
-    
     boolean asV3000 = type.equalsIgnoreCase("V3000");
     boolean asSDF = type.equalsIgnoreCase("SDF");
     boolean noAromatic = type.equalsIgnoreCase("MOL");
@@ -1165,7 +1162,10 @@
     BS bsModels = vwr.ms.getModelBS(bsAtoms, true);
     if (!isXYZ && !asJSON) {
       String title = vwr.ms.getFrameTitle(bsModels.nextSetBit(0));      
-      mol.append(title != null ? title.replace('\n',' ') : isModelKit ? "Jmol 
Model Kit" : FileManager.fixDOSName(vwr.fm.getFullPathName(false)));
+      title = (title != null ? title.replace('\n',' ') : isModelKit ? "Jmol 
Model Kit" : FileManager.fixDOSName(vwr.fm.getFullPathName(false)));
+      if (title.length() > 80)
+        title = title.substring(0, 77) + "...";
+      mol.append(title);
       String version = Viewer.getJmolVersion();
       mol.append("\n__Jmol-").append(version.substring(0, 2));
       int cMM, cDD, cYYYY, cHH, cmm;
@@ -1308,12 +1308,16 @@
   }
 
   private boolean addMolFile(int iModel, SB mol, BS bsAtoms, BS bsBonds,
-                             boolean asV3000, boolean asJSON, boolean 
noAromatic, Quat q) {
+                             boolean asV3000, boolean asJSON,
+                             boolean noAromatic, Quat q) {
     int nAtoms = bsAtoms.cardinality();
     int nBonds = bsBonds.cardinality();
     if (!asV3000 && !asJSON && (nAtoms > 999 || nBonds > 999))
       return false;
     boolean asSDF = (iModel >= 0);
+    @SuppressWarnings("unchecked")
+    Map<String, Object> molData = (asSDF ? (Map<String, Object>) vwr.ms
+        .getInfo(iModel, "molData") : null);
     ModelSet ms = vwr.ms;
     int[] atomMap = new int[ms.ac];
     P3 pTemp = new P3();
@@ -1320,7 +1324,8 @@
     if (asV3000) {
       mol.append("  0  0  0  0  0  0            999 V3000");
     } else if (asJSON) {
-       mol.append("{\"mol\":{\"createdBy\":\"Jmol "+ Viewer.getJmolVersion() + 
"\",\"a\":[");
+      mol.append("{\"mol\":{\"createdBy\":\"Jmol " + Viewer.getJmolVersion()
+          + "\",\"a\":[");
     } else {
       PT.rightJustify(mol, "   ", "" + nAtoms);
       PT.rightJustify(mol, "   ", "" + nBonds);
@@ -1330,22 +1335,26 @@
       mol.append("\n");
     if (asV3000) {
       mol.append("M  V30 BEGIN CTAB\nM  V30 COUNTS ").appendI(nAtoms)
-          .append(" ").appendI(nBonds).append(" 0 0 0\n").append(
-              "M  V30 BEGIN ATOM\n");
+          .append(" ").appendI(nBonds).append(" 0 0 0\n")
+          .append("M  V30 BEGIN ATOM\n");
     }
+    Object o = (molData == null ? null : molData.get("atom_value_name"));
+    if (o instanceof SV)
+      o = ((SV) o).asString();
+    int valueType = (o == null ? T.nada : T.getTokFromName("" + o));
+    SB atomValues = (valueType == T.nada ? null : new SB());
     for (int i = bsAtoms.nextSetBit(0), n = 0; i >= 0; i = bsAtoms
         .nextSetBit(i + 1))
-      getAtomRecordMOL(iModel, ms, mol, atomMap[i] = ++n, ms.at[i], q, pTemp, 
asV3000,
-          asJSON);
+      getAtomRecordMOL(iModel, ms, mol, atomMap[i] = ++n, ms.at[i], q, pTemp,
+          asV3000, asJSON, atomValues, valueType);
     if (asV3000) {
       mol.append("M  V30 END ATOM\nM  V30 BEGIN BOND\n");
     } else if (asJSON) {
       mol.append("],\"b\":[");
-    }
+    } 
     for (int i = bsBonds.nextSetBit(0), n = 0; i >= 0; i = bsBonds
         .nextSetBit(i + 1))
       getBondRecordMOL(mol, ++n, ms.bo[i], atomMap, asV3000, asJSON, 
noAromatic);
-    // 21 21 0 0 0
     if (asV3000) {
       mol.append("M  V30 END BOND\nM  V30 END CTAB\n");
     }
@@ -1352,29 +1361,31 @@
     if (asJSON)
       mol.append("]}}");
     else {
+      if (atomValues != null && atomValues.length() > 0)
+        mol.append(atomValues.toString());
       mol.append("M  END\n");
     }
     if (asSDF) {
       try {
-      @SuppressWarnings("unchecked")
-      Map<String, Object> molData = (Map<String, Object>) 
vwr.ms.getInfo(iModel, "molData");
-      float[] pc = ms.getPartialCharges();
-      if (pc != null) {
-        if (molData == null)
-          molData = new Hashtable<String, Object>();
-        SB sb = new SB();
-        sb.appendI(nAtoms).appendC('\n');
-        for (int i = bsAtoms.nextSetBit(0), n = 0; i >= 0; i = bsAtoms
-            .nextSetBit(i + 1))
-          sb.appendI(++n).append(" ").appendF(pc[i]).appendC('\n');
-        molData.put("jmol_partial_charges",   sb.toString());
-      }
-      for (String key: molData.keySet()) {
-        Object o = molData.get(key);
-        if (o instanceof SV)
-          o = ((SV)o).asString();
-        mol.append("> <" + key.toUpperCase() + 
">\n").append(PT.trim(o.toString(), "\n")).append("\n\n");
-      }
+        float[] pc = ms.getPartialCharges();
+        if (pc != null) {
+          if (molData == null)
+            molData = new Hashtable<String, Object>();
+          SB sb = new SB();
+          sb.appendI(nAtoms).appendC('\n');
+          for (int i = bsAtoms.nextSetBit(0), n = 0; i >= 0; i = bsAtoms
+              .nextSetBit(i + 1))
+            sb.appendI(++n).append(" ").appendF(pc[i]).appendC('\n');
+          molData.put("jmol_partial_charges", sb.toString());
+        }
+        for (String key : molData.keySet()) {
+          o = molData.get(key);
+          if (o instanceof SV)
+            o = ((SV) o).asString();
+          mol.append("> <" + key.toUpperCase() + ">\n");
+          output80CharWrap(mol, o.toString(), 80);
+          mol.append("\n\n");
+        }
       } catch (Throwable e) {
         // ignore
       }
@@ -1383,6 +1394,33 @@
     return true;
   }
 
+  /**
+   * 
+   * @param mol
+   * @param data
+   * @param maxN 80 for multi-line wrap; something smaller for single line 
output
+   */
+  private void output80CharWrap(SB mol, String data, int maxN) {
+    if (maxN < 80)
+      data = PT.rep(data, "\n", "|");
+    String[] lines = PT.split(PT.trim(PT.rep(data, "\n\n", "\n"), "\n"), "\n");
+    for (int i = 0; i < lines.length; i++)
+      outputLines(mol, lines[i], maxN);
+  }
+
+  private void outputLines(SB mol, String data, int maxN) {
+    boolean done = false;    
+    for (int  i = 0, n = data.length(); i < n && !done; i += 80) {
+      mol.append(data.substring(i, Math.min(i + maxN, n)));
+      if (!(done = (maxN != 80)) && i + 80 < n)
+        mol.append("+");
+      mol.append("\n");
+    }
+
+    // TODO
+    
+  }
+
   private static BS getCovalentBondsForAtoms(Bond[] bonds, int bondCount, BS 
bsAtoms) {
     BS bsBonds = new BS();
     for (int i = 0; i < bondCount; i++) {
@@ -1421,9 +1459,15 @@
    */
 
   private void getAtomRecordMOL(int iModel, ModelSet ms, SB mol, int n, Atom 
a, Quat q,
-                                P3 pTemp, boolean asV3000, boolean asJSON) {
-    //   -0.9920    3.2030    9.1570 Cl  0  0  0  0  0
-    //    3.4920    4.0920    5.8700 Cl  0  0  0  0  0
+                                P3 pTemp, boolean asV3000, boolean asJSON, SB 
atomValues, int tokValue) {
+    
//https://cactus.nci.nih.gov/chemical/structure/caffeine/file?format=sdf&get3d=true
+    //__Jmol-14_06161707413D 1   1.00000     0.00000     0
+    //Jmol version 14.19.1  2017-06-12 00:33 EXTRACT: ({0:23})
+    // 24 25  0  0  0  0              1 V2000
+    //    1.3120   -1.0479    0.0025 N   0  0  0  0  0  0
+    //    2.2465   -2.1762    0.0031 C   0  0  0  0  0  0
+    //    1.7906    0.2081    0.0011 C   0  0  0  0  0  0
+    //xxxxx.xxxxyyyyy.yyyyzzzzz.zzzz aaaddcccssshhhbbbvvvHHHrrriiimmmnnneee
     //012345678901234567890123456789012
     getPointTransf(iModel, ms, a, q, pTemp);
     int elemNo = a.getElementNumber();
@@ -1453,7 +1497,7 @@
       mol.append("\"x\":").appendF(a.x).append(",\"y\":").appendF(a.y)
           .append(",\"z\":").appendF(a.z).append("}");
     } else {
-      mol.append(PT.sprintf("%10.5p%10.5p%10.5p", "p", o));
+      mol.append(PT.sprintf("%10.4p%10.4p%10.4p", "p", o));
       mol.append(" ").append(sym);
       if (sym.length() == 1)
         mol.append(" ");
@@ -1463,9 +1507,36 @@
       PT.rightJustify(mol, "  ", "" + iso);
       PT.rightJustify(mol, "   ", "" + (charge == 0 ? 0 : 4 - charge));
       mol.append("  0  0  0  0\n");
+
+      String label = (atomValues == null || asV3000 ? null : 
+        getAtomPropertyAsString(a, tokValue));
+      if (label != null && (label = label.trim()).length() > 0) {
+        String sn = "   " + n + " ";
+        atomValues.append("V  ").append(sn.substring(sn.length() - 4));
+        output80CharWrap(atomValues, label, 73);
+      }
     }
   }
 
+  private P3 ptTemp;
+  private String getAtomPropertyAsString(Atom a, int tok) {
+    switch (tok & T.PROPERTYFLAGS) {
+    case T.intproperty:
+      int i = a.atomPropertyInt(tok);
+      return (tok == T.color ? 
PT.trim(Escape.escapeColor(i),"[x]").toUpperCase() : "" + i);
+    case T.strproperty:
+      return a.atomPropertyString(vwr, tok);
+    case T.floatproperty:
+      float f = a.atomPropertyFloat(vwr, tok, null);
+      return (Float.isNaN(f) ? null : "" + f);
+    default: // point property
+      if (ptTemp == null)
+        ptTemp = new P3();
+      a.atomPropertyTuple(vwr, tok, ptTemp);
+      return  (ptTemp == null ? null : ptTemp.toString());
+    }
+  }
+
   /**
    * pick up the appropriate value for this atom
    * @param i 

This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.


------------------------------------------------------------------------------
Check out the vibrant tech community on one of the world's most
engaging tech sites, Slashdot.org! http://sdm.link/slashdot
_______________________________________________
Jmol-commits mailing list
Jmol-commits@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/jmol-commits

Reply via email to