/// <summary> /// draws the two parallel lines of a double bond /// These bonds can either straddle the atom-atom line or fall to one or other side of it /// </summary> /// <param name="descriptor">DoubleBondDescriptor which is populated</param> /// <param name="standardBondLength">Standard bond length as defined by the model</param> /// <param name="standoff"></param> /// <returns></returns> public static void GetDoubleBondGeometry(DoubleBondLayout descriptor, double standardBondLength, double standoff) { //get the standard points for a double bond GetDoubleBondPoints(descriptor, standardBondLength); //adjust the line ends if (descriptor.StartAtomHull != null) { AdjustTerminus(ref descriptor.Start, descriptor.End, descriptor.StartAtomHull, standoff); AdjustTerminus(ref descriptor.SecondaryStart, descriptor.SecondaryEnd, descriptor.StartAtomHull, standoff); } if (descriptor.EndAtomHull != null) { AdjustTerminus(ref descriptor.End, descriptor.Start, descriptor.EndAtomHull, standoff); AdjustTerminus(ref descriptor.SecondaryEnd, descriptor.SecondaryStart, descriptor.EndAtomHull, standoff); } //and draw it var sg = new StreamGeometry(); using (var sgc = sg.Open()) { sgc.BeginFigure(descriptor.Start, false, false); sgc.LineTo(descriptor.End, true, false); sgc.BeginFigure(descriptor.SecondaryStart, false, false); sgc.LineTo(descriptor.SecondaryEnd, true, false); sgc.Close(); } sg.Freeze(); descriptor.DefiningGeometry = sg; }
/// <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 }); } }
/// <summary> /// Gets an unadjusted set of points for a double bond /// </summary> /// <param name="descriptor">DoubleBondDescriptor which is populated</param> /// <param name="standardBondLength">Standard bond length as defined by the model</param> private static void GetDefaultDoubleBondPoints(DoubleBondLayout descriptor, double standardBondLength) { var v = descriptor.PrincipleVector; var normal = v.Perpendicular(); normal.Normalize(); var distance = standardBondLength * BondOffsetPercentage; //first, calculate the default bond points as if there were no rings involved var tempStart = descriptor.Start; //offset according to placement switch (descriptor.Placement) { case BondDirection.None: descriptor.Start = tempStart + normal * distance; descriptor.End = descriptor.Start + v; descriptor.SecondaryStart = tempStart - normal * distance; descriptor.SecondaryEnd = descriptor.SecondaryStart + v; break; case BondDirection.Clockwise: { descriptor.SecondaryStart = tempStart - normal * 2 * distance; descriptor.SecondaryEnd = descriptor.SecondaryStart + v; break; } case BondDirection.Anticlockwise: descriptor.SecondaryStart = tempStart + normal * 2 * distance; descriptor.SecondaryEnd = descriptor.SecondaryStart + v; break; default: descriptor.Start = tempStart + normal * distance; descriptor.End = descriptor.Start + v; descriptor.SecondaryStart = tempStart - normal * distance; descriptor.SecondaryEnd = descriptor.SecondaryStart + v; break; } }
/// <summary> /// Draws the crossed double bond to indicate indeterminate geometry /// </summary> /// <param name="descriptor">BondLayout to hadle the physical bond shape</param> /// <param name="standardBondLength">Model's standard bond length</param> /// <param name="standoff">Boundary width between atom label and bond terminus</param> public static void GetCrossedDoubleGeometry(DoubleBondLayout descriptor, double standardBondLength, double standoff) { var v = descriptor.PrincipleVector; var normal = v.Perpendicular(); normal.Normalize(); Point point1, point2, point3, point4; var distance = standardBondLength * Globals.BondOffsetPercentage; point1 = descriptor.Start + normal * distance; point2 = point1 + v; point3 = descriptor.Start - normal * distance; point4 = point3 + v; if (descriptor.StartAtomHull != null) { AdjustTerminus(ref point1, point2, descriptor.StartAtomHull, standoff); AdjustTerminus(ref point3, point4, descriptor.StartAtomHull, standoff); } if (descriptor.StartAtomHull != null) { AdjustTerminus(ref point2, point1, descriptor.StartAtomHull, standoff); AdjustTerminus(ref point4, point3, descriptor.StartAtomHull, standoff); } var sg = new StreamGeometry(); using (var sgc = sg.Open()) { sgc.BeginFigure(point1, false, false); sgc.LineTo(point4, true, false); sgc.BeginFigure(point2, false, false); sgc.LineTo(point3, true, false); sgc.Close(); } sg.Freeze(); descriptor.DefiningGeometry = sg; descriptor.Boundary.Clear(); descriptor.Boundary.AddRange(new[] { point1, point2, point4, point3 }); }
/// <summary> /// Renders a bond to the display /// </summary> public override void Render() { //set up the shared variables first Point startPoint, endPoint; startPoint = ParentBond.StartAtom.Position; endPoint = ParentBond.EndAtom.Position; var bondLength = ParentBond.Model.XamlBondLength; var cv1 = ChemicalVisuals.ContainsKey(ParentBond.StartAtom); var cv2 = ChemicalVisuals.ContainsKey(ParentBond.EndAtom); //bale out in case we have a null start or end if (!cv1 || !cv2) { // Abort if either ChemicalVisual is missing ! return; } //now get the geometry of start and end atoms var startVisual = (AtomVisual)ChemicalVisuals[ParentBond.StartAtom]; var endVisual = (AtomVisual)ChemicalVisuals[ParentBond.EndAtom]; //first grab the main descriptor BondDescriptor = GetBondDescriptor(ParentBond, startVisual, endVisual, bondLength); _enclosingPoly = BondDescriptor.Boundary; //set up the default pens for rendering _mainBondPen = new Pen(Brushes.Black, BondThickness) { StartLineCap = PenLineCap.Round, EndLineCap = PenLineCap.Round, LineJoin = PenLineJoin.Miter }; _subsidiaryBondPen = _mainBondPen.Clone(); switch (ParentBond.Order) { case Globals.OrderZero: case Globals.OrderOther: case "unknown": // Handle Zero Bond _mainBondPen.DashStyle = DashStyles.Dot; using (DrawingContext dc = RenderOpen()) { dc.DrawGeometry(Brushes.Black, _mainBondPen, BondDescriptor.DefiningGeometry); //we need to draw another transparent rectangle to expand the bounding box DrawHitTestOverlay(dc); dc.Close(); } DoubleBondLayout dbd = new DoubleBondLayout { Start = startPoint, End = endPoint, Placement = ParentBond.Placement }; BondGeometry.GetDoubleBondPoints(dbd, bondLength); _enclosingPoly = dbd.Boundary; break; case Globals.OrderPartial01: _mainBondPen.DashStyle = DashStyles.Dash; using (DrawingContext dc = RenderOpen()) { dc.DrawGeometry(Brushes.Black, _mainBondPen, BondDescriptor.DefiningGeometry); //we need to draw another transparent thicker line on top of the existing one DrawHitTestOverlay(dc); dc.Close(); } //grab the enclosing polygon as for a double ParentBond - this overcomes a hit testing bug DoubleBondLayout dbd2 = new DoubleBondLayout { Start = startPoint, End = endPoint, Placement = ParentBond.Placement }; BondGeometry.GetDoubleBondPoints(dbd2, bondLength); _enclosingPoly = dbd2.Boundary; break; case "1": case Globals.OrderSingle: // Handle Single bond switch (ParentBond.Stereo) { case Globals.BondStereo.Indeterminate: case Globals.BondStereo.None: case Globals.BondStereo.Wedge: using (DrawingContext dc = RenderOpen()) { dc.DrawGeometry(Brushes.Black, _mainBondPen, BondDescriptor.DefiningGeometry); //we need to draw another transparent rectangle to expand the bounding box DrawHitTestOverlay(dc); dc.Close(); } break; case Globals.BondStereo.Hatch: using (DrawingContext dc = RenderOpen()) { dc.DrawGeometry(GetHatchBrush(ParentBond.Angle), _mainBondPen, BondDescriptor.DefiningGeometry); //we need to draw another transparent rectangle to expand the bounding box DrawHitTestOverlay(dc); dc.Close(); } break; } break; case Globals.OrderPartial12: case Globals.OrderAromatic: case "2": case Globals.OrderDouble: DoubleBondLayout dbd3 = (DoubleBondLayout)BondDescriptor; Point? centroid = ParentBond.Centroid; dbd3.PrimaryCentroid = centroid; if (ParentBond.Order == Globals.OrderPartial12 || ParentBond.Order == Globals.OrderAromatic) // Handle 1.5 bond { _subsidiaryBondPen.DashStyle = DashStyles.Dash; } _enclosingPoly = dbd3.Boundary; if (ParentBond.Stereo != Globals.BondStereo.Indeterminate) { using (DrawingContext dc = RenderOpen()) { dc.DrawLine(_mainBondPen, BondDescriptor.Start, BondDescriptor.End); dc.DrawLine(_subsidiaryBondPen, dbd3.SecondaryStart, dbd3.SecondaryEnd); dc.Close(); } } else { using (DrawingContext dc = RenderOpen()) { dc.DrawGeometry(_mainBondPen.Brush, _mainBondPen, BondDescriptor.DefiningGeometry); dc.Close(); } } break; case Globals.OrderPartial23: case "3": case Globals.OrderTriple: if (ParentBond.Order == Globals.OrderPartial23) // Handle 2.5 bond { _subsidiaryBondPen.DashStyle = DashStyles.Dash; } var tbd = (BondDescriptor as TripleBondLayout); using (DrawingContext dc = RenderOpen()) { if (ParentBond.Placement == Globals.BondDirection.Clockwise) { dc.DrawLine(_mainBondPen, tbd.SecondaryStart, tbd.SecondaryEnd); dc.DrawLine(_mainBondPen, tbd.Start, tbd.End); dc.DrawLine(_subsidiaryBondPen, tbd.TertiaryStart, tbd.TertiaryEnd); } else { dc.DrawLine(_subsidiaryBondPen, tbd.SecondaryStart, tbd.SecondaryEnd); dc.DrawLine(_mainBondPen, tbd.Start, tbd.End); dc.DrawLine(_mainBondPen, tbd.TertiaryStart, tbd.TertiaryEnd); } dc.Close(); } break; } void DrawHitTestOverlay(DrawingContext dc) { SolidColorBrush outliner = new SolidColorBrush(Colors.Salmon); #if SHOWBOUNDS outliner.Opacity = 0.2d; #else outliner.Opacity = 0d; #endif Pen outlinePen = new Pen(outliner, BondThickness * 5); dc.DrawGeometry(outliner, outlinePen, BondDescriptor.DefiningGeometry); } }
public static BondLayout GetBondDescriptor(AtomVisual startAtomVisual, AtomVisual endAtomVisual, double modelXamlBondLength, Globals.BondStereo parentStereo, Point startAtomPosition, Point endAtomPosition, double?parentOrderValue, Globals.BondDirection parentPlacement, Point?centroid, Point?secondaryCentroid) { if (parentStereo == Globals.BondStereo.Wedge || parentStereo == Globals.BondStereo.Hatch) { WedgeBondLayout wbd = new WedgeBondLayout() { Start = startAtomPosition, End = endAtomPosition, StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual }; var endAtom = endAtomVisual.ParentAtom; var otherBonds = endAtom.Bonds.Except(new[] { startAtomVisual.ParentAtom.BondBetween(endAtom) }).ToList(); Bond bond = null; if (otherBonds.Any()) { bond = otherBonds.ToArray()[0]; } bool chamferBond = (otherBonds.Any() && (endAtom.Element as Element) == Globals.PeriodicTable.C && endAtom.SymbolText == "" && bond.Order == Globals.OrderSingle); if (!chamferBond) { wbd.CappedOff = false; BondGeometry.GetWedgeBondGeometry(wbd, modelXamlBondLength); } else { var nonHPs = (from b in otherBonds select b.OtherAtom(endAtom).Position).ToList(); if (nonHPs.Any()) { wbd.CappedOff = true; BondGeometry.GetChamferedWedgeGeometry(wbd, modelXamlBondLength, nonHPs); } else { wbd.CappedOff = false; BondGeometry.GetWedgeBondGeometry(wbd, modelXamlBondLength); } } return(wbd); } //wavy bond if (parentStereo == Globals.BondStereo.Indeterminate && parentOrderValue == 1.0) { BondLayout sbd = new BondLayout { Start = startAtomPosition, End = endAtomPosition, StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual }; BondGeometry.GetWavyBondGeometry(sbd, modelXamlBondLength); return(sbd); } switch (parentOrderValue) { //indeterminate double case 2 when parentStereo == Globals.BondStereo.Indeterminate: DoubleBondLayout dbd = new DoubleBondLayout() { StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual, Start = startAtomPosition, End = endAtomPosition }; BondGeometry.GetCrossedDoubleGeometry(dbd, modelXamlBondLength); return(dbd); //partial or undefined bonds case 0: case 0.5: case 1.0: BondLayout sbd = new BondLayout { Start = startAtomPosition, End = endAtomPosition, StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual }; BondGeometry.GetSingleBondGeometry(sbd); return(sbd); //double bond & 1.5 bond case 1.5: case 2: DoubleBondLayout dbd2 = new DoubleBondLayout() { StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual, Start = startAtomPosition, End = endAtomPosition, Placement = parentPlacement, PrimaryCentroid = centroid, SecondaryCentroid = secondaryCentroid }; BondGeometry.GetDoubleBondGeometry(dbd2, modelXamlBondLength); return(dbd2); //triple and 2.5 bond case 2.5: case 3: TripleBondLayout tbd = new TripleBondLayout() { StartAtomVisual = startAtomVisual, EndAtomVisual = endAtomVisual, Start = startAtomPosition, End = endAtomPosition, Placement = parentPlacement, PrimaryCentroid = centroid, SecondaryCentroid = secondaryCentroid }; BondGeometry.GetTripleBondGeometry(tbd, modelXamlBondLength); return(tbd); default: return(null); } }
/// <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 static BondLayout GetBondDescriptor(AtomVisual startAtomVisual, AtomVisual endAtomVisual, double modelXamlBondLength, Globals.BondStereo parentStereo, Point startAtomPosition, Point endAtomPosition, double?parentOrderValue, Globals.BondDirection parentPlacement, Point?centroid, Point?secondaryCentroid, double standoff) { List <Point> startAtomHull = new List <Point>(); List <Point> endAtomHull = new List <Point>(); if (startAtomVisual.ParentAtom.SymbolText != "" || startAtomVisual.ShowAllCarbons) { startAtomHull = startAtomVisual.Hull; } if (endAtomVisual.ParentAtom.SymbolText != "" || endAtomVisual.ShowAllCarbons) { endAtomHull = endAtomVisual.Hull; } if ((parentStereo == Globals.BondStereo.Wedge || parentStereo == Globals.BondStereo.Hatch) && parentOrderValue == 1) { WedgeBondLayout wbd = new WedgeBondLayout { Start = startAtomPosition, End = endAtomPosition, StartAtomHull = startAtomHull, EndAtomHull = endAtomHull }; var endAtom = endAtomVisual.ParentAtom; var otherBonds = endAtom.Bonds.Except(new[] { startAtomVisual.ParentAtom.BondBetween(endAtom) }).ToList(); Bond bond = null; bool oblique = true; if (otherBonds.Any()) { bond = otherBonds.ToArray()[0]; Vector wedgevector = wbd.End - wbd.Start; foreach (Bond b in otherBonds) { Atom otherAtom = b.OtherAtom(endAtom); Vector v = wbd.End - otherAtom.Position; double angle = System.Math.Abs(Vector.AngleBetween(wedgevector, v)); if (angle < 109.5 || angle > 130.5) { oblique = false; break; } } } bool chamferBond = otherBonds.Any() && oblique && (endAtom.Element as Element) == Globals.PeriodicTable.C && endAtom.SymbolText == "" && bond.Order == Globals.OrderSingle; if (!chamferBond) { wbd.CappedOff = false; BondGeometry.GetWedgeBondGeometry(wbd, modelXamlBondLength, standoff); } else { var nonHPs = (from b in otherBonds select b.OtherAtom(endAtom).Position).ToList(); if (nonHPs.Any()) { wbd.CappedOff = true; BondGeometry.GetChamferedWedgeGeometry(wbd, modelXamlBondLength, nonHPs, standoff); } else { wbd.CappedOff = false; BondGeometry.GetWedgeBondGeometry(wbd, modelXamlBondLength, standoff); } } return(wbd); } //wavy bond if (parentStereo == Globals.BondStereo.Indeterminate && parentOrderValue == 1.0) { BondLayout sbd = new BondLayout { Start = startAtomPosition, End = endAtomPosition, StartAtomHull = startAtomHull, EndAtomHull = endAtomHull }; BondGeometry.GetWavyBondGeometry(sbd, modelXamlBondLength, standoff); return(sbd); } switch (parentOrderValue) { //indeterminate double case 2 when parentStereo == Globals.BondStereo.Indeterminate: DoubleBondLayout dbd = new DoubleBondLayout() { StartAtomHull = startAtomHull, EndAtomHull = endAtomHull, Start = startAtomPosition, End = endAtomPosition, StartNeigbourPositions = (from Atom a in startAtomVisual.ParentAtom.NeighboursExcept(endAtomVisual.ParentAtom) select a.Position).ToList(), EndNeighbourPositions = (from Atom a in endAtomVisual.ParentAtom.NeighboursExcept(startAtomVisual.ParentAtom) select a.Position).ToList() }; BondGeometry.GetCrossedDoubleGeometry(dbd, modelXamlBondLength, standoff); return(dbd); //partial or undefined bonds case 0: case 0.5: case 1.0: BondLayout sbd = new BondLayout { Start = startAtomPosition, End = endAtomPosition, StartAtomHull = startAtomHull, EndAtomHull = endAtomHull }; BondGeometry.GetSingleBondGeometry(sbd, standoff); return(sbd); //double bond & 1.5 bond case 1.5: case 2: DoubleBondLayout dbd2 = new DoubleBondLayout() { StartAtomHull = startAtomHull, EndAtomHull = endAtomHull, Start = startAtomPosition, End = endAtomPosition, Placement = parentPlacement, PrimaryCentroid = centroid, SecondaryCentroid = secondaryCentroid, StartNeigbourPositions = (from Atom a in startAtomVisual.ParentAtom.NeighboursExcept(endAtomVisual.ParentAtom) select a.Position).ToList(), EndNeighbourPositions = (from Atom a in endAtomVisual.ParentAtom.NeighboursExcept(startAtomVisual.ParentAtom) select a.Position).ToList() }; BondGeometry.GetDoubleBondGeometry(dbd2, modelXamlBondLength, standoff); return(dbd2); //triple and 2.5 bond case 2.5: case 3: TripleBondLayout tbd = new TripleBondLayout() { StartAtomHull = startAtomHull, EndAtomHull = endAtomHull, Start = startAtomPosition, End = endAtomPosition, Placement = parentPlacement, PrimaryCentroid = centroid, SecondaryCentroid = secondaryCentroid }; BondGeometry.GetTripleBondGeometry(tbd, modelXamlBondLength, standoff); return(tbd); default: return(null); } }