It would seem that you have no useful skill or talent whatsoever (…) have you thought of going into teaching?
Mort
Stationary objects.
The code below provides text that maintains it’s location and size in the viewport, i.e., on the screen, regardless of the viewport translation, rotation or distance. The text rendering remains good for distances between about 20 and 100.000 units. The scalar '40' in the code below works on my display, your mileage may vary.
The GHOUL has a
ScreenText() module that takes care of this.
// Text that stays in the same location on the screen.
// (Requires regen...)
// Some settings:
Location=[0,-5,0];// Location in 'screen-coordinates'; about mid-bottom.
GoodLooks=[15,45,0];// Set to [0,0,0] for dull text.
Size=1;
// Compensate for viewport translation and rotation ($vpt and $vpr).
translate($vpt)
rotate($vpr)
// Place on screen, compensated for distance ($vpd).
translate(Location*$vpd/40)
// Make the font look good.
rotate(GoodLooks)
// Text size is compensated for distance ($vpd).
SimpleText(
"Some Example Text."
,Size*$vpd/40,Thickness=0.1,Center=true,Font="Noto Mono",Spacing=1);
The code below provides text that maintains it’s location in the coordinate system but always faces the viewport, regardless of rotation of the modelling space.
// Text that stays at the same 3D coordinates, but always faces the viewport.
// (Requires regen...)
// Some settings:
Location=[10,5,0];// Actual coordinates.
GoodLooks=[15,45,0];// Set to [0,0,0] for dull text.
Size=1;
color(DSG)
// Place on coordinates.
translate(Location)
// Compensate for viewport rotation ($vpr).
rotate($vpr)
// Make the font look good.
rotate(GoodLooks)
// Text size is compensated for distance ($vpd).
SimpleText(
"Some Example Text."
,Size*$vpd/40,Thickness=0.1,Center=true,Font="Noto Mono",Spacing=1);
For loop rounding errors.
|
|
Do not be tempted to step through a for() loop with a calculated step size. This will cause misses due to rounding errors; count steps, i.e., use integer steps instead, and multiply with the calculated step size afterwards.
|
include<GHOUL/Config.scad>
// This:
Sing=
[
for (I=[0:90/7:90]) I
]
;
Echo(Sing);
// Because of rounding errors, yields this:
// ECHO: "[0, 12.8571, 25.7143, 38.5714, 51.4286, 64.2857, 77.1429]"
// But this:
Sang=
[
for (I=[0:7]) I*90/7
]
;
Echo(Sang);
// Gives the expected result:
// ECHO: "[0, 12.8571, 25.7143, 38.5714, 51.4286, 64.2857, 77.1429, 90]"
In-call namespace.
What may not immediately be clear from
the manual section
is that when a call is made to a function or module, and a special variable is handed down with the call’s parameters or it is one of the parameters, or a special variable is assigned inside a module or let() statement, that special variable exists in all other functions and modules called by the function or module, and in all the functions and modules they call and so on and on.
And it gets better; even if another function is called from within a function’s call that contains a special variable declaration, it’s value is available to that function:
|
|
If a special variable is declared inside the parentheses of a function or module call, and one of the parameters in that call is itself a function call, the special variable is available inside that function also. |
Notice that $Bar=3 is given as a parameter to circle() and not to Multiply() in the example below, nevertheless, Multiply() 'sees' this value of $Bar, and not the value of $Bar that was declared in the top-level code!
This is because, essentially, the call to Multiply() is made from within the namespace of the circle() call, in which $Bar has the value '3'.
$Bar=0;
function Multiply(Foo)=
Foo*$Bar
;
circle(r=Multiply(2),$Bar=3);
// Is equivalent to:
circle(r=6);
This happens when
OutRadius() uses the $fn value from within the 'in-call' namespace of a circle() or cylinder() call.
Syntax error in WHERE?
|
|
Sometimes when OpenSCAD warns you about a syntax error, it gets the location of the error completely wrong. Here are some examples. |
Have you had that? You’re working on one of the files that’s included by your top-level code, file X.scad. You save it. You have ticked, and immediately OpenSCAD complains about a syntax error in file Y.scad. What? You, what? You didn’t touch Y.scad, so what the…
|
|
If you forget a semi-colon at the end of an included file, and there is another file included after that one, the warning about a syntax error will reference the first line of code in the next included file :'( |
How about this one: You get a syntax error but the referenced line is flawless… What happened?
1 function ArrayShift(Array,Shift)=
2 let(
3 /* Turn negative shifts into positive equivalent. */
4 _Shift=(Shift%len(Array)+len(Array) // Missing parenthesis here.
5 )
6 [
7 for(Idex=[0:Len(Array)]) // Syntax error warning references this line...
8 Array[(Idex+_Shift)%len(Array)]
9 ]
10 ;
ERROR: Parser error in file "/OpenSCAD/libraries/GHOUL/Lib/Arrays/ArrayShift.scad", line 7: syntax error
ERROR: Compilation failed!
/* NOTE: There's no need to put the modulo operation in line 5 in parentheses. I just needed an example so I fabricated this one... */
|
|
If you forget a closing parenthesis, and there are a bunch of other parentheses following, the warning about a syntax error may reference a line of code much later in the file than the actual location of the error :'( |
Another doozie; You are happily editing your file and like always, you have ticked so every save OpenSCAD automatically refreshes the preview. Neat. Then, all of a sudden your preview goes blank. No error messages… Hit and do another Preview. Hopla, there it is:
ERROR: Unable to convert point [1, undef] at index 2 to a vec2 of numbers, in file ../../Lib/SVG/SVG.scad, line 60
Recursion.
Single vs. Split recursive functions.
Recursive functions usually need a counter and a result storage parameter so each call can hand these parameters to their successor. These parameters are usually named Index and Result.
Recursive functions often have to initialise parameters depending on the call conditions. In 'single part' recursive functions, much time is wasted by running 'If this is the first call do this' checks, initialisations, and error checks on each of the recursive calls.
SingleRecursiveFunction(SomeInput,SomeSettings,Result)=
Do: trap errors.
Test: Is this the initial call?
Then: SingleRecursiveFunction(SomeInput,SomeSettings,Result)
Else: Is this the last element?
Then: Result
Else: is this condition HighOccurence?
Then: SingleRecursiveFunction(SomeInput,SomeSettings,HighOccurrenceResult)
Else: SingleRecursiveFunction(SomeInput,SomeSettings,LowOccurenceResult)
;
This can be greatly improved by splitting the 'error trap and initialisor' and the 'recursor' part. The initial call is made to the 'error trap and initialisor' which hands over to the 'recursor' for all recursive calls.
SplitRecursiveMain(SomeInput)=
Do: trap errors.
Do: SplitRecursor(SomeInput,SomeSettings)
;
SplitRecursor(SomeInput,SomeSettings,Result)=
Test: Is this the last element?
Then: Result
Else: is this condition HighOccurence?
Then: SplitRecursor(SomeInput,SomeSettings,HighOccurrenceResult)
Else: SplitRecursor(SomeInput,SomeSettings,LowOccurenceResult)
;
A split function can easily be 25% faster, just because error checks and initialising only happen once. Will it matter? It does when processing large arrays of vertices &c. To get a real world idea, I clocked a routine checking for identical values in an array. It took 13 seconds for the single version and 10 seconds for the split version on 1e6 operations.
Result-less recursion.
Sometimes, a counter and even a Result variable can be avoided by the following method:
/* Instead of this: */
function Factorial(Int,Result=1)=
Int==1
? Result
: Factorial(Int-1,Int*Result)
;
/*
You can use this more elegant version (which is sadly 15% slower on 1e5 operations of 100!, but who even checks that? I do, evidently...).
*/
function _Factorial(Int)=
Int==1
? 1
: _Factorial(Int-1)*Int
;
Payload reduction.
Instead of handing a recursive function an array to walk through in it’s function call, have the main function declare $Array and refer to that in the recursive function. I timed 1e5 recursions and achieved a reduction in run-time of more than 16%.
/*
Instead of:
*/
function Main(Array)=
_Recursor(Array,Len(Array),0)
;
function _Recursor(Array,Idex,R)=
Idex<0
? R
: _Recursor(Array,Idex-1,R+Array[Idex])
;
/*
Do:
*/
function Main(Array)=
let(
$Array=Array
)
_Recursor(Len(Array),0)
;
function _Recursor(Idex,R)=
Idex<0
? R
: _Recursor(Idex-1,R+$Array[Idex])
;
Grab that tiger by the tail!
Compare that last function pair in the examples above with the one below… This one is so much prettier—yes, in my opinon—but not Tail Recursive and as a result it is much slower, by about a factor of three! Make sure your recursive functions have proper tail calls, like in the previous examples, or you’ll pay a price.
function Main(Array)=
let(
$Array=Array
)
_Recursor(Len(Array))
;
function _Recursor(Idex)=
Idex<0
? 0
: _Recursor(Idex-1)+$Array[Idex]
;
Search me!
The native search()… Powerful? Perhaps. Exasperating? Certainly!
Have a look at this:
LastCommand=["C",1,2];
Test=search(LastCommand[0],["C",1,"c",2,"S","s","T","t","Q","q"]);
echo(Test[0]);
You’d expect echo() to print 0 to the console, right? That would be logical, since LastCommand[0] equals "C", which is the first (index 0) element of the search array. Yeah, no. Not according to search(). It will give you this lovely bit of advice:
WARNING: Invalid entry in search vector at index 0, required number of values in the entry: 1. Invalid entry: "C" in file X.scad, line Y
So what do you have to do? You must do this:
LastCommand=["C",1,2];
Test=search([LastCommand[0]],["C",1,"c",2,"S","s","T","t","Q","q"]);
echo(Test[0]);
I’m sure it makes logical sense to everyone but me, and you of course…