Sunday, February 28, 2010

How To Use NaroCAD To View A Custom Shape (Part I)

NaroCAD is build on top of OpenCascade library and it tries to make not only a wrapping over OpenCascade but also to simplify for .NET folks, like in workflow patterns like adding a new shape.

For this you will need to break down your shape in the following parts that will be described bellow:
- persistence (part I)
- function (part II)
- (meta)action (part III)

Persistence
NaroCAD keeps all data you view in a tree that can be saved and restored from xml. If your custom shape needs more than: a real (a double value), a color, a 3D point, another shape, or other defaults that NaroCAD provides, you will need to define your custom data that NaroCAD will save in the Document's tree.
Here I will define a Point2D class that is not defined by NaroCAD and someone may not want to use Point3D as a substitute for Point2D.
All objects that does keep custom data are named in NaroCAD's code language as Interpreters (or Attribute Interpreters) that make you store custom data. Those interpreters have to do two things: to inherit from AttributeInterpreterBase and to implement methods: Serialize and Deserialize.

public class Point2DInterpreter : AttributeInterpreterBase
{
public Point2DInterpreter() : base(){}
double X { get; private set; }
double Y { get; private set; }
public void SetCoordinate(double x, double y)
{
X = x;
Y = y;
OnModified();
}

public override void Serialize(AttributeData data)
{
data.WriteAttribute("X", X);
data.WriteAttribute("Y", Y);
}

public override void Deserialize(AttributeData data)
{
X = data.ReadAttributeDouble("X");
Y = data.ReadAttributeDouble("Y");
OnModified();
}

}

Some questions may occur on that custom code, like: what is OnModified? The constructor is mandatory to be defined? What is "AttributeData data" parameter defined in Serialize/Deserialize methods?
Let's answer one by one:
- OnModified() call announce NaroCAD's framework to make updates because your value was changed. If your Point2D was a part of a Line2D shape, the Line2D will get notified that it should generate the line
- The costructor is the default one and may not need to be defined (you can remove the line of constructor in the previous example) but you may use it to define your defaults there
- data variable is used to save and restore your data that defines your custom interpreter. When you define your Serialize method, you should save (write) all things that define your interpreter and you should do the load (read) at the Deserialize method. It is guaranteed that Serialize will be called always before Deserialize and the data will be always setup by a Serialize method of the same class. Deserialize call OnModify just because this Deserialize is mostly called on two events: Undo step (or Redo one) or on opening a file. Which makes sense to notify about changes to apply all on view
Before loading: AttributeInterpreterFactory.Register<Point2DInterpreter>(); should be called once. This is because this type may not be known at the runtime because .NET use lazy loading of code at least in the NaroCAD's code. This code is under PartModelling/DefaultInterpreters.cs so you may need to add this line there.

How you access attributes?
You may get access to two entities: a Document or a Node from that tree.
If you start with the Document, you can obtain the root node by: Document.Root property.
Any Node may have any number of AttributeInterpreters, and they can be either queried by using:
Point2DInterpreter aPoint = aNode.Get
<Point2DInterpreter>();
if(aPoint != null) //if there is an already setup point
{
aPoint.SetCoordinate(aPoint.X, aPoint.Y+10); //move the point 10 units over Y axis
}
If you will want just to setup or create in place, use directly Set method of node like following:
Point2DInterpreter aPoint = aNode.Set<Point2DInterpreter>(); //get the current Point2D
//or creates a new one of none exist
aPoint.SetCoordinate(aPoint.X, aPoint.Y+10); //move the point 10 units over Y axis

As you see the null test is not needed in those cases.

The following part in this mini series it will show how to create a custom OpenCascade shape that will be visible by adding a lua command to enable it.

No comments: