void rotate(boolean isSpin) throws ScriptException {

		/*
		 * The Chime spin method:
		 * 
		 * set spin x 10;set spin y 30; set spin z 10; spin | spin ON spin OFF
		 * 
		 * Jmol does these "first x, then y, then z" I don't know what Chime does.
		 * 
		 * spin and rotate are now consolidated here.
		 * 
		 * far simpler is
		 * 
		 * spin x 10 spin y 10
		 * 
		 * these are pure x or y spins or
		 * 
		 * spin axisangle {1 1 0} 10
		 * 
		 * this is the same as the old "spin x 10; spin y 10" -- or is it? anyway, it's better!
		 * 
		 * note that there are many defaults
		 * 
		 * spin # defaults to spin y 10 spin 10 # defaults to spin y 10 spin x # defaults to spin x 10
		 * 
		 * and several new options
		 * 
		 * spin -x spin axisangle {1 1 0} 10 spin 10 (atomno=1)(atomno=2) spin 20 {0 0 0} {1 1 1}
		 * 
		 * spin MOLECULAR {0 0 0} 20
		 * 
		 * The MOLECULAR keyword indicates that spins or rotations are to be carried out in the internal molecular
		 * coordinate frame, not the fixed room frame. Fractional coordinates may be indicated:
		 * 
		 * spin 20 {0 0 0/} {1 1 1/}
		 * 
		 * In association with this, TransformManager and associated functions are TOTALLY REWRITTEN and consolideated.
		 * It is VERY clean now - just two methods here -- one fixed and one molecular, two in Viewer, and two in
		 * TransformManager. All the centering stuff has been carefully inspected are reorganized as well.
		 * 
		 * Bob Hanson 5/21/06
		 */

		if (statementLength == 2)
			switch (statement[1].tok) {
			case Token.on:
				viewer.setSpinOn(true);
				return;
			case Token.off:
				viewer.setSpinOn(false);
				return;
			}

		float degrees = Float.MIN_VALUE;
		int nPoints = 0;
		float endDegrees = Float.MAX_VALUE;
		boolean isAxisAngle = false;
		boolean isInternal = false;
		Point3f[] points = new Point3f[3];
		Point3f rotCenter = null;
		Vector3f rotAxis = new Vector3f(0, 1, 0);
		String axisID;
		int direction = 1;
		boolean axesOrientationRasmol = viewer.getAxesOrientationRasmol();

		for (int i = 0; i < 3; ++i)
			points[i] = new Point3f(0, 0, 0);
		for (int i = 1; i < statementLength; ++i) {
			Token token = statement[i];
			switch (token.tok) {
			case Token.hyphen:
				direction = -1;
				break;
			case Token.axisangle:
				isAxisAngle = true;
				break;
			case Token.identifier:
				String str = (String) statement[i].value;
				if (str.equalsIgnoreCase("x")) {
					rotAxis.set(direction, 0, 0);
					break;
				}
				if (str.equalsIgnoreCase("y")) {
					if (axesOrientationRasmol)
						direction = -direction;
					rotAxis.set(0, direction, 0);
					break;
				}
				if (str.equalsIgnoreCase("z")) {
					rotAxis.set(0, 0, direction);
					break;
				}
				if (str.equalsIgnoreCase("internal") || str.equalsIgnoreCase("molecular"))
					isInternal = true;
				break;
			case Token.leftbrace:
				// {X, Y, Z}
				Point3f pt = getCoordinate(i, true);
				i = pcLastExpressionInstruction;
				if (isAxisAngle) {
					if (axesOrientationRasmol)
						pt.y = -pt.y;
					rotAxis.set(pt);
					isAxisAngle = false;
				}
				else {
					points[nPoints++].set(pt);
				}
				break;
			case Token.dollarsign:
				// $drawObject
				isInternal = true;
				axisID = objectNameParameter(++i);
				rotCenter = viewer.getDrawObjectCenter(axisID);
				rotAxis = viewer.getDrawObjectAxis(axisID);
				if (rotCenter == null)
					drawObjectNotDefined(axisID);
				points[nPoints++].set(rotCenter);
				break;
			case Token.opOr:
				break;
			case Token.integer:
			case Token.decimal:
				// spin: degrees per second followed by final value
				// rotate: end degrees followed by degrees per second
				// rotate is a full replacement for spin
				// spin is DEPRECATED

				if (degrees == Float.MIN_VALUE)
					degrees = floatParameter(i);
				else {
					endDegrees = degrees;
					degrees = floatParameter(i);
					isSpin = true;
				}
				break;
			case Token.expressionBegin:
				BitSet bs = expression(statement, i + 1);
				rotCenter = viewer.getAtomSetCenter(bs);
				points[nPoints++].set(rotCenter);
				i = pcLastExpressionInstruction;
				break;
			default:
				invalidArgument();
			}
			if (nPoints >= 3) // only 2 allowed for rotation -- for now
				tooManyRotationPoints();
		}
		if (nPoints < 2 && !isInternal) {
			// simple, standard fixed-frame rotation
			// rotate x 10
			// rotate axisangle {0 1 0} 10

			if (nPoints == 1)
				rotCenter = new Point3f(points[0]);

			// point-centered rotation, but not internal -- "frieda"
			// rotate x 10 (atoms)
			// rotate x 10 $object
			// rotate x 10
			if (degrees == Float.MIN_VALUE)
				degrees = 10;
			viewer.rotateAxisAngleAtCenter(rotCenter, rotAxis, degrees, endDegrees, isSpin);
			return;
		}
		if (nPoints < 2) {
			// rotate MOLECULAR
			// rotate MOLECULAR (atom1)
			// rotate MOLECULAR x 10 (atom1)
			// rotate axisangle MOLECULAR (atom1)
			points[1].set(points[0]);
			points[1].sub(rotAxis);
		}
		else {
			// rotate 10 (atom1) (atom2)
			// rotate 10 {x y z} {x y z}
			// rotate 10 (atom1) {x y z}
		}

		if (points[0].distance(points[1]) == 0)
			rotationPointsIdentical();
		if (degrees == Float.MIN_VALUE)
			degrees = 10;
		viewer.rotateAboutPointsInternal(points[0], points[1], degrees, endDegrees, isSpin);
	}