QCAD
Open Source 2D CAD
 All Classes Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
Library Browser Scripts

The new QCAD Part Library Browser can not only contain static part library items but also dynamic items that are created based on parameters, mathematical formulas, user input, SQL data bases, XML files or other data sources. A dynamic item is rendered when the item is being inserted.

Script items may display a custom user interface component for the user to enter script parameters. For example, an item that shows the top view of a dining table might allow the user to input the length and width of the table.

The part library included with QCAD contains such a sample script item called "DiningTable.js" located under "library/misc".

Creating a Dynamic Part Library Item

In this tutorial we will create a new dynamic item for the library browser. The item should generate a cut-out template for a cube as shown here:

cube_template.png

The dynamic item has two parameters which can be specified by the user when the script item is being inserted into the drawing:

  • The length of the cube's edge (in drawing units).
  • If the glue flaps should be generated or not (true or false).

First, we create a new script file that will do the work behind our new dynamic part library item:

  1. Create a new directory for your script file(s). This directory can be created inside the part library directory of your QCAD installation or anywhere else on your hard drive.
  2. Start you favorite text editor and create a new text file.
  3. Save the file in the newly created directory as "CubeCuttingOut.js".
    Note that the extension has to be ".js" since we're about to write an ECMAScript (JavaScript) file.
  4. If you have added the directory and script to the library folder of your QCAD installation, you can skip the following step.
  5. Add the newly created directory to the library browser sources:
    1. Open the QCAD Library Browser.
    2. Open the application preferences of the Library Browser.
    3. In section Library Sources click Add and choose the directory you have created in step 1.
    4. Restart the QCAD Library Browser for the changes to take effect.
    5. The library browser now has an additional source for library browser items.

The Script Structure

Copy / paste the following code into your script file 'CubeCuttingOut.js':

function CubeCuttingOut() {
}
CubeCuttingOut.init = function(formWidget) {
};
CubeCuttingOut.generate = function(documentInterface, file) {
};
CubeCuttingOut.generatePreview = function(documentInterface, iconSize) {
};

This adds a class named CubeCuttingOut to the script file. Note that the class must have the same name as the script file (case sensitive).

Besides the constructor, three functions are required to make this a valid, functional script item:

  • init(formWidget)
    The function init() is called whenever the preview icon (see below) is generated or when the script item is being inserted into a drawing.
    The parameter formWidget is the widget that displays the script item's parameters (if applicable).
  • generate(documentInterface, file)
    The function generate() is called when the user is about to insert the script item.
    The parameter documentInterface is a valid document interface (RDocumentInterface) that is used to create the library item. This is not the document the user is editing. The part library script has no direct access to the drawing of the user.
    The parameter file is the name of the current script file (String).
    This function is expected to return an object of type RAddObjectsOperation.
  • generatePreview(documentInterface, iconSize)
    The function generatePreview() is called to create an icon for the library browser preview. Note that at the moment the preview icon is generated, no user input is available. Usually an icon with default parameters is generated.
    The parameter documentInterface is a valid document interface (RDocumentInterface).
    The parameter iconSize is the user configurable size of the icon (integer).
    This function is expected to return an object of type RAddObjectsOperation.

The script item is now valid. Locate your new library folder in the 'File System' tab of the QCAD Library Browser. Right-click on the folder and click 'Regenerate Icons'. Your item should show up with an empty icon that is decorated with a small cog wheel to indicate that this is a dynamic library item backed by a script file.

Implementation

In most cases, the functions generate() and generatePreview() do almost the same. The main difference between generate() and generatePreview() is that generate() works with user input to define the script parameters, while generatePreview() has no user input and should generate the item with default parameters to create a recognizable icon.

It is usually recommendable to write a helper function that creates the RAddObjectsOperation object that is returned by both functions based on the script parameters.

For this example, we name that function createCuttingOut():

CubeCuttingOut.createCuttingOut = function(documentInterface) {
CubeCuttingOut.size = 10;
var va = new Array(
new RVector(0, 0),
new RVector(0, CubeCuttingOut.size),
new RVector(CubeCuttingOut.size, 0)
);
var addOperation = new RAddObjectsOperation(false);
for ( var i = 0; i < va.length; ++i) {
var lineData = new RLineData(va[i], va[(i + 1) % va.length]);
var line = new RLineEntity(documentInterface.getDocument(), lineData);
addOperation.addObject(line);
}
return addOperation;
};

For now, the size of the cube is fixed to 10 drawing units (variable CubeCuttingOut.size). We will later use CubeCuttingOut.size as an input parameter for our helper function.

Based on the cube size, the helper function generates the CAD entities that represent a square. These entities are added to the operation that will be applied to the document that represents the item.

In function generate(), we simply call our helper function:

CubeCuttingOut.generate = function(documentInterface, file) {
return CubeCuttingOut.createCuttingOut(documentInterface);
};

The script is now functional but does not display an icon and only creates one single square with a fixed size.

Save the modified script file and then drag-n-drop the script item from the library browser into the drawing area.

When moving the mouse cursor inside the drawing area, a square with a size of 10 drawing units is shown. Left-click to place the square somewhere in your drawing. Note that you may also specify a scale factor and rotation angle or flip the item in the options tool bar. These are standard operations available for all items that are being inserted, including dynamic items.

lb01.png

We now modify the script to create the full cut-out template for the cube:

CubeCuttingOut.init = function(formWidget) {
if (!isNull(formWidget)) {
CubeCuttingOut.widgets = getWidgets(formWidget);
}
};
CubeCuttingOut.createCuttingOut = function(documentInterface) {
var addOperation = new RAddObjectsOperation(false);
// create squares
for ( var i = 0; i < 4; ++i) {
var pos = new RVector(i * CubeCuttingOut.size, 0);
CubeCuttingOut.createSquare(documentInterface, addOperation, pos);
}
var posTop = new RVector(CubeCuttingOut.size * 2, CubeCuttingOut.size);
CubeCuttingOut.createSquare(documentInterface, addOperation, posTop);
var posBottom = new RVector(CubeCuttingOut.size * 2, -CubeCuttingOut.size);
CubeCuttingOut.createSquare(documentInterface, addOperation, posBottom);
// create plates
if (CubeCuttingOut.drawPlates) {
var plates = new Array(
[ new RVector(4 * CubeCuttingOut.size, 0), 0 ],
[ new RVector(2 * CubeCuttingOut.size, -3 * CubeCuttingOut.size), 90 ],
[ new RVector(1 * CubeCuttingOut.size, -4 * CubeCuttingOut.size), 90 ],
[ new RVector(-2 * CubeCuttingOut.size, -2 * CubeCuttingOut.size), 180 ],
[ new RVector(-2 * CubeCuttingOut.size, 0), 180 ],
[ new RVector(1 * CubeCuttingOut.size, 2 * CubeCuttingOut.size), 270 ],
[ new RVector(0, 3 * CubeCuttingOut.size), 270 ]
);
for ( var i = 0; i < plates.length; ++i) {
var pos = plates[i][0];
var angle = RMath.deg2rad(plates[i][1]);
CubeCuttingOut.createPlate(documentInterface, addOperation, pos, angle);
}
}
return addOperation;
};
CubeCuttingOut.createSquare = function(documentInterface, addOperation, pos) {
var va = new Array(
new RVector(0, 0),
new RVector(0, CubeCuttingOut.size),
new RVector(CubeCuttingOut.size, 0)
);
for ( var i = 0; i < va.length; ++i) {
var v1 = va[i].operator_add(pos);
var v2 = va[(i + 1) % va.length].operator_add(pos);
var lineData = new RLineData(v1, v2);
var line = new RLineEntity(documentInterface.getDocument(), lineData);
addOperation.addObject(line);
}
};

Providing Script Parameters

For the user to enter script parameters, we need to define a user interface component (widget). We use Qt Designer to design the user interface for this widget. Qt Designer is available for free as part of the Qt SDK or Qt Creator: http://qt.nokia.com/downloads

The UI file must have the same name as the script file but with the extension .ui.

  1. Start the Qt Designer.
  2. Create a new file and choose Widget as template.
  3. Add an element of type QLineEdit, set its object name to "CubeSize" and its value to "10".
  4. Add an element of type QCheckBox, set its object name to "DrawPlates" and set its checked flag.

You may also want to add some labels to indicate to the user what is being defined where. In the end, the widget may for example look like this:

widget.png

Save the UI file as "CubeCuttingOut.ui". If you don't have Qt Designer, you can find the file source at the bottom of this page.

The library browser will now display that user interface component whenever this script item is being inserted.

All the script item implementation has to do is to get the script parameters from the user interface component:

// library.js contains some convenience functions like 'isNull':
include("scripts/library.js");
CubeCuttingOut.init = function(formWidget) {
if (!isNull(formWidget)) {
CubeCuttingOut.widgets = getWidgets(formWidget);
}
};
CubeCuttingOut.generate = function(documentInterface, file) {
CubeCuttingOut.size = parseInt(CubeCuttingOut.widgets["CubeSize"].text, 10);
if (isNaN(CubeCuttingOut.size)) {
// can't parse value as integer, set default size
CubeCuttingOut.size = 10;
}
if (CubeCuttingOut.widgets["DrawPlates"].checked) {
CubeCuttingOut.drawPlates = true;
} else {
CubeCuttingOut.drawPlates = false;
}
return CubeCuttingOut.createCuttingOut(documentInterface);
};

Save the script file again, and insert the script item from the library browser into your drawing. The cut-out template is still drawn with a size of 10. As soon as you enter a different value in the user interface component, the cut-out template is drawn with that size.

The script item parameter Draw plates can be handled in the same way:

CubeCuttingOut.generate = function(documentInterface, file) {
CubeCuttingOut.size = parseInt(CubeCuttingOut.widgets["CubeSize"].text, 10);
if (isNaN(CubeCuttingOut.size)) {
// can't parse value as integer, set default size
CubeCuttingOut.size = 10;
}
if (CubeCuttingOut.widgets["DrawPlates"].checked) {
CubeCuttingOut.drawPlates = true;
} else {
CubeCuttingOut.drawPlates = false;
}
return CubeCuttingOut.createCuttingOut(documentInterface);
};

Providing a Script Preview

Finally, we provide a script preview that is shown as icon in the library browser. For that, we simply set some appropriate default values as script item parameters and call our helper function.

CubeCuttingOut.generatePreview = function(documentInterface, iconSize) {
CubeCuttingOut.size = iconSize / 6;
CubeCuttingOut.drawPlates = true;
return CubeCuttingOut.createCuttingOut(documentInterface);
};

The library browser icons are updated on every start of QCAD. You can also right-click on a directory in the 'File System' tab and choose Regenerate Icons to rebuild the icons in that directory.

lb02.png

The Complete Script and User Interface Sources

// CubeCuttingOut.js
// library.js contains some convenience functions like 'isNull':
include("scripts/library.js");
function CubeCuttingOut() {
CubeCuttingOut.size = 10;
CubeCuttingOut.drawPlates = true;
}
CubeCuttingOut.init = function(formWidget) {
if (!isNull(formWidget)) {
CubeCuttingOut.widgets = getWidgets(formWidget);
}
};
CubeCuttingOut.generate = function(documentInterface, file) {
CubeCuttingOut.size = parseInt(CubeCuttingOut.widgets["CubeSize"].text, 10);
if (isNaN(CubeCuttingOut.size)) {
// can't parse value as integer, set default size
CubeCuttingOut.size = 10;
}
if (CubeCuttingOut.widgets["DrawPlates"].checked) {
CubeCuttingOut.drawPlates = true;
} else {
CubeCuttingOut.drawPlates = false;
}
return CubeCuttingOut.createCuttingOut(documentInterface);
};
CubeCuttingOut.generatePreview = function(documentInterface, iconSize) {
CubeCuttingOut.size = iconSize / 6;
CubeCuttingOut.drawPlates = true;
return CubeCuttingOut.createCuttingOut(documentInterface);
};
CubeCuttingOut.createCuttingOut = function(documentInterface) {
var addOperation = new RAddObjectsOperation(false);
// create squares
for ( var i = 0; i < 4; ++i) {
var pos = new RVector(i * CubeCuttingOut.size, 0);
CubeCuttingOut.createSquare(documentInterface, addOperation, pos);
}
var posTop = new RVector(CubeCuttingOut.size * 2, CubeCuttingOut.size);
CubeCuttingOut.createSquare(documentInterface, addOperation, posTop);
var posBottom = new RVector(CubeCuttingOut.size * 2, -CubeCuttingOut.size);
CubeCuttingOut.createSquare(documentInterface, addOperation, posBottom);
// create plates
if (CubeCuttingOut.drawPlates) {
var plates = new Array(
[ new RVector(4 * CubeCuttingOut.size, 0), 0 ],
[ new RVector(2 * CubeCuttingOut.size, -3 * CubeCuttingOut.size), 90 ],
[ new RVector(1 * CubeCuttingOut.size, -4 * CubeCuttingOut.size), 90 ],
[ new RVector(-2 * CubeCuttingOut.size, -2 * CubeCuttingOut.size), 180 ],
[ new RVector(-2 * CubeCuttingOut.size, 0), 180 ],
[ new RVector(1 * CubeCuttingOut.size, 2 * CubeCuttingOut.size), 270 ],
[ new RVector(0, 3 * CubeCuttingOut.size), 270 ]
);
for ( var i = 0; i < plates.length; ++i) {
var pos = plates[i][0];
var angle = RMath.deg2rad(plates[i][1]);
CubeCuttingOut.createPlate(documentInterface, addOperation, pos, angle);
}
}
return addOperation;
};
CubeCuttingOut.createSquare = function(documentInterface, addOperation, pos) {
var va = new Array(
new RVector(0, 0),
new RVector(0, CubeCuttingOut.size),
new RVector(CubeCuttingOut.size, 0)
);
for ( var i = 0; i < va.length; ++i) {
var v1 = va[i].operator_add(pos);
var v2 = va[(i + 1) % va.length].operator_add(pos);
var lineData = new RLineData(v1, v2);
var line = new RLineEntity(documentInterface.getDocument(), lineData);
addOperation.addObject(line);
}
};
CubeCuttingOut.createPlate = function(documentInterface, addOperation, pos, angle) {
var plateSize = CubeCuttingOut.size / 7;
var off = plateSize * Math.sqrt(2);
var va = new Array(
new RVector(0, 0),
new RVector(off, off),
new RVector(off, CubeCuttingOut.size - off),
new RVector(0, CubeCuttingOut.size)
);
for ( var i = 0; i < va.length; ++i) {
var v1 = va[i].operator_add(pos);
v1 = v1.rotate(angle);
var v2 = va[(i + 1) % va.length].operator_add(pos);
v2 = v2.rotate(angle);
var lineData = new RLineData(v1, v2);
var line = new RLineEntity(documentInterface.getDocument(), lineData);
addOperation.addObject(line);
}
};
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>239</width>
<height>76</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Cube size:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="CubeSize">
<property name="text">
<string>10</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QCheckBox" name="DrawPlates">
<property name="text">
<string>Draw plates</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>