After talking about the architecture of our JavaScript API in this recent post – and mentioning the approach we expect developers to take when creating geometry in one of the comments – I thought it would be worth spending the time to write my first JavaScript jig in AutoCAD.
For those of you who haven’t come across jigs in either ObjectARX or AutoCAD .NET, jigs are the primary way applications request users to dynamically specify geometric parameters while providing them with graphical feedback. As users select points or specify distances, the object or objects they’re creating are updated in real-time to reflect these choices. Much as you’d use a standard AutoCAD command to create geometry.
If you want to define a jig for a single object using .NET, you’ll probably use an EntityJig. The other main type of jig – a DrawJig – is not bound to a single object, although it can be used to create one. It basically allows the application to decide what gets drawn, rather than calling a particular object’s drawing methods.
Given the fact the JavaScript API doesn’t provide access to AutoCAD drawing objects – and there are currently no plans to provide this access – it made sense to expose the DrawJig capability to JavaScript. This allows applications to display custom, transient graphics during the jig’s operation, and then fire one or more AutoCAD commands to generate the drawing object(s) once done. Given there’s some IPC needed to communicated between the process hosting JavaScript and AutoCAD, you clearly don’t want to be marshaling huge objects (such as we saw in this recent post): not only would the XML generated to communicate the transient graphics to be displayed be large, so would the command-string you’d need to compose and send to generate the final database-resident representation.
So with that in mind, I set about creating what’s probably the simplest of jigs: to create a circle at a given point, asking the user to select the radius.
Here’s the JavaScript code I created, embedded into an HTML page to be loaded into a palette, based on the one we saw in this previous post:
<html>
<head>
<title>Jig a circle</title>
<script
type="text/javascript"
src="http://www.autocadws.com/jsapi/v1/Autodesk.AutoCAD.js">
</script>
<script type="text/javascript">
var doc = Acad.Application.activedocument;
var center = new Acad.Point3d(0, 0, 0);
var radius = 0;
var trId;
var colors = new Array();
colors['Red'] = "#ff0000";
colors['Yellow'] = "#ffff00";
colors['Green'] = "#00ff00";
colors['Cyan'] = "#00ffff";
colors['Blue'] = "#0000ff";
colors['Magenta'] = "#ff00ff";
colors['White'] = "#ffffff";
function pointToString(pt) {
var ret =
pt.x.toString() + "," +
pt.y.toString() + "," +
pt.z.toString();
return ret;
}
function createCircle(cen, rad, first) {
// Build an XML string containing data to create
// an AcGiTransient that represents the circle
var props = '';
var cursor = '';
if (first != true) {
var color = document.getElementById('ColCB').value;
var linetype = document.getElementById('LTCB').value;
var lineweight = document.getElementById('LWCB').value;
var filled = document.getElementById('Filled').checked;
props =
'color="' + colors[color] +
'" lineweight="' + lineweight +
'" linetype="' + linetype +
'" filled="' + filled + '"';
var cursorType = document.getElementById('Cursor').value;
if (cursorType == 'None')
cursor = '';
else
cursor = ' cursor="' + cursorType + '"';
}
var drawable =
'<?xml version="1.0" encoding="utf-8"?>' +
'<drawable ' +
'xmlns="http://www.autodesk.com/AutoCAD/drawstream.xsd" ' +
'xmlns:t="http://www.autodesk.com/AutoCAD/transient.xsd" '+
't:onmouseover="onmouseover"' + cursor + '>' +
'<graphics id="id1" ' + props + '>' +
'<circle center ="' + pointToString(cen) +
'" radius ="' + rad.toString() + '"/>' +
'</graphics></drawable>';
return drawable;
}
function circleJig() {
function onJigUpdate(args) {
var res = JSON.parse(args);
if (res) {
// The value being updated is the distance
radius = res.distance;
// Use it to create the XML for a transient
// circle and ask for it to be displayed
var x = createCircle(center, radius);
doc.transientManager.updateTransient(trId, x);
}
return true;
}
function onJigComplete(args) {
// When the jig is over, remove the transient
doc.transientManager.eraseTransient(trId);
var res = JSON.parse(args);
if (res && res.dragStatus == Acad.DragStatus.kNormal) {
Acad.Editor.executeCommandAsync(
'_.CIRCLE ' + pointToString(center) + ' ' + radius
);
}
}
function onJigError(args) {
write('\nUnable to create circle: ' + args);
}
function onPointSelected(args) {
var res = JSON.parse(args);
if (res) {
center =
new Acad.Point3d(
res.value.x,
res.value.y,
res.value.z
);
// Now we can create our transient
var tran = new Acad.Transient();
trId = tran.getId();
// And ask for it to be drawn
var x = createCircle(center, 0, true);
doc.transientManager.addTransient(tran, x);
// Set up our jig options
var opts =
new Acad.JigPromptDistanceOptions('Point on radius');
opts.basePoint = center;
opts.useBasePoint = true;
// Run the jig to select the distance
var jig = new Acad.DrawJig(onJigUpdate, opts);
Acad.Editor.drag(jig).then(onJigComplete, onJigError);
}
}
function onPointError(args) {
write('\nUnable to select point: ' + args);
}
// Ask the user to select the center point before we
// start the jig
var opts = new Acad.PromptPointOptions('Select center');
Acad.Editor.getPoint(opts).then(
onPointSelected, onPointError
);
}
</script>
<style type="text/css">
td, body
{ font-family: sans-serif; font-size: 10pt; }
body
{ background-color: #686868;
padding:0;
margin:5px 5px 5px 5px;
color:#FFF; }
textarea
{ font-family: Consolas; font-size: 8pt; }
</style>
</head>
<body>
<div id="canvas" class="shadow_box">
<h3>Jig a circle</h3>
<table border="0">
<tr>
<td align='right'>Color</td>
<td>
<select id='ColCB'>
<option selected="selected">Red</option>
<option>Yellow</option>
<option>Green</option>
<option>Cyan</option>
<option>Blue</option>
<option>Magenta</option>
<option>White</option>
</select>
</td>
</tr>
<tr>
<td align='right'>LineType</td>
<td>
<select id='LTCB'>
<option selected="selected">LineTypeSolid</option>
<option>Dashed</option>
<option>Dotted</option>
<option>Dash_Dot</option>
<option>Short_Dash</option>
<option>Medium_Dash</option>
<option>Long_Dash</option>
<option>Short_Dash_X2</option>
<option>Medium_Dash_X2</option>
<option>Long_Dash_X2</option>
<option>Medium_Long_Dash</option>
<option>Medium_Dash_Short_Dash_Short_Dash</option>
<option>Long_Dash_Short_Dash</option>
<option>Long_Dash_Dot_Dot</option>
<option>Long_Dash_Dot</option>
<option>Medium_Dash_Dot_Short_Dash_Dot</option>
<option>Sparse_Dot</option>
<option>Solid_6_Pixels_Blank_6_Pixels</option>
</select>
</td>
</tr>
<tr>
<td align='right'>LineWeight</td>
<td>
<select id='LWCB'>
<option>0</option>
<option>5</option>
<option>9</option>
<option>13</option>
<option>15</option>
<option>18</option>
<option>20</option>
<option>25</option>
<option selected="selected">30</option>
<option>35</option>
<option>40</option>
<option>50</option>
<option>53</option>
<option>60</option>
<option>70</option>
<option>80</option>
<option>90</option>
<option>100</option>
<option>106</option>
<option>120</option>
<option>140</option>
<option>158</option>
<option>200</option>
<option>211</option>
</select>
</td>
</tr>
<tr>
<td align='right'>Filled</td>
<td>
<input type='checkbox' id='Filled'/>
</td>
</tr>
<tr>
<td align='right'>Cursor</td>
<td>
<select
id='Cursor'
title='Cursor to be displayed on hover'>
<option selected="selected">None</option>
<option>None</option>
<option>Arrow</option>
<option>Ibeam</option>
<option>Wait</option>
<option>Cross</option>
<option>UpArrow</option>
<option>SizeNWSE</option>
<option>SizeNESW</option>
<option>SizeWE</option>
<option>SizeNS</option>
<option>SizeAll</option>
<option>No</option>
<option>Hand</option>
<option>AppStarting</option>
<option>Help</option>
</select>
</td>
</tr>
<tr>
<th> </th>
<td>
<input
type='button'
onclick='circleJig()'
value='Jig Circle' />
</td>
</tr>
</table>
</div>
</body>
</html>
When we load and run the palette – using the same .NET loader we saw in the post that first showed how to implement a palette using JavaScript – we see we can use it to launch our jig and create circles. Bear in mind that the settings in the palette are only respected during the jig: the CIRCLE command will simply pick up the current layer, linetype etc. when it creates the final circle.
The jig doesn’t operate as smoothly as you’d expect it to from ObjectARX or .NET, but then there is some marshaling overhead involved, after all. You’ll have to weigh the trade-offs between (future) portability, development effort and performance. (It’s also not clear it’s easier to develop using JavaScript than .NET… it’s probably easier than C++ but the tools available to debug your JavaScript code are still not that great).
Also, I found some trickiness when selecting the point to terminate the jig which then causes the command to be launched inside AutoCAD. If we comment out the code to display transient graphics it works perfectly, but otherwise you may have to click a few times to make it work. This is something that requires some further research – it feels as though some messages are getting swallowed, but it could be due to something else entirely.
Anyway, this is to say that there are still some rough edges around this API, which is natural given its relative age. My recommendation is to play around with it, but not to over-invest just yet: use the opportunity to get familiar with the tools and technology, and you’ll hopefully see us make the experience smoother over the next few releases.