public void release() {
			bsptIter.release();
			bsptIter = null;
		}
	}

	// //////////////////////////////////////////////////////////////
	// autobonding/connection stuff
	// //////////////////////////////////////////////////////////////
	void doAutobond() {
		// perform bonding if necessary
		boolean doBond = (bondCount == 0 || isMultiFile || isPDB && bondCount < atomCount / 2);
		if (viewer.getForceAutoBond() || doBond && viewer.getAutoBond() && getModelSetProperty("noautobond") == null) {
			autoBond(null, null);
		}
		viewer.setShapeProperty(JmolConstants.SHAPE_STICKS, "reset", null);
	}

	// XIE: remove the final keyword
	static boolean showRebondTimes = true;

	@SuppressWarnings("unchecked")
	void rebond() {
		// from eval "connect" or from app preferences panel
		stateScripts.add("connect;");
		deleteAllBonds();
		autoBond(null, null);
	}

	int autoBond(short order, BitSet bsA, BitSet bsB) {
		if (order == JmolConstants.BOND_ORDER_NULL)
			return autoBond(bsA, bsB);
		else if (order == JmolConstants.BOND_H_REGULAR)
			return autoHbond(bsA, bsB);
		else Logger.warn("autoBond() unknown order: " + order);
		return 0;
	}

	private int autoBond(BitSet bsA, BitSet bsB) {
		if (atomCount == 0)
			return 0;
		// null values for bitsets means "all"
		if (maxBondingRadius == Float.MIN_VALUE)
			findMaxRadii();
		float bondTolerance = viewer.getBondTolerance();
		float minBondDistance = viewer.getMinBondDistance();
		float minBondDistance2 = minBondDistance * minBondDistance;
		short mad = viewer.getMadBond();
		// char chainLast = '?';
		// int indexLastCA = -1;
		// Atom atomLastCA = null;
		int nNew = 0;
		initializeBspf();

		long timeBegin = 0;
		if (showRebondTimes)
			timeBegin = System.currentTimeMillis();
		/*
		 * miguel 2006 04 02 note that the way that these loops + iterators are constructed, everything assumes that all
		 * possible pairs of atoms are going to be looked at. for example, the hemisphere iterator will only look at
		 * atom indexes that are >= (or <= ?) the specified atom. if we are going to allow arbitrary sets bsA and bsB,
		 * then this will not work. so, for now I will do it the ugly way. maybe enhance/improve in the future.
		 */
		for (int i = atomCount; --i >= 0;) {
			boolean isAtomInSetA = (bsA == null || bsA.get(i));
			boolean isAtomInSetB = (bsB == null || bsB.get(i));
			if (!isAtomInSetA & !isAtomInSetB)
				continue;
			Atom atom = atoms[i];
			// Covalent bonds
			float myBondingRadius = atom.getBondingRadiusFloat();
			if (myBondingRadius == 0)
				continue;
			float searchRadius = myBondingRadius + maxBondingRadius + bondTolerance;
			SphereIterator iter = bspf.getSphereIterator(atom.modelIndex);
			iter.initializeHemisphere(atom, searchRadius);
			while (iter.hasMoreElements()) {
				Atom atomNear = (Atom) iter.nextElement();
				if (atomNear == atom)
					continue;
				int atomIndexNear = atomNear.atomIndex;
				boolean isNearInSetA = (bsA == null || bsA.get(atomIndexNear));
				boolean isNearInSetB = (bsB == null || bsB.get(atomIndexNear));
				if (!isNearInSetA & !isNearInSetB)
					continue;
				if (!(isAtomInSetA & isNearInSetB || isAtomInSetB & isNearInSetA))
					continue;
				short order = getBondOrder(atom, myBondingRadius, atomNear, atomNear.getBondingRadiusFloat(), iter
						.foundDistance2(), minBondDistance2, bondTolerance);
				if (order > 0) {
					checkValencesAndBond(atom, atomNear, order, mad);
					nNew++;
				}
			}
			iter.release();
		}
		if (showRebondTimes) {
			long timeEnd = System.currentTimeMillis();
			Logger.debug("Time to autoBond=" + (timeEnd - timeBegin));
		}

		return nNew;
	}

	private short getBondOrder(Atom atomA, float bondingRadiusA, Atom atomB, float bondingRadiusB, float distance2,
			float minBondDistance2, float bondTolerance) {
		if (bondingRadiusA == 0 || bondingRadiusB == 0)
			return 0;
		float maxAcceptable = bondingRadiusA + bondingRadiusB + bondTolerance;
		float maxAcceptable2 = maxAcceptable * maxAcceptable;
		if (distance2 < minBondDistance2) {
			return 0;
		}
		if (distance2 <= maxAcceptable2) {
			return 1;
		}
		return 0;
	}

	boolean haveWarned = false;

	void checkValencesAndBond(Atom atomA, Atom atomB, short order, short mad) {
		if (atomA.getCurrentBondCount() > JmolConstants.MAXIMUM_AUTO_BOND_COUNT
				|| atomB.getCurrentBondCount() > JmolConstants.MAXIMUM_AUTO_BOND_COUNT) {
			if (!haveWarned)
				Logger.warn("maximum auto bond count reached");
			haveWarned = true;
			return;
		}
		int formalChargeA = atomA.getFormalCharge();
		if (formalChargeA != 0) {
			int formalChargeB = atomB.getFormalCharge();
			if ((formalChargeA < 0 && formalChargeB < 0) || (formalChargeA > 0 && formalChargeB > 0))
				return;
		}
		if (atomA.alternateLocationID != atomB.alternateLocationID && atomA.alternateLocationID != 0
				&& atomB.alternateLocationID != 0)
			return;
		addBond(bondMutually(atomA, atomB, order, mad));
	}

	float hbondMax = 3.25f;
	float hbondMin = 2.5f;
	float hbondMin2 = hbondMin * hbondMin;

	boolean hbondsCalculated;

	boolean useRasMolHbondsCalculation = true;

	int autoHbond(BitSet bsA, BitSet bsB) {
		if (useRasMolHbondsCalculation && bondCount > 0) {
			if (mmset != null)
				mmset.calcHydrogenBonds(bsA, bsB);
			return 0;
		}
		// this method is not enabled and is probably error-prone.
		// it does not take into account anything but distance,
		// and as such is not really practical.

		int nNew = 0;
		initializeBspf();
		long timeBegin = 0;
		if (showRebondTimes)
			timeBegin = System.currentTimeMillis();
		for (int i = atomCount; --i >= 0;) {
			Atom atom = atoms[i];
			int elementNumber = atom.getElementNumber();
			if (elementNumber != 7 && elementNumber != 8)
				continue;
			// float searchRadius = hbondMax;
			SphereIterator iter = bspf.getSphereIterator(atom.modelIndex);
			iter.initializeHemisphere(atom, hbondMax);
			while (iter.hasMoreElements()) {
				Atom atomNear = (Atom) iter.nextElement();
				int elementNumberNear = atomNear.getElementNumber();
				if (elementNumberNear != 7 && elementNumberNear != 8)
					continue;
				if (atomNear == atom)
					continue;
				if (iter.foundDistance2() < hbondMin2)
					continue;
				if (atom.isBonded(atomNear))
					continue;
				addBond(bondMutually(atom, atomNear, JmolConstants.BOND_H_REGULAR, (short) 1));
				nNew++;
			}
			iter.release();
		}
		if (showRebondTimes) {
			long timeEnd = System.currentTimeMillis();
			Logger.debug("Time to hbond=" + (timeEnd - timeBegin));
		}
		return nNew;
	}

	void deleteAllBonds() {
		stateScripts.clear();
		viewer.setShapeProperty(JmolConstants.SHAPE_STICKS, "reset", null);
		for (int i = 0; i < bonds.length; i++) {
			if (bonds[i] != null) {
				bonds[i].deleteAtomReferences();
				bonds[i] = null;
			}
		}
		bondCount = 0;
	}

	// XIE v10.2 method
	void deleteBond(Bond bond) {
		// what a disaster ... I hate doing this
		for (int i = bondCount; --i >= 0;) {
			if (bonds[i] == bond) {
				bonds[i].deleteAtomReferences();
				System.arraycopy(bonds, i + 1, bonds, i, bondCount - i - 1);
				--bondCount;
				bonds[bondCount] = null;
				return;
			}
		}
	}

	void deleteBonds(BitSet bs) {
		int iSrc = 0;
		int iDst = 0;
		for (; iSrc < bondCount; ++iSrc) {
			Bond bond = bonds[iSrc];
			if (!bs.get(iSrc))
				bonds[iDst++] = bond;
			else bond.deleteAtomReferences();
		}
		for (int i = bondCount; --i >= iDst;)
			bonds[i] = null;
		bondCount = iDst;
	}

	void deleteCovalentBonds() {
		int indexNoncovalent = 0;
		for (int i = 0; i < bondCount; ++i) {
			Bond bond = bonds[i];
			if (bond == null)
				continue;
			if (!bond.isCovalent()) {
				if (i != indexNoncovalent) {
					bonds[indexNoncovalent++] = bond;
					bonds[i] = null;
				}
			}
			else {
				bond.deleteAtomReferences();
				bonds[i] = null;
			}
		}
		bondCount = indexNoncovalent;
	}

	Vector stateScripts = new Vector();
	int thisFrame = 0;

	@SuppressWarnings("unchecked")
	void addStateScript(String script) {
		int iFrame = viewer.getDisplayModelIndex();
		if (thisFrame != iFrame) {
			thisFrame = iFrame;
			script = "frame " + this.getModelNumber(iFrame) + ";\n" + script;
		}
		stateScripts.add(script);
	}

	@SuppressWarnings("unchecked")
	int makeConnections(float minDistance, float maxDistance, short order, int connectOperation, BitSet bsA, BitSet bsB) {
		String stateScript = "connect " + minDistance + " " + maxDistance + " " + StateManager.escape(bsA) + " "
				+ StateManager.escape(bsB) + " " + JmolConstants.getBondOrderNameFromOrder(order) + " "
				+ JmolConstants.connectOperationName(connectOperation) + ";";
		stateScripts.add(stateScript);
		Logger.debug("makeConnections(" + minDistance + "," + maxDistance + "," + order + "," + connectOperation + ","
				+ bsA + "," + bsB + ")");

		if (connectOperation == JmolConstants.DELETE_BONDS) {
			return deleteConnections(minDistance, maxDistance, order, bsA, bsB);
		}
		if (connectOperation == JmolConstants.AUTO_BOND) {
			return autoBond(order, bsA, bsB);
		}
		if (order == JmolConstants.BOND_ORDER_NULL)
			order = JmolConstants.BOND_COVALENT_SINGLE; // default
		float minDistanceSquared = minDistance * minDistance;
		float maxDistanceSquared = maxDistance * maxDistance;
		defaultCovalentMad = viewer.getMadBond();
		short mad = getDefaultMadFromOrder(order);
		int nNew = 0;
		int nModified = 0;
		for (int iA = atomCount; --iA >= 0;) {
			if (!bsA.get(iA))
				continue;
			Atom atomA = atoms[iA];
			Point3f pointA = atomA;
			for (int iB = atomCount; --iB >= 0;) {
				if (iB == iA)
					continue;
				if (!bsB.get(iB))
					continue;
				Atom atomB = atoms[iB];
				if (atomA.modelIndex != atomB.modelIndex)
					continue;
				if (atomA.alternateLocationID != atomB.alternateLocationID && atomA.alternateLocationID != 0
						&& atomB.alternateLocationID != 0)
					continue;
				Bond bondAB = atomA.getBond(atomB);
				if (JmolConstants.MODIFY_ONLY == connectOperation && bondAB == null)
					continue;
				if (JmolConstants.CREATE_ONLY == connectOperation && bondAB != null)
					continue;
				float distanceSquared = pointA.distanceSquared(atomB);
				if (distanceSquared < minDistanceSquared || distanceSquared > maxDistanceSquared)
					continue;
				if (bondAB != null) {
					bondAB.setOrder(order);
					nNew++;
				}
				else {
					bondAtoms(atomA, atomB, order, mad);
					nModified++;
				}
			}
		}
		Logger.info(nNew + " new bonds; " + nModified + " modified");
		return nNew + nModified;
	}

	int deleteConnections(float minDistance, float maxDistance, short order, BitSet bsA, BitSet bsB) {
		BitSet bsDelete = new BitSet();
		float minDistanceSquared = minDistance * minDistance;
		float maxDistanceSquared = maxDistance * maxDistance;
		if (order != JmolConstants.BOND_ORDER_NULL && (order & JmolConstants.BOND_HYDROGEN_MASK) != 0)
			order = JmolConstants.BOND_HYDROGEN_MASK;
		int nDeleted = 0;
		for (int i = bondCount; --i >= 0;) {
			Bond bond = bonds[i];
			Atom atom1 = bond.atom1;
			Atom atom2 = bond.atom2;
			if (bsA.get(atom1.atomIndex) && bsB.get(atom2.atomIndex) || bsA.get(atom2.atomIndex)
					&& bsB.get(atom1.atomIndex)) {
				if (bond.atom1.isBonded(bond.atom2)) {
					float distanceSquared = atom1.distanceSquared(atom2);
					if (distanceSquared >= minDistanceSquared && distanceSquared <= maxDistanceSquared)
						if (order == JmolConstants.BOND_ORDER_NULL
								|| order == (bond.order & ~JmolConstants.BOND_SULFUR_MASK)
								|| (order & bond.order & JmolConstants.BOND_HYDROGEN_MASK) != 0) {
							bsDelete.set(i);
							nDeleted++;
						}
				}
			}
		}
		deleteBonds(bsDelete);
		Logger.info(nDeleted + " bonds deleted");
		return nDeleted;
	}

	// ///////////////////////

	// for Dots only

	float getMaxVanderwaalsRadius() {
		if (maxVanderwaalsRadius == Float.MIN_VALUE)
			findMaxRadii();
		return maxVanderwaalsRadius;
	}

	ShapeRenderer getRenderer(int shapeID) {
		return frameRenderer.getRenderer(shapeID, g3d);
	}

	void doUnitcellStuff() {
		/*
		 * really THREE issues here: 1) does a model have an associated unit cell that could be displayed? 2) are the
		 * coordinates fractional and so need to be transformed? 3) does the model have symmetry operations that were
		 * applied?
		 * 
		 * This must be done for each model individually.
		 * 
		 */

		if (someModelsHaveUnitcells) {
			boolean doPdbScale = (modelCount == 1);
			cellInfos = new CellInfo[modelCount];
			for (int i = 0; i < modelCount; i++)
				cellInfos[i] = new CellInfo(i, doPdbScale);
		}
		if (someModelsHaveSymmetry) {
			for (int i = 0; i < modelCount; i++) {
				mmset.setSymmetryAtomInfo(i, mmset.getModelAuxiliaryInfoInt(i, "presymmetryAtomIndex"), mmset
						.getModelAuxiliaryInfoInt(i, "presymmetryAtomCount"));
			}
		}
		else {
			int ipt = 0;
			for (int i = 0; i < modelCount; i++) {
				ipt = mmset.setSymmetryAtomInfo(i, ipt, getAtomCountInModel(i));
			}
		}
		if (someModelsHaveFractionalCoordinates) {
			for (int i = atomCount; --i >= 0;) {
				int modelIndex = atoms[i].modelIndex;
				if (!cellInfos[modelIndex].coordinatesAreFractional)
					continue;
				cellInfos[modelIndex].toCartesian(atoms[i]);
				if (Logger.isActiveLevel(Logger.LEVEL_DEBUG))
					Logger.debug("atom " + i + ": " + atoms[i]);
			}
		}
	}

	void calcAtomsMinMax(Point3f pointMin, Point3f pointMax) {
		if (atomCount <= 0)
			return; // XIE
		if (atomCount < 2) {
			pointMin.set(-10, -10, -10);
			pointMax.set(10, 10, 10);
			return;
		}
		pointMin.set(atoms[0]);
		pointMax.set(atoms[0]);
		for (int i = atomCount; --i > 0;) {
			// note that the 0 element was set above
			checkMinMax(atoms[i], pointMin, pointMax);
		}
	}

	void calcUnitCellMinMax(Point3f pointMin, Point3f pointMax) {
		if (cellInfos == null)
			return;
		for (int i = 0; i < modelCount; i++) {
			if (!cellInfos[i].coordinatesAreFractional)
				continue;
			Point3f[] vertices = cellInfos[i].unitCell.getVertices();
			for (int j = 0; j < 8; j++)
				checkMinMax(vertices[j], pointMin, pointMax);
		}
	}

	void checkMinMax(Point3f pt, Point3f pointMin, Point3f pointMax) {
		float t = pt.x;
		if (t < pointMin.x)
			pointMin.x = t;
		else if (t > pointMax.x)
			pointMax.x = t;
		t = pt.y;
		if (t < pointMin.y)
			pointMin.y = t;
		else if (t > pointMax.y)
			pointMax.y = t;
		t = pt.z;
		if (t < pointMin.z)
			pointMin.z = t;
		else if (t > pointMax.z)
			pointMax.z = t;
	}

	Point3f getAtomSetCenter(BitSet bs) {
		Point3f ptCenter = new Point3f(0, 0, 0);
		int nPoints = viewer.cardinalityOf(bs);
		if (nPoints == 0)
			return ptCenter;
		for (int i = atomCount; --i >= 0;) {
			if (bs.get(i))
				ptCenter.add(atoms[i]);
		}
		ptCenter.scale(1.0f / nPoints);
		return ptCenter;
	}

	int firstAtomOf(BitSet bs) {
		if (bs == null)
			return -1;
		for (int i = 0; i < atomCount; i++)
			if (bs.get(i)) {
				return i;
			}
		return -1;
	}

	Atom getSymmetryBaseAtom(int modelIndex, int site, int symop) {
		Frame.CellInfo[] c = cellInfos;
		if (c != null)
			for (int i = 0; i < atomCount; i++)
				if (atoms[i].modelIndex == modelIndex && atoms[i].atomSite == site && atoms[i].atomSymmetry.get(symop))
					return atoms[i];
		return null;
	}

	// /// this set of methods is used by Eval

	/**
	 * general unqualified lookup of atom set type
	 * 
	 * @param setType
	 * @return BitSet; or null if we mess up the type
	 */
	BitSet getAtomBits(String setType) {
		if (setType.equals("specialPosition"))
			return getSpecialPosition();
		if (setType.equals("symmetry"))
			return getSymmetrySet();
		if (setType.equals("unitcell"))
			return getUnitCellSet();
		if (setType.equals("hetero"))
			return getHeteroSet();
		if (setType.equals("hydrogen"))
			return getHydrogenSet();
		if (setType.equals("protein"))
			return getProteinSet();
		if (setType.equals("carbohydrate"))
			return getCarbohydrateSet();
		if (setType.equals("nucleic"))
			return getNucleicSet();
		if (setType.equals("dna"))
			return getDnaSet();
		if (setType.equals("rna"))
			return getRnaSet();
		if (setType.equals("purine"))
			return getPurineSet();
		if (setType.equals("pyrimidine"))
			return getPyrimidineSet();
		return null;
	}

	private BitSet getSpecialPosition() {
		BitSet bs = new BitSet(atomCount);
		for (int i = atomCount; --i >= 0;) {
			BitSet bsSym = atoms[i].getAtomSymmetry();
			if (bsSym != null && viewer.cardinalityOf(bsSym) > 1)
				bs.set(i);
		}
		return bs;
	}

	private BitSet getUnitCellSet() {
		BitSet bsCell = new BitSet();
		UnitCell unitcell = viewer.getCurrentUnitCell();
		if (unitcell == null)
			return bsCell;
		Point3f cell = new Point3f(unitcell.getFractionalOffset());
		cell.x += 1;
		cell.y += 1;
		cell.z += 1;
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isInLatticeCell(cell))
				bsCell.set(i);
		return bsCell;
	}

	private BitSet getSymmetrySet() {
		// presumption here is that one cannot DELETE atoms
		BitSet bs = new BitSet(atomCount);
		for (int i = atomCount; --i >= 0;)
			bs.set(i);
		for (int i = 0; i < modelCount; i++) {
			int atomIndex = mmset.getPreSymmetryAtomIndex(i);
			int preSymAtomCount = mmset.getPreSymmetryAtomCount(i);
			if (atomIndex < 0)
				continue;
			for (int iatom = atomIndex + preSymAtomCount; --iatom >= atomIndex;)
				bs.clear(iatom);
		}
		return bs;
	}

	private BitSet getHeteroSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isHetero())
				bs.set(i);
		return bs;
	}

	private BitSet getHydrogenSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			if (atoms[i].getElementNumber() == 1)
				bs.set(i);
		}
		return bs;
	}

	private BitSet getProteinSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isProtein())
				bs.set(i);
		return bs;
	}

	private BitSet getCarbohydrateSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isCarbohydrate())
				bs.set(i);
		return bs;
	}

	private BitSet getNucleicSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isNucleic())
				bs.set(i);
		return bs;
	}

	private BitSet getDnaSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isDna())
				bs.set(i);
		return bs;
	}

	private BitSet getRnaSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isRna())
				bs.set(i);
		return bs;
	}

	private BitSet getPurineSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isPurine())
				bs.set(i);
		return bs;
	}

	private BitSet getPyrimidineSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isPyrimidine())
				bs.set(i);
		return bs;
	}

	/**
	 * general lookup for String type
	 * 
	 * @param setType
	 * @param specInfo
	 * @return BitSet or null in certain cases
	 */
	BitSet getAtomBits(String setType, String specInfo) {
		if (setType.equals("IdentifierOrNull"))
			return getIdentifierOrNull(specInfo);
		if (setType.equals("SpecAtom"))
			return getSpecAtom(specInfo);
		if (setType.equals("SpecName"))
			return getSpecName(specInfo);
		if (setType.equals("SpecAlternate"))
			return getSpecAlternate(specInfo);
		if (setType.equals("SpecModel"))
			return getSpecModel(specInfo);
		return null;
	}

	/**
	 * overhauled by RMH Nov 1, 2006.
	 * 
	 * @param identifier
	 * @return null or bs
	 */
	private BitSet getIdentifierOrNull(String identifier) {
		// a primitive lookup scheme when [ ] are not used
		// nam
		// na?
		// nam45
		// nam45C
		// nam45^
		// nam45^A
		// nam45^AC -- note, no colon here -- if present, handled separately
		// nam4? does NOT match anything for PDB files, but might for others
		// atom specifiers:
		// H?
		// H32
		// H3?

		// in the case of a ?, we take the whole thing

		BitSet bs = getSpecNameOrNull(identifier);
		if (bs != null || identifier.indexOf("?") > 0)
			return bs;

		int pt = identifier.indexOf("*");
		if (pt > 0)
			return getSpecNameOrNull(identifier.substring(0, pt) + "??????????" + identifier.substring(pt + 1));
		int len = identifier.length();
		pt = 0;
		while (pt < len && Character.isLetter(identifier.charAt(pt)))
			++pt;
		bs = getSpecNameOrNull(identifier.substring(0, pt));
		if (pt == len)
			return bs;
		if (bs == null)
			bs = new BitSet();
		//
		// look for a sequence number or sequence number ^ insertion code
		//
		int pt0 = pt;
		while (pt < len && Character.isDigit(identifier.charAt(pt)))
			++pt;
		int seqNumber = 0;
		try {
			seqNumber = Integer.parseInt(identifier.substring(pt0, pt));
		}
		catch (NumberFormatException nfe) {
			return null;
		}
		char insertionCode = ' ';
		if (pt < len && identifier.charAt(pt) == '^')
			if (++pt < len)
				insertionCode = identifier.charAt(pt);
		int seqcode = Group.getSeqcode(seqNumber, insertionCode);
		BitSet bsInsert = getSpecSeqcode(seqcode, false);
		if (bsInsert == null) {
			if (insertionCode != ' ')
				bsInsert = getSpecSeqcode(Character.toUpperCase(identifier.charAt(pt)), false);
			if (bsInsert == null)
				return null;
			pt++;
		}
		bs.and(bsInsert);
		if (pt >= len)
			return bs;
		//
		// look for a chain spec -- no colon
		//
		char chainID = identifier.charAt(pt++);
		bs.and(getSpecChain(chainID));
		if (pt == len)
			return bs;
		//
		// not applicable
		//
		return null;
	}

	private BitSet getSpecAtom(String atomSpec) {
		BitSet bs = new BitSet();
		atomSpec = atomSpec.toUpperCase();
		for (int i = atomCount; --i >= 0;) {
			if (atoms[i].isAtomNameMatch(atomSpec)) {
				bs.set(i);
			}
		}
		return bs;
	}

	private BitSet getSpecName(String name) {
		BitSet bs = getSpecNameOrNull(name);
		if (bs != null)
			return bs;
		int pt = name.indexOf("*");
		if (pt > 0) {
			bs = getSpecNameOrNull(name.substring(0, pt) + "??????????" + name.substring(pt + 1));
		}
		return (bs == null ? new BitSet() : bs);
	}

	private BitSet getSpecNameOrNull(String name) {
		BitSet bs = null;
		name = name.toUpperCase();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isGroup3OrNameMatch(name)) {
				if (bs == null)
					bs = new BitSet(i + 1);
				bs.set(i);
			}
		return bs;
	}

	private BitSet getSpecAlternate(String alternateSpec) {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			if (atoms[i].isAlternateLocationMatch(alternateSpec))
				bs.set(i);
		}
		return bs;
	}

	private BitSet getSpecModel(String modelTag) {
		int modelNumber = -1;
		try {
			modelNumber = Integer.parseInt(modelTag);
		}
		catch (NumberFormatException nfe) {
		}
		return getModelAtomBitSet(getModelNumberIndex(modelNumber));
	}

	/**
	 * general lookup for integer type -- from Eval
	 * 
	 * @param setType
	 * @param specInfo
	 * @return bitset; null only if we mess up with name
	 */
	BitSet getAtomBits(String setType, int specInfo) {
		if (setType.equals("SpecResid"))
			return getSpecResid(specInfo);
		if (setType.equals("SpecSeqcode"))
			return getSpecSeqcode(specInfo, true);
		if (setType.equals("SpecChain"))
			return getSpecChain((char) specInfo);
		if (setType.equals("atomno"))
			return getSpecAtomNumber(specInfo);
		return null;
	}

	private BitSet getSpecResid(int resid) {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			if (atoms[i].getGroupID() == resid)
				bs.set(i);
		}
		return bs;
	}

	private BitSet getSpecSeqcode(int seqcode, boolean returnEmpty) {
		BitSet bs = new BitSet();
		int seqNum = (seqcode >> 8);
		boolean isEmpty = true;
		char insCode = Group.getInsertionCode(seqcode);
		switch (insCode) {
		case '?':
			for (int i = atomCount; --i >= 0;) {
				int atomSeqcode = atoms[i].getSeqcode();
				if ((seqNum == 0 || seqNum == (atomSeqcode >> 8)) && (atomSeqcode & 0xFF) != 0) {
					bs.set(i);
					isEmpty = false;
				}
			}
			break;
		default:
			for (int i = atomCount; --i >= 0;) {
				int atomSeqcode = atoms[i].getSeqcode();
				if (seqcode == atomSeqcode || seqNum == 0 && seqcode == (atomSeqcode & 0xFF) || insCode == '*'
						&& seqNum == (atomSeqcode >> 8)) {
					bs.set(i);
					isEmpty = false;
				}
			}
		}
		return (!isEmpty || returnEmpty ? bs : null);
	}

	private BitSet getSpecChain(char chain) {
		boolean caseSensitive = viewer.getChainCaseSensitive();
		if (!caseSensitive)
			chain = Character.toUpperCase(chain);
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			char ch = atoms[i].getChainID();
			if (!caseSensitive)
				ch = Character.toUpperCase(ch);
			if (chain == ch)
				bs.set(i);
		}
		return bs;
	}

	private BitSet getSpecAtomNumber(int atomno) {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			if (atoms[i].getAtomNumber() == atomno)
				bs.set(i);
		}
		return bs;
	}

	/**
	 * general lookup involving a range
	 * 
	 * @param setType
	 * @param specInfo
	 * @return BitSet; or null if mess up with type
	 */
	BitSet getAtomBits(String setType, int[] specInfo) {
		if (setType.equals("SpecSeqcodeRange"))
			return getSpecSeqcodeRange(specInfo[0], specInfo[1]);
		if (setType.equals("Cell"))
			return getCellSet(specInfo[0], specInfo[1], specInfo[2]);
		return null;
	}

	private BitSet getSpecSeqcodeRange(int seqcodeA, int seqcodeB) {
		BitSet bs = new BitSet();
		mmset.selectSeqcodeRange(seqcodeA, seqcodeB, bs);
		return bs;
	}

	private BitSet getCellSet(int ix, int jy, int kz) {
		BitSet bs = new BitSet();
		Point3f cell = new Point3f(ix / 1000f, jy / 1000f, kz / 1000f);
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isInLatticeCell(cell))
				bs.set(i);
		return bs;
	}

	BitSet getModelAtomBitSet(int modelIndex) {
		BitSet bs = new BitSet();
		for (int i = 0; i < atomCount; i++)
			if (atoms[i].modelIndex == modelIndex)
				bs.set(i);
		return bs;
	}

	BitSet getModelBitSet(BitSet atomList) {
		BitSet bs = new BitSet();
		for (int i = 0; i < atomCount; i++)
			if (atomList.get(i))
				bs.set(atoms[i].modelIndex);
		return bs;
	}

	void setLabel(String label, int atomIndex) {
	}

	void findElementsPresent() {
		elementsPresent = new BitSet();
		for (int i = atomCount; --i >= 0;) {
			int n = atoms[i].getAtomicAndIsotopeNumber();
			if (n >= 128)
				n = JmolConstants.elementNumberMax + JmolConstants.altElementIndexFromNumber(n);
			elementsPresent.set(n);
		}
	}

	BitSet getElementsPresentBitSet() {
		return elementsPresent;
	}

	void calcSelectedGroupsCount(BitSet bsSelected) {
		mmset.calcSelectedGroupsCount(bsSelected);
	}

	void calcSelectedMonomersCount(BitSet bsSelected) {
		mmset.calcSelectedMonomersCount(bsSelected);
	}

	BitSet selectedMolecules = new BitSet();
	BitSet bsTemp = new BitSet();
	int selectedMoleculeCount;

	void calcSelectedMoleculesCount(BitSet bsSelected) {
		if (moleculeCount == 0)
			getMolecules();
		selectedMolecules.xor(selectedMolecules);
		selectedMoleculeCount = 0;
		for (int i = 0; i < moleculeCount; i++) {
			bsTemp.clear();
			bsTemp.or(bsSelected);
			bsTemp.and(molecules[i].atomList);
			if (bsTemp.length() > 0) {
				selectedMolecules.set(i);
				selectedMoleculeCount++;
			}
		}
	}

	void findMaxRadii() {
		for (int i = atomCount; --i >= 0;) {
			Atom atom = atoms[i];
			float bondingRadius = atom.getBondingRadiusFloat();
			if (bondingRadius > maxBondingRadius)
				maxBondingRadius = bondingRadius;
			float vdwRadius = atom.getVanderwaalsRadiusFloat();
			if (vdwRadius > maxVanderwaalsRadius)
				maxVanderwaalsRadius = vdwRadius;
		}
	}

	BitSet bsSurfaceSet;

	void setSurfaceAtoms(BitSet bsSurface, BitSet bsEnclosed) {
		bsSurfaceSet = (BitSet) bsEnclosed.clone();
		surfaceDistances = null;
		int n = viewer.cardinalityOf(bsSurface);
		if (n == 0) {
			surfaceAtoms = null;
			return;
		}
		surfaceAtoms = new int[n];
		for (int i = atomCount, pt = 0; --i >= 0;)
			if (bsSurface.get(i))
				surfaceAtoms[pt++] = i;
	}

	float getSurfaceDistance(int atomIndex) {
		if (surfaceAtoms == null)
			return -1;
		if (surfaceDistances == null)
			calcSurfaceDistances();
		return surfaceDistances[atomIndex];
	}

	float surfaceDistanceMax;

	float getSurfaceDistanceMax() {
		if (surfaceDistances == null)
			calcSurfaceDistances();
		return surfaceDistanceMax;
	}

	private void calcSurfaceDistances() {
		surfaceDistanceMax = 0;
		if (bsSurfaceSet == null)
			return;
		surfaceDistances = new float[atomCount];
		for (int i = 0; i < atomCount; i++) {
			surfaceDistances[i] = -1;
			if (bsSurfaceSet.get(i)) {
				float dMin = Float.MAX_VALUE;
				Atom atom = atoms[i];
				for (int j = surfaceAtoms.length; --j >= 0;) {
					float d = atoms[surfaceAtoms[j]].distance(atom);
					dMin = Math.min(d, dMin);
				}
				surfaceDistances[i] = dMin;
				surfaceDistanceMax = Math.max(surfaceDistanceMax, dMin);
			}
		}
	}

	void calcBfactorRange() {
		calcBfactorRange(null);
	}

	void clearBfactorRange() {
		hasBfactorRange = false;
	}

	void calcBfactorRange(BitSet bs) {
		if (!hasBfactorRange) {
			bfactor100Lo = Integer.MAX_VALUE;
			bfactor100Hi = Integer.MIN_VALUE;
			for (int i = atomCount; --i > 0;)
				if (bs == null || bs.get(i)) {
					int bf = atoms[i].getBfactor100();
					if (bf < bfactor100Lo)
						bfactor100Lo = bf;
					else if (bf > bfactor100Hi)
						bfactor100Hi = bf;
				}
			hasBfactorRange = true;
		}
	}

	int getBfactor100Lo() {
		if (!hasBfactorRange) {
			if (viewer.isRangeSelected()) {
				calcBfactorRange(viewer.getSelectionSet());
			}
			else {
				calcBfactorRange(null);
			}
		}
		return bfactor100Lo;
	}

	int getBfactor100Hi() {
		getBfactor100Lo();
		return bfactor100Hi;
	}

	BitSet getVisibleSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].isVisible())
				bs.set(i);
		return bs;
	}

	BitSet getClickableSet() {
		BitSet bs = new BitSet();
		for (int i = atomCount; --i >= 0;)
			if (atoms[i].clickabilityFlags != 0)
				bs.set(i);
		return bs;
	}

	// //////////////////////////////////////////////////////////////
	// measurements
	// //////////////////////////////////////////////////////////////

	float getMeasurement(int[] countPlusIndices) {
		float value = Float.NaN;
		if (countPlusIndices == null)
			return value;
		int count = countPlusIndices[0];
		if (count < 2)
			return value;
		for (int i = count; --i >= 0;)
			if (countPlusIndices[i + 1] < 0) {
				return value;
			}
		switch (count) {
		case 2:
			value = getDistance(countPlusIndices[1], countPlusIndices[2]);
			break;
		case 3:
			value = getAngle(countPlusIndices[1], countPlusIndices[2], countPlusIndices[3]);
			break;
		case 4:
			value = getTorsion(countPlusIndices[1], countPlusIndices[2], countPlusIndices[3], countPlusIndices[4]);
			break;
		default:
			Logger.error("Invalid count in measurement calculation:" + count);
			throw new IndexOutOfBoundsException();
		}

		return value;
	}

	float getDistance(int atomIndexA, int atomIndexB) {
		return atoms[atomIndexA].distance(atoms[atomIndexB]);
	}

	Vector3f vectorBA;
	Vector3f vectorBC;

	float getAngle(int atomIndexA, int atomIndexB, int atomIndexC) {
		if (vectorBA == null) {
			vectorBA = new Vector3f();
			vectorBC = new Vector3f();
		}
		Point3f pointA = atoms[atomIndexA];
		Point3f pointB = atoms[atomIndexB];
		Point3f pointC = atoms[atomIndexC];
		vectorBA.sub(pointA, pointB);
		vectorBC.sub(pointC, pointB);
		float angle = vectorBA.angle(vectorBC);
		float degrees = toDegrees(angle);
		return degrees;
	}

	float getTorsion(int atomIndexA, int atomIndexB, int atomIndexC, int atomIndexD) {
		return computeTorsion(atoms[atomIndexA], atoms[atomIndexB], atoms[atomIndexC], atoms[atomIndexD]);
	}

	static float toDegrees(float angleRadians) {
		return angleRadians * 180 / (float) Math.PI;
	}

	static float computeTorsion(Point3f p1, Point3f p2, Point3f p3, Point3f p4) {

		float ijx = p1.x - p2.x;
		float ijy = p1.y - p2.y;
		float ijz = p1.z - p2.z;

		float kjx = p3.x - p2.x;
		float kjy = p3.y - p2.y;
		float kjz = p3.z - p2.z;

		float klx = p3.x - p4.x;
		float kly = p3.y - p4.y;
		float klz = p3.z - p4.z;

		float ax = ijy * kjz - ijz * kjy;
		float ay = ijz * kjx - ijx * kjz;
		float az = ijx * kjy - ijy * kjx;
		float cx = kjy * klz - kjz * kly;
		float cy = kjz * klx - kjx * klz;
		float cz = kjx * kly - kjy * klx;

		float ai2 = 1f / (ax * ax + ay * ay + az * az);
		float ci2 = 1f / (cx * cx + cy * cy + cz * cz);

		float ai = (float) Math.sqrt(ai2);
		float ci = (float) Math.sqrt(ci2);
		float denom = ai * ci;
		float cross = ax * cx + ay * cy + az * cz;
		float cosang = cross * denom;
		if (cosang > 1) {
			cosang = 1;
		}
		if (cosang < -1) {
			cosang = -1;
		}

		float torsion = toDegrees((float) Math.acos(cosang));
		float dot = ijx * cx + ijy * cy + ijz * cz;
		float absDot = Math.abs(dot);
		torsion = (dot / absDot > 0) ? torsion : -torsion;
		return torsion;
	}

	// //////////////////////////////////////////////////////////////

	BitSet getMoleculeBitSet(int atomIndex) {
		if (moleculeCount == 0)
			getMolecules();
		for (int i = 0; i < moleculeCount; i++)
			if (molecules[i].atomList.get(atomIndex))
				return molecules[i].atomList;
		return null;
	}

	BitSet getMoleculeBitSet(BitSet bs) {
		// returns cumulative sum of all atoms in molecules containing these atoms
		if (moleculeCount == 0)
			getMolecules();
		BitSet bsResult = (BitSet) bs.clone();
		BitSet bsInitial = (BitSet) bs.clone();
		int iLastBit;
		while ((iLastBit = bsInitial.length()) > 0) {
			bsTemp = getMoleculeBitSet(iLastBit - 1);
			bsInitial.andNot(bsTemp);
			bsResult.or(bsTemp);
		}
		return bsResult;
	}

	private void getMolecules() {
		if (moleculeCount > 0)
			return;
		moleculeCount = 0;
		int atomCount = getAtomCount();
		BitSet atomlist = new BitSet(atomCount);
		BitSet bs = new BitSet(atomCount);
		int thisModelIndex = -1;
		int modelIndex = -1;
		int indexInModel = -1;
		int moleculeCount0 = -1;
		for (int i = 0; i < atomCount; i++)
			if (!atomlist.get(i) && !bs.get(i)) {
				modelIndex = atoms[i].modelIndex;
				if (modelIndex != thisModelIndex) {
					indexInModel = -1;
					mmset.getModel(modelIndex).firstMolecule = moleculeCount;
					moleculeCount0 = moleculeCount - 1;
					thisModelIndex = modelIndex;
				}
				indexInModel++;
				bs = getConnectedBitSet(i);
				atomlist.or(bs);
				if (moleculeCount == molecules.length)
					molecules = (Molecule[]) ArrayUtil.setLength(molecules, moleculeCount * 2);
				molecules[moleculeCount] = new Molecule(moleculeCount, bs, thisModelIndex, indexInModel);
				mmset.getModel(thisModelIndex).moleculeCount = moleculeCount - moleculeCount0;
				moleculeCount++;
			}
	}

	private BitSet getConnectedBitSet(int atomIndex) {
		int atomCount = getAtomCount();
		BitSet bs = new BitSet(atomCount);
		BitSet bsToTest = getModelAtomBitSet(atoms[atomIndex].modelIndex);
		getCovalentlyConnectedBitSet(atoms[atomIndex], bs, bsToTest);
		return bs;
	}

	private void getCovalentlyConnectedBitSet(Atom atom, BitSet bs, BitSet bsToTest) {
		int atomIndex = atom.atomIndex;
		if (!bsToTest.get(atomIndex))
			return;
		bsToTest.clear(atomIndex);
		bs.set(atomIndex);
		if (atom.bonds == null)
			return;
		for (int i = atom.bonds.length; --i >= 0;) {
			Bond bond = atom.bonds[i];
			if ((bond.order & JmolConstants.BOND_HYDROGEN_MASK) != 0)
				continue;
			if (bond.atom1 == atom) {
				getCovalentlyConnectedBitSet(bond.atom2, bs, bsToTest);
			}
			else {
				getCovalentlyConnectedBitSet(bond.atom1, bs, bsToTest);
			}
		}
	}

	int getMoleculeCount() {
		return moleculeCount;
	}

	@SuppressWarnings("unchecked")
	Vector getMoleculeInfo(BitSet bsAtoms) {
		if (moleculeCount == 0)
			getMolecules();
		Vector V = new Vector();
		for (int i = 0; i < moleculeCount; i++) {
			bsTemp = (BitSet) bsAtoms.clone();
			bsTemp.and(molecules[i].atomList);
			if (bsTemp.length() > 0)
				V.add(molecules[i].getInfo());
		}
		return V;
	}

	int getMoleculeIndex(int atomIndex) {
		if (moleculeCount == 0)
			getMolecules();
		for (int i = 0; i < moleculeCount; i++) {
			if (molecules[i].atomList.get(atomIndex))
				return molecules[i].indexInModel;
		}
		return 0;
	}

	int getFirstMoleculeIndexInModel(int modelIndex) {
		if (moleculeCount == 0)
			getMolecules();
		return mmset.getModel(modelIndex).firstMolecule;
	}

	int getMoleculeCountInModel(int modelIndex) {
		// not implemented for pop-up menu -- will slow it down.
		int n = 0;
		if (moleculeCount == 0)
			getMolecules();
		for (int i = 0; i < modelCount; i++) {
			if (modelIndex == i || modelIndex < 0)
				n += mmset.getModel(i).moleculeCount;
		}
		return n;
	}

	// //////////////////////////////////////////////////////////////

	final static int MAX_BONDS_LENGTH_TO_CACHE = 5;
	final static int MAX_NUM_TO_CACHE = 200;
	int[] numCached = new int[MAX_BONDS_LENGTH_TO_CACHE];
	Bond[][][] freeBonds = new Bond[MAX_BONDS_LENGTH_TO_CACHE][][];
	{
		for (int i = MAX_BONDS_LENGTH_TO_CACHE; --i > 0;)
			// .GT. 0
			freeBonds[i] = new Bond[MAX_NUM_TO_CACHE][];
	}

	void addBondToAtom(Atom atom, Bond bond) {
		if (atom.bonds == null) {
			atom.bonds = new Bond[1];
			atom.bonds[0] = bond;
		}
		else {
			atom.bonds = addToBonds(bond, atom.bonds);
		}
	}

	Bond[] addToBonds(Bond newBond, Bond[] oldBonds) {
		Bond[] newBonds;
		if (oldBonds == null) {
			if (numCached[1] > 0)
				newBonds = freeBonds[1][--numCached[1]];
			else newBonds = new Bond[1];
			newBonds[0] = newBond;
		}
		else {
			int oldLength = oldBonds.length;
			int newLength = oldLength + 1;
			if (newLength < MAX_BONDS_LENGTH_TO_CACHE && numCached[newLength] > 0)
				newBonds = freeBonds[newLength][--numCached[newLength]];
			else newBonds = new Bond[newLength];
			newBonds[oldLength] = newBond;
			for (int i = oldLength; --i >= 0;)
				newBonds[i] = oldBonds[i];
			if (oldLength < MAX_BONDS_LENGTH_TO_CACHE && numCached[oldLength] < MAX_NUM_TO_CACHE)
				freeBonds[oldLength][numCached[oldLength]++] = oldBonds;
		}
		return newBonds;
	}

	void freeBondsCache() {
		for (int i = MAX_BONDS_LENGTH_TO_CACHE; --i > 0;) { // .GT. 0
			numCached[i] = 0;
			Bond[][] bondsCache = freeBonds[i];
			for (int j = bondsCache.length; --j >= 0;)
				bondsCache[j] = null;
		}
	}

	Point3f getAveragePosition(int atomIndex1, int atomIndex2) {
		Atom atom1 = atoms[atomIndex1];
		Atom atom2 = atoms[atomIndex2];
		return new Point3f((atom1.x + atom2.x) / 2, (atom1.y + atom2.y) / 2, (atom1.z + atom2.z) / 2);
	}

	Vector3f getAtomVector(int atomIndex1, int atomIndex2) {
		Vector3f V = new Vector3f(atoms[atomIndex1]);
		V.sub(atoms[atomIndex2]);
		return V;
	}

	Vector3f getModelDipole() {
		Vector3f dipole;
		dipole = (Vector3f) mmset.getModelSetAuxiliaryInfo("dipole");
		if (dipole == null)
			dipole = (Vector3f) mmset.getModelSetAuxiliaryInfo("DIPOLE_VEC");
		return dipole;
	}

	final static float E_ANG_PER_DEBYE = 0.208194f;

	void getBondDipoles() {
		if (partialCharges == null)
			return;
		loadShape(JmolConstants.SHAPE_DIPOLES);
		Dipoles dipoles = (Dipoles) shapes[JmolConstants.SHAPE_DIPOLES];
		dipoles.clear(true);
		for (int i = bondCount; --i >= 0;) {
			if (!bonds[i].isCovalent())
				continue;
			Atom atom1 = bonds[i].atom1;
			Atom atom2 = bonds[i].atom2;
			float c1 = partialCharges[atom1.atomIndex];
			float c2 = partialCharges[atom2.atomIndex];
			if (c1 != c2) {
				Dipole dipole = dipoles.findDipole(atom1, atom2, true);
				float value = (c1 - c2) / 2f * atom1.distance(atom2) / E_ANG_PER_DEBYE;
				if (value < 0) {
					dipole.set(atom2, atom1, -value);
				}
				else {
					dipole.set(atom1, atom2, value);
				}
				dipole.type = Dipole.DIPOLE_TYPE_BOND;
				dipole.modelIndex = atom1.modelIndex;
			}
		}
	}

	String getSymmetryInfoAsString(int modelIndex) {
		if (cellInfos == null)
			return "no symmetry information";
		return cellInfos[modelIndex].symmetryInfoString;
	}

	final static float toRadians = (float) Math.PI * 2 / 360;
	final static Point3f[] unitCubePoints = { new Point3f(0, 0, 0), new Point3f(0, 0, 1), new Point3f(0, 1, 0),
			new Point3f(0, 1, 1), new Point3f(1, 0, 0), new Point3f(1, 0, 1), new Point3f(1, 1, 0),
			new Point3f(1, 1, 1), };

	class CellInfo {

		int modelIndex;
		boolean coordinatesAreFractional;
		String spaceGroup;
		int symmetryCount;
		String[] symmetryOperations;
		String symmetryInfoString;
		UnitCell unitCell;

		CellInfo(int modelIndex, boolean doPdbScale) {
			notionalUnitcell = (float[]) mmset.getModelAuxiliaryInfo(modelIndex, "notionalUnitcell");
			this.modelIndex = modelIndex;
			spaceGroup = (String) getModelAuxiliaryInfo(modelIndex, "spaceGroup");
			if (spaceGroup == null || spaceGroup == "")
				spaceGroup = "spacegroup unspecified";
			symmetryCount = mmset.getModelAuxiliaryInfoInt(modelIndex, "symmetryCount");
			symmetryOperations = (String[]) mmset.getModelAuxiliaryInfo(modelIndex, "symmetryOperations");
			symmetryInfoString = "Spacegroup: " + spaceGroup;
			if (symmetryOperations == null) {
				symmetryInfoString += "\nNumber of symmetry operations: ?" + "\nSymmetry Operations: unspecified\n";
			}
			else {
				symmetryInfoString += "\nNumber of symmetry operations: " + (symmetryCount == 0 ? 1 : symmetryCount)
						+ "\nSymmetry Operations:";
				for (int i = 0; i < symmetryCount; i++)
					symmetryInfoString += "\n" + symmetryOperations[i];
			}
			symmetryInfoString += "\n";
			coordinatesAreFractional = mmset.getModelAuxiliaryInfoBoolean(modelIndex, "coordinatesAreFractional");
			if (notionalUnitcell == null || notionalUnitcell[0] == 0)
				return;
			unitCell = new UnitCell(notionalUnitcell);
			showInfo();
		}

		UnitCell getUnitCell() {
			return unitCell;
		}

		float[] getNotionalUnitCell() {
			return (unitCell == null ? null : unitCell.getNotionalUnitCell());
		}

		void toCartesian(Point3f pt) {
			unitCell.toCartesian(pt);
		}

		void toFractional(Point3f pt) {
			unitCell.toFractional(pt);
		}

		void showInfo() {
			if (Logger.isActiveLevel(Logger.LEVEL_DEBUG))
				Logger.debug("cellInfos[" + modelIndex + "]:\n" + unitCell.dumpInfo(true));
		}

		String getUnitCellInfo() {
			return (unitCell == null ? "no unit cell information" : unitCell.dumpInfo(false));
		}
	}

	void convertFractionalCoordinates(int modelIndex, Point3f pt) {
		if (modelIndex < 0)
			modelIndex = 0;
		if (modelIndex >= cellInfos.length || cellInfos[modelIndex] == null)
			return;
		String str = "Frame convertFractional " + pt + "--->";
		cellInfos[modelIndex].toCartesian(pt);
		Logger.info(str + pt);
	}

	boolean reportFormalCharges = false;

	void setFormalCharges(BitSet bs, int formalCharge) {
		for (int i = 0; i < atomCount; i++)
			if (bs.get(i))
				atoms[i].setFormalCharge(formalCharge);
		reportFormalCharges = true;
	}

	void setAtomCoord(int atomIndex, float x, float y, float z) {
		if (atomIndex < 0 || atomIndex >= atomCount)
			return;
		atoms[atomIndex].x = x;
		atoms[atomIndex].y = y;
		atoms[atomIndex].z = z;
	}

	void setAtomCoordRelative(int atomIndex, float x, float y, float z) {
		if (atomIndex < 0 || atomIndex >= atomCount)
			return;
		atoms[atomIndex].x += x;
		atoms[atomIndex].y += y;
		atoms[atomIndex].z += z;
	}

	void setAtomCoordRelative(BitSet atomSet, float x, float y, float z) {
		for (int i = 0; i < atomCount; i++)
			if (atomSet.get(i))
				setAtomCoordRelative(i, x, y, z);
	}

	String hybridization;

	boolean getPrincipalAxes(int atomIndex, Vector3f z, Vector3f x, String lcaoTypeRaw, boolean hybridizationCompatible) {
		String lcaoType = (lcaoTypeRaw.length() > 0 && lcaoTypeRaw.charAt(0) == '-' ? lcaoTypeRaw.substring(1)
				: lcaoTypeRaw);
		Atom atom = atoms[atomIndex];
		hybridization = "";
		z.set(0, 0, 0);
		x.set(0, 0, 0);
		Atom atom1 = atom;
		Atom atom2 = atom;
		int nBonds = 0;
		float _180 = (float) Math.PI * 0.95f;
		Vector3f n = new Vector3f();
		Vector3f x2 = new Vector3f();
		Vector3f x3 = new Vector3f(3.14159f, 2.71828f, 1.41421f);
		Vector3f x4 = new Vector3f();
		Vector3f y1 = new Vector3f();
		Vector3f y2 = new Vector3f();
		if (atom.bonds != null)
			for (int i = atom.bonds.length; --i >= 0;)
				if (atom.bonds[i].isCovalent()) {
					++nBonds;
					atom1 = atom.bonds[i].getOtherAtom(atom);
					n.sub(atom, atom1);
					n.normalize();
					z.add(n);
					switch (nBonds) {
					case 1:
						x.set(n);
						atom2 = atom1;
						break;
					case 2:
						x2.set(n);
						break;
					case 3:
						x3.set(n);
						x4.set(-z.x, -z.y, -z.z);
						break;
					case 4:
						x4.set(n);
						break;
					default:
						i = -1;
					}
				}
		switch (nBonds) {
		case 0:
			z.set(0, 0, 1);
			x.set(1, 0, 0);
			break;
		case 1:
			if (lcaoType.indexOf("sp3") == 0) { // align z as sp3 orbital
				hybridization = "sp3";
				x.cross(x3, z);
				y1.cross(z, x);
				x.normalize();
				y1.normalize();
				y2.set(x);
				z.normalize();
				x.scaleAdd(2.828f, x, z); // 2*sqrt(2)
				if (!lcaoType.equals("sp3a") && !lcaoType.equals("sp3")) {
					x.normalize();
					AxisAngle4f a = new AxisAngle4f(z.x, z.y, z.z, (lcaoType.equals("sp3b") ? 1 : -1) * 2.09439507f); // PI*2/3
					Matrix3f m = new Matrix3f();
					m.setIdentity();
					m.set(a);
					m.transform(x);
				}
				z.set(x);
				x.cross(y1, z);
				break;
			}
			hybridization = "sp";
			if (atom1.getCovalentBondCount() == 3) {
				// special case, for example R2C=O oxygen
				getPrincipalAxes(atom1.atomIndex, z, x3, lcaoType, false);
				x3.set(x);
				if (lcaoType.indexOf("sp2") == 0) { // align z as sp2 orbital
					hybridization = "sp2";
					z.scale(-1);
				}
			}
			x.cross(x3, z);
			break;
		case 2:
			if (z.length() < 0.1) {
				// linear A--X--B
				hybridization = "sp";
				if (!lcaoType.equals("pz")) {
					if (atom1.getCovalentBondCount() != 3)
						atom1 = atom2;
					if (atom1.getCovalentBondCount() == 3) {
						// special case, for example R2C=C=CR2 central carbon
						getPrincipalAxes(atom1.atomIndex, x, z, "pz", false);
						if (lcaoType.equals("px"))
							x.scale(-1);
						z.set(x2);
						break;
					}
				}
				z.set(x);
				x.cross(x3, z);
				break;
			}
			// bent A--X--B
			hybridization = (lcaoType.indexOf("sp3") == 0 ? "sp3" : "sp2");
			x3.cross(z, x);
			if (lcaoType.indexOf("sp") == 0) { // align z as sp2 orbital
				if (lcaoType.equals("sp2a") || lcaoType.equals("sp2b")) {
					z.set(lcaoType.indexOf("b") >= 0 ? x2 : x);
					z.scale(-1);
				}
				x.cross(z, x3);
				break;
			}
			if (lcaoType.indexOf("lp") == 0) { // align z as lone pair
				hybridization = "lp"; // any is OK
				x3.normalize();
				z.normalize();
				y1.scaleAdd(1.2f, x3, z);
				y2.scaleAdd(-1.2f, x3, z);
				z.set(lcaoType.indexOf("b") >= 0 ? y2 : y1);
				x.cross(z, x3);
				break;
			}
			hybridization = lcaoType;
			// align z as p orbital
			x.cross(z, x3);
			z.set(x3);
			if (z.z < 0) {
				z.set(-z.x, -z.y, -z.z);
				x.set(-x.x, -x.y, -x.z);
			}
			break;
		default:
			// 3 or 4 bonds
			if (x.angle(x2) < _180)
				y1.cross(x, x2);
			else y1.cross(x, x3);
			y1.normalize();
			if (x2.angle(x3) < _180)
				y2.cross(x2, x3);
			else y2.cross(x, x3);
			y2.normalize();
			if (Math.abs(y2.dot(y1)) < 0.95f) {
				hybridization = "sp3";
				if (lcaoType.indexOf("sp") == 0) { // align z as sp3 orbital
					z.set(lcaoType.equalsIgnoreCase("sp3") || lcaoType.indexOf("d") >= 0 ? x4
							: lcaoType.indexOf("c") >= 0 ? x3 : lcaoType.indexOf("b") >= 0 ? x2 : x);
					z.scale(-1);
					x.set(y1);
				}
				else { // needs testing here
					x.cross(z, x);
				}
				break;
			}
			hybridization = "sp2";
			if (lcaoType.indexOf("sp") == 0) { // align z as sp2 orbital
				z.set(lcaoType.equalsIgnoreCase("sp3") || lcaoType.indexOf("d") >= 0 ? x4
						: lcaoType.indexOf("c") >= 0 ? x3 : lcaoType.indexOf("b") >= 0 ? x2 : x);
				z.scale(-1);
				x.set(y1);
				break;
			}
			// align z as p orbital
			z.set(y1);
			if (z.z < 0) {
				z.set(-z.x, -z.y, -z.z);
				x.set(-x.x, -x.y, -x.z);
			}
		}

		x.normalize();
		z.normalize();

		Logger.debug(atom.getIdentity() + " nBonds=" + nBonds + " " + hybridization);
		if (hybridizationCompatible) {
			if (hybridization == "")
				return false;
			if (lcaoType.indexOf("p") == 0) {
				if (hybridization == "sp3")
					return false;
			}
			else {
				if (lcaoType.indexOf(hybridization) < 0)
					return false;
			}
		}
		return true;
	}

	Point3f[] getAdditionalHydrogens(BitSet atomSet) {
		int n = 0;
		Vector3f z = new Vector3f();
		Vector3f x = new Vector3f();
		Point3f pt;
		// just not doing aldehydes here -- all A-X-B bent == sp3 for now
		for (int i = 0; i < atomCount; i++)
			if (atomSet.get(i) && atoms[i].getElementNumber() == 6) {
				Atom atom = atoms[i];
				int nBonds = (atom.getCovalentHydrogenCount() > 0 ? 0 : atom.getCovalentBondCount());
				if (nBonds == 3 || nBonds == 2) { // could be XA3 sp2 or XA2 sp
					if (!viewer.getPrincipalAxes(i, z, x, "sp3", true) || hybridization == "sp")
						nBonds = 0;
				}
				if (nBonds > 0 && nBonds <= 4)
					n += 4 - nBonds;
			}
		Point3f[] hAtoms = new Point3f[n];
		n = 0;
		for (int i = 0; i < atomCount; i++)
			if (atomSet.get(i) && atoms[i].getElementNumber() == 6) {
				Atom atom = atoms[i];
				int nBonds = (atom.getCovalentHydrogenCount() > 0 ? 0 : atom.getCovalentBondCount());
				switch (nBonds) {
				case 1:
					viewer.getPrincipalAxes(i, z, x, "sp3a", false);
					pt = new Point3f(z);
					pt.scaleAdd(1.1f, atom);
					hAtoms[n++] = pt;
					viewer.getPrincipalAxes(i, z, x, "sp3b", false);
					pt = new Point3f(z);
					pt.scaleAdd(1.1f, atom);
					hAtoms[n++] = pt;
					viewer.getPrincipalAxes(i, z, x, "sp3c", false);
					pt = new Point3f(z);
					pt.scaleAdd(1.1f, atom);
					hAtoms[n++] = pt;
					break;
				case 2:
					if (viewer.getPrincipalAxes(i, z, x, "sp3", true) && hybridization != "sp") {
						viewer.getPrincipalAxes(i, z, x, "lpa", false);
						pt = new Point3f(z);
						pt.scaleAdd(1.1f, atom);
						hAtoms[n++] = pt;
						viewer.getPrincipalAxes(i, z, x, "lpb", false);
						pt = new Point3f(z);
						pt.scaleAdd(1.1f, atom);
						hAtoms[n++] = pt;
					}
					break;
				case 3:
					if (viewer.getPrincipalAxes(i, z, x, "sp3", true)) {
						pt = new Point3f(z);
						pt.scaleAdd(1.1f, atom);
						hAtoms[n++] = pt;
					}
				default:
				}
			}
		return hAtoms;
	}

	// leaving this one here, because it is volatile --
	// it will be reset when a new frame is instantiated
	boolean selectionHaloEnabled = false;

	public void setSelectionHaloEnabled(boolean selectionHaloEnabled) {
		this.selectionHaloEnabled = selectionHaloEnabled;
	}

	boolean getSelectionHaloEnabled() {
		return selectionHaloEnabled;
	}

	boolean echoShapeActive = false;

	boolean getEchoStateActive() {
		return echoShapeActive;
	}

	public void setEchoStateActive(boolean TF) {
		echoShapeActive = TF;
	}

	String getState() {
		StringBuffer commands = new StringBuffer("# connections;\n");
		String cmd;
		if (reportFormalCharges) {
			Hashtable ht = new Hashtable();
			for (int i = 0; i < atomCount; i++)
				StateManager.setStateInfo(ht, i, i, "set formalCharge " + atoms[i].getFormalCharge());
			commands.append(StateManager.getCommands(ht));
		}

		// connections

		Vector fs = stateScripts;
		int len = fs.size();
		for (int i = 0; i < len; i++)
			commands.append(fs.get(i) + "\n");

		viewer.loadShape(JmolConstants.SHAPE_LABELS);
		((Labels) shapes[JmolConstants.SHAPE_LABELS]).getShapeState(commands);

		commands.append("\n# model state;\n");
		// shape construction

		for (int i = 0; i < JmolConstants.SHAPE_MAX; ++i) {
			Shape shape = shapes[i];
			if (shape != null && (cmd = shape.getShapeState()) != null && cmd.length() > 1)
				commands.append(cmd);
		}