Open Source 2D CAD
Creating a New Tool

A good point to start QCAD script development is to create your own simple CAD tool that adds a menu and / or tool button to the QCAD user interface and handles the user interaction to do something.

In this example, we create a drawing tool which creates three point entities with one click. We call our tool "ExThreePoints" (Ex for example).

Quite often, we can start by copying a similar tool that exists already. However, in this case, we start from scratch.

Create a directory for our new tool. For this example, we create a directory with the name "ExThreePoints". We place our tool inside the DrawExamples directory "scripts/Misc/Examples/DrawExamples", so that the path for our new tool becomes: "scripts/Misc/Examples/DrawExamples/ExThreePoints" (relative to the QCAD installation directory).

Inside this directory, create an empty text file called "ExThreePoints.js". You can also create an icon for the tool in SVG format and call it "ExThreePoints.svg".

Each tool in QCAD is bundled this way inside its own directory.

The tool directory contains the tool implementation ("ExThreePoints.js"), the tool icon ("ExThreePoints.svg"), an optional Qt project file which is mainly used to handle translations ("ExThreePoints.pro") and all other resources that are exclusively used by this tool (user interface definitions, icons, script files, etc.).

Inside the file "ExThreePoints.js", define a class with the same name as the tool: "ExThreePoints".

The name of the main class of a tool always has to match the tools directory name (in this case "ExThreePoints").

A tool class is usually derived from the class that is defined in its parent directory or from EAction, defined in scripts/EAction.js. For that reason, we have to include the script file of EAction. In this case "scripts/EAction.js":

1 include("scripts/EAction.js");

Then, we define the class constructor, which calls the base class constructor:

1 /**
2  * \class ExThreePoints
3  * \ingroup ecma_misc_examples_draw
4  */
5 function ExThreePoints(guiAction) {
6  EAction.call(this, guiAction);
7 }

Finally, we derive our class from the base class, in this case "DrawExamples":

1 ExThreePoints.prototype = new EAction();

When QCAD finds the tool directory "ExThreePoints", it looks inside for a file called "ExThreePoints.js" and inside that script file for the definition of a class called "ExThreePoints". This class is instantiated when the tool is started by the user.

Create a static "init" method at the bottom of the file "ExThreePoints.js" as follows:

1 ExThreePoints.init = function(basePath) {
2  var action = new RGuiAction(qsTr("Three Points"), RMainWindowQt.getMainWindow());
3  action.setRequiresDocument(true);
4  action.setScriptFile(basePath + "/ExThreePoints.js");
5  //action.setIcon(basePath + "/ExThreePoints.svg");
6  action.setStatusTip(qsTr("Draw three points"));
7  action.setDefaultShortcut(new QKeySequence("p,3"));
8  action.setDefaultCommands(["point3"]);
9  action.setGroupSortOrder(73100);
10  action.setSortOrder(400);
11  action.setWidgetNames(["DrawExamplesMenu"]);
12 };

The static "init" method is called during the startup of QCAD.

The "init" method usually creates a menu or tool button to launch the tool or performs other necessary initialization.

You can now start QCAD to test if the new tool is correctly shown under menu "Examples - Drawing". To make sure that QCAD scans the scripts directory for new script tools, start QCAD with the parameter -rescan:

./qcad -rescan

You also might want to enable the script debugger to get a meaningful error message if there is a typo in your script file:

./qcad -rescan -enable-script-debugger

Note that the script debugger should only be enabled for debugging script files and never during production use of QCAD. Enabling the script debugger can change the behavior of scripts and lead to unexpected results or even crashes.

At this point, our new tool does not appear to do anything. It only waits until Escape is pressed or the right mouse button is clicked and then terminates.

We can now add functionality by implementing some methods. We start with the method "beginEvent", which is called immediately when the user starts the tool. Some tools only use "beginEvent" and terminate themselves at the end of it (for example the auto zoom tool). This is the case if there is no user interaction required and the tool does something and then terminates when it's done.

We start by calling the "beginEvent" implementation of the parent class (good practice), then write some debugging output to the console and terminate our tool:

ExThreePoints.prototype.beginEvent = function() {
qDebug("ExThreePoints.prototype.beginEvent was called.");

Note that you don't have to restart QCAD after editing the tool script file. If you start the tool again after adding the "beginEvent" method as described above, the tool should write the debugging output to the console and then terminate itself.

In the next step, we add some interaction to our tool. Our tool should wait for the user to click a coordinate in the drawing, draw three points at and beside that coordiante and then termiante.

If a tool is interactive (i.e. waits for the user to do something), we have to keep track of its progress. One way to do this is by adding a state to it. Even if our tool can only be in one state, it's good practice to define this single state:

1 ExThreePoints.State = {
2  SettingPosition : 0
3 };

We can now implement "setState", which is called whenever the tools state changes:

1 ExThreePoints.prototype.setState = function(state) {
2  EAction.prototype.setState.call(this, state);
4  // set crosshair cursor for choosing the coordinate:
5  this.setCrosshairCursor();
7  // we are interested in coordinates the user clicks
8  // (as opposed to entities):
9  this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
11  // status bar user information:
12  var appWin = RMainWindowQt.getMainWindow();
13  this.setLeftMouseTip(qsTr("Position"));
14  this.setRightMouseTip(EAction.trCancel);
16  // show the snap toolbar, so the user can choose an alternative
17  // snap tool if desired:
18  EAction.showSnapTools();
19 };

In our "beginEvent", we set the initial state of the tool to our one and only state. We also remove the tool termination:

1 ExThreePoints.prototype.beginEvent = function() {
2  EAction.prototype.beginEvent.call(this);
4  this.setState(ExThreePoints.State.SettingPosition);
5 };

Now we can implement "coordinateEvent", which is called when the user clicks or enters a coordinate. Our implementation draws three points beside each other.

1 ExThreePoints.prototype.coordinateEvent = function(event) {
2  // the exact position that was clicked or
3  // entered by the user:
4  var pos = event.getModelPosition();
6  // move relative zero point to that position:
7  this.getDocumentInterface().setRelativeZero(pos);
9  // create an operation for adding objects:
10  var op = new RAddObjectsOperation();
11  for (var i=0; i<3; i++) {
12  // create a point entity and add it to the operation:
13  var point = new RPointEntity(
14  this.getDocument(),
15  new RPointData(new RVector(pos.x + i, pos.y))
16  );
17  op.addObject(point);
18  }
20  // apply the operation to the current drawing:
21  this.getDocumentInterface().applyOperation(op);
22 };

Here's the complete code listing of file ExThreePoints.js:

1 //! [include]
2 include("scripts/EAction.js");
3 //! [include]
5 //! [constructor]
6 /**
7  * \class ExThreePoints
8  * \ingroup ecma_misc_examples_draw
9  */
10 function ExThreePoints(guiAction) {
11  EAction.call(this, guiAction);
12 }
13 //! [constructor]
15 //! [State]
16 ExThreePoints.State = {
17  SettingPosition : 0
18 };
19 //! [State]
21 //! [inheritance]
22 ExThreePoints.prototype = new EAction();
23 //! [inheritance]
25 //! [setState]
26 ExThreePoints.prototype.setState = function(state) {
27  EAction.prototype.setState.call(this, state);
29  // set crosshair cursor for choosing the coordinate:
30  this.setCrosshairCursor();
32  // we are interested in coordinates the user clicks
33  // (as opposed to entities):
34  this.getDocumentInterface().setClickMode(RAction.PickCoordinate);
36  // status bar user information:
37  var appWin = RMainWindowQt.getMainWindow();
38  this.setLeftMouseTip(qsTr("Position"));
39  this.setRightMouseTip(EAction.trCancel);
41  // show the snap toolbar, so the user can choose an alternative
42  // snap tool if desired:
43  EAction.showSnapTools();
44 };
45 //! [setState]
47 //! [beginEvent]
48 ExThreePoints.prototype.beginEvent = function() {
49  EAction.prototype.beginEvent.call(this);
51  this.setState(ExThreePoints.State.SettingPosition);
52 };
53 //! [beginEvent]
55 //! [coordinateEvent]
56 ExThreePoints.prototype.coordinateEvent = function(event) {
57  // the exact position that was clicked or
58  // entered by the user:
59  var pos = event.getModelPosition();
61  // move relative zero point to that position:
62  this.getDocumentInterface().setRelativeZero(pos);
64  // create an operation for adding objects:
65  var op = new RAddObjectsOperation();
66  for (var i=0; i<3; i++) {
67  // create a point entity and add it to the operation:
68  var point = new RPointEntity(
69  this.getDocument(),
70  new RPointData(new RVector(pos.x + i, pos.y))
71  );
72  op.addObject(point);
73  }
75  // apply the operation to the current drawing:
76  this.getDocumentInterface().applyOperation(op);
77 };
78 //! [coordinateEvent]
80 //! [init]
81 ExThreePoints.init = function(basePath) {
82  var action = new RGuiAction(qsTr("Three Points"), RMainWindowQt.getMainWindow());
83  action.setRequiresDocument(true);
84  action.setScriptFile(basePath + "/ExThreePoints.js");
85  //action.setIcon(basePath + "/ExThreePoints.svg");
86  action.setStatusTip(qsTr("Draw three points"));
87  action.setDefaultShortcut(new QKeySequence("p,3"));
88  action.setDefaultCommands(["point3"]);
89  action.setGroupSortOrder(73100);
90  action.setSortOrder(400);
91  action.setWidgetNames(["DrawExamplesMenu"]);
92 };
93 //! [init]