Introduction
I am on a project that needs to provide some way to draw a Floor Plan (something like AutoCAD, only not that complex), I tried many ways, but no good. I randomly read this article by Nicolas Dorier :
http://www.codeproject.com/Articles/637818/WPF-Geometries-the-Processing-way
it's for WPF, so I made some modification, it just works well.
GDL is short for Graphic Description Language, just something I made up, and I don't know whether there is already such thing.
Background
Nothing really hard, you should know something about Path.Data, 'cause GDL is similar to that, but with changes. The coordinate system stays unchanged no matter what commands are used. What is changed is the target point or the very figure. eg. If you use ROT command to rotate 30 degrees, only the target point will be rotated, not the coordinate system.
COMMANDS
All commands are case-insensitive, separators: space,tab,newline,comma are all acceptable. Separators must be provided between commands and params.
There are 5 kinds of commands.
- Move commands: M MT MH MV MC ROT
These commands will move current point to calculated point. If the next command is drawing command, it will start or centered at this point. - Line-Curve commands: L LT H V LC A AT CB CBT QB QBT
These commands will draw a line or a curve from current point to the calculated end point. Except LC command, current point will be set to the end point. - Shape commands: R E
These commands will draw a shape that centered at current point. Current point will stay un-moved. - Set commands: Z ZF CNT
These commands has no params. They do something to cooperate other commands. - Pre-processing commands: LOOP LOOPEND #
These commands are pre-processed unlike other commands.
The following are instructions of the commands, in those images, the squares of the grid is 20 by 20, and the left-upper corner is 0,0.
M offsetX, offsetY [,rotateAngle]
Move current point to target point offset to current point is offsetX, offsetY. If rotateAngle is given, rotation point will be current point.
MT X, Y
Move current point to X,Y
MH offsetX [,rotateAngle]
Equals to M offsetX, 0 [, rotateAngle]
MV offsetY [,rotateAngle]
Equals to M 0, offsetY [,rotateAngle]
MC
Move current point to rotate center wich is set by CNT command.
ROT rotateAngle
Move current point the target point that rotate center is set by CNT
e.g.
#Move to 100,100, then set it as rotation center, then move horizontally 100 unit(to 200,100)
#Then rotate 90 degrees (to 100,200)
MT 100,100 CNT MH 100 ROT 90
L offsetX, offsetY [,rotateAngle]
Draw a line from current to the target point offset to current point is offsetX,offsetY.
If rotateAngle is given, rotation point will be current point.
e.g.
#draw a line from 100,100 to 130
MT 100,100 L 100 100
LT X, Y
Draw a line from current point to X,Y
H length [,rotateAngle]
Equals to L length, 0 [, rotateAngle]
V length [,rotateAngle]
Equals to L 0, length [, rotateAngle]
LC
Draw a line from current point to rotate center set by CNT command
A offsetX, offsetY, radiusX, radiusY [, sweepDirection, isLargeArc, angle]
Draw a ellipse arc line from current point to target point offset to current point is offsetX,offsetY, and radiuses of ellipse is radiusX, radiusY.
If sweepDirection is set to 1, it will be counter-clockwise, otherwise clockwise.
If isLargeArc is set to 1, then it's true, otherwise false.
Param angle is for the ellipse to rotate, not target point
e.g.
MT 100,60 A 80 0 80 40 1 1
AT X, Y, radiusX, radiusY [, sweepDirection, isLargeArc, angle]
Draw a ellipse arc line from current point to X,Y, and radiuses of ellipse is radiusX, radiusY.
If sweepDirection is set to 1, it will be counter-clockwise, otherwise clockwise.
If isLargeArc is set to 1, then it's true, otherwise false.
Param angle is for the ellipse to rotate, not target point
CB offsetX, offsetY, controlOffsetX1, controlOffsetY1, controlOffsetX2, controlOffsetY2
Draw a cubic-bezier curve from current point to target point.
The offset from target point to current point is offsetX, offsetY.
The offset from the 1st control point to current point is controlOffsetX1, controlOffsetY1
The offset from the 2nd control point to current point is controlOffsetX2, controlOffsetY2
e.g.
#draw a cubic-bezier from 0,140 to 300,140. 1st control point is 100,300. 2nd control point is 200 ,0
MT 0 140 CB 300 0 100 150 200 -150
CBT X, Y, controlX1, controlY1 , controlX2, controlY2
Draw a cubic-bezier curve from current point to X,Y.
The 1st control point is
controlX1, controlY1
The 2nd control point is controlX2, controlY2
e.g.
#same as the example given in CB command
MT 0,140 CBT 300,140 100,300 200,0
QB offsetX, offsetY, controlOffsetX, controlOffsetY
Draw a quadratic-bezier curve from current point to target point.
The offset from target point to current point is offsetX, offsetY.
The offset from the control point to current point is controlOffsetX, controlOffsetY
e.g.
#draw a quadratic-bezier curve from 50,140 to 250,140 with control point 50,290
MT 50 140 QB 200 0 0 150
QBT X, Y, controlX, controlY
Draw a quadratic-bezier curve from current point to X,Y.
The control point is
controlX, controlY
e.g.
#same as the example given in QB command
MT 50,140 QBT 250,140 50,290
R width [, height, rotateAngle]
Draw a rectangle centered at current point. Rotate center is also the current point if rotateAngle is given.
e.g.
#draw a rectangle centered at 150,150m size 80 by 150, rotate 30 degrees
MT 150,150 R 80 150 30
#draw a square centered at 140,120 size 100 by 100
MT 140,120 R 100
E radiusX [, radiusY, rotateAngle]
Draw an ellipse centered at current point. Rotate center is also the current point if rotateAngle is given.
e.g.
#draw a ellipse centered at 150,150, size 80 by 150, roate 45 degrees
MT 150,150 E 80 150 45
#draw a circle with radius 100 centered at 120,140
MT 120,140 E 100
Z
Close current figure. The start point is set by the latest M commands(M, MC, MH, MV, MT, ROT) and the end point is current point. Current point will stay the same.
ZF
Same as Z command, but fill the area with color predefined(hard-coded).
CNT
Set current point as rotate center for ROT command
LOOP [count] .... LOOPEND
Pre-processed commands. Like macro, it will be expanded. If count is not given, loop will repeat once.
LOOP comand will try to find the very first LOOPEND command to be the end of the loop. If LOOPEND is not used, it will be treated as the end of the whole GDL.
Nested loops are not supported.
e.g.
MT 100,100 CNT MH 100 LOOP 3 ROT 30 LC LOOPEND MC E 50
equals to:
MT 100,100 CNT MH 100 ROT 30 LC ROT 30 LC ROT 30 LC MC E 50
Using the code
Proj. GDL in the downloaded solution it the core Silverlight DLL proj. It contains the whole processing code and a UserContol.
Proj. GDLEditor is a test silverlight proj. It runs like the first image of the article.
The very thought of processing way I use is from this article:
http://www.codeproject.com/Articles/637818/WPF-Geometries-the-Processing-way
I just made some changes to accommodate my needs.
If you downloaded the WPF version, these are the mainly issues that won't work in Silverlight:
- code in : ProcessingContext.FindOperations(string)
There is no IsDefined(Type) method for MethodInfo, IsDefined(Type,bool) instead
No GetCustomAttribute<T>() ext method for MethodInfo (sl just can't use the right dll for that), but GetCustomAttributes(Type,bool) instead)
This is my code for FindOperations(string name):
private List<CandidateMethod> FindOperations(string name)
{
return
this.GetType()
.GetMethods()
.Where(m => m.IsDefined(typeof(GDLCommandAttribute), false))
.Where(m => m.GetCustomAttributes(typeof(GDLCommandAttribute), false)
.OfType<GDLCommandAttribute>()
.First()
.MatchCommand(name))
.Select(m => new CandidateMethod(this)
{
Method = m,
ParameterValues = new List<object>()
})
.OrderByDescending(o => o.Method.GetParameters().Length)
.ToList();
;
}
- BezierSegment, TranslateTransform and some other classes only have default constructor, modifications are needed, it's only easy
- There is no GetOutlinedPathGeometry in Geometry class, pain in the ass really. So the ExtractPath method is not valid.Instead of calling ExtractPath method, the method Rectangle(x,x,x,x) itself does the four-line drawing (it draw 4 lines to form a rect in a new PathFigure) as a whole, and Ellipse use 2 ArcSegments to form an ellipse.
- Transform has no Identity property, so I wrote a GetIdentityTransform() helper method to directly generates the matrix.
- Transform has no Value property for the matrix, I googled how to extract the matrix
I put the transform object into a TransformGroup object, and then I got the matrix Value property. Wrapped it into an ext method for Tranform class.
There is no method given to mix two matrix up, so convert them to MatrixTransform, and mix them in a TransformGroup object, and then get the result matrix. The two matrix should be added in particular order.
public static Matrix AppendMatrix(Matrix m1, Matrix m2)
{
var mt1 = new MatrixTransform() { Matrix = m1 };
var mt2 = new MatrixTransform() { Matrix = m2 };
var tg = new TransformGroup();
tg.Children.Add(mt1);
tg.Children.Add(mt2);
return tg.Value;
}
GDLCommandAttribute above all commands can provide multiple strings for a same command function.
eg.
[GDLCommand("M","MOV","MOVE")]
public void Move()
{
....
}
Processing Flow :
- Using GDLGeometry.CleanGDL method to remove all comments, extra separators, and expand loops.
- Break up GDL into a string queue.
- Process each element in the queue and try to find the target method or to put it into a method
- Invoke the method
- Go back to step 3 until all the queue is empty.
Points of Interest
Thanks to Nicolas Dorier , I learnd how to make something that has a "design mode", and also a way to find method overload by a string.
History