Contour a hatch

Discussion forum for C++ and script developers who are using the QCAD development platform or who are looking to contribute to QCAD (translations, documentation, etc).

Moderator: andrew

Forum rules

Always indicate your operating system and QCAD version.

Attach drawing files, scripts and screenshots.

Post one question per topic.

Post Reply
ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Contour a hatch

Post by ScottP » Wed Jul 22, 2020 12:23 am

Hi,

Is there a way to generate contours (boundary lines) around solid hatches?

I have a dwg file of solid hatches with no boundaries and I need to put a different colored boundary line with some thickness around each hatch. Autocad has a "generate boundary" function for this, and I'm looking for something similar. The boundary is just for "looks" from the output of dwg2bmp later - I don't care what type of boundary object it is, as long as it hugs the hatch closely.

I've tried using different hatch Lineweight and Linetype combinations in Qcad, but I haven't found a combination that displays a hatch boundary with nonzero thickness (if it's even there).

Thanks for any help!

CVH
Premier Member
Posts: 3467
Joined: Wed Sep 27, 2017 4:17 pm

Re: Contour a hatch

Post by CVH » Wed Jul 22, 2020 7:02 am

Create a duplicate, set solid filled, Explode.
That would give you only the boundary of a hatch.

Regards,
CVH

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Wed Jul 22, 2020 5:00 pm

Perfect, thank you CVH!

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Fri Jul 24, 2020 11:26 pm

Create a duplicate, set solid filled, Explode.
To do that GUI procedure without GUI interaction, I'm trying the following (to make a polyline boundary of a hatch entity):

Code: Select all

var addOp = new RAddObjectOperation();
addOp.addObject(pointerToHatchEntity, true, true);
addOp.apply(document);
pointerToHatchEntity.setSolid(true);
var qSharedPointerToHatchBoundary = pointerToHatchEntity.getExploded();

// my issue is on the next three lines
var hatch = new RExplodable();
hatch.castToExplodable(qSharedPointerToHatchBoundary);
pointerToPolylineHatchBoundary = ?

pointerToPolylineHatchBoundary.setGlobalWidth(0.1);
How do I get the hatch boundary as a polyline? castToExplodable takes Rshape input and outputs an RExplodable constant, but I only have a qSharedPointer as input and I need a polyline as the ultimate output.

Many thanks for your help!

CVH
Premier Member
Posts: 3467
Joined: Wed Sep 27, 2017 4:17 pm

Re: Contour a hatch

Post by CVH » Sat Jul 25, 2020 1:03 am

Remark that a boundary is not always one single thing.
The boundary of a Hatch can have more than one boundary loop.

I read in https://www.qcad.org/doc/qcad/latest/de ... dable.html
Implemented in RPolylineData, RPolyline, RSpline, RLeaderData, and RTriangle.
Not for hatches I think.

However there is RHatchEntity.getBoundaryAsPolylines() what returns a list of RPolyline.
And has Shared Pointer support.
I am not fond with the segmentLength as parameter ...
This will rely on explosions of some shapes and that is governed by App.Preferences settings.
Or worse: hardcoded preferences.
Then there is RHatchEntity.getShapes() what is more vage.

The hard way ... With full control of what is going on.
Tile2Hatch (in the pipeline) already uses this ... based on explode.js
Lets have a peek ...

Code: Select all

    else if (isHatchEntity(entity)) {  // Functional  SPLINES in painterPaths???
//debugger;
        // Check IsNOT Solid: Process painterPaths
        if (!entity.isSolid()) {    // When patterned
        .
        ..
        ...
      Code to approximate the pattern of a hatch with lines and dots
      For the moment this will swap to debug on catching splines in painterpaths
      Don't worry, this is the patterned section
        ...
        ..
        .
        }
        
        // Check if is Solid or if asked to export the boundary: Process boundary
        if (entity.isSolid() || options.includeBoundary) {
            for (m=0; m<entity.getLoopCount(); m++) {    // Cycle loops
                news = entity.getLoopBoundary(m);
                // Can be : Line, Arc, full Arc, Spline, Ellipse
                // # Weak # No test for news.length > 0
                for (n=0; n<news.length; n++) {    // Cycle new-ones
                    // Recursively handle boundary loop entities:
                    shapes = Tile2Hatch.linApproxEntity(shapeToEntity(doc, news[n].data()), options);
                    // # Weak # No test for shapes.length > 0
                    for (k=0; k<shapes.length; k++) {    // Cycle shapes
                        shape = shapes[k];
                        ret.push(shape.clone());
                    } // Loop shapes
                } // Loop new-ones
            } // Loop boundary loops
        } // End boundary
    } // End isHatchEntity
    
So:
Every boundary loop is stored under the hatch entity
How many loops: entity.getLoopCount()
To get a loop: news = entity.getLoopBoundary(m) (m is the loop number)
How many entities in a loop: news.length
To get the entities: loopShape = news[n].data() (n is the shape number)
Can be : Line, Arc, full Arc, Spline, Ellipse
BTW: Tile2Hatch send these to itself to further aproximate them with line pieces to reuse as a hatch.
shapes = Tile2Hatch.linApproxEntity(shapeToEntity(doc, news[n].data()), options);
But that you don't need.

The point is now to reconstruct a poly from the resulting loopShapes.
(not 'shape' nor 'shapes' in the code above)
Line & Arc are simple, add poly nodes & adapt for bulging. (There are resources for that)
A circle or a full Arc is a shape that is a loop on its own. (Closed splines and full ellipses too)
A full arc isn't possible in a poly, a math thing.
So you have to splice that up in two semi circle arcs before creating a stand-alone poly.
Splines and ellipse are to be converted/exploded to polylines.
Several options present. see RSplineEntity and REllipseEntity or the data variant.

Regards,
CVH

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Tue Jul 28, 2020 6:58 pm

Thanks for your suggestions, CVH.

I'm attempting your 'easy way', as

Code: Select all

hatchPolylines = myHatchEntity.getBoundaryAsPolyLines(double segmentLength)
. My hatch boundary doesn't have to exactly hug the boundary, so some minimum segmentLength of the returned polyline doesn't bother me (if that's what segmentLength means).

In one instance of the easy way, hatchPolylines gets a single RPolyline entity. I know this because the debugger shows that hatchPolylines has taken the value "RPolyline(RShape(address: "0xfffffffff4ac5340"), vertices: ("RVector(-0.001595, 25.171929, 0.000000, 1)", "RVector(-0.001595, 0.117009, 0.000000, 1)...bulges: (0, 0, 0, ..., start widths: (0, 0, 0,... end widths: (0, 0, 0, ...)".

I can do hatchPolylines[0].setGlobalWidth(0.02); without error, since the debugger shows that 'start widths' and 'end widths' have all been changed to 0.02.

Now I need to add the hatchPolylines[0] RPolyine entity onto the the document. I've attempted three methods...

(1)
var expAddOp = new RAddObjectOperation();
expAddOp.addObject(hatchPolylines[0], true, true);
I get the error "RAddObjectsOperation: Argument 0 is not of type RObject".

(2)
var clip = new RClipboardOperation();
clip.copyEntityLinetype(hatchPolylines[0], document, document, true, new RTransaction(storage));
clip.apply(document);
I get the error "RClipboardOperation: Argument 0 is not of type REntity*".

(3)
var ret = [];
ret.push(hatchPolylines[0].clone());
Gives no error, but dosn't seem to do anything.

... RPolyline is a subclass of RObject and REntity, so I don't know why method (1) and (2) can't add the polyline from getBoundaryAsPolyLines() to the document. Is there some casting that I need to do?

I've started looking at your 'hard way' in the meantime.

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Tue Jul 28, 2020 9:56 pm

Ok, got some success by starting with your "hard way". In the code below, I first get each loopShape as an entity with shapeToEntity() and then set its PropertyLineweight, which is common to all Line, Arc, Spline, Ellipse, and Circle entities. This way I don't have to form a polyline from all of the loopShapes (and also avoid splitting up a circle or full arc as you mentioned). So I'm not longer setting the hatch boundary thickness by polylineEntity.setGlobalWidth(0.02)) but rather by loopEntity.setLineWeight(RLineweight.Weight070).

The code below successfully puts visible boundary shape entities around each hatch, but their thickness is "Bylayer" instead of the specified 'Weight070'. There is a similar problem with the code below not putting loopEntity on the right layer (every loopEntity is on layer 0 instead of layer ID = oLayerIds). Any idea why the thicknesses and layers aren't set correctly?

Code: Select all

var op = new RAddObjectOperation();
for (var m=0; m<hatchEntity.getLoopCount(); m++) {    // Cycle loops
	news = hatchEntity.getLoopBoundary(m);
	// Can be : Line, Arc, full Arc, Spline, Ellipse
	// # Weak # No test for news.length > 0
	for (var n=0; n<news.length; n++) {    // Cycle new-ones
		// Recursively handle boundary loop entities:
		loopShape = news[n].data();
		var loopEntity = shapeToEntity(document,loopShape);
		loopEntity.setLineweight(RLineweight.Weight070);
		loopEntity.setLayerId(oLayerIds[i]);
		op.addObject(loopEntity);
	} // Loop new-ones
} // Loop boundary loops
di.applyOperation(op);

CVH
Premier Member
Posts: 3467
Joined: Wed Sep 27, 2017 4:17 pm

Re: Contour a hatch

Post by CVH » Wed Jul 29, 2020 1:53 am

So you dropped poly as boundary route.
I would wipe the line: '// Recursively handle boundary loop entities:' because your code isn't recursive.

Your first trials are missing shapeToEntity() I think.

What you are doing is fairly the same as addshape from simple_create.js
You could use addshape directly, or addshapes ... remember to include simple.js
Odd why it doesn't work out.

Lets have a look at explode.js about line 295.
The shape is derived from shapes(n) not shapes(n).data (recursive I needed that)
The shape is cloned and collected (you have to work on a copy)
The collection is casted in line 119 and on

Maybe that helps.

The layer thing I solve with setting the current layer.
Something with the layer of an entity in a drawing vs an entity from a shape to put in a drawing.

Fuzzy I know. I useally solve this with trials and error. :oops:

Regards,
CVH

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Wed Jul 29, 2020 10:57 pm

CVH, you're correct: if I use addShape() from simple_create.js it should let me control boundary entity linewidths as an argument of any value, e.g. addShape(shape, args, 0.07). This is a better alternative to what I have working now, which is to get loop boundary shapes as entities and then set their line weights by only the discrete options in RLineweight, e.g. loopEntity.setLineweight(RLineweight.Weight070).

So I tried using addShape() in a few different ways (with library.js, simple.js, simple_create.js and simple_modify.js all included), and the script runs without error but unfortunately addShape() didn't add any entity onto the document. I have two questions to troubleshoot this:

1. Onto which document are the addShape() entities being added? From the API, addShape() puts the entity on "the document", but I have two documents always open in my script: originalDocument, and document, which is a copy of originalDocument. originalDocument is not used in any of the code posted here but it stays loaded while the script runs and is used later. I didn't find an explicit way to control which document addShape() operates on. In the code below,

Code: Select all

solidHatchEntity
is an entity already on document, so does that mean that addShape() entities are placed by default on document and not on originalDocument?

2. Is a transaction event required to add the addshape() entities to the document? From the API, addShape() looks like it returns void but the description states that it returns "the added entity. The entity does not yet have a valid ID if it was added within a transaction." If an RAddObjectsOperation transaction is required to add the addShape() entities to the document, then how would I even access those entities given that

Code: Select all

var addedEntities = addshape(shape, args)
results in addedEntities being undefined? So I don't know how to access the entities to even add them to a transaction.

What I have:
Here I tried using addShape() without a transaction. This code results in no error and no addShape() entities added to document.

Code: Select all

// >> 
for (var m=0; m<solidHatchEntity.getLoopCount(); m++) {    // Cycle loops
	shapes = solidHatchEntity.getLoopBoundary(m);
	// Can be : Line, Arc, full Arc, Spline, Ellipse
	for (var n=0; n<shapes.length; n++) {    // Cycle shapes
		// add RShape to the drawing as a new entity with current layer and attributes. The entity does not yet have a valid ID if it was added within a transaction.		
		addShape(shapes[n],new RColor (50,50,50),"CONTINUOUS", 0.07);
		}
}

Here I tried using addShape() without transaction just as a workaround to setting RLineweight.Weight070. Below is the pseudocode first, then actual code further down:

loopShape = solidHatchEntity.loopBoundary()
loopEntity = loopShape.shapeToEntity() //shape to entity
<set some non-lineweight loopEntity properties>
shape = loopEntity.castToShape() //now go back to shape to set any lineweight
addShape(shape, args, 0.07) //adds shape as entity to "the document"

This code results in no error and no addShape() entities added to document.

Code: Select all

for (var m=0; m<solidHatchEntity.getLoopCount(); m++) {    // Cycle loops
	loopShapes = solidHatchEntity.getLoopBoundary(m);
	// Can be : Line, Arc, full Arc, Spline, Ellipse
	for (var n=0; n<loopShapes.length; n++) {    // Cycle new-ones
		var loopEntity = shapeToEntity(document,loopShape[m]);
		loopEntity.setColor(new RColor(50,50,50));							
		//loopEntity.setLineweight(RLineweight.Weight070);
		var shape = loopEntity.castToShape();
		addShape(shape,new RColor (50,50,50),"CONTINUOUS", 0.07);
	}
}

CVH
Premier Member
Posts: 3467
Joined: Wed Sep 27, 2017 4:17 pm

Re: Contour a hatch

Post by CVH » Thu Jul 30, 2020 7:05 am

addShape runs within a transaction.
Best example I could find regarding your usage is:
...qcad-master 3.24\qcad-master\scripts\Misc\Examples\MathExamples\ExMandelbrot\ExMandelbrot.js
startTransaction needs a DocumentInterface so I presume the document thing is covered.

Regards,
CVH

ScottP
Junior Member
Posts: 14
Joined: Wed Jul 08, 2020 5:08 pm

Re: Contour a hatch

Post by ScottP » Fri Jul 31, 2020 9:53 pm

Ah, that's a much a simpler solution than I was trying

startTransaction(documentInterface);
addShape(shape, new RColor(r,g,b), "CONTINUOUS", 0.07);
endTransaction();

works! :)
Thanks so much for the help!

Post Reply

Return to “QCAD Programming, Script Programming and Contributing”