Beispiel #1
0
 /// <summary>
 ///Splits the angle between a secondary bond and any neighbouring bonds.
 ///This is used in special cases where we can't get a meaningful centroid
 /// </summary>
 /// <param name="secondaryStart">Start of the double bond's secondary: to be clipped</param>
 /// <param name="secondaryEnd">End of the double bond's secondary</param>
 /// <param name="atomPosList">Positions of atoms attached to the primary bond's atom</param>
 /// <param name="primaryAtomPos">Position of the primary bond's atom</param>
 private static void SplitBondAngles(ref Point secondaryStart, Point secondaryEnd, List <Point> atomPosList, Point primaryAtomPos)
 {
     foreach (Point neighbourPos in atomPosList)
     {
         Point?intersection =
             BasicGeometry.LineSegmentsIntersect(secondaryStart, secondaryEnd, primaryAtomPos, neighbourPos);
         if (intersection != null)
         {
             //need to shorten the line again
             Vector splitVector     = neighbourPos - primaryAtomPos;
             Vector secondaryVector = secondaryEnd - secondaryStart;
             double splitAngle      = Vector.AngleBetween(secondaryVector, splitVector) / 2;
             Matrix rotator         = new Matrix();
             rotator.Rotate(-splitAngle);
             splitVector = splitVector * rotator;
             Point?temp = BasicGeometry.LineSegmentsIntersect(secondaryStart, secondaryEnd, primaryAtomPos,
                                                              primaryAtomPos + splitVector);
             if (temp != null)
             {
                 secondaryStart = temp.Value;
             }
             break;
         }
     }
 }
Beispiel #2
0
        /// <summary>
        /// Are the atoms cis to the double bond?
        /// </summary>
        /// <param name="atomA"></param>
        /// <param name="atomB"></param>
        /// <returns></returns>
        public bool AtomsAreCis(Atom atomA, Atom atomB)
        {
            //do an assert to make sure that we're not calling this routine with atoms detached from the bond atoms
            Debug.Assert(StartAtom.Neighbours.Contains(atomA) & EndAtom.Neighbours.Contains(atomB) ||
                         StartAtom.Neighbours.Contains(atomB) & EndAtom.Neighbours.Contains(atomA));

            // Note: Add null checks as this has been found to be blowing up
            if (atomA != null && atomB != null &&
                StartAtom.Neighbours != null & EndAtom.Neighbours != null &&
                StartAtom.Neighbours.Count > 0 && EndAtom.Neighbours.Count > 0)
            {
                if (StartAtom.Neighbours.Contains(atomA))
                {
                    //draw two lines from the end atom to atom a and start atom to atom b and see if they intersect
                    return(BasicGeometry.LineSegmentsIntersect(EndAtom.Position, atomA.Position,
                                                               StartAtom.Position, atomB.Position) != null);
                }
                else
                {
                    //draw the lines the other way around
                    return(BasicGeometry.LineSegmentsIntersect(EndAtom.Position, atomB.Position,
                                                               StartAtom.Position, atomA.Position) != null);
                }
            }
            else
            {
                return(false);
            }
        }
Beispiel #3
0
        private static ClockDirections GetGeneralDir(Vector bondVector)
        {
            double bondAngle = Vector.AngleBetween(BasicGeometry.ScreenNorth, bondVector);

            ClockDirections hour = (ClockDirections)BasicGeometry.SnapToClock(bondAngle);

            return(hour);
        }
Beispiel #4
0
        /// <summary>
        ///     Defines a double bond
        /// </summary>
        /// <param name="descriptor">DoubleBondDescriptor which is populated</param>
        /// <param name="standardBondLength">Standard bond length as defined by the model</param>
        /// <returns></returns>
        public static void GetDoubleBondPoints(DoubleBondLayout descriptor, double standardBondLength)
        {
            Point?point3a;
            Point?point4a;

            //use a struct here to return the values
            GetDefaultDoubleBondPoints(descriptor, standardBondLength);

            if (descriptor.PrimaryCentroid != null)
            //now, if there is a centroid defined, the bond is part of a ring
            {
                Point?workingCentroid = null;
                //work out whether the bond is place inside or outside the ring
                var bondvector   = descriptor.PrincipleVector;
                var centreVector = descriptor.PrimaryCentroid - descriptor.Start;

                var computedPlacement = (BondDirection)Math.Sign(Vector.CrossProduct(centreVector.Value, bondvector));

                if (descriptor.Placement != BondDirection.None)
                {
                    if (computedPlacement == descriptor.Placement) //then we have nothing to worry about
                    {
                        workingCentroid = descriptor.PrimaryCentroid;
                    }
                    else //we need to adjust the points according to the other centroid
                    {
                        workingCentroid = descriptor.SecondaryCentroid;
                    }
                }

                if (workingCentroid != null)

                {
                    //shorten the secondto fit neatly within the ring
                    point3a = BasicGeometry.LineSegmentsIntersect(descriptor.Start, workingCentroid.Value,
                                                                  descriptor.SecondaryStart,
                                                                  descriptor.SecondaryEnd);
                    point4a = BasicGeometry.LineSegmentsIntersect(descriptor.End, workingCentroid.Value,
                                                                  descriptor.SecondaryStart,
                                                                  descriptor.SecondaryEnd);
                    var tempPoint3 = point3a ?? descriptor.SecondaryStart;
                    var tempPoint4 = descriptor.SecondaryEnd = point4a ?? descriptor.SecondaryEnd;

                    descriptor.SecondaryStart = tempPoint3;
                    descriptor.SecondaryEnd   = tempPoint4;
                }
                //get the boundary for hit testing purposes
                descriptor.Boundary.Clear();
                descriptor.Boundary.AddRange(new[]
                {
                    descriptor.Start, descriptor.End, descriptor.SecondaryEnd,
                    descriptor.SecondaryStart
                });
            }
        }
Beispiel #5
0
        private void ShowHull(List <Point> points, DrawingContext drawingContext)
        {
            var path = BasicGeometry.BuildPath(points);

            // Diag: Show the Hull or it's Points
#if SHOWHULLS
            drawingContext.DrawGeometry(null, new Pen(new SolidColorBrush(Colors.GreenYellow), 0.01), path.Data);
            //ShowPoints(Hull, drawingContext);
#endif
            // End Diag
        }
Beispiel #6
0
        public CompassPoints GetDefaultHOrientation()
        {
            var orientation = CompassPoints.East;

            if (ImplicitHydrogenCount >= 1 && Bonds.Any())
            {
                double angleFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth, BalancingVector(true));
                orientation = Bonds.Count() == 1 ? BasicGeometry.SnapTo2EW(angleFromNorth) : BasicGeometry.SnapTo4NESW(angleFromNorth);
            }

            return(orientation);
        }
Beispiel #7
0
 /// <summary>
 /// Returns the intersection point of a line with the Convex Hull
 /// </summary>
 /// <param name="start">Start point of line</param>
 /// <param name="end">End point of line</param>
 /// <returns>Point? defining the crossing point</returns>
 public Point?GetIntersection(Point start, Point end)
 {
     for (int i = 0; i < Hull.Count; i++)
     {
         Point?p;
         if ((p = BasicGeometry.LineSegmentsIntersect(start, end, Hull[i], Hull[(i + 1) % Hull.Count])) != null)
         {
             return(p);
         }
     }
     return(null);
 }
Beispiel #8
0
 private static Point?GetIntersection(Point start, Point end, List <Point> atomHull)
 {
     for (int i = 0; i < atomHull.Count; i++)
     {
         Point?p;
         if ((p = BasicGeometry.LineSegmentsIntersect(start, end, atomHull[i], atomHull[(i + 1) % atomHull.Count])) != null)
         {
             return(p);
         }
     }
     return(null);
 }
Beispiel #9
0
        private Atom GetCisLigand(Atom startLigand, List <Atom> endLigands, Atom startAtom, Atom endAtom)
        {
            //assume there are two endLigands

            if (BasicGeometry.LineSegmentsIntersect(startLigand.Position, endAtom.Position, endLigands[0].Position,
                                                    startAtom.Position) != null)
            {
                return(endLigands[0]);
            }
            else if (BasicGeometry.LineSegmentsIntersect(startLigand.Position, endAtom.Position, endLigands[1].Position, startAtom.Position) != null)

            {
                return(endLigands[1]);
            }
            return(null);
        }
Beispiel #10
0
        /// <summary>
        /// Copies this feature, creating an independant, but identical feature.
        /// </summary>
        /// <returns></returns>
        public Feature Copy()
        {
            Feature clone = (Feature)MemberwiseClone();

            clone.BasicGeometry = BasicGeometry.Copy();
            clone.Envelope      = Envelope.Copy();
            DataTable table = ParentFeatureSet.DataTable;

            clone._dataRow = table.NewRow();
            if (DataRow != null)
            {
                for (int i = 0; i < ParentFeatureSet.DataTable.Columns.Count; i++)
                {
                    clone._dataRow[i] = DataRow[i];
                }
            }
            return(clone);
        }
Beispiel #11
0
        public bool AtomsAreCis(Atom atomA, Atom atomB)
        {
            // Note: Add null checks as this has been found to be blowing up
            if (atomA != null && atomB != null &&
                StartAtom.Neighbours != null & EndAtom.Neighbours != null &&
                StartAtom.Neighbours.Count() > 0 && EndAtom.Neighbours.Count() > 0)
            {
                if (StartAtom.Neighbours.Contains(atomA))
                {
                    //draw two lines from the end atom to atom a and start atom to atom b and see if they intersect
                    return(BasicGeometry.LineSegmentsIntersect(EndAtom.Position, atomA.Position,
                                                               StartAtom.Position, atomB.Position) != null);
                }

                //draw the lines the other way around
                return(BasicGeometry.LineSegmentsIntersect(EndAtom.Position, atomB.Position,
                                                           StartAtom.Position, atomA.Position) != null);
            }

            return(false);
        }
Beispiel #12
0
        /// <summary>
        /// Common to both edge and hatch bonds.  The filling of this shape
        /// is done purely in XAML through styles
        /// </summary>
        /// <param name="startPoint">Point object where the bond starts</param>
        /// <param name="angle">The angle from ScreenNorth:  clockwise +ve, anticlockwise -ve</param>
        /// <param name="bondlength">How long the bond is in pixels</param>
        /// <returns></returns>
        public static System.Windows.Media.Geometry WedgeBondGeometry(Point startPoint, double angle, double bondlength)
        {
            //List<PathSegment> wedgesegments = new List<PathSegment>(4);

            //get a right sized vector first
            Vector bondVector = BasicGeometry.ScreenNorth();

            bondVector.Normalize();
            bondVector = bondVector * bondlength;

            //then rotate it to the proper angle
            Matrix rotator = new Matrix();

            rotator.Rotate(angle);
            bondVector = bondVector * rotator;

            //then work out the points at the thick end of the wedge
            var perpVector = bondVector.Perpendicular();

            perpVector.Normalize();
            perpVector = perpVector * Globals.WedgeWidth / 2;

            Point point2 = startPoint + bondVector + perpVector;
            Point point3 = startPoint + bondVector - perpVector;
            //and draw it
            StreamGeometry sg = new StreamGeometry();

            using (StreamGeometryContext sgc = sg.Open())
            {
                sgc.BeginFigure(startPoint, true, true);
                sgc.LineTo(point2, true, true);
                sgc.LineTo(point3, true, true);
                sgc.Close();
            }
            sg.Freeze();

            return(sg);
        }
Beispiel #13
0
        public CompassPoints GetDefaultHOrientation()
        {
            if (ImplicitHydrogenCount >= 1)
            {
                if (!Bonds.Any())
                {
                    return(CompassPoints.East);
                }
                else if (Bonds.Count() == 1)
                {
                    var angle = Vector.AngleBetween(BasicGeometry.ScreenNorth,
                                                    Bonds.First().OtherAtom(this).Position - Position);
                    int clockDirection = BasicGeometry.SnapToClock(angle);

                    if (clockDirection == 0 || clockDirection == 6)
                    {
                        return(CompassPoints.East);
                    }
                    else if (clockDirection >= 6 && clockDirection <= 11)
                    {
                        return(CompassPoints.East);
                    }
                    else
                    {
                        return(CompassPoints.West);
                    }
                }
                else
                {
                    double baFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth,
                                                             BalancingVector(true));

                    return(BasicGeometry.SnapTo4NESW(baFromNorth));
                }
            }
            return(CompassPoints.East);
        }
Beispiel #14
0
        /// <summary>
        /// tells you where to put a new atom
        /// </summary>
        /// <param name="lastAtomVisual"></param>
        /// <param name="congestedPositions">Places to avoid dumping the new atom</param>
        /// <returns></returns>
        private (Point NewPos, ClockDirections sproutDir) GetNewChainEndPos(AtomVisual lastAtomVisual)
        {
            ClockDirections GetGeneralDir(Vector bondVector)
            {
                double bondAngle = Vector.AngleBetween(BasicGeometry.ScreenNorth, bondVector);

                ClockDirections hour = (ClockDirections)BasicGeometry.SnapToClock(bondAngle);

                return(hour);
            }

            var    lastAtom = lastAtomVisual.ParentAtom;
            Vector newDirection;

            ClockDirections newTag;

            if (lastAtom.Degree == 0) //isolated atom
            {
                newDirection = ClockDirections.II.ToVector() * EditViewModel.Model.XamlBondLength;
                newTag       = ClockDirections.II;
            }
            else if (lastAtom.Degree == 1)
            {
                Vector bondVector = lastAtom.Position - lastAtom.Neighbours.First().Position;

                var hour = GetGeneralDir(bondVector);

                if (VirginAtom(lastAtom)) //it hasn't yet sprouted
                {
                    //Tag is used to store the direction the atom sprouted from its previous atom
                    newTag       = GetNewSproutDirection(hour);
                    newDirection = newTag.ToVector() * EditViewModel.Model.XamlBondLength;
                }
                else //it has sprouted, so where to put the new branch?
                {
                    var vecA = ((ClockDirections)lastAtom.Tag).ToVector();
                    vecA.Normalize();
                    var vecB = -bondVector;
                    vecB.Normalize();

                    var balancingVector = -(vecA + vecB);
                    balancingVector.Normalize();
                    newTag       = GetGeneralDir(balancingVector);
                    newDirection = balancingVector * EditViewModel.Model.XamlBondLength;
                }
            }
            else if (lastAtom.Degree == 2)
            {
                var balancingVector = lastAtom.BalancingVector();
                balancingVector.Normalize();
                newDirection = balancingVector * EditViewModel.Model.XamlBondLength;
                newTag       = GetGeneralDir(balancingVector);
            }
            else //lastAtom.Degree >= 2:  could get congested
            {
                FindOpenSpace(lastAtom, EditViewModel.Model.XamlBondLength, out newDirection);
                newTag = GetGeneralDir(newDirection);
            }

            return(newDirection + lastAtom.Position, newTag);
        }
Beispiel #15
0
        /// <summary>
        /// Defines the 4 points that characterise a double bond and returns a list of them in polygon order
        /// </summary>
        /// <param name="startPoint"></param>
        /// <param name="endPoint"></param>
        /// <param name="doubleBondPlacement"></param>
        /// <param name="ringCentroid"></param>
        /// <param name="point1"></param>
        /// <param name="point2"></param>
        /// <param name="point3"></param>
        /// <param name="point4"></param>
        /// <returns></returns>
        public static List <Point> GetDoubleBondPoints(Point startPoint, Point endPoint, BondDirection doubleBondPlacement,
                                                       Point?ringCentroid, out Point point1, out Point point2, out Point point3, out Point point4)
        {
            List <Point> enclosingPoly;
            Vector       v      = endPoint - startPoint;
            Vector       normal = v.Perpendicular();

            normal.Normalize();

            Point?point3a, point4a;

            double distance = Globals.Offset;

            if (ringCentroid == null)
            {
                switch (doubleBondPlacement)
                {
                case BondDirection.None:

                    point1 = startPoint + normal * distance;
                    point2 = point1 + v;

                    point3 = startPoint - normal * distance;
                    point4 = point3 + v;

                    break;

                case BondDirection.Clockwise:
                {
                    point1 = startPoint;

                    point2 = endPoint;
                    point3 = startPoint - normal * 2 * distance;
                    point4 = point3 + v;

                    break;
                }

                case BondDirection.Anticlockwise:
                    point1 = startPoint;
                    point2 = endPoint;
                    point3 = startPoint + normal * 2 * distance;
                    point4 = point3 + v;
                    break;

                default:

                    point1 = startPoint + normal * distance;
                    point2 = point1 + v;

                    point3 = startPoint - normal * distance;
                    point4 = point3 + v;
                    break;
                }
            }
            else
            {
                point1 = startPoint;
                point2 = endPoint;

                var bondvector    = endPoint - startPoint;
                var centreVector  = ringCentroid - startPoint;
                var bondPlacement = (BondDirection)Math.Sign(Vector.CrossProduct(centreVector.Value, bondvector));
                if (bondPlacement == BondDirection.Clockwise)
                {
                    point3 = startPoint - normal * 2 * distance;
                    point4 = point3 + v;
                }
                else
                {
                    point3 = startPoint + normal * 2 * distance;
                    point4 = point3 + v;
                }

                point3a = BasicGeometry.LineSegmentsIntersect(startPoint, ringCentroid.Value, point3, point4);

                var tempPoint3 = point3a ?? point3;

                point4a = BasicGeometry.LineSegmentsIntersect(endPoint, ringCentroid.Value, point3, point4);

                var tempPoint4 = point4 = point4a ?? point4;

                point3 = tempPoint3;
                point4 = tempPoint4;
            }
            //capture  the enclosing polygon for hit testing later

            enclosingPoly = new List <Point>()
            {
                point1, point2, point4, point3
            };

            //shorten the supporting bond if it's a ring bond
            if (ringCentroid != null)
            {
            }
            return(enclosingPoly);
        }
Beispiel #16
0
        /// <summary>
        /// Draws any implicit hydrogens in the right place
        /// </summary>
        /// <param name="parentAtom">Owner of the hydrogens</param>
        /// <param name="nextPos">Where the insertion point ends up</param>
        /// <param name="symbolGeometry">Pre-drawn geometry of the atomic symbol text</param>
        /// <param name="nextSymbolPadding">How much space to insert between symbols</param>
        /// <returns>geometry of the symbol text plus hydrogens and any subscripts</returns>
        private System.Windows.Media.Geometry DrawHydrogens(Atom parentAtom, Point nextPos, System.Windows.Media.Geometry symbolGeometry, double nextSymbolPadding)
        {
            if (parentAtom.ImplicitHydrogenCount >= 1)
            {
                System.Windows.Media.Geometry hGeometry;
                if (parentAtom.Bonds.Count == 0)
                {
                    hGeometry           = DrawHEast(parentAtom, nextPos);
                    DefaultHOrientation = CompassPoints.East;
                }
                else if (parentAtom.Bonds.Count == 1)
                {
                    if (Vector.AngleBetween(BasicGeometry.ScreenNorth(),
                                            parentAtom.Bonds[0].OtherAtom(parentAtom).Position - parentAtom.Position) > 0)
                    //the bond is on the right
                    {
                        hGeometry           = DrawHWest(parentAtom, nextPos, symbolGeometry, nextSymbolPadding);
                        DefaultHOrientation = CompassPoints.West;
                    }
                    else
                    {
                        //default to any old rubbish for now
                        DefaultHOrientation = CompassPoints.East;
                        hGeometry           = DrawHEast(parentAtom, nextPos);
                    }
                }
                else
                {
                    double baFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth(),
                                                             parentAtom.BalancingVector());

                    switch (BasicGeometry.SnapTo4NESW(baFromNorth))
                    {
                    case CompassPoints.East:
                        hGeometry           = DrawHEast(parentAtom, nextPos);
                        DefaultHOrientation = CompassPoints.East;
                        break;

                    case CompassPoints.North:
                        hGeometry           = DrawHNorth(parentAtom, nextPos, symbolGeometry, nextSymbolPadding);
                        DefaultHOrientation = CompassPoints.North;
                        break;

                    case CompassPoints.South:
                        hGeometry           = DrawHSouth(parentAtom, nextPos, symbolGeometry, nextSymbolPadding);
                        DefaultHOrientation = CompassPoints.South;
                        break;

                    case CompassPoints.West:
                        hGeometry           = DrawHWest(parentAtom, nextPos, symbolGeometry, nextSymbolPadding);
                        DefaultHOrientation = CompassPoints.West;
                        break;

                    default:
                        hGeometry           = DrawHEast(parentAtom, nextPos);
                        DefaultHOrientation = CompassPoints.East;
                        break;
                    }
                }
                symbolGeometry = new CombinedGeometry(symbolGeometry, hGeometry);
            }
            return(symbolGeometry);
        }
Beispiel #17
0
 private void SetCentroid()
 {
     _centroid      = BasicGeometry.GetCentroid(CurrentEditor.GetMoleculeBoundingBox(AdornedMolecules));
     _rotateSnapper = new Snapper(_centroid, CurrentEditor.ViewModel as EditViewModel);
 }
Beispiel #18
0
 // gets signed angle between three points
 /// direction is anticlockwise
 /// example:
 /// GetAngle(new Point2(1,0), new Point2(0,0), new Point2(0,1)) => Math.PI/2
 /// GetAngle(new Point2(-1,0), new Point2(0,0), new Point2(0,1)) => -Math.PI/2
 /// GetAngle(new Point2(0,1), new Point2(0,0), new Point2(1,0)) => -Math.PI/2
 public static double?GetAngle(Atom atom0, Atom atom1, Atom atom2)
 {
     return(BasicGeometry.GetAngle(atom0.Position, atom1.Position, atom2.Position, 0.0001));
 }
        public void CreateElementCharacters(Atom atom, Options options)
        {
            string module = $"{_product}.{_class}.{MethodBase.GetCurrentMethod().Name}()";

            //Point atomCentre = new Point((double)atom.X2, (double)atom.Y2);
            string atomLabel = atom.Element.Symbol;
            Rect   labelBounds;

            // Get Charge and Isotope values for use later on
            int iCharge    = atom.FormalCharge ?? 0;
            int iAbsCharge = Math.Abs(iCharge);
            int isoValue   = atom.IsotopeNumber ?? 0;

            // Get Implicit Hydrogen Count for use later on
            int implicitHCount = atom.ImplicitHydrogenCount;

            Point cursorPosition        = atom.Position;
            Point chargeCursorPosition  = atom.Position;
            Point isotopeCursorPosition = atom.Position;

            double lastOffset = 0;

            int bondCount = atom.Bonds.ToList().Count;

            #region Decide if atom label is to be displayed

            bool showLabel = true;
            if (atomLabel == "C")
            {
                if (atom.ShowSymbol.HasValue)
                {
                    showLabel = atom.ShowSymbol.Value;
                }
                else
                {
                    if (atom.IsInRing || bondCount > 1)
                    {
                        showLabel = false;
                    }

                    if (bondCount == 2)
                    {
                        Point p1 = atom.Bonds.ToList()[0].OtherAtom(atom).Position;
                        Point p2 = atom.Bonds.ToList()[1].OtherAtom(atom).Position;

                        double angle1 = Vector.AngleBetween(-(atom.Position - p1), atom.Position - p2);

                        if (Math.Abs(angle1) < 8)
                        {
                            showLabel = true;
                        }
                    }
                }

                // Force on if atom has charge
                if (iAbsCharge > 0)
                {
                    showLabel = true;
                }
                // Force on if atom has isotope value
                if (isoValue > 0)
                {
                    showLabel = true;
                }
            }

            #endregion Decide if atom label is to be displayed

            if (showLabel)
            {
                #region Set Up Atom Colours

                string atomColour = "000000";
                if (options.ColouredAtoms)
                {
                    if (atom.Element.Colour != null)
                    {
                        atomColour = atom.Element.Colour;
                        // Strip out # as OoXml does not use it
                        atomColour = atomColour.Replace("#", "");
                    }
                }

                #endregion Set Up Atom Colours

                #region Step 1 - Measure Bounding Box for all Characters of label

                double xMin = double.MaxValue;
                double yMin = double.MaxValue;
                double xMax = double.MinValue;
                double yMax = double.MinValue;

                Point thisCharacterPosition;
                for (int idx = 0; idx < atomLabel.Length; idx++)
                {
                    char         chr = atomLabel[idx];
                    TtfCharacter c   = _TtfCharacterSet[chr];
                    if (c != null)
                    {
                        thisCharacterPosition = GetCharacterPosition(cursorPosition, c);

                        xMin = Math.Min(xMin, thisCharacterPosition.X);
                        yMin = Math.Min(yMin, thisCharacterPosition.Y);
                        xMax = Math.Max(xMax, thisCharacterPosition.X + OoXmlHelper.ScaleCsTtfToCml(c.Width, _meanBondLength));
                        yMax = Math.Max(yMax, thisCharacterPosition.Y + OoXmlHelper.ScaleCsTtfToCml(c.Height, _meanBondLength));

                        if (idx < atomLabel.Length - 1)
                        {
                            // Move to next Character position
                            cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX, _meanBondLength), 0);
                        }
                    }
                }

                #endregion Step 1 - Measure Bounding Box for all Characters of label

                #region Step 2 - Reset Cursor such that the text is centered about the atom's co-ordinates

                double width  = xMax - xMin;
                double height = yMax - yMin;
                cursorPosition        = new Point(atom.Position.X - width / 2, atom.Position.Y + height / 2);
                chargeCursorPosition  = new Point(cursorPosition.X, cursorPosition.Y);
                isotopeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                labelBounds           = new Rect(cursorPosition, new Size(width, height));

                #endregion Step 2 - Reset Cursor such that the text is centered about the atom's co-ordinates

                #region Step 3 - Place the characters

                foreach (char chr in atomLabel)
                {
                    TtfCharacter c = _TtfCharacterSet[chr];
                    if (c != null)
                    {
                        thisCharacterPosition = GetCharacterPosition(cursorPosition, c);
                        AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, c, atomColour, chr, atom.Path, atom.Parent.Path);
                        _AtomLabelCharacters.Add(alc);

                        // Move to next Character position
                        lastOffset = OoXmlHelper.ScaleCsTtfToCml(c.IncrementX, _meanBondLength);
                        cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX, _meanBondLength), 0);
                        chargeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                    }
                }

                #endregion Step 3 - Place the characters

                #region Determine NESW

                double        baFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth, atom.BalancingVector(true));
                CompassPoints nesw        = CompassPoints.East;

                if (bondCount == 1)
                {
                    nesw = BasicGeometry.SnapTo2EW(baFromNorth);
                }
                else
                {
                    nesw = BasicGeometry.SnapTo4NESW(baFromNorth);
                }

                #endregion Determine NESW

                #region Step 4 - Add Charge if required

                if (iCharge != 0)
                {
                    TtfCharacter hydrogenCharacter = _TtfCharacterSet['H'];

                    char         sign = '.';
                    TtfCharacter chargeSignCharacter = null;
                    if (iCharge >= 1)
                    {
                        sign = '+';
                        chargeSignCharacter = _TtfCharacterSet['+'];
                    }
                    else if (iCharge <= 1)
                    {
                        sign = '-';
                        chargeSignCharacter = _TtfCharacterSet['-'];
                    }

                    if (iAbsCharge > 1)
                    {
                        string digits = iAbsCharge.ToString();
                        // Insert digits
                        foreach (char chr in digits)
                        {
                            TtfCharacter chargeValueCharacter = _TtfCharacterSet[chr];
                            thisCharacterPosition = GetCharacterPosition(chargeCursorPosition, chargeValueCharacter);

                            // Raise the superscript Character
                            thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(chargeValueCharacter.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR, _meanBondLength));

                            AtomLabelCharacter alcc = new AtomLabelCharacter(thisCharacterPosition, chargeValueCharacter, atomColour, chr, atom.Path, atom.Parent.Path);
                            alcc.IsSmaller   = true;
                            alcc.IsSubScript = true;
                            _AtomLabelCharacters.Add(alcc);

                            // Move to next Character position
                            chargeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(chargeValueCharacter.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                            cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(chargeValueCharacter.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                        }
                    }

                    // Insert sign at raised position
                    thisCharacterPosition = GetCharacterPosition(chargeCursorPosition, chargeSignCharacter);
                    thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR, _meanBondLength));
                    thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(chargeSignCharacter.Height / 2 * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR, _meanBondLength));

                    AtomLabelCharacter alcs = new AtomLabelCharacter(thisCharacterPosition, chargeSignCharacter, atomColour, sign, atom.Path, atom.Parent.Path);
                    alcs.IsSmaller   = true;
                    alcs.IsSubScript = true;
                    _AtomLabelCharacters.Add(alcs);

                    if (iAbsCharge != 0)
                    {
                        cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(chargeSignCharacter.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                    }
                }

                #endregion Step 4 - Add Charge if required

                #region Step 5 - Add Implicit H if required

                if (options.ShowHydrogens && implicitHCount > 0)
                {
                    TtfCharacter hydrogenCharacter      = _TtfCharacterSet['H'];
                    string       numbers                = "012345";
                    TtfCharacter implicitValueCharacter = _TtfCharacterSet[numbers[implicitHCount]];

                    #region Add H

                    if (hydrogenCharacter != null)
                    {
                        switch (nesw)
                        {
                        case CompassPoints.North:
                            if (atom.Bonds.ToList().Count > 1)
                            {
                                cursorPosition.X = labelBounds.X
                                                   + (labelBounds.Width / 2)
                                                   - (OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width, _meanBondLength) / 2);
                                cursorPosition.Y = cursorPosition.Y
                                                   + OoXmlHelper.ScaleCsTtfToCml(-hydrogenCharacter.Height, _meanBondLength)
                                                   - OoXmlHelper.CHARACTER_VERTICAL_SPACING;
                                if (iCharge > 0)
                                {
                                    if (implicitHCount > 1)
                                    {
                                        cursorPosition.Offset(0,
                                                              OoXmlHelper.ScaleCsTtfToCml(
                                                                  -implicitValueCharacter.Height *
                                                                  OoXmlHelper.SUBSCRIPT_SCALE_FACTOR / 2, _meanBondLength)
                                                              - OoXmlHelper.CHARACTER_VERTICAL_SPACING);
                                    }
                                }
                            }
                            break;

                        case CompassPoints.East:
                            // Leave as is
                            break;

                        case CompassPoints.South:
                            if (atom.Bonds.ToList().Count > 1)
                            {
                                cursorPosition.X = labelBounds.X + (labelBounds.Width / 2)
                                                   - (OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width, _meanBondLength) / 2);
                                cursorPosition.Y = cursorPosition.Y
                                                   + OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height, _meanBondLength)
                                                   + OoXmlHelper.CHARACTER_VERTICAL_SPACING;
                            }
                            break;

                        case CompassPoints.West:
                            if (implicitHCount == 1)
                            {
                                if (iAbsCharge == 0)
                                {
                                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-(hydrogenCharacter.IncrementX * 2), _meanBondLength), 0);
                                }
                                else
                                {
                                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-((hydrogenCharacter.IncrementX * 2 + implicitValueCharacter.IncrementX * 1.25)), _meanBondLength), 0);
                                }
                            }
                            else
                            {
                                if (iAbsCharge == 0)
                                {
                                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-(hydrogenCharacter.IncrementX * 2.5), _meanBondLength), 0);
                                }
                                else
                                {
                                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-((hydrogenCharacter.IncrementX * 2 + implicitValueCharacter.IncrementX * 1.25)), _meanBondLength), 0);
                                }
                            }
                            break;
                        }

                        thisCharacterPosition = GetCharacterPosition(cursorPosition, hydrogenCharacter);
                        AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, hydrogenCharacter, atomColour, 'H', atom.Path, atom.Parent.Path);
                        _AtomLabelCharacters.Add(alc);

                        // Move to next Character position
                        cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.IncrementX, _meanBondLength), 0);

                        if (nesw == CompassPoints.East)
                        {
                            chargeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                        }
                        if (nesw == CompassPoints.West)
                        {
                            isotopeCursorPosition = new Point(thisCharacterPosition.X, isotopeCursorPosition.Y);
                        }
                    }

                    #endregion Add H

                    #region Add number

                    if (implicitHCount > 1)
                    {
                        if (implicitValueCharacter != null)
                        {
                            thisCharacterPosition = GetCharacterPosition(cursorPosition, implicitValueCharacter);

                            // Drop the subscript Character
                            thisCharacterPosition.Offset(0, OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width * OoXmlHelper.SUBSCRIPT_DROP_FACTOR, _meanBondLength));

                            AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, implicitValueCharacter, atomColour, numbers[implicitHCount], atom.Path, atom.Parent.Path);
                            alc.IsSmaller   = true;
                            alc.IsSubScript = true;
                            _AtomLabelCharacters.Add(alc);

                            // Move to next Character position
                            cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(implicitValueCharacter.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                        }
                    }

                    #endregion Add number
                }

                #endregion Step 5 - Add Implicit H if required

                #region Step 6 - Add IsoTope Number if required

                if (isoValue > 0)
                {
                    string digits = isoValue.ToString();

                    xMin = double.MaxValue;
                    yMin = double.MaxValue;
                    xMax = double.MinValue;
                    yMax = double.MinValue;

                    Point isoOrigin = isotopeCursorPosition;

                    // Calculate width of digits
                    foreach (char chr in digits)
                    {
                        TtfCharacter c = _TtfCharacterSet[chr];
                        thisCharacterPosition = GetCharacterPosition(isotopeCursorPosition, c);

                        // Raise the superscript Character
                        thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(c.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR, _meanBondLength));

                        xMin = Math.Min(xMin, thisCharacterPosition.X);
                        yMin = Math.Min(yMin, thisCharacterPosition.Y);
                        xMax = Math.Max(xMax, thisCharacterPosition.X + OoXmlHelper.ScaleCsTtfToCml(c.Width, _meanBondLength));
                        yMax = Math.Max(yMax, thisCharacterPosition.Y + OoXmlHelper.ScaleCsTtfToCml(c.Height, _meanBondLength));

                        // Move to next Character position
                        isotopeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                    }

                    // Re-position Isotope Cursor
                    width = xMax - xMin;
                    isotopeCursorPosition = new Point(isoOrigin.X - width, isoOrigin.Y);

                    // Insert digits
                    foreach (char chr in digits)
                    {
                        TtfCharacter c = _TtfCharacterSet[chr];
                        thisCharacterPosition = GetCharacterPosition(isotopeCursorPosition, c);

                        // Raise the superscript Character
                        thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(c.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR, _meanBondLength));

                        AtomLabelCharacter alcc = new AtomLabelCharacter(thisCharacterPosition, c, atomColour, chr, atom.Path, atom.Parent.Path);
                        alcc.IsSmaller   = true;
                        alcc.IsSubScript = true;
                        _AtomLabelCharacters.Add(alcc);

                        // Move to next Character position
                        isotopeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                    }
                }

                #endregion Step 6 - Add IsoTope Number if required

                #region Step 7 - Create Convex Hull

                _convexhHulls.Add(atom.Path, ConvexHull(atom.Path));

                #endregion Step 7 - Create Convex Hull
            }
        }
        public void CreateFunctionalGroupCharacters(Atom atom, Options options)
        {
            FunctionalGroup fg          = atom.Element as FunctionalGroup;
            double          baFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth, atom.BalancingVector(true));

            CompassPoints nesw    = BasicGeometry.SnapTo2EW(baFromNorth);
            bool          reverse = nesw == CompassPoints.West;

            #region Set Up Atom Colours

            string atomColour = "000000";
            if (options.ColouredAtoms)
            {
                if (!string.IsNullOrEmpty(fg.Colour))
                {
                    atomColour = fg.Colour;
                    // Strip out # as OoXml does not use it
                    atomColour = atomColour.Replace("#", "");
                }
            }

            #endregion Set Up Atom Colours

            List <FunctionalGroupTerm> terms = fg.ExpandIntoTerms(reverse);

            #region Step 1 - Generate the characters and measure Bounding Boxes

            var cursorPosition = atom.Position;

            List <AtomLabelCharacter> fgCharacters = new List <AtomLabelCharacter>();
            TtfCharacter hydrogenCharacter         = _TtfCharacterSet['H'];

            Rect fgBoundingBox     = Rect.Empty;
            Rect anchorBoundingBox = Rect.Empty;

            foreach (var term in terms)
            {
                foreach (var part in term.Parts)
                {
                    foreach (char c in part.Text)
                    {
                        Rect bb = AddCharacter(c, part.Type);
                        fgBoundingBox.Union(bb);
                        if (term.IsAnchor)
                        {
                            anchorBoundingBox.Union(bb);
                        }
                    }
                }
            }

            #endregion Step 1 - Generate the characters and measure Bounding Boxes

            #region Step 2 - Move all characters such that the anchor term is centered on the atom position

            double offsetX;
            double offsetY;
            if (reverse)
            {
                offsetX = fgBoundingBox.Width - anchorBoundingBox.Width / 2;
                offsetY = anchorBoundingBox.Height / 2;
            }
            else
            {
                offsetX = anchorBoundingBox.Width / 2;
                offsetY = anchorBoundingBox.Height / 2;
            }

            offsetY = offsetY + anchorBoundingBox.Top - atom.Position.Y;

            foreach (var alc in fgCharacters)
            {
                alc.Position = new Point(alc.Position.X - offsetX, alc.Position.Y - offsetY);
            }

            #endregion Step 2 - Move all characters such that the anchor term is centered on the atom position

            #region Step 3 - Transfer characters into main list

            foreach (var alc in fgCharacters)
            {
                _AtomLabelCharacters.Add(alc);
            }

            #endregion Step 3 - Transfer characters into main list

            #region Step 4 - Convex Hull

            _convexhHulls.Add(atom.Path, ConvexHull(atom.Path));

            #endregion Step 4 - Convex Hull

            // Local Function
            Rect AddCharacter(char c, FunctionalGroupPartType type)
            {
                TtfCharacter ttf = _TtfCharacterSet[c];
                var          thisCharacterPosition = GetCharacterPosition(cursorPosition, ttf);
                var          alc = new AtomLabelCharacter(thisCharacterPosition, ttf, atomColour, c, atom.Path, atom.Parent.Path);

                alc.IsSubScript   = type == FunctionalGroupPartType.Subscript;
                alc.IsSuperScript = type == FunctionalGroupPartType.Superscript;
                alc.IsSmaller     = alc.IsSubScript || alc.IsSuperScript;

                Rect thisBoundingBox;

                if (alc.IsSmaller)
                {
                    // Start by assuming it's SubScript
                    thisCharacterPosition.Offset(0, OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height * OoXmlHelper.SUBSCRIPT_DROP_FACTOR, _meanBondLength));
                    if (alc.IsSuperScript)
                    {
                        // Shift up by height of H to make it SuperScript
                        thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height, _meanBondLength));
                    }

                    // Reset the character's position
                    alc.Position = thisCharacterPosition;

                    thisBoundingBox = new Rect(alc.Position,
                                               new Size(OoXmlHelper.ScaleCsTtfToCml(alc.Character.Width, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR,
                                                        OoXmlHelper.ScaleCsTtfToCml(alc.Character.Height, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR));

                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(alc.Character.IncrementX, _meanBondLength) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                }
                else
                {
                    thisBoundingBox = new Rect(alc.Position,
                                               new Size(OoXmlHelper.ScaleCsTtfToCml(alc.Character.Width, _meanBondLength),
                                                        OoXmlHelper.ScaleCsTtfToCml(alc.Character.Height, _meanBondLength)));

                    cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(alc.Character.IncrementX, _meanBondLength), 0);
                }

                fgCharacters.Add(alc);

                return(thisBoundingBox);
            }
        }
Beispiel #21
0
        public void DrawNRing(DrawingContext drawingContext, List <NewAtomPlacement> newPlacements)
        {
            StreamGeometry ringGeometry = new StreamGeometry();

            if (_fixedRingAdorner.Unsaturated) //bit complicated as we have unsaturated bonds
            {
                using (var sgc = ringGeometry.Open())
                {
                    int newPlacementsCount = newPlacements.Count;

                    var locations = (from p in newPlacements.ToArray().Reverse()
                                     select p.Position).ToArray();
                    HashSet <NewAtomPlacement> visited = new HashSet <NewAtomPlacement>();
                    Point?centroid = Geometry <Point> .GetCentroid(locations, p => p);

                    var startAt =
                        newPlacementsCount % 2; //place the double bonds in odd membered rings where they should start

                    for (int i = startAt; i < newPlacementsCount + startAt; i++)
                    {
                        int firstIndex       = i % newPlacementsCount;
                        var oldAtomPlacement = newPlacements[firstIndex];
                        int secondIndex      = (firstIndex + 1) % newPlacementsCount;

                        var newAtomPlacement = newPlacements[secondIndex];

                        if (!visited.Contains(oldAtomPlacement) & !visited.Contains(newAtomPlacement) &&
                            !(oldAtomPlacement.ExistingAtom?.IsUnsaturated ??
                              false) && !(newAtomPlacement.ExistingAtom?.IsUnsaturated ?? false))
                        {
                            List <Point> dummy = new List <Point>();

                            DoubleBondLayout dbd = new DoubleBondLayout
                            {
                                Start           = oldAtomPlacement.Position,
                                End             = newAtomPlacement.Position,
                                Placement       = Globals.BondDirection.Anticlockwise,
                                PrimaryCentroid = centroid
                            };
                            BondGeometry.GetDoubleBondGeometry(dbd, dbd.PrincipleVector.Length);
                            BasicGeometry.DrawGeometry(sgc, dbd.DefiningGeometry);
                            visited.Add(oldAtomPlacement);
                            visited.Add(newAtomPlacement);
                        }
                        else
                        {
                            BondLayout sbd = new BondLayout
                            {
                                Start = oldAtomPlacement.Position,
                                End   = newAtomPlacement.Position
                            };
                            BondGeometry.GetSingleBondGeometry(sbd);
                            BasicGeometry.DrawGeometry(sgc, sbd.DefiningGeometry);
                        }

                        oldAtomPlacement = newAtomPlacement;
                    }

                    sgc.Close();
                }

                drawingContext.DrawGeometry(null, _fixedRingAdorner.BondPen, ringGeometry);
            }
            else //saturated ring, just draw a polygon
            {
                drawingContext.DrawGeometry(null, _fixedRingAdorner.BondPen, BasicGeometry.BuildPolyPath(_fixedRingAdorner.Placements, true));
            }
        }
Beispiel #22
0
        /// <summary>
        ///     Defines a double bond
        /// </summary>
        /// <param name="descriptor">DoubleBondDescriptor which is populated</param>
        /// <param name="standardBondLength">Standard bond length as defined by the model</param>
        /// <returns></returns>
        public static void GetDoubleBondPoints(DoubleBondLayout descriptor, double standardBondLength)
        {
            Point?descriptorSecondaryStart;
            Point?descriptorSecondaryEnd;

            //use a struct here to return the values
            GetDefaultDoubleBondPoints(descriptor, standardBondLength);

            if (descriptor.PrimaryCentroid != null)
            {
                //now, if there is a centroid defined, the bond is part of a ring
                Point?workingCentroid = null;
                //work out whether the bond is place inside or outside the ring
                var bondvector   = descriptor.PrincipleVector;
                var centreVector = descriptor.PrimaryCentroid - descriptor.Start;

                var computedPlacement = (Globals.BondDirection)Math.Sign(Vector.CrossProduct(centreVector.Value, bondvector));

                if (descriptor.Placement != Globals.BondDirection.None)
                {
                    if (computedPlacement == descriptor.Placement) //then we have nothing to worry about
                    {
                        workingCentroid = descriptor.PrimaryCentroid;
                    }
                    else //we need to adjust the points according to the other centroid
                    {
                        workingCentroid = descriptor.SecondaryCentroid;
                    }
                }

                if (workingCentroid != null)
                {
                    var bondVector = (descriptor.End - descriptor.Start);
                    var midPoint   = bondVector / 2 + descriptor.Start;

                    var perpAngle = Math.Abs(Vector.AngleBetween(workingCentroid.Value - midPoint, bondVector));

                    if (perpAngle >= 80 && perpAngle <= 100) //probably convex ring
                    {
                        //shorten the second bond to fit neatly within the ring
                        descriptorSecondaryStart = BasicGeometry.LineSegmentsIntersect(descriptor.Start, workingCentroid.Value,
                                                                                       descriptor.SecondaryStart,
                                                                                       descriptor.SecondaryEnd);
                        descriptorSecondaryEnd = BasicGeometry.LineSegmentsIntersect(descriptor.End, workingCentroid.Value,
                                                                                     descriptor.SecondaryStart,
                                                                                     descriptor.SecondaryEnd);
                        var tempPoint3 = descriptorSecondaryStart ?? descriptor.SecondaryStart;
                        var tempPoint4 = descriptorSecondaryEnd ?? descriptor.SecondaryEnd;

                        descriptor.SecondaryStart = tempPoint3;
                        descriptor.SecondaryEnd   = tempPoint4;
                    }
                    else //probably concave ring, so shorten by half the bond offset value
                    {
                        if (descriptor.StartNeigbourPositions != null && descriptor.EndNeighbourPositions != null)
                        {
                            SplitBondAngles(ref descriptor.SecondaryStart, descriptor.SecondaryEnd,
                                            descriptor.StartNeigbourPositions, descriptor.Start);
                            SplitBondAngles(ref descriptor.SecondaryEnd, descriptor.SecondaryStart,
                                            descriptor.EndNeighbourPositions, descriptor.End);
                        }
                    }
                }
                else //no centroid so split the angles anyway
                {
                    if (descriptor.StartNeigbourPositions != null && descriptor.EndNeighbourPositions != null)
                    {
                        SplitBondAngles(ref descriptor.SecondaryStart, descriptor.SecondaryEnd,
                                        descriptor.StartNeigbourPositions, descriptor.Start);
                        SplitBondAngles(ref descriptor.SecondaryEnd, descriptor.SecondaryStart,
                                        descriptor.EndNeighbourPositions, descriptor.End);
                    }
                }

                //get the boundary for hit testing purposes
                descriptor.Boundary.Clear();
                descriptor.Boundary.AddRange(new[]
                {
                    descriptor.Start, descriptor.End, descriptor.SecondaryEnd,
                    descriptor.SecondaryStart
                });
            }
        }
        public void CreateCharacters(Atom atom, Options options)
        {
            string module = $"{_product}.{_class}.{MethodBase.GetCurrentMethod().Name}()";
            //Debug.WriteLine("Atom: " + atom.Id + " is " + atom.ElementType);

            //Point atomCentre = new Point((double)atom.X2, (double)atom.Y2);
            string atomLabel = atom.Element.Symbol;
            Rect   labelBounds;

            //Debug Code
            //atomLabel = "H" + "abcdefHghijklmnopqrstuvwxyz" + "H";
            //atomLabel = "ABCHDEFGHIJKLMNOHPQRSHTUVWXYZ";
            //atomLabel = "-+H01234567890H+-";

            // Get Charge and Isotope valuesfor use later on
            int iCharge    = atom.FormalCharge ?? 0;
            int iAbsCharge = Math.Abs(iCharge);
            int isoValue   = atom.IsotopeNumber ?? 0;

            // Get Implicit Hydrogen Count for use later on
            int implicitHCount = atom.ImplicitHydrogenCount;

            Point cursorPosition        = atom.Position;
            Point chargeCursorPosition  = atom.Position;
            Point isotopeCursorPosition = atom.Position;

            double lastOffset = 0;

            //Debug.WriteLine("  X: " + atom.X2 + " Y: " + atom.Y2 + " Implicit H Count: " + implicitHCount);

            int ringCount = atom.Rings.Count();
            int bondCount = atom.Bonds.Count;

            //var bv = atom.BalancingVector;
            //_telemetry.Write(module, "Debugging", $"Atom {atomLabel} [{atom.Id}] at {atom.Position} BalancingVector {bv} [{CoordinateTool.BearingOfVector(bv)}°]");

            #region Decide if atom label is to be displayed

            bool showLabel = true;
            if (atomLabel == "C")
            {
                if (!options.ShowCarbons)
                {
                    if (ringCount > 0 || bondCount > 1)
                    {
                        showLabel = false;
                    }

                    if (bondCount == 2)
                    {
                        Point p1 = atom.Bonds[0].OtherAtom(atom).Position;
                        Point p2 = atom.Bonds[1].OtherAtom(atom).Position;

                        double angle1 = Vector.AngleBetween(-(atom.Position - p1), atom.Position - p2);

                        if (Math.Abs(angle1) < 8)
                        {
                            showLabel = true;
                        }
                    }

                    // Force on if atom has charge
                    if (iAbsCharge > 0)
                    {
                        showLabel = true;
                    }
                    // Force on if atom has isotope value
                    if (isoValue > 0)
                    {
                        showLabel = true;
                    }
                }
            }

            #endregion Decide if atom label is to be displayed

            if (showLabel)
            {
                #region Set Up Atom Colours

                string atomColour = "000000";
                if (options.ColouredAtoms)
                {
                    if ((atom.Element as Element).Colour != null)
                    {
                        atomColour = ((Element)atom.Element).Colour;
                        // Strip out # as OoXml does not use it
                        atomColour = atomColour.Replace("#", "");
                    }
                }

                #endregion Set Up Atom Colours

                #region Step 1 - Measure Bounding Box for all Characters of label

                double xMin = double.MaxValue;
                double yMin = double.MaxValue;
                double xMax = double.MinValue;
                double yMax = double.MinValue;

                Point thisCharacterPosition;
                for (int idx = 0; idx < atomLabel.Length; idx++)
                {
                    char         chr = atomLabel[idx];
                    TtfCharacter c   = m_TtfCharacterSet[chr];
                    if (c != null)
                    {
                        thisCharacterPosition = GetCharacterPosition(cursorPosition, c);

                        xMin = Math.Min(xMin, thisCharacterPosition.X);
                        yMin = Math.Min(yMin, thisCharacterPosition.Y);
                        xMax = Math.Max(xMax, thisCharacterPosition.X + OoXmlHelper.ScaleCsTtfToCml(c.Width));
                        yMax = Math.Max(yMax, thisCharacterPosition.Y + OoXmlHelper.ScaleCsTtfToCml(c.Height));

                        // Uncomment the following to disable offsetting for terminal atoms
                        //if (bonds.Count == 1)
                        //{
                        //    break;
                        //}

                        if (idx < atomLabel.Length - 1)
                        {
                            // Move to next Character position
                            cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX), 0);
                        }
                    }
                }

                #endregion Step 1 - Measure Bounding Box for all Characters of label

                #region Step 2 - Reset Cursor such that the text is centered about the atom's co-ordinates

                double width  = xMax - xMin;
                double height = yMax - yMin;
                cursorPosition        = new Point(atom.Position.X - width / 2, atom.Position.Y + height / 2);
                chargeCursorPosition  = new Point(cursorPosition.X, cursorPosition.Y);
                isotopeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                labelBounds           = new Rect(cursorPosition, new Size(width, height));
                //_telemetry.Write(module, "Debugging", $"Atom {atomLabel} [{atom.Id}] Label Bounds {labelBounds}");

                #endregion Step 2 - Reset Cursor such that the text is centered about the atom's co-ordinates

                #region Step 3 - Place the characters

                foreach (char chr in atomLabel)
                {
                    TtfCharacter c = m_TtfCharacterSet[chr];
                    if (c != null)
                    {
                        thisCharacterPosition = GetCharacterPosition(cursorPosition, c);
                        AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, c, atomColour, chr, atom.Id);
                        m_AtomLabelCharacters.Add(alc);

                        // Move to next Character position
                        lastOffset = OoXmlHelper.ScaleCsTtfToCml(c.IncrementX);
                        cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX), 0);
                        chargeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                    }
                }

                #endregion Step 3 - Place the characters

                #region Determine NESW

                double        baFromNorth = Vector.AngleBetween(BasicGeometry.ScreenNorth(), atom.BalancingVector);
                CompassPoints nesw        = CompassPoints.East;

                if (bondCount == 1)
                {
                    nesw = BasicGeometry.SnapTo2EW(baFromNorth);
                }
                else
                {
                    nesw = BasicGeometry.SnapTo4NESW(baFromNorth);
                }

                #endregion Determine NESW

                #region Step 4 - Add Implicit H if required

                if (options.ShowHydrogens && implicitHCount > 0)
                {
                    TtfCharacter hydrogenCharacter      = m_TtfCharacterSet['H'];
                    string       numbers                = "012345";
                    TtfCharacter implicitValueCharacter = m_TtfCharacterSet[numbers[implicitHCount]];

                    #region Add H

                    if (hydrogenCharacter != null)
                    {
                        switch (nesw)
                        {
                        case CompassPoints.North:
                            if (atom.Bonds.Count > 1)
                            {
                                cursorPosition.X = labelBounds.X + (labelBounds.Width / 2) - (OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width) / 2);
                                cursorPosition.Y = cursorPosition.Y +
                                                   OoXmlHelper.ScaleCsTtfToCml(-hydrogenCharacter.Height) -
                                                   OoXmlHelper.CHARACTER_CLIPPING_MARGIN;
                                if (iCharge > 0)
                                {
                                    if (implicitHCount > 1)
                                    {
                                        cursorPosition.Offset(0, OoXmlHelper.ScaleCsTtfToCml(-implicitValueCharacter.Height * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR / 2) - OoXmlHelper.CHARACTER_CLIPPING_MARGIN);
                                    }
                                }
                            }
                            break;

                        case CompassPoints.East:
                            // Leave as is
                            break;

                        case CompassPoints.South:
                            if (atom.Bonds.Count > 1)
                            {
                                cursorPosition.X = labelBounds.X + (labelBounds.Width / 2) - (OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width) / 2);
                                cursorPosition.Y = cursorPosition.Y +
                                                   OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height) +
                                                   OoXmlHelper.CHARACTER_CLIPPING_MARGIN;
                            }
                            break;

                        case CompassPoints.West:
                            if (implicitHCount == 1)
                            {
                                cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-(hydrogenCharacter.IncrementX * 2)), 0);
                            }
                            else
                            {
                                cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(-(hydrogenCharacter.IncrementX * 2.5)), 0);
                            }
                            break;
                        }

                        //_telemetry.Write(module, "Debugging", $"Adding H at {cursorPosition}");
                        thisCharacterPosition = GetCharacterPosition(cursorPosition, hydrogenCharacter);
                        AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, hydrogenCharacter, atomColour, 'H', atom.Id);
                        m_AtomLabelCharacters.Add(alc);

                        // Move to next Character position
                        cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.IncrementX), 0);

                        if (nesw == CompassPoints.East)
                        {
                            chargeCursorPosition = new Point(cursorPosition.X, cursorPosition.Y);
                        }
                        if (nesw == CompassPoints.West)
                        {
                            isotopeCursorPosition = new Point(thisCharacterPosition.X, isotopeCursorPosition.Y);
                        }
                    }

                    #endregion Add H

                    #region Add number

                    if (implicitHCount > 1)
                    {
                        if (implicitValueCharacter != null)
                        {
                            thisCharacterPosition = GetCharacterPosition(cursorPosition, implicitValueCharacter);

                            // Drop the subscript Character
                            thisCharacterPosition.Offset(0, OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Width * OoXmlHelper.SUBSCRIPT_DROP_FACTOR));

                            AtomLabelCharacter alc = new AtomLabelCharacter(thisCharacterPosition, implicitValueCharacter, atomColour, numbers[implicitHCount], atom.Id);
                            alc.IsSubScript = true;
                            m_AtomLabelCharacters.Add(alc);

                            // Move to next Character position
                            cursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(implicitValueCharacter.IncrementX) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                        }
                    }

                    #endregion Add number
                }

                #endregion Step 4 - Add Implicit H if required

                #region Step 5 - Add Charge if required

                if (iCharge != 0)
                {
                    TtfCharacter hydrogenCharacter = m_TtfCharacterSet['H'];

                    char         sign = '.';
                    TtfCharacter chargeSignCharacter = null;
                    if (iCharge >= 1)
                    {
                        sign = '+';
                        chargeSignCharacter = m_TtfCharacterSet['+'];
                    }
                    else if (iCharge <= 1)
                    {
                        sign = '-';
                        chargeSignCharacter = m_TtfCharacterSet['-'];
                    }

                    if (iAbsCharge > 1)
                    {
                        string digits = iAbsCharge.ToString();
                        // Insert digits
                        foreach (char chr in digits)
                        {
                            TtfCharacter chargeValueCharacter = m_TtfCharacterSet[chr];
                            thisCharacterPosition = GetCharacterPosition(chargeCursorPosition, chargeValueCharacter);

                            // Raise the superscript Character
                            thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(chargeValueCharacter.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR));

                            AtomLabelCharacter alcc = new AtomLabelCharacter(thisCharacterPosition, chargeValueCharacter, atomColour, chr, atom.Id);
                            alcc.IsSubScript = true;
                            m_AtomLabelCharacters.Add(alcc);

                            // Move to next Character position
                            chargeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(chargeValueCharacter.IncrementX) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                        }
                    }

                    // Insert sign at raised position
                    thisCharacterPosition = GetCharacterPosition(chargeCursorPosition, chargeSignCharacter);
                    thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(hydrogenCharacter.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR));
                    thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(chargeSignCharacter.Height / 2 * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR));

                    AtomLabelCharacter alcs = new AtomLabelCharacter(thisCharacterPosition, chargeSignCharacter, atomColour, sign, atom.Id);
                    alcs.IsSubScript = true;
                    m_AtomLabelCharacters.Add(alcs);
                }

                #endregion Step 5 - Add Charge if required

                #region Step 6 Add IsoTope Number if required

                if (isoValue > 0)
                {
                    string digits = isoValue.ToString();

                    xMin = double.MaxValue;
                    yMin = double.MaxValue;
                    xMax = double.MinValue;
                    yMax = double.MinValue;

                    Point isoOrigin = isotopeCursorPosition;

                    // Calculate width of digits
                    foreach (char chr in digits)
                    {
                        TtfCharacter c = m_TtfCharacterSet[chr];
                        thisCharacterPosition = GetCharacterPosition(isotopeCursorPosition, c);

                        // Raise the superscript Character
                        thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(c.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR));

                        xMin = Math.Min(xMin, thisCharacterPosition.X);
                        yMin = Math.Min(yMin, thisCharacterPosition.Y);
                        xMax = Math.Max(xMax, thisCharacterPosition.X + OoXmlHelper.ScaleCsTtfToCml(c.Width));
                        yMax = Math.Max(yMax, thisCharacterPosition.Y + OoXmlHelper.ScaleCsTtfToCml(c.Height));

                        // Move to next Character position
                        isotopeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                    }

                    // Re-position Isotope Cursor
                    width = xMax - xMin;
                    isotopeCursorPosition = new Point(isoOrigin.X - width, isoOrigin.Y);

                    // Insert digits
                    foreach (char chr in digits)
                    {
                        TtfCharacter c = m_TtfCharacterSet[chr];
                        thisCharacterPosition = GetCharacterPosition(isotopeCursorPosition, c);

                        // Raise the superscript Character
                        thisCharacterPosition.Offset(0, -OoXmlHelper.ScaleCsTtfToCml(c.Height * OoXmlHelper.CS_SUPERSCRIPT_RAISE_FACTOR));

                        AtomLabelCharacter alcc = new AtomLabelCharacter(thisCharacterPosition, c, atomColour, chr, atom.Id);
                        alcc.IsSubScript = true;
                        m_AtomLabelCharacters.Add(alcc);

                        // Move to next Character position
                        isotopeCursorPosition.Offset(OoXmlHelper.ScaleCsTtfToCml(c.IncrementX) * OoXmlHelper.SUBSCRIPT_SCALE_FACTOR, 0);
                    }
                }

                #endregion Step 6 Add IsoTope Number if required
            }
        }
Beispiel #24
0
        /// <summary>
        /// Chamfers or forks the end of a wedge bond under special circumstances
        /// (one or more incoming single bonds)
        /// </summary>
        /// <param name="descriptor"> WedgeBondDescriptor to be populated</param>
        /// <param name="standardBondLength">Standard bond length as defined by the model</param>
        /// <param name="otherAtomPoints">List of positions of atoms splaying from the end atom</param>
        /// <param name="standoff"></param>
        public static void GetChamferedWedgeGeometry(WedgeBondLayout descriptor,
                                                     double standardBondLength,
                                                     List <Point> otherAtomPoints, double standoff)
        {
            var bondVector = descriptor.PrincipleVector;

            //first get an unaltered bond
            GetWedgeBondGeometry(descriptor, standardBondLength, standoff);

            var firstEdgeVector  = descriptor.FirstCorner - descriptor.Start;
            var secondEdgeVector = descriptor.SecondCorner - descriptor.Start;

            //get the two bonds with widest splay

            var widestPoints = from Point p in otherAtomPoints
                               orderby Math.Abs(Vector.AngleBetween(bondVector, p - descriptor.End)) descending
                               select p;

            //the scaling factors are what we multiply the bond edge vectors by
            double firstScalingFactor  = 0d;
            double secondScalingFactor = 0d;

            //work out the biggest scaling factor for either long edge
            foreach (var point in widestPoints)
            {
                BasicGeometry.IntersectLines(descriptor.Start,
                                             descriptor.FirstCorner,
                                             descriptor.End,
                                             point, out var firstEdgeCut, out var otherBond1Cut);
                BasicGeometry.IntersectLines(descriptor.Start,
                                             descriptor.SecondCorner,
                                             descriptor.End,
                                             point, out var secondEdgeCut, out var otherBond2Cut);
                if (otherAtomPoints.Count == 1)
                {
                    if (firstEdgeCut > firstScalingFactor)
                    {
                        firstScalingFactor = firstEdgeCut;
                    }
                    if (secondEdgeCut > secondScalingFactor)
                    {
                        secondScalingFactor = secondEdgeCut;
                    }
                }
                else
                {
                    if (firstEdgeCut > firstScalingFactor && otherBond1Cut < 1d && otherBond1Cut > 0d)
                    {
                        firstScalingFactor = firstEdgeCut;
                    }
                    if (secondEdgeCut > secondScalingFactor && otherBond2Cut < 1d && otherBond2Cut > 0d)
                    {
                        secondScalingFactor = secondEdgeCut;
                    }
                }
            }

            //and multiply the edges by the scaling factors
            descriptor.FirstCorner  = firstEdgeVector * firstScalingFactor + descriptor.Start;
            descriptor.SecondCorner = secondEdgeVector * secondScalingFactor + descriptor.Start;

            descriptor.CappedOff = true;

            var sg = descriptor.GetOutline();

            sg.Freeze();
            descriptor.DefiningGeometry = sg;
        }