/// <summary> /// Place ring with user provided angles. /// </summary> /// <param name="ring">the ring to place.</param> /// <param name="ringCenter">center coordinates of the ring.</param> /// <param name="bondLength">given bond length.</param> /// <param name="startAngles">a map with start angles when drawing the ring.</param> public virtual void PlaceRing(IRing ring, Vector2 ringCenter, double bondLength, IReadOnlyDictionary <int, double> startAngles) { var radius = GetNativeRingRadius(ring, bondLength); var addAngle = 2 * Math.PI / ring.RingSize; var startAtom = ring.Atoms[0]; Vector2 p = new Vector2((ringCenter.X + radius), ringCenter.Y); startAtom.Point2D = p; var startAngle = Math.PI * 0.5; // Different ring sizes get different start angles to have visually // correct placement int ringSize = ring.RingSize; startAngle = startAngles[ringSize]; var bonds = ring.GetConnectedBonds(startAtom); // Store all atoms to draw in consecutive order relative to the chosen bond. var atomsToDraw = new List <IAtom>(); IAtom currentAtom = startAtom; IBond currentBond = (IBond)bonds.First(); for (int i = 0; i < ring.Bonds.Count; i++) { currentBond = ring.GetNextBond(currentBond, currentAtom); currentAtom = currentBond.GetOther(currentAtom); atomsToDraw.Add(currentAtom); } AtomPlacer.PopulatePolygonCorners(atomsToDraw, ringCenter, startAngle, addAngle, radius); }
public virtual void TestGetNextBond_IBond_IAtom() { IRing ring = (IRing)NewChemObject(); IAtom c1 = ring.Builder.NewAtom("C"); IAtom c2 = ring.Builder.NewAtom("C"); IAtom c3 = ring.Builder.NewAtom("C"); IBond b1 = ring.Builder.NewBond(c1, c2, BondOrder.Single); IBond b2 = ring.Builder.NewBond(c3, c2, BondOrder.Single); IBond b3 = ring.Builder.NewBond(c1, c3, BondOrder.Single); ring.Atoms.Add(c1); ring.Atoms.Add(c2); ring.Atoms.Add(c3); ring.Bonds.Add(b1); ring.Bonds.Add(b2); ring.Bonds.Add(b3); Assert.AreEqual(b1, ring.GetNextBond(b2, c2)); Assert.AreEqual(b1, ring.GetNextBond(b3, c1)); Assert.AreEqual(b2, ring.GetNextBond(b1, c2)); Assert.AreEqual(b2, ring.GetNextBond(b3, c3)); Assert.AreEqual(b3, ring.GetNextBond(b1, c1)); Assert.AreEqual(b3, ring.GetNextBond(b2, c3)); }
/// <summary> /// Generated coordinates for a given ring, which is fused to another ring. /// The rings share exactly one bond. /// </summary> /// <param name="ring">The ring to be placed</param> /// <param name="sharedAtoms">The atoms of this ring, also members of another ring, which are already placed</param> /// <param name="ringCenterVector">A vector pointing the the center of the new ring</param> /// <param name="bondLength">The standard bondlength</param> public virtual void PlaceFusedRing(IRing ring, IAtomContainer sharedAtoms, Vector2 ringCenterVector, double bondLength) { Debug.WriteLine("RingPlacer.PlaceFusedRing() start"); IAtom beg = sharedAtoms.Atoms[0]; IAtom end = sharedAtoms.Atoms[1]; Vector2 pBeg = beg.Point2D.Value; Vector2 pEnd = end.Point2D.Value; // fuse the ring perpendicular to the bond, ring center is not // sub-optimal if non-regular/convex polygon (e.g. macro cycle) ringCenterVector = GetPerpendicular(pBeg, pEnd, ringCenterVector); double radius = GetNativeRingRadius(ring, bondLength); double newRingPerpendicular = Math.Sqrt(Math.Pow(radius, 2) - Math.Pow(bondLength / 2, 2)); ringCenterVector = Vector2.Normalize(ringCenterVector); Debug.WriteLine($"placeFusedRing->: ringCenterVector.Length {ringCenterVector.Length()}"); ringCenterVector *= newRingPerpendicular; Vector2 ringCenter = GetMidPoint(pBeg, pEnd); ringCenter += ringCenterVector; Vector2 originRingCenterVector = ringCenter; pBeg -= originRingCenterVector; pEnd -= originRingCenterVector; double occupiedAngle = Angle(pBeg, pEnd); double remainingAngle = (2 * Math.PI) - occupiedAngle; double addAngle = remainingAngle / (ring.RingSize - 1); Debug.WriteLine("placeFusedRing->occupiedAngle: " + Vectors.RadianToDegree(occupiedAngle)); Debug.WriteLine("placeFusedRing->remainingAngle: " + Vectors.RadianToDegree(remainingAngle)); Debug.WriteLine("placeFusedRing->addAngle: " + Vectors.RadianToDegree(addAngle)); IAtom startAtom; double centerX = ringCenter.X; double centerY = ringCenter.Y; double xDiff = beg.Point2D.Value.X - end.Point2D.Value.X; double yDiff = beg.Point2D.Value.Y - end.Point2D.Value.Y; double startAngle;; int direction = 1; // if bond is vertical if (xDiff == 0) { Debug.WriteLine("placeFusedRing->Bond is vertical"); //starts with the lower Atom if (beg.Point2D.Value.Y > end.Point2D.Value.Y) { startAtom = beg; } else { startAtom = end; } //changes the drawing direction if (centerX < beg.Point2D.Value.X) { direction = 1; } else { direction = -1; } } // if bond is not vertical else { //starts with the left Atom if (beg.Point2D.Value.X > end.Point2D.Value.X) { startAtom = beg; } else { startAtom = end; } //changes the drawing direction if (centerY - beg.Point2D.Value.Y > (centerX - beg.Point2D.Value.X) * yDiff / xDiff) { direction = 1; } else { direction = -1; } } startAngle = GeometryUtil.GetAngle(startAtom.Point2D.Value.X - ringCenter.X, startAtom.Point2D.Value.Y - ringCenter.Y); IAtom currentAtom = startAtom; // determine first bond in Ring // int k = 0; // for (k = 0; k < ring.GetElectronContainerCount(); k++) { // if (ring.GetElectronContainer(k) is IBond) break; // } IBond currentBond = sharedAtoms.Bonds[0]; var atomsToDraw = new List <IAtom>(); for (int i = 0; i < ring.Bonds.Count - 2; i++) { currentBond = ring.GetNextBond(currentBond, currentAtom); currentAtom = currentBond.GetOther(currentAtom); atomsToDraw.Add(currentAtom); } addAngle = addAngle * direction; #if DEBUG Debug.WriteLine("placeFusedRing->startAngle: " + Vectors.RadianToDegree(startAngle)); Debug.WriteLine("placeFusedRing->addAngle: " + Vectors.RadianToDegree(addAngle)); Debug.WriteLine("placeFusedRing->startAtom is: " + (Molecule.Atoms.IndexOf(startAtom) + 1)); Debug.WriteLine("AtomsToDraw: " + AtomPlacer.ListNumbers(Molecule, atomsToDraw)); #endif AtomPlacer.PopulatePolygonCorners(atomsToDraw, ringCenter, startAngle, addAngle, radius); }
/// <summary> /// Generated coordinates for a given ring, which is connected to a spiro ring. /// The rings share exactly one atom. /// </summary> /// <param name="ring">The ring to be placed</param> /// <param name="sharedAtoms">The atoms of this ring, also members of another ring, which are already placed</param> /// <param name="sharedAtomsCenter">The geometric center of these atoms</param> /// <param name="ringCenterVector">A vector pointing the center of the new ring</param> /// <param name="bondLength">The standard bond length</param> public virtual void PlaceSpiroRing(IRing ring, IAtomContainer sharedAtoms, Vector2 sharedAtomsCenter, Vector2 ringCenterVector, double bondLength) { var startAtom = sharedAtoms.Atoms[0]; var mBonds = Molecule.GetConnectedBonds(sharedAtoms.Atoms[0]).ToReadOnlyList(); var degree = mBonds.Count; Debug.WriteLine($"placeSpiroRing: D={degree}"); // recalculate the ringCentreVector if (degree != 4) { int numPlaced = 0; foreach (var bond in mBonds) { var nbr = bond.GetOther(sharedAtoms.Atoms[0]); if (!nbr.IsPlaced) { continue; } numPlaced++; } if (numPlaced == 2) { // nudge the shared atom such that bond lengths will be // equal startAtom.Point2D += ringCenterVector; sharedAtomsCenter += ringCenterVector; } var theta = Math.PI - (2 * Math.PI / (degree / 2)); Rotate(ringCenterVector, theta); } var radius = GetNativeRingRadius(ring, bondLength); var ringCenter = sharedAtomsCenter; if (degree == 4) { ringCenterVector = Vector2.Normalize(ringCenterVector); ringCenterVector *= radius; } else { // spread things out a little for multiple spiro centres ringCenterVector = Vector2.Normalize(ringCenterVector); ringCenterVector *= (2 * radius); } ringCenter += ringCenterVector; var addAngle = 2 * Math.PI / ring.RingSize; var currentAtom = startAtom; var startAngle = GeometryUtil.GetAngle( startAtom.Point2D.Value.X - ringCenter.X, startAtom.Point2D.Value.Y - ringCenter.Y); // Get one bond connected to the spiro bridge atom. It doesn't matter in which direction we draw. var rBonds = ring.GetConnectedBonds(startAtom); var currentBond = rBonds.First(); var atomsToDraw = new List <IAtom>(); // Store all atoms to draw in consequtive order relative to the chosen bond. for (int i = 0; i < ring.Bonds.Count; i++) { currentBond = ring.GetNextBond(currentBond, currentAtom); currentAtom = currentBond.GetOther(currentAtom); if (!currentAtom.Equals(startAtom)) { atomsToDraw.Add(currentAtom); } } Debug.WriteLine($"currentAtom {currentAtom}"); Debug.WriteLine($"startAtom {startAtom}"); AtomPlacer.PopulatePolygonCorners(atomsToDraw, ringCenter, startAngle, addAngle, radius); }
private void PlaceBridgedRing(IRing ring, IAtomContainer sharedAtoms, Vector2 sharedAtomsCenter, Vector2 ringCenterVector, double bondLength) { IAtom[] bridgeAtoms = GetBridgeAtoms(sharedAtoms); IAtom bondAtom1 = bridgeAtoms[0]; IAtom bondAtom2 = bridgeAtoms[1]; List <IAtom> otherAtoms = new List <IAtom>(); foreach (IAtom atom in sharedAtoms.Atoms) { if (atom != bondAtom1 && atom != bondAtom2) { otherAtoms.Add(atom); } } bool snap = ring.GetProperty <bool>(SnapHint, false); bool swap = snap ? Det(bondAtom1.Point2D.Value, GeometryUtil.Get2DCenter(otherAtoms), bondAtom2.Point2D.Value) < 0 : Det(bondAtom1.Point2D.Value, GeometryUtil.Get2DCenter(otherAtoms), bondAtom2.Point2D.Value) > 0; if (swap) { IAtom tmp = bondAtom1; bondAtom1 = bondAtom2; bondAtom2 = tmp; } Vector2 bondAtom1Vector = bondAtom1.Point2D.Value; Vector2 bondAtom2Vector = bondAtom2.Point2D.Value; Vector2 midPoint = GetMidPoint(bondAtom1Vector, bondAtom2Vector); Vector2 ringCenter; double radius = GetNativeRingRadius(ring, bondLength); double offset = 0; if (snap) { ringCenter = midPoint; ringCenterVector = GetPerpendicular(bondAtom1Vector, bondAtom2Vector, new Vector2(midPoint.X - sharedAtomsCenter.X, midPoint.Y - sharedAtomsCenter.Y)); offset = 0; foreach (IAtom atom in otherAtoms) { double dist = Vector2.Distance(atom.Point2D.Value, midPoint); if (dist > offset) { offset = dist; } } } else { ringCenter = sharedAtomsCenter; } Vector2.Normalize(ringCenterVector); ringCenterVector = ringCenterVector * (radius - offset); ringCenter += ringCenterVector; bondAtom1Vector -= ringCenter; bondAtom2Vector -= ringCenter; int numUnplaced = ring.RingSize - sharedAtoms.Atoms.Count; double dot = bondAtom2Vector.X * bondAtom1Vector.X + bondAtom2Vector.Y * bondAtom1Vector.Y; double det = bondAtom2Vector.X * bondAtom1Vector.Y - bondAtom2Vector.Y * bondAtom1Vector.X; // theta remain/step double tRemain = Math.Atan2(det, dot); if (tRemain < 0) { tRemain = Math.PI + (Math.PI + tRemain); } double tStep = tRemain / (numUnplaced + 1); Debug.WriteLine($"placeBridgedRing->tRemain: {Vectors.RadianToDegree(tRemain)}"); Debug.WriteLine($"placeBridgedRing->tStep: {Vectors.RadianToDegree(tStep)}"); double startAngle; startAngle = GeometryUtil.GetAngle(bondAtom1.Point2D.Value.X - ringCenter.X, bondAtom1.Point2D.Value.Y - ringCenter.Y); IAtom currentAtom = bondAtom1; IBond currentBond = sharedAtoms.GetConnectedBonds(currentAtom).First(); List <IAtom> atoms = new List <IAtom>(); for (int i = 0; i < ring.Bonds.Count; i++) { currentBond = ring.GetNextBond(currentBond, currentAtom); currentAtom = currentBond.GetOther(currentAtom); if (!sharedAtoms.Contains(currentAtom)) { atoms.Add(currentAtom); } } #if DEBUG Debug.WriteLine($"{nameof(PlaceBridgedRing)}->atomsToPlace: {AtomPlacer.ListNumbers(Molecule, atoms)}"); Debug.WriteLine($"{nameof(PlaceBridgedRing)}->startAngle: {Vectors.RadianToDegree(startAngle)}"); Debug.WriteLine($"{nameof(PlaceBridgedRing)}->tStep: {Vectors.RadianToDegree(tStep)}"); #endif AtomPlacer.PopulatePolygonCorners(atoms, ringCenter, startAngle, -tStep, radius); }