/// <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); }
/// <summary> /// Layout all rings in the given RingSet that are connected to a given Ring /// </summary> /// <param name="rs">The RingSet to be searched for rings connected to Ring</param> /// <param name="ring">The Ring for which all connected rings in RingSet are to be layed out.</param> public void PlaceConnectedRings(IRingSet rs, IRing ring, int handleType, double bondLength) { var connectedRings = rs.GetConnectedRings(ring); // Debug.WriteLine(rs.ReportRingList(Molecule)); foreach (var container in connectedRings) { IRing connectedRing = (IRing)container; if (!connectedRing.IsPlaced) { // Debug.WriteLine(ring.ToString(Molecule)); // Debug.WriteLine(connectedRing.ToString(Molecule)); IAtomContainer sharedAtoms = AtomContainerManipulator.GetIntersection(ring, connectedRing); int numSharedAtoms = sharedAtoms.Atoms.Count; Debug.WriteLine("placeConnectedRings-> connectedRing: " + (ring.ToString())); if ((numSharedAtoms == 2 && handleType == Fused) || (numSharedAtoms == 1 && handleType == Spiro) || (numSharedAtoms > 2 && handleType == Bridged)) { Vector2 sharedAtomsCenter = GeometryUtil.Get2DCenter(sharedAtoms); Vector2 oldRingCenter = GeometryUtil.Get2DCenter(ring); Vector2 tempVector = sharedAtomsCenter; Vector2 newRingCenterVector = tempVector; newRingCenterVector -= oldRingCenter; // zero (or v. small ring center) if (Math.Abs(newRingCenterVector.X) < 0.001 && Math.Abs(newRingCenterVector.Y) < 0.001) { // first see if we can use terminal bonds IAtomContainer terminalOnly = Molecule.Builder.NewAtomContainer(); foreach (IAtom atom in ring.Atoms) { if (ring.GetConnectedBonds(atom).Count() == 1) { terminalOnly.Atoms.Add(atom); } } if (terminalOnly.Atoms.Count == 2) { newRingCenterVector = GeometryUtil.Get2DCenter(terminalOnly); newRingCenterVector = oldRingCenter; connectedRing.SetProperty(RingPlacer.SnapHint, true); } else { // project coordinates on 12 axis (30 degree snaps) and choose one with most spread Vector2 vec = new Vector2(0, 1); double bestLen = -double.MaxValue; for (int i = 0; i < 12; i++) { Vector2 orth = new Vector2(-vec.Y, vec.X); orth = Vector2.Normalize(orth); double min = double.MaxValue, max = -double.MaxValue; foreach (IAtom atom in sharedAtoms.Atoms) { // s: scalar projection double s = Vector2.Dot(orth, atom.Point2D.Value); if (s < min) { min = s; } if (s > max) { max = s; } } double len = max - min; if (len > bestLen) { bestLen = len; newRingCenterVector = vec; } Rotate(vec, RAD_30); } } } Vector2 oldRingCenterVector = newRingCenterVector; Debug.WriteLine($"placeConnectedRing -> tempVector: {tempVector}, tempVector.Length: {tempVector.Length()}"); Debug.WriteLine($"placeConnectedRing -> bondCenter: {sharedAtomsCenter}"); Debug.WriteLine($"placeConnectedRing -> oldRingCenterVector.Length: {oldRingCenterVector.Length()}"); Debug.WriteLine($"placeConnectedRing -> newRingCenterVector.Length: {newRingCenterVector.Length()}"); Vector2 tempPoint = sharedAtomsCenter; tempPoint += newRingCenterVector; PlaceRing(connectedRing, sharedAtoms, sharedAtomsCenter, newRingCenterVector, bondLength); connectedRing.IsPlaced = true; PlaceConnectedRings(rs, connectedRing, handleType, bondLength); } } } }
/// <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); }