2D CAD Draw Application with C# Source Code
1
Have you ever wondered how a 2D CAD application
is designed and implemented? I have, so I decided to sit down and write one.
Now, implementing a fully functional 2D CAD
is a tremendous tucancode.net for a single person, and not something that can be
completed in just a couple of months when only working on it on and off in the
evenings. So, what I have implemented so far is the basic framework and only the
most basic tools, but it does demonstrate how a CAD
application could be implemented.
What this program demonstrates is:
-
Using world units and a coordinate system instead of screen units and a
coordinate system.
-
Grid layer and drawing
layers.
-
Zoom and pan.
-
Selection rectangle. Select enclosed objects (when moving left to right) or
select any partially enclosed objects (moving right to left).
-
Basic draw tool:
line, circle, and arc.
-
Basic edit tool. Join two lines, extend line to line.
-
Running snap, and quick snap. Snap is used to precisely attach a draw object
to another object.
-
Moving selected objects. Copy of selected object (for now, only when
moving).
Note! I am aware of several bugs in the code at this point, but these bugs are
related to the snap and selection tool, and do not affect the overall design.
2
A commonly used coordinate system when drawing
in CAD is the world coordinate system where
the origin is located at the lower left corner and the positive X direction is
right, and the positive Y direction is up, as opposed to the default screen
coordinates where the origin is upper left and the Y direction is down.
(Note, The correct name for the coordinate system is the Cartesian system, Wikipedia explains
the coordinate system.)
To keep a clear separation between the screen and the data units, I created the UnitPoint class.
All points used by any draw tools,
edit tools, snap points etc., are done using the unit point.
What is a unit? In this case, it doesn’t really matter what a unit is, it could
be an inch, a centimeter, a mile. It would matter only at the time of printing
if printing to scale, changing the base measurement, or when exporting and
importing to a library or another drawing
(neither of which are implemented yet).
3
Although this is a very simple and basic CAD
application, it still contains quite a bit of code, too much to
explain in details; instead, I will just explain the main interfaces and the
high level design.
These are the main interfaces:
-
public interface ICanvas
Provides the translation from unit points to screen points, and vice versa.
-
public interface IModel
Collection of all the data objects such as draw tool
objects, edit tool objects, and layers.
-
public interface ICanvasLayer
Contains the collection of draw objects
for the given layer.
-
public interface IDrawObject
Each draw tool
class must implement this interface.
-
public interface IEditTool
Each edit tool class must implement this interface.
-
CanvasCtrl
The CanvasCtrl is
where the actual drawing
to screen is done. As mentioned earlier, all drawing
objects use unit points for their location, and the canvas control provides
the translation between the unit point and the screen point. This control
also provides the panning and zooming functionality, which basically is just
a matter of offsetting the origin and scaling the unit/screen ratio.
If you look in the OnPaint code
for CanvasCtrl ,
you will notice that I use a bitmap for drawing
what I call the ‘static’ objects, where a static object is an existing
object that is not selected. The reason for this is to speed up the drawing
when drawing
a new object or moving existing objects. You can think of this static bitmap
as a background, where the static draw objects
are painted to the background (bitmap) only once or until the background is
invalidated which can be caused by several different events.
Then on top of this background is where the paint of the new or selected draw objects
happens. This way, only the area previously covered by the selected draw object
needs to be repainted, and since it has already beendrawn
to the bitmap, all that needs to happen is copy the invalidated area from
the bitmap to the screen.
Usually, it is recommended to call Invalidate(rectangle) on
a control when a repaint is required. This will cause a paint event to be
queued, and ‘after a while’, the repaint is performed. The other option is
to callRefresh() which
will cause the OnPaint to
be called immediately, but unfortunately, this API doesn’t allow to specify
the area that needs to be invalidated, so the entire control will have to be
repainted.
The Invalidate(rect) call
was causing the draw tool
to behave sluggish as the update would happen ‘a while after’ the mouse was
moved, and Refresh() was
causing a high CPU utilization since the entire area was being repainted
each time the mouse was moved. The solution for me was to implement a couple
of paint methods which does the paint immediately.
-
RepaintStatic(Rectangle r)
-
RepaintSnappoint(ISnapPoint snappoint)
-
RepaintObject(IDrawObject
obj)
The ICanvas interface
is not implemented directly by the CanvasCtrl but
instead by a wrapper classICanvasCtrlWrapper .
The reason for this is during OnPaint with
the static bitmap marked as dirty, two different Graphics are
used, and therefore, I need two different ICanvas objects.
The ICanvas – IModel design
supports multiple layers. The order the layers are drawn
in theCanvasCtrl is
shown here. The drawing
happens from the bottom up.
Collapse | Copy
Code
<active layer >
<draw layer n – if not the active layer >
…
<draw layer 0 – if not the active layer>
<grid layer>
<background>
-
DataModel : IModel
The DataModel is
where all the data lives. This, of course, includes all the drawing
layers and their drawing
objects, and also the background layer, grid layer, and all the draw and
edit tools.
Serializing data in and out are also done in the DataModel with Load(filename) andSave(filename) .
I chose to use XML format for the data file (.cadxml).
The reasons are that it is easier to verify data, and it allows you to
modify data without having the part of the GUI hooked up. E.g., the only way
to add or remove layers for now is to manually modify the cadxml file.
You have come to expect undo/redo from
even the simplest applications today, and this is also supported by the DataModel .
-
DrawingLayer : ICanvasLayer
There is not much to explain about this layer. This class contains the list
of draw objects
and a few properties such as line width, line color, and the enabled flag.
-
DrawTools and more
The following are also supported, and will be explained in more details:
-
Draw tools:
lines, two circle tools, and four arc tools.
-
Edit tools: join two lines, extend lines.
-
Running snap and quick snap.
4
One of the basic features you expect from a CAD
program is the ability to precisely snap one object to another when
drawing. The
simplest example is starting a new line at the exact endpoint of an existing
line. To find the snap point for the end point (vertex point) of a line is
simple, as it is simply one of the the two UnitPoint s
that defines the line. But, what if I want to snap to the center point of the
line, or I want to snap to the nearest point on the line from where the mouse is
located, or I want the line to snap to the tangent point on a circle – how can I
do that?
The answer is math and trigonometry.
Trigonometry is used:
-
By all draw tools
when calculating if the object is included in the selection. Each draw tool
must implement a
PointInObject(unitpoint) and ObjectInRectangle(…) ,
where PointInObject is
called when the selection is done by a mouse click, and ObjectInRectangle is
called when the selection is done by the selection rectangle – ‘rubberband’
selection.
-
Some tools use it while drawing.
E.g., the line tool draws
in Ortho mode when the control key is pressed. In this mode, the angle of
the line is limited to a 45 degree step.
-
All the snap point classes to calculate the snap point location.
-
All edit tools, e.g., the ‘2 Lines Meet’ tool calculates the intersection
points of the two selected lines and then move both lines' vertex points to
this intersection point.
Now, I didn’t remember any of my math or trigonometry when I started on this,
but I found a lot of good information available online, and I was even able to
find the exact equations needed for some of the tools.
I have kept all math utility methods in the HitUtil class.
I am aware of a couple of bugs in there, and will have to get back to fix some
of the methods and enhance others so they work correct with the Arc tool.
5
All draw tools
must implement the IDrawObject interface
which is used by the canvas control. The available draw
tools are registered in the data model, and referenced by
an ID. When a draw tool
is selected from the menu,CommandSelectDrawTool(string drawobjectid) is
called on the canvas. This sets the canvas into
drawing mode, and it is now ready to create a new draw tool
object when either the mouse is clicked or a quick snap is performed.
The canvas calls into the model m_model.CreateObject to
create a new tool of the given type. The model then finds the registered tool
object and returns a clone of the object. The reason for having the actual
object and not just the type registered is because some of the draw objects
(circle and arcs) has different modes (2 point, center – radius, and 3 point
modes), and so it is simpler to register two objects of the same class but with
the mode set differently than it is to create two derived classes which is
required if only the type was registered.
The draw tools
that I have implemented does support individual width and color, but the default
is to use the color and width inherited from the layer, and the flag to disable
this default behavior is not exposed in the GUI yet.
All draw tools
are in the DrawTools folder,
with each tool in its separate source file.
The draw tool
is considered active from the time it is created and until the draw with
the tool is complete. While the
draw tool is active, keyboard
and mouse events are forwarded to the tool. What determines when the tool is
complete is the return value of the OnMouseDown call.
This method can return:
-
Done , meaning the tool is complete and will be added to the data
model.
-
DoneRepeat , meaning the tool is complete, add it to the data and
create a new tool of the same type.
-
Continue , meaning the tool needs more input in order to complete.
6
When an existing object is selected, its nodes become available for edit. When
the mouse is clicked over a node, the selected object returns a node edit object
which implements INodePoint .
Like when the draw tool
is active, the canvas forwards mouse and key events into the node edit object,
and the node edit object is then responsible for modifying its owner (the draw object)
accordingly.
7
The edit tools are similar to the draw tools.
They must implement the IEditTool interface.
They are registered in the data model with an ID, and they are activated by
calling CommandEdit(string editid) on
the canvas.
8
Snaps are used while in drawing
mode to precisely ‘snap’ to another object. There are three types of snaps
supported, snap to grid, running snap, and quick snap.
-
Snap to grid is quiet obvious.
-
RunningSnap is the snap point that shows when the mouse is moved over an
existing object, e.g., you will notice when moving the mouser over the end
points of the center point of a line that the snap rectangle is shown.
Running snap can be toggled on and off with Ctrl + S.
-
QuickSnap is performed by a keyboard command. This snap is used to snap to
specific points on the object by a single letter command. E.g., if you want
to start a line from the nearest end point (vertex point) of an existing
line, you will move the mouse over the existing line and press ‘V’, which
will then find the nearest of the two end points and return that as the snap
point, and the new line will then use this location as its starting point.
So how does it work?
The type of snap point supported depends on the draw object,
so each draw object
is responsible for returning the snap object.
For RunningSnap, the canvas calls m_model.SnapPoint on
mouse move. The model finds the possible target objects from the mouse location,
and then calls obj.SnapPoint for
each of the target objects until a snap point is found.
QuickSnap is checked in the canvas on OnKeyDown .
First, it checks if the key has been registered for snap, and if it has, it
calls the model to get the snap point of the registered type.
Each snap point type is implemented as a class derived from SnapPointBase ,
and one of the parameters passed to the draw object's SnapPoint method
is a list of requested running snap types, and another parameter is the quick
snap type.
If you look at DrawTools.Line.SnapPoint ,
you will notice that for running snaps, it iterates through the types, and for
each type, checks if the mouse point is within the snap distance. Whereas for
QuickSnap, the snap point is calculated and returned regardless of the mouse
point.
The registration of the snap points is done in the main view, DocumentForm .
This is how the list of snap points is registered:
Collapse | Copy
Code
m_canvas.RunningSnaps = new Type[]
{
typeof(VertextSnapPoint),
typeof(MidpointSnapPoint),
typeof(IntersectSnapPoint),
typeof(QuadrantSnapPoint),
typeof(CenterSnapPoint),
typeof(DivisionSnapPoint),
};
m_canvas.AddQuickSnapType(Keys.N, typeof(NearestSnapPoint));
m_canvas.AddQuickSnapType(Keys.M, typeof(MidpointSnapPoint));
m_canvas.AddQuickSnapType(Keys.I, typeof(IntersectSnapPoint));
m_canvas.AddQuickSnapType(Keys.V, typeof(VertextSnapPoint));
m_canvas.AddQuickSnapType(Keys.P, typeof(PerpendicularSnapPoint));
m_canvas.AddQuickSnapType(Keys.Q, typeof(QuadrantSnapPoint));
m_canvas.AddQuickSnapType(Keys.C, typeof(CenterSnapPoint));
m_canvas.AddQuickSnapType(Keys.T, typeof(TangentSnapPoint));
m_canvas.AddQuickSnapType(Keys.D, typeof(DivisionSnapPoint));
All snap point classes are in SnapPoints.cs.
9
Undo / Redo is
done using the UndoRedoBuffer class
(located in Utils\Undo.cs).
Each undo-able
command must be derived from EditCommandBase .
The following commands have been implemented: Add, Remove, Move, NodeMove,
EditTool.
10
For now, any edits of layer settings have to be done by modifying the XML
directly.
11
Of course, I have a long list of GUI features and draw and
edit tools I would like to implement into this application, and they might get
implemented as time permits. These are all features required for the app to
actually be useful as a CAD application.
But, in addition to all the features, there are three bottlenecks in the current
design which should also be addressed if this app is to be used with any large
scale data.
-
The drawing
performance. While panning, the entire static image is redrawn
each time. This could be optimized so only the ‘new’ part of the image is
actually re-painted while the rest of the image is just moved. I have tried
with 20,000 lines, and on my computer, it takes about 130ms to redraw the
layer when all objects are visible, but I tried the same config on a
different computer where it took almost 400ms, enough to make the panning
appear sluggish.
-
Finding the objects for a given point, or objects within a given area. Right
now, this is done by iterating through the list of objects, which clearly
does not scale well if each layer contains thousands of objects. The
solution for this could be to use the R-tree.
But implementing such an algorithm would take too much of my time, so I
decided to ignore it for now.
-
Issue with the GDI when zooming in on a circle. I don’t know why this
happens, but I assume the GDI actually attempts to draw the
entire circle, even though only a tiny fraction of the circle is visible on
the screen.
12
This has been a fun and interesting little project to work on. I achieved my
objective, to find out how a 2D CAD application
could be designed. It took me about two months of on and off work in the
evenings to get this far, and I will probably continue adding features little by
little, but I have no illusions of being able to develop a full featured app on
my own as I simply do not have enough time to dedicate; instead you would need
to share the load, maybe in an open source community.
Now, if I could only figure out how a 3D app like Google SketchUp works!
CAD_Application_source_code.zip
If you want to develop very professional CAD application software, it is
recommended to use E-Form++ products directly.
Note:
UCanCode Advance E-XD++
CAD Drawing and Printing Solution
Source Code Solution for C/C++, .NET V2023 is released! The
VC++ source codes of this solution is shipped with E-XD++
Visualization Enterprise Edition,
order it now.
Contact UCanCode Software
To buy the source code or learn more about with:
Next-->
Promotional personalized database
document printing Solution
|
|