k-ujihara / NCDK

The Chemistry Development Kit ported to .NET
https://kazuyaujihara.github.io/NCDK/
GNU Lesser General Public License v2.1
35 stars 11 forks source link

WPFDrawVisitor complained when DrawingContext is closed. #16

Closed Frankr0 closed 4 years ago

Frankr0 commented 5 years ago

I am trying to follow that page to create minimal implementation of a "molecule editor" by ncdk.

I don't know how to trans the Java code to C#.

Graphics2D g2 = (Graphics2D) image.getGraphics();
...
AWTDrawVisitor drawVisitor = new AWTDrawVisitor(g2);
renderer.paint(myMol, drawVisitor);

At first, I wrote that code:

DrawingVisual drawingVisual = new DrawingVisual();
DrawingContext g2 = drawingVisual.RenderOpen();
...
drawVisitor = new WPFDrawVisitor(g2);
renderer.Paint(myMol, drawVisitor);

I found the drawingVisual will not update until I close the DrawingContext by g2.Close(). And WPFDrawVisitor complained can not find dc when DrawingContext is closed.

I use DrawingGroup instead of DrawingVisual now, but the window show nothing.

So what should I do? Thx

The whole code is here:

using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

using NCDK;
using NCDK.Numerics;
using NCDK.Renderers;
using NCDK.Renderers.Fonts;
using NCDK.Renderers.Generators;
using NCDK.Renderers.Generators.Standards;
using NCDK.Renderers.Visitors;
using NCDK.Silent;

namespace WpfTest
{

    public partial class MainWindow : Window
    {
        Point mousePosition;
        int maxSquareDistance = 30;
        IAtomContainer myMol;
        Rect drawArea;

        DrawingGroup dGroup;

        AtomContainerRenderer renderer;
        DrawingContext g2;
        WPFDrawVisitor drawVisitor;

        public MainWindow()
        {
            InitializeComponent();            
        }
        protected override void OnRender(DrawingContext dc)
        {
            Console.WriteLine("onRender");
            base.OnRender(dc);
            dc.DrawImage(new DrawingImage(dGroup),drawArea);
        }

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            dGroup = new DrawingGroup();

            myMol = new AtomContainer();
            myMol.Add(new Atom("C", new Vector2(1, 1)));

            myMol.Add(new Atom("C", new Vector2(10, 10)));
            myMol.AddBond(myMol.Atoms[0], myMol.Atoms[1], BondOrder.Single);
            myMol.Add(new Atom("N", new Vector2(-10, 20)));
            myMol.AddBond(myMol.Atoms[1], myMol.Atoms[2], BondOrder.Double);

            myMol.Atoms[0].FormalCharge = 2;

            List<IGenerator<IAtomContainer>> generators = new List<IGenerator<IAtomContainer>>();
            generators.Add(new BasicSceneGenerator());

            Typeface font = new Typeface(new FontFamily("Arial"),
                FontStyles.Normal,
                FontWeights.Normal,
                FontStretches.Normal);

            generators.Add(new StandardGenerator(font, 18));

            drawArea = new Rect(0, 0, this.Width, this.Height);

            renderer = new AtomContainerRenderer(generators, new WPFFontManager());
            renderer.Setup(myMol, drawArea);

            g2 = dGroup.Open();
            g2.DrawRectangle(Brushes.White, null, drawArea);

            renderer.SetZoom(2);
            renderer.SetDrawCenter(this.Width / 2, this.Height / 2);

            drawVisitor = new WPFDrawVisitor(g2);
            renderer.Paint(myMol, drawVisitor);

            renderer.GetRenderer2DModel().SetHighlighting(HighlightStyle.OuterGlow);

            this.InvalidateVisual();
        }

        private void Window_MouseUp(object sender, MouseButtonEventArgs e)
        {

            if (mousePosition != null && ((mousePosition -  e.GetPosition(this)).LengthSquared >= maxSquareDistance))
            {
                int atomA = -1;
                int atomB = -1;

                Point startPoint = renderer.ToModelCoordinates(mousePosition.X, mousePosition.Y);
                Point endPoint = renderer.ToModelCoordinates(e.GetPosition(this).X, e.GetPosition(this).Y);

                int counter = 0;
                foreach (IAtom currentAtom in myMol.Atoms)
                {
                    if ((new Point(currentAtom.Point2D.Value.X,currentAtom.Point2D.Value.Y) - startPoint).LengthSquared <= maxSquareDistance)                    {
                        atomA = counter;
                    }
                    else if ((new Point(currentAtom.Point2D.Value.X, currentAtom.Point2D.Value.Y) - endPoint).LengthSquared <= maxSquareDistance)
                    {        
                        atomB = counter;
                    }

                    counter++;
                }

                if (atomA >= 0 && atomB >= 0)
                {
                    myMol.AddBond(myMol.Atoms[atomA], myMol.Atoms[atomB], BondOrder.Single);
                    g2.DrawRectangle(Brushes.White, null, drawArea);
                    renderer.Paint(myMol, drawVisitor);
                    this.InvalidateVisual();
                }
            }
            mousePosition = e.GetPosition(this);
        }

        private void Window_MouseDown(object sender, MouseButtonEventArgs e)
        {
            Point mousePos = renderer.ToModelCoordinates(e.GetPosition(this).X, e.GetPosition(this).Y);

            if (e.LeftButton == MouseButtonState.Pressed)
            {
                myMol.Add(new Atom("C", new Vector2(mousePos.X, mousePos.Y)));
            }
            if (e.RightButton == MouseButtonState.Pressed)
            {
                foreach (IAtom currentAtom in myMol.Atoms)
                {
                    if ((mousePos - new Point(currentAtom.Point2D.Value.X, currentAtom.Point2D.Value.Y)).LengthSquared <= maxSquareDistance)
                    {
                        myMol.RemoveAtom(currentAtom);
                        foreach (IBond bond in myMol.Bonds)
                        {
                            if (bond.Contains(currentAtom))
                            {
                                myMol.RemoveBond(bond);
                            }
                        }
                    }
                }

                foreach (IBond currentBond in myMol.Bonds)
                {
                    if ((mousePos - new Point(currentBond.GetGeometric2DCenter().X, currentBond.GetGeometric2DCenter().Y)).LengthSquared <= maxSquareDistance)
                    {
                        myMol.RemoveBond(currentBond);
                    }
                }
            }
            g2.DrawRectangle(Brushes.White, null, drawArea);
            renderer.Paint(myMol, drawVisitor);
            this.InvalidateVisual();
        }

        private void Window_MouseMove(object sender, MouseEventArgs e)
        {
            Point mousePos = renderer.ToModelCoordinates(e.GetPosition(this).X, e.GetPosition(this).Y);

            bool changed;

            IAtom highlightedAtom = renderer.GetRenderer2DModel().GetHighlightedAtom();

            // unhighlight previous atom
            renderer.GetRenderer2DModel().SetHighlightedAtom(null);

            if (highlightedAtom != null)
            {
                highlightedAtom.RemoveProperty(StandardGenerator.HighlightColorKey);
            }

            bool isHighlightingAtom = false;
            foreach (IAtom currentAtom in myMol.Atoms)
            {

                if ((mousePos - new Point(currentAtom.Point2D.Value.X, currentAtom.Point2D.Value.Y)).LengthSquared <= maxSquareDistance)
                {
                    renderer.GetRenderer2DModel().SetHighlightedAtom(currentAtom);
                    currentAtom.SetProperty(StandardGenerator.HighlightColorKey, Colors.Red);
                    isHighlightingAtom = true;
                    break;
                }
            }

            changed = !((highlightedAtom == null && !isHighlightingAtom)
                    && renderer.GetRenderer2DModel().GetHighlightedAtom() != null
                    && renderer.GetRenderer2DModel().GetHighlightedAtom().Equals(highlightedAtom));

            IBond highlightedBond = renderer.GetRenderer2DModel().GetHighlightedBond();
            if (highlightedBond != null)
            {
                highlightedBond.RemoveProperty(StandardGenerator.HighlightColorKey);
            }

            // unhighlight previous bond
            renderer.GetRenderer2DModel().SetHighlightedBond(null);

            bool isHighlightingBond = false;
            if (!isHighlightingAtom)
            {
                foreach (IBond currentBond in myMol.Bonds)
                {
                    if ((mousePos - new Point(currentBond.GetGeometric2DCenter().X, currentBond.GetGeometric2DCenter().Y)).LengthSquared <= maxSquareDistance)
                    {
                        renderer.GetRenderer2DModel().SetHighlightedBond(currentBond);
                        currentBond.SetProperty(StandardGenerator.HighlightColorKey, Colors.Red);
                        isHighlightingBond = true;
                        break;
                    }
                }
            }

            changed = changed || !((highlightedBond == null && !isHighlightingBond)
                    && renderer.GetRenderer2DModel().GetHighlightedBond() != null
                    && renderer.GetRenderer2DModel().GetHighlightedBond().Equals(highlightedBond));

            if (changed)
            {
                g2.DrawRectangle(Brushes.White, null, drawArea);
                renderer.Paint(myMol, drawVisitor);
                this.InvalidateVisual();
            }
        }
    }
}
k-ujihara commented 5 years ago

Thank you for your post. I am so busy this month. so wait a while please.

Frankr0 commented 5 years ago

Thanks for your contributions.

Frankr0 commented 5 years ago

After fews trying, I still can not find a solution. Please help. According to MSDN, "A DrawingContext must be closed before its content can be rendered".

Frankr0 commented 5 years ago

I use that trick make window show something, is this a good way to show a chem object?

protected override void OnRender(DrawingContext drawingContext)
        {
            base.OnRender(drawingContext);
            dc.Close();
            drawingContext.DrawDrawing(dg);
            dc = dg.Open();
            drawVisitor = new WPFDrawVisitor(dc);
        }

And how to highlight a atom? thx @kazuyaujihara

k-ujihara commented 5 years ago

Use atom.SetProperty(StandardGenerator.HighlightColorKey, colorValue) to highlight atoms.

Frankr0 commented 5 years ago

thx! The highlight will not work until I set it manually, default value seems not work well. renderer.GetRenderer2DModel().SetHighlighting(HighlightStyle.OuterGlow);

Why the charge show like this? It seems flip vertically. image

myMol = new AtomContainer();

myMol.Add(new Atom("C", new Vector2(1, 1)));
myMol.Add(new Atom("C", new Vector2(100, 100)));
myMol.AddBond(myMol.Atoms[0], myMol.Atoms[1], BondOrder.Single);
myMol.Add(new Atom("N", new Vector2(-100, 200)));
myMol.AddBond(myMol.Atoms[1], myMol.Atoms[2], BondOrder.Double);

myMol.Atoms[0].FormalCharge = 2;

@kazuyaujihara

k-ujihara commented 5 years ago

Geomeric System is opposite between Java and WPF. Current porting is only for DepictionGenerator.

Frankr0 commented 5 years ago

Very thanks! How can I get the coordinates of mouse position if I use a Depiction instead of a AtomContainerRenderer?

I do it like this before:

 Point mousePos = renderer.ToModelCoordinates(e.GetPosition(this).X, e.GetPosition(this).Y);

And can I set the color of a specific atom to display? like:

atom.setColor(Color.Red);

Actually, I wanna know is there such a way to change a specific atom's font family, font size, color? @kazuyaujihara

k-ujihara commented 5 years ago

Refer SVG generation code, ie NCDK.Display/Depict/SvgDrawVisitor.cs. This code is embedding atom and bond information in id argument of svg file. It is very ad hoc way, but we may do it.

Frankr0 commented 5 years ago

Thanks for you reply! The moleculeSetRenderer in ChemModelRenderer is not instantiated but used in Paint, is this a bug?

public ChemModelRenderer(List<IGenerator<IAtomContainer>> generators, IList<IGenerator<IReaction>> reactionGenerators, IFontManager fontManager) : base(new RendererModel())
{
    this.fontManager = fontManager;
    reactionSetRenderer = new ReactionSetRenderer(rendererModel, generators, reactionGenerators, fontManager);
    this.Setup();
}
public Rect Paint(IChemModel chemModel, IDrawVisitor drawVisitor)
{
    ...
        ...

    if (moleculeSet != null && reactionSet != null)
    {
                ...
        ElementGroup diagram = new ElementGroup
        {
            reactionSetRenderer.GenerateDiagram(reactionSet),
    HERE===>    moleculeSetRenderer.GenerateDiagram(moleculeSet)

        };
                ...
    }
}

@kazuyaujihara

k-ujihara commented 5 years ago

It looks bug!! Thanks.

Frankr0 commented 5 years ago

Hello! The drawing of OralElement in WPFDrawVisitor seems not right, it seems should not to subtract the radius. @kazuyaujihara

 private void Visit(OvalElement oval)
        {
            var radius = oval.Radius;
            var diameter = oval.Radius * 2;
            var center = oval.Coord;

            if (oval.Fill)
            {
                this.dc.DrawEllipse(
                    GetBrush(oval.Color),
                    null,
    HERE===>        new WPF.Point(center.X - radius, center.Y - radius), diameter, diameter);
            }
            else
            {
                this.dc.DrawEllipse(
                    null,
                    GetPen(oval.Color, 1),
    HERE===>        new WPF.Point(center.X - radius, center.Y - radius), diameter, diameter);
            }
        }

And the multiplication of fromPoint * Scale(unit, distance) in StandardBondGenerator.cs should be addition?

        internal IRenderingElement GenerateDashedBond(Vector2 fromPoint, Vector2 toPoint, double start, double end)
        {
            Vector2 unit = NewUnitVector(fromPoint, toPoint);

            int nDashes = parameters.GetDashSection();

            double step = Vector2.Distance(fromPoint, toPoint) / ((3 * nDashes) - 2);

            ElementGroup group = new ElementGroup();

            double distance = 0;

            for (int i = 0; i < nDashes; i++)
            {

                // draw a full dash section
                if (distance > start && distance + step < end)
                {   
       HERE==>      group.Add(NewLineElement(fromPoint * Scale(unit, distance),
                            Sum(fromPoint, Scale(unit, distance + step))));
                }
Frankr0 commented 5 years ago

Hi! the query of dictionary may cause a KeyNotFoundException in RingPlacer.cs.

// Different ring sizes get different start angles to have visually
// correct placement
int ringSize = ring.RingSize;
startAngle = startAngles[ringSize];    <==HERE

Should it be replaced by TryGetValue ?

if(startAngles.TryGetValue(ringSize, out double angle))
    startAngle = angle;

@kazuyaujihara

Frankr0 commented 5 years ago

The operation of Transform.Value will not change the value of Matrix:

Transform transform = new TranslateTransform(100, 100);
Matrix matrix = transform.Value;

transform.Value.Scale(2, 1);
transform.Value.Scale(1, 2);
transform.Value.Translate(20, 20);
Console.WriteLine(transform.Value);
// 1,0,0,1,100,100

matrix.Scale(2, 1);
matrix.Scale(1, 2);
matrix.Translate(20, 20);
Console.WriteLine(matrix);
// 2,0,0,2,220,220

So it may not get the desired result in AbstractRenderer.cs:

protected virtual void Setup()
{
    double scale = rendererModel.GetScale();
    double zoom = rendererModel.GetZoomFactor();
    // set the transform
    try
    {
        transform = new TranslateTransform(drawCenter.X, drawCenter.Y);
HERE==>     //transform.Value.Scale(1, -1); // Converts between CDK Y-up & Java2D Y-down coordinate-systems
HERE==>     transform.Value.Scale(scale, scale);
HERE==>     transform.Value.Scale(zoom, zoom);
HERE==>     transform.Value.Translate(-this.modelCenter.X, -this.modelCenter.Y);
    }
    catch (NullReferenceException)
    {
        // one of the drawCenter or modelCenter points have not been set!
        Console.Error.WriteLine($"null pointer when setting transform: drawCenter={drawCenter} scale={scale} zoom={zoom} modelCenter={modelCenter}");
    }
}

I change it to the following code and the result seems correct:

Matrix matrix = new Matrix(1, 0, 0, 1, drawCenter.X, drawCenter.Y);
matrix.Scale(scale, scale);
matrix.Scale(zoom, zoom);
matrix.Translate(-this.modelCenter.X, -this.modelCenter.Y);

transform = Transform.Parse(matrix.ToString());
k-ujihara commented 5 years ago

@Frankr0 Thank you for your contribution. Could you make patches to fix these bugs and make the PR?

Frankr0 commented 5 years ago

ok, I will try it.

Frankr0 commented 5 years ago

Is ncdk must use 4.61 or higher? 4.6.1 is too high for me.
@kazuyaujihara

k-ujihara commented 5 years ago

Which version are you using?

Frankr0 commented 5 years ago

The idealisation version maybe is 4.0.

k-ujihara commented 4 years ago

NCDK is using IReadOnlyList interface, which is introduced in .NET Framework 4.5. So it is not compatible with .NET Framework 4.0.