📜 ⬆️ ⬇️

"... They want to know what will happen" or write a fortunetech ball in C # NanoCAD CAD software (MultiCAD .NET API)

If you believe one old song from the Soviet movie , then people are always interested in the future in a difficult situation. Someone throws up a coin, someone torments Paul octopus, and absolutely brutal-minded people pluck daisies. We will do much more humanely and find for CAD NanoCAD a very unconventional application, namely, we will make our analogue of the fortunetelling ball (almost as in the picture below).

In this article, we once again will practice creating NanoCAD user primitives using the MultiCAD.NET API, and also attach to the object our interaction with Windows.Forms.

The code today will be only in C #, we will write it for the paid version (NC 8.5) and for the free version (NC 5.1), and of course Linux users will be able to build it in Mono and run it under Wine, so you are welcome to…
')




If you have not encountered, with articles from this mini-cycle, you can look under the spoiler. In previous articles, we looked at various issues ranging from what NanoCAD is to trying to run our projects under Linux.


As always, let me remind you that I am not a programmer and therefore not all my thoughts in this article may be correct, and also that I’m not engaged in any way with the developers of NanoCAD. Although I definitely consider it necessary to thank the entire community of NanoCAD users and developers for their help on the forum.

Do not be surprised that we will again use CAD to create objects that are not related to design. Simply, I had to practice “insert” Windows.Forms into user objects of NanoCAD, and since the training materials on the API for Nanocad - “the cat wept”, I decided to share with you my simple and illustrative example.

The article will be short and could be dispensed with, but I will leave it for ease of navigation just in case.

Content:
Part I: Introduction
Part II: we write code on C #
Part III: Conclusion

I must say that in spite of the fact that there are less teaching materials than we would like, we will rely on what to write when writing code.

In particular, about the creation of a user primitive, the developers have already written in their blog on Habré , well, and the issue with intelligent pens, they also previously understood . In principle, today we will not go far beyond these two articles.

Usually at the beginning of the article I am writing how to create a project for NanoCAD from scratch, but this time, because of the presence of a class with a window form, I decided to post the entire project on GitHub , so you can simply download it, connect the libraries and immediately start experimenting.

But if the development for you under the Nanocad is new, look at this piece of the previous article ( for NC 8.5 and for NC 5.1 ).

I decided, just in case, not to "offend" the developers of NanoCAD and did not attach the necessary libraries from the SDK package to the project. These libraries can be found either in the “bin” folder of the installed program, or by receiving the SDK. For NC 8.5 and other versions, you must register with the developer club . Just in case, I remind you that download any available version of NC for development purposes, club members can be completely free. Well, for free NC 5.1 - the SDK seems to come bundled with the program (if nothing has changed).

So, let's begin to parse the code, I will not attach the automatically generated code of the form, and I will confine myself only to the class of the user primitive (the ball itself) and the logic of the form.

To begin, hide the full code for the paid version of NanoCAD under the spoiler.

Full code for NC 8.5 C #
// Version 1.0 //Use Microsoft .NET Framework 4 and MultiCad.NET API 7.0 //Class for demonstrating the capabilities of MultiCad.NET //Assembly for the Nanocad 8.5 SDK is recommended (however, it is may be possible in the all 8. family) //Link imapimgd, mapimgd.dll and mapibasetypes.dll from SDK //Link System.Windows.Forms and System.Drawing //The commands: draws a fortune-teller ball //This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways. //For the consequences of the code application, the developer is not responsible. //More detailed - https://habrahabr.ru/post/347720/ using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using Multicad.Runtime; using Multicad.DatabaseServices; using Multicad.Geometry; using Multicad.CustomObjectBase; using Multicad; using Multicad.AplicationServices; namespace Fortuneteller { [CustomEntity(typeof(Ball), "2e814ea6-f1f0-469d-9767-269fedb32226", "Ball", "Fortuneteller Ball for NC85 Entity")] [Serializable] public class Ball : McCustomBase { private Point3d _basePnt = new Point3d(0, 0, 0); double _radius=300; string _predText = "..."; public List<String> predictions = new List<String>() {"Act now!", "Do not do this!", "Maybe", "I dont know", "Everything is unclear", "Yes!", "No!", "Take rest" }; public override void OnDraw(GeometryBuilder dc) { dc.Clear(); dc.Color = McDbEntity.ByObject; dc.DrawCircle(_basePnt, _radius); dc.DrawCircle(_basePnt, _radius/2.0); dc.TextHeight = 31; dc.DrawMText(_basePnt, Vector3d.XAxis, _predText, HorizTextAlign.Center, VertTextAlign.Center, _radius / 2.05); } public override void OnTransform(Matrix3d tfm) { // To be able to cancel(Undo) McUndoPoint undo = new McUndoPoint(); undo.Start(); // Get the coordinates of the base point and the rotation vector this.TryModify(); this._basePnt = this._basePnt.TransformBy(tfm); undo.Stop(); } public override hresult OnEdit(Point3d pnt, EditFlags lInsertType) { CallForm(); return hresult.s_Ok; } private void CallForm() { ListEditorForm frm = new ListEditorForm(this); frm.Lpredictions.Items.AddRange(predictions.ToArray()); frm.ShowDialog(); } [CommandMethod("DFTBall", CommandFlags.NoCheck | CommandFlags.NoPrefix)] public void DrawBall () { Ball ball = new Ball(); ball.PlaceObject(); McContext.ShowNotification("Use green grip or shake (move) ball to get prediction"); } public override bool GetGripPoints(GripPointsInfo info) { //frist grip to move info.AppendGrip(new McSmartGrip<Ball>(_basePnt+new Vector3d(0, _radius,0), (obj, g, offset) => { obj.TryModify(); obj._basePnt += offset; obj.TryModify(); obj.ShakePredict(); })); //command grip var ctxGrip = new McSmartGrip<Ball>(McBaseGrip.GripType.PopupMenu, 2, _basePnt - 1.0 * new Vector3d(_radius, 0, 0), McBaseGrip.GripAppearance.PopupMenu, 0, "Select menu", Color.Lime); ctxGrip.GetContextMenu = (obj, items) => { items.Add(new ContextMenuItem("Get prediction", "none", 1)); items.Add(new ContextMenuItem("Edit predictions", "none", 2)); }; ctxGrip.OnCommand = (obj, commandId, grip) => { if (grip.Id == 2) { switch (commandId) { case 1: { ShakePredict(); break; } case 2: { CallForm(); break; } } } }; info.AppendGrip(ctxGrip); return true; } public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select center point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _basePnt = res.Point; // Add the object to the database DbEntity.AddToCurrentDocument(); return hresult.s_Ok; } private void ShakePredict() { Random rand = new Random(); int val = rand.Next(0, predictions.Count); this.TryModify(); _predText = predictions[val]; } } } 


Now we will sort the key moments in parts.

 using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using Multicad.Runtime; using Multicad.DatabaseServices; using Multicad.Geometry; using Multicad.CustomObjectBase; using Multicad; using Multicad.AplicationServices; namespace Fortuneteller { [CustomEntity(typeof(Ball), "2e814ea6-f1f0-469d-9767-269fedb32226", "Ball", "Fortuneteller Ball for NC85 Entity")] [Serializable] public class Ball : McCustomBase { 

We connect namespaces, create a custom object class, assign some randomly generated GUID to it, inherit our class from McCustomBase.

  private Point3d _basePnt = new Point3d(0, 0, 0); double _radius=300; string _predText = "..."; public List<String> predictions = new List<String>() {"Act now!", "Do not do this!", "Maybe", "I dont know", "Everything is unclear", "Yes!", "No!", "Take rest" }; 

We set the main variables for our fortuneteller: the point of the geometry center, the radius, the text in the prediction window and the list of prediction variants.

  public override void OnDraw(GeometryBuilder dc) { dc.Clear(); dc.Color = McDbEntity.ByObject; dc.DrawCircle(_basePnt, _radius); dc.DrawCircle(_basePnt, _radius/2.0); dc.TextHeight = 31; dc.DrawMText(_basePnt, Vector3d.XAxis, _predText, HorizTextAlign.Center, VertTextAlign.Center, _radius / 2.05); } 

The method is responsible for drawing the object. Draw two circles and a multiline text object.

  public override void OnTransform(Matrix3d tfm) { // To be able to cancel(Undo) McUndoPoint undo = new McUndoPoint(); undo.Start(); // Get the coordinates of the base point and the rotation vector this.TryModify(); this._basePnt = this._basePnt.TransformBy(tfm); undo.Stop(); } 

The method is called when the object changes, I do not understand 100% how it works, but it will be needed in order to correctly move the object.

 public override hresult OnEdit(Point3d pnt, EditFlags lInsertType) { CallForm(); return hresult.s_Ok; } 

The method will call our form (see the picture at the end of the article) at the moment when we double-click the ball.

  private void CallForm() { ListEditorForm frm = new ListEditorForm(this); frm.Lpredictions.Items.AddRange(predictions.ToArray()); frm.ShowDialog(); } 

Directly form call. We need the form in order to add or remove versions of the predictions that fall in the ball.

We created the ListEditorForm form class in advance and now, if necessary, create an object, passing it a link to our ball (needed for feedback), before calling the form, fill it with a ListBox with the current list of predictions.

  [CommandMethod("DFTBall", CommandFlags.NoCheck | CommandFlags.NoPrefix)] public void DrawBall() { Ball ball = new Ball(); ball.PlaceObject(); McContext.ShowNotification("Use green grip or shake (move) ball to get prediction"); } 

The team with the help of which we will create our fortune-telling ball.
In the simplest case, you will need to enter DFTBall in the NanoCAD console and it will call our DrawBall method (do not forget to load the library with the Netload command if necessary).

 public override bool GetGripPoints(GripPointsInfo info) { //first grip to move info.AppendGrip(new McSmartGrip<Ball>(_basePnt+new Vector3d(0, _radius,0), (obj, g, offset) => { obj.TryModify(); obj._basePnt += offset; obj.TryModify(); obj.ShakePredict(); })); //command grip var ctxGrip = new McSmartGrip<Ball>(McBaseGrip.GripType.PopupMenu, 2, _basePnt - 1.0 * new Vector3d(_radius, 0, 0), McBaseGrip.GripAppearance.PopupMenu, 0, "Select menu", Color.Lime); ctxGrip.GetContextMenu = (obj, items) => { items.Add(new ContextMenuItem("Get prediction", "none", 1)); items.Add(new ContextMenuItem("Edit predictions", "none", 2)); }; ctxGrip.OnCommand = (obj, commandId, grip) => { if (grip.Id == 2) { switch (commandId) { case 1: { ShakePredict(); break; } case 2: { CallForm(); break; } } } }; info.AppendGrip(ctxGrip); return true; } 

Here we set the handles of the object - blue and green.

The first - the blue handle is needed to move the object. Dragging the ball by the blue handle you can shake it and you will see how the line of predictions changes.

The second one is a green handle (// command grip section), we need to display a window with two commands. The first generates a new prediction, the second is the editor of the list of predictions.

  public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select center point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _basePnt = res.Point; // Add the object to the database DbEntity.AddToCurrentDocument(); return hresult.s_Ok; } 

This code is called to place an object in the model space. First, we create an InputJig object, request the insertion point through it, change the coordinates of our point of the geometric center of the ball, and add an object to the document.

  private void ShakePredict() { Random rand = new Random(); int val = rand.Next(0, predictions.Count); this.TryModify(); _predText = predictions[val]; } 

Well, here, using the simplest random number generator, we return some prediction from the general list.

We will not analyze in detail the logic of the form, I will hide the full code under the spoiler

class code ListEditorForm
 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; namespace Fortuneteller { public partial class ListEditorForm : Form { private Ball ball; public ListEditorForm() { InitializeComponent(); } public ListEditorForm(Ball ball) { this.ball = ball; InitializeComponent(); } private void listView1_SelectedIndexChanged(object sender, EventArgs e) { } private void DelBtn_Click(object sender, EventArgs e) { if (Lpredictions.SelectedItem !=null) { Lpredictions.Items.Remove(Lpredictions.SelectedItem); } } private void AdBtn_Click(object sender, EventArgs e) { if (textBox.Text!="" | textBox.Text != " ") { Lpredictions.Items.Add(textBox.Text); } } private void SaceBtn_Click(object sender, EventArgs e) { ball.predictions = Lpredictions.Items.OfType<String>().ToList(); this.Close(); } } } 


Perhaps the only thing that is somehow connected with NanoCAD is the event handler of the “save and close” button event.

  private void SaceBtn_Click(object sender, EventArgs e) { ball.predictions = Lpredictions.Items.OfType<String>().ToList(); this.Close(); } 

Remember we previously passed the form link to our ball? Now, turning to it we write all the values ​​of our ListBox to the list of predictions and close the form, after that the ball will start issuing updated predictions. If the form is closed by clicking on the "cross", the result will not be saved.

The code for the old - free Nanocad, will not be very different.

code for NanoCAD 5.1
 // Version 1.0 //Use Microsoft .NET Framework 3.5 and MultiCad.NET API //Class for demonstrating the capabilities of MultiCad.NET //Assembly for the Nanocad 5.1 SDK is recommended //Link mapimgd.dll and hostmgd.dll from SDK //Link System.Windows.Forms and System.Drawing //The commands: draws a fortune-teller ball //This code in the part of non-infringing rights Nanosoft can be used and distributed in any accessible ways. //For the consequences of the code application, the developer is not responsible. //More detailed - https://habrahabr.ru/post/347720/ using System; using System.Collections.Generic; using System.Windows.Forms; using System.Drawing; using Multicad.Runtime; using Multicad.DatabaseServices; using Multicad.Geometry; using Multicad.CustomObjectBase; using Multicad; using HostMgd.ApplicationServices; using HostMgd.EditorInput; namespace Fortuneteller { [CustomEntity(typeof(Ball), "2e814ea6-f1f0-469d-9767-269fedb32195", "Ball", "Fortuneteller Ball for NC51 Entity")] [Serializable] public class Ball : McCustomBase { private Point3d _basePnt = new Point3d(0, 0, 0); double _radius=300; string _predText = "..."; public List<String> predictions = new List<String>() {"Act now!", "Do not do this!", "Maybe", "I dont know", "Everything is unclear", "Yes!", "No!", "Take rest" }; public override void OnDraw(GeometryBuilder dc) { dc.Clear(); dc.Color = McDbEntity.ByObject; dc.DrawCircle(_basePnt, _radius); dc.DrawCircle(_basePnt, _radius/2.0); dc.TextHeight = 31; dc.DrawMText(_basePnt, Vector3d.XAxis, _predText, HorizTextAlign.Center, VertTextAlign.Center, _radius / 2.05); } public override void OnTransform(Matrix3d tfm) { // To be able to cancel(Undo) McUndoPoint undo = new McUndoPoint(); undo.Start(); // Get the coordinates of the base point and the rotation vector this.TryModify(); this._basePnt = this._basePnt.TransformBy(tfm); undo.Stop(); } public override hresult OnEdit(Point3d pnt, EditFlags lInsertType) { CallForm(); return hresult.s_Ok; } private void CallForm() { ListEditorForm frm = new ListEditorForm(this); frm.Lpredictions.Items.AddRange(predictions.ToArray()); frm.ShowDialog(); } [CommandMethod("DFTBall", CommandFlags.NoCheck | CommandFlags.NoPrefix)] public void DrawBall() { Ball ball = new Ball(); ball.PlaceObject(); DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; ed.WriteMessage("Use green grip or shake (move) ball to get prediction"); } public override bool GetGripPoints(GripPointsInfo info) { //frist grip to move info.AppendGrip(new McSmartGrip<Ball>(_basePnt+new Vector3d(0, _radius,0), (obj, g, offset) => { obj.TryModify(); obj._basePnt += offset; obj.TryModify(); obj.ShakePredict(); })); //command grip var ctxGrip = new McSmartGrip<Ball>(McBaseGrip.GripType.PopupMenu, 2, _basePnt - 1.0 * new Vector3d(_radius, 0, 0), McBaseGrip.GripAppearance.PopupMenu, 0, "Select menu", Color.Lime); ctxGrip.GetContextMenu = (obj, items) => { items.Add(new ContextMenuItem("Get prediction", "none", 1)); items.Add(new ContextMenuItem("Edit predictions", "none", 2)); }; ctxGrip.OnCommand = (obj, commandId, grip) => { if (grip.Id == 2) { switch (commandId) { case 1: { ShakePredict(); break; } case 2: { CallForm(); break; } } } }; info.AppendGrip(ctxGrip); return true; } public override hresult PlaceObject(PlaceFlags lInsertType) { InputJig jig = new InputJig(); // Get the first box point from the jig InputResult res = jig.GetPoint("Select center point:"); if (res.Result != InputResult.ResultCode.Normal) return hresult.e_Fail; _basePnt = res.Point; // Add the object to the database DbEntity.AddToCurrentDocument(); return hresult.s_Ok; } private void ShakePredict() { Random rand = new Random(); int val = rand.Next(0, predictions.Count); this.TryModify(); _predText = predictions[val]; } } } 


The whole difference is only in the following: due to the fact that McContext.ShowNotification (“Use green grip or shake (move) ball to get prediction”) is not yet implemented in the old version of the MultiCAD.NET API, we replaced it with an analog from simple .NET API.

  DocumentCollection dm = HostMgd.ApplicationServices.Application.DocumentManager; Editor ed = dm.MdiActiveDocument.Editor; ed.WriteMessage("Use green grip or shake (move) ball to get prediction"); 


So, in the end, we got a ball that can predict if it is dragged by the blue pen or at the command hidden in the green pen.

We also examined the simplest example of the interaction of a graphic form and an object by implementing the editing of a variable containing a list of predictions. Let me remind you that the form is invoked by double clicking on the object or through the green handle.
That's what got in the end

Nanocad 8.5



Nanocad 5.1 Free



It is clear that this example is comic and has no practical use, but I hope that it will still be useful to someone.

Have a great day everyone!

PS Just in case, I’ll warn you that the latest Windows 10 update breaks the x64 version of NanoCAD 8 a bit, so all the code was tested in x86 versions.

Source: https://habr.com/ru/post/347720/


All Articles