Variable from command line / user input

Use this forum to ask questions about how to do things in QCAD.

Moderator: andrew

Forum rules
Always indicate Operating system, QCAD version
Attach Drawing files, Screenshots
Post one question per topic.
Post Reply
cjm
Newbie Member
Posts: 8
Joined: Sun Jun 25, 2017 5:54 pm

Variable from command line / user input

Post by cjm » Sun Jun 25, 2017 7:12 pm

QCAD version: 3.17.1
OS: Windows 10 at work and Devuan GNU/Linux at home

Hello, QCAD community, I am new here and would hugely appreciate some pointers on my first project.

I am working on a tool/script to automatically generate a 2D template for a brick arch (a trapezium consisting of smaller parallelograms cut from bricks with gaps between).

The image I have created as an icon for this tool should give a decent visualisation of what I mean (its half of what the output of this tool should look like):
Image

The user needs to be able to specify archLength, archHeight (the total length and height of the drawing), skew (the angle of the ends of the arch), jointSize (the size of the gap between the bricks) and brickWidth (the width of the bricks being used) in order for the tool to calculate how many bricks will be used and draw the points for each parallelogram.

I have had trouble finding a comprehensive tutorial for this software so can somebody please show me how this user input can be achieved, or even better direct me to a guide/tutorial? Thanks in advance!

cjm
Newbie Member
Posts: 8
Joined: Sun Jun 25, 2017 5:54 pm

Re: Variable from command line / user input

Post by cjm » Sun Jun 25, 2017 8:11 pm

Here is the code I have written so far. Its a bit of a mess and I expect a lot of things are probably in the wrong place. Trying to run it in QCAD produces no results and I don't know how to make QCAD print error messages.

Code: Select all

//! [include]
include("scripts/EAction.js");
//! [include]

//! [constructor]
/**
 * \class brickArch
 * \ingroup ecma_misc_draw
 */
function brickArch(guiAction) {
    EAction.call(this, guiAction);
}
//! [constructor]

//! [inheritance]
brickArch.prototype = new EAction();
//! [inheritance]

//! [beginEvent]
brickArch.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    qDebug("brickArch.prototype.beginEvent was called.");
    var di = this.getDocumentInterface();
    var document = this.getDocument();
    
    var op = new RAddObjectsOperation();
    // Collect dimensions from user: archLength, archHeight, jointSize, brickWidth
    //var archLength = 1000, archHeight = 300, jointSize = 5, brickWidth = 100;
    var trArchLength = qsTr("Arch length"), trArchHeight = qsTr("Arch height"), trJointSize = qsTr("Joint size"), trBrickWidth = qsTr("Brick width");
    this.getDocumentInterface().setRelativeZero(new RVectorData(new RVector(round(-archLength / 2), 0)));
    var brickCount = brickArch.prototype.countBricks(brickWidth, jointSize, archLength);
    var bottomWidth = brickArch.prototype.findBottomWidth(archLength, brickCount, jointSize);
    var topStart = brickArch.prototype.findTopStart(skew, archHeight);
    var topWidth = brickArch.prototype.findTopWidth(topStart, archLength, brickCount, jointSize);
    var brickLines = brickArch.prototype.createLines(brickArch.prototype.calculatePoints(numberOfBricks, bottomWidth, topStart, topWidth, jointSize));
    // Draws the calculated lines onto the document
    for (var i in brickLines) {
		for (var x in brickLines[i]) {
			op.addObject(new RLineEntity(document, new RLineData(brickLines[i][x])));
		}
	}
    
    this.terminate();
};
//! [beginEvent]

//! [countBricks]
// Counts how many bricks are needed based on width of bricks, joint size and total arch length
brickArch.prototype.countBricks = function(brickWidth, jointSize, archLength) {
    // Calculate number of bricks from width making sure it is odd
    // Calculates size of 1 brick + 1 joint
    var brickAndJoint = brickWidth + jointSize;
    var brickCount = floor((archLength - jointSize) / brickAndJoint);
    }
    return brickCount;
};
//! [countBricks]

//! [findBottomWidth]
// Finds the width the bottom of each brick needs to be
brickArch.prototype.findBottomWidth = function(archLength, numberOfBricks, jointSize) {
    justBricks = archLength - jointSize * (numberOfBricks - 1);
    return round(justbricks / numberOfBricks);
};
//! [findBottomWidth]

//! [findTopStart]
// Finds the horizontal start point of the top left point of the arch
brickArch.prototype.findTopStart = function(skew, archHeight) {
    // Finds the angle of the virtual triangle created between the vertical rule and the end of the arch
    a = 180 - skew;
    return round(-tan(a) * archHeight);
};
//! findTopStart

//! [findTopWidth]
// Finds the width the top of each brick needs to be
brickArch.prototype.findTopWidth = function(topStart, archLength, numberOfBricks, jointSize) {
    fullTopLength = archLength - topStart * 2;
    justBricks = fullTopLength - jointSize * (numberOfBricks - 1);
    return round(justBricks / numberOfBricks);
};
//! [findTopWidth]

//! [calculatePoints]
// Returns a nested array of coordinates for each point in the arch
brickArch.prototype.calculatePoints = function(numberOfBricks, bottomWidth, topStart, topWidth, jointSize, archHeight) {
    // Creates one array per brick to store the coordinates of their corners
    var brickPoints = [];
    for (var i = 0; i < numberOfBricks; i++) {
        brickPoints[i] = [];
    }
    for (i = 0; i < numberOfBricks; i++) {
        brickPoints[i][0] = new RVector(i * bottomWidth + i * jointSize, 0);
        brickPoints[i][1] = new RVector(i * bottomWidth + i * jointSize + bottomWidth, 0);
        brickPoints[i][2] = new RVector((i * topWidth + i * jointSize) + topStart, archHeight);
        brickPoints[i][3] = new RVector((i * topWidth + i * jointSize + topWidth) + topStart, archHeight);
    }
    return brickPoints
};
//! [calculatePoints]

//! [createLines]
// Creates lines from coordinates array
brickArch.prototype.createLines = function(brickPoints) {
	for (var x in bricks) {
		brickLines[x] = [];
	}
	for (var i = 0; i < brickLines.length; i++) {
		brickLines[i][0] = new RLine(brickPoints[i][0], brickPoints[i][1]);
		brickLines[i][1] = new RLine(brickPoints[i][1], brickPoints[i][3]);
		brickLines[i][2] = new RLine(brickPoints[i][3], brickPoints[i][2]);
		brickLines[i][3] = new RLine(brickPoints[i][2], brickPoints[i][0]);
	}
	return brickLines;
};
//! [createLines]

//! [init]
brickArch.init = function(basePath) {
    var action = new RGuiAction(qsTr("Brick Arch"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "brickArch.js");
    action.setIcon(basePath + "/brickArch.svg");
    action.setStatusTip(qsTr("Draw a brick arch"));
    //action.setDefaultShortcut(new QKeySequence("p,3"));
    action.setDefaultCommands(["point1"]);
    action.setGroupSortOrder(73100);
    action.setSortOrder(400);
    action.setWidgetNames(["DrawExamplesMenu"]);
};
//! [init]

User avatar
andrew
Site Admin
Posts: 6059
Joined: Fri Mar 30, 2007 6:07 am

Re: Variable from command line / user input

Post by andrew » Mon Jun 26, 2017 12:56 pm

cjm wrote:Trying to run it in QCAD produces no results and I don't know how to make QCAD print error messages.
You can run QCAD with the command line switch -enable-script-debugger to show an interactive debugger whenever a problem is found:

Linux:
./qcad -enable-script-debugger

Windows:

qcad.exe -enable-script-debugger

This will yield:

Uncaught exception at /Users/andrew/data/RibbonSoft/projects/QCAD3/qcad/scripts/Misc/brickArc/brickArc.js:56: SyntaxError: Parse error

There's an error with a curly braket there.

Some general tips about programming:
- Start small (as a beginner, start really really small, think about the absolute minimum of code to start with):

Code: Select all

include("scripts/EAction.js");

function brickArch(guiAction) {
    EAction.call(this, guiAction);
}

brickArch.prototype = new EAction();

brickArch.prototype.beginEvent = function() {
    EAction.prototype.beginEvent.call(this);
    qDebug("brickArch.prototype.beginEvent was called.");
    this.terminate();
};

brickArch.init = function(basePath) {
    var action = new RGuiAction(qsTr("Brick Arch"), RMainWindowQt.getMainWindow());
    action.setRequiresDocument(true);
    action.setScriptFile(basePath + "/brickArch.js");
    action.setGroupSortOrder(73100);
    action.setSortOrder(400);
    action.setWidgetNames(["DrawExamplesMenu"]);
};
- Once this works as expected, slowly start adding more code / functionality.
- After every change, test your changes.

BTW: Classes in QCAD usually start with a capital letter (i.e. BrickArch instead of brickArch). Make sure to also rename the file to BrickArch.js. Variables start with a small letter (e.g. var action;).

cjm
Newbie Member
Posts: 8
Joined: Sun Jun 25, 2017 5:54 pm

Re: Variable from command line / user input

Post by cjm » Thu Jun 29, 2017 9:03 pm

Thank you for your advice, andrew! I have done as you suggested and completely rewritten my tool, testing it periodically for errors and making some substantial improvements from the original.

There are only 3 things missing now:
  • Receiving the variables from the command line

    Math formulas that work in the QCAD command line such as

    Code: Select all

    floor() ceil() tan()
    are not recognised in the context of a script (is there a Math script i need to import?)

    Creating line data to be written into the document (I've tried various configurations of

    Code: Select all

    RLineEntity(RLineData(RLine()))
    to no avail)
I have created a github repo with my code in it now at https://github.com/cjm-1/BrickArch and I'd really appreciate some more guidance.

User avatar
andrew
Site Admin
Posts: 6059
Joined: Fri Mar 30, 2007 6:07 am

Re: Variable from command line / user input

Post by andrew » Thu Jun 29, 2017 9:37 pm

cjm wrote:Receiving the variables from the command line
If the user enters a value in the command line, your action receives a commandEvent:

Code: Select all

BrickArch.prototype.commandEvent = function(event) {
    var cmd = event.getCommand();

    if (cmd==="mycommand") {
        // do something
        event.accept();
    }
};
cjm wrote:Math formulas that work in the QCAD command line such as

Code: Select all

floor() ceil() tan()
are not recognised in the context of a script (is there a Math script i need to import?)
In a script, you can use the standard ECMAScript / JavaScript functions available in the JavaScript Math Object:
Math.floor(...);
Math.ceil(...);
Math.tan(...);
cjm wrote:Creating line data to be written into the document (I've tried various configurations of

Code: Select all

RLineEntity(RLineData(RLine()))
to no avail)
If you include the QCAD Simple API, you can do something like this:

Code: Select all

include("simple.js");
...
addLine(x1,y1, x2,y2);
addLine([x1,y1], [x2,y2]);
addLine(new RVector(x1,y1), new RVector(x2,y2));
All three calls have the same result.

cjm
Newbie Member
Posts: 8
Joined: Sun Jun 25, 2017 5:54 pm

Re: Variable from command line / user input

Post by cjm » Thu Aug 10, 2017 5:53 pm

Sorry to bother you again with this, I had to take a break from this project for a while but I now have a few days free to dedicate to making this tool work and would appreciate some more help.
andrew wrote:If the user enters a value in the command line, your action receives a commandEvent:

Code: Select all

BrickArch.prototype.commandEvent = function(event) {
    var cmd = event.getCommand();

    if (cmd==="mycommand") {
        // do something
        event.accept();
    }
};
I need to collect regular number variables from these command events several times over, with a different variable entered each time. For example, the first time I would need to collect the archLength variable, then the archHeight variable, etc. I'm guessing something like:

Code: Select all

BrickArch.prototype.getVariables = function() {
	for (i = 0; i < 5; i++) {
		input = event.getCommand();
		if (i == 0) {var archLength = input;};
		if (i == 1) {var archHeight = input;};
		if (i == 2) {var jointSize = input;};
		if (i == 3) {var brickWidth = input;};
		if (i == 4) {var skew = input;};
	}
};
I remember finding a way to set the command prompt to a custom string created with qsTr but I cant find it now. That would be handy to prompt the user for the seperate
andrew wrote:In a script, you can use the standard ECMAScript / JavaScript functions available in the JavaScript Math Object:
Math.floor(...);
Math.ceil(...);
Math.tan(...);
D'oh! This was how I had it originally, then I changed it before I knew about the script debugger, this seems to work as expected now, thank you!
andrew wrote:If you include the QCAD Simple API, you can do something like this:

Code: Select all

include("simple.js");
...
addLine(x1,y1, x2,y2);
addLine([x1,y1], [x2,y2]);
addLine(new RVector(x1,y1), new RVector(x2,y2));
All three calls have the same result.
This works as well to draw my lines but throws some errors when dealing with a large number of points and for some reason some points are in the wrong place despite using the same function to calculate them, as shown here:
Image
(The shorter ones are correct but the taller ones seem to be skewed to the left, the top row doesn't appear at all and there is a long error message in the debugger)

The Github repo is up to date, if you can help with either of these problems it would be much appreciated.

User avatar
andrew
Site Admin
Posts: 6059
Joined: Fri Mar 30, 2007 6:07 am

Re: Variable from command line / user input

Post by andrew » Fri Aug 11, 2017 11:12 am

cjm wrote:I need to collect regular number variables from these command events several times over, with a different variable entered each time.
You could use the a state to advance the state of collecting information. E.g. a variable this.dateEnteringState = 0 which advances whenever the user enters another value.

You can also use the action's this.state if you really want to force the user to input all data in the correct order before proceeding.

Alternatively, you could use widgets in the options toolbar or a dialog.
cjm wrote:I remember finding a way to set the command prompt to a custom string created with qsTr but I cant find it now. That would be handy to prompt the user for the seperate

Code: Select all

this.setCommandPrompt("My Prompt:");
This works as well to draw my lines but throws some errors when dealing with a large number of points and for some reason some points are in the wrong place despite using the same function to calculate them, as shown here:
This looks like it would need some debugging (use qDebug() to output intermediate values).

Post Reply

Return to “QCAD 'How Do I' Questions”