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