discuss@lists.openscad.org

OpenSCAD general discussion

View all threads

Re: Making modules first class without breaking existing Openscad code

JB
Jordan Brown
Sat, Sep 3, 2022 3:33 AM

On 9/2/2022 5:53 PM, Andy Little via Discuss wrote:

For now, accessing the member assignments of a module_alias with
parameters without instantiation into the CSG tree is just a case of
reusing the same syntax that you can use to access the assignments of
a module:

module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);}

|diagonal = module m([30,20]).diagonal; // an anonymous module_alias|
|m = module (size) television(size, 2); // named alias diagonal1 =
module m([30,20]).diagonal;|

So I think we're getting at least sort of close to a mutual understanding.

I see a couple of points there where I have trouble.

  • Getting the value of "diagonal" requires evaluating the module's
    assignments.  I can't see doing that unless we also evaluate its
    module invocations.  (That does happen in a couple of obscure
    cases in OpenSCAD, but I wouldn't want to make it be a primary
    case.)  But evaluating the module invocations doesn't necessarily
    mean putting the geometry into the model.  It could go into a
    geometry-as-data structure that might or might not eventually end up
    as part of the model.
  • I don't think that either "diagonal" or "diagonal1" above require
    special syntax, if we have geometry-as-data and a way to peek at the
    as-evaluated local variables.  m([30,20]) would evaluate the module
    with the specified argument, returning some kind of structure that
    includes both the as-evaluated local variables and the geometry, and
    then you could look at the "diagonal" local variable.  (But see below.)
  • The middle line, the "m=" line, has only a single module invocation
    in it.  But what if it had more than one module invocation?  What if
    it was { television(size); credenza(size); }?

That last one is the hardest.

In any other OpenSCAD context, anywhere that you can have a module
invocation, you can have a { ... } block with any number of assignments
and module invocations.  Also, a single module invocation is equivalent
to that same module invocation with braces around it.

That is:

translate([10,0,0]) cube(10);
translate([10,0,0]) { cube(10); }

are equivalent.  Similarly:

module box(size) cube(size);
module box(size) { cube(size); }

are equivalent.

So if you can have

m = module (size) television(size);

then you could also have

m = module (size) { television(size); };

and

m = module (size) { television(size); credenza(size); };

and then what would m([30,20]).xxx mean?  Would it peek inside
television's local variables, or credenza's?  Or the (empty in this
case) local variables for the { ... } block?

And if there's no good answer for that (and I don't think there is), then

m = module (size) { television(size); };

is also in trouble.  And if that's in trouble, by the equivalence above,

m = module (size) television(size);

is in trouble.


What I've played with so far is

o = { diagonal = sqrt(16^2 + 9^2); square([16,9]); };

which is not a module; it's an expression that evaluates some
assignments and some geometry and returns the result as an object.  You
can then say "o.diagonal" and get 18-point-something.

That alone has some interesting effects, because since it is an
expression it can be returned by a function and it can use a function's
arguments in its evaluation:

function television(size) = {
    diagonal = sqrt( size.x^2 + size.y^2);
    square(size);
};

and then  you could talk about television(size).diagonal, which would
evaluate the function, evaluate the object, evaluate the assignment and
the geometry, return "diagonal" and the square, discard the square, and
return "diagonal".

In the discussion above I've concentrated on retrieving the values
from the evaluation, and ignored the geometry.  Given o above, or
the television() function, how would you get the geometry added to
the model?  So far the best answer seems to be that the render()
module would be extended to accept such an object as an argument,
and would add it to the model.  (Furiously handwaving whether it
would actually *render* the object, as it is defined in OpenSCAD, or
just pass it through into the CSG tree.)

It would be nice if you could take a module defined in the traditional
way, and use it directly in an expression.  But that would introduce a
number of problems, and I don't immediately see any real advantages.

On 9/2/2022 5:53 PM, Andy Little via Discuss wrote: > For now, accessing the member assignments of a module_alias with > parameters without instantiation into the CSG tree is just a case of > reusing the same syntax that you can use to access the assignments of > a module: > > module television(size, b) { diagonal = sqrt( size.x^2 + size.y^2);} > > |diagonal = module m([30,20]).diagonal; // an anonymous module_alias| > |m = module (size) television(size, 2); // named alias diagonal1 = > module m([30,20]).diagonal;| So I think we're getting at least sort of close to a mutual understanding. I see a couple of points there where I have trouble. * Getting the value of "diagonal" requires evaluating the module's assignments.  I can't see doing that unless we also evaluate its module invocations.  (That *does* happen in a couple of obscure cases in OpenSCAD, but I wouldn't want to make it be a primary case.)  But evaluating the module invocations doesn't necessarily mean putting the geometry into the model.  It could go into a geometry-as-data structure that might or might not eventually end up as part of the model. * I don't think that either "diagonal" or "diagonal1" above require special syntax, if we have geometry-as-data and a way to peek at the as-evaluated local variables.  m([30,20]) would evaluate the module with the specified argument, returning some kind of structure that includes both the as-evaluated local variables and the geometry, and then you could look at the "diagonal" local variable.  (But see below.) * The middle line, the "m=" line, has only a single module invocation in it.  But what if it had more than one module invocation?  What if it was { television(size); credenza(size); }? That last one is the hardest. In any other OpenSCAD context, anywhere that you can have a module invocation, you can have a { ... } block with any number of assignments and module invocations.  Also, a single module invocation is equivalent to that same module invocation with braces around it. That is: translate([10,0,0]) cube(10); translate([10,0,0]) { cube(10); } are equivalent.  Similarly: module box(size) cube(size); module box(size) { cube(size); } are equivalent. So if you can have m = module (size) television(size); then you could also have m = module (size) { television(size); }; and m = module (size) { television(size); credenza(size); }; and then what would m([30,20]).xxx mean?  Would it peek inside television's local variables, or credenza's?  Or the (empty in this case) local variables for the { ... } block? And if there's no good answer for that (and I don't think there is), then m = module (size) { television(size); }; is also in trouble.  And if *that's* in trouble, by the equivalence above, m = module (size) television(size); is in trouble. ------------------------------------------------------------------------ What I've played with so far is o = { diagonal = sqrt(16^2 + 9^2); square([16,9]); }; which is *not* a module; it's an expression that evaluates some assignments and some geometry and returns the result as an object.  You can then say "o.diagonal" and get 18-point-something. That alone has some interesting effects, because since it is an expression it can be returned by a function and it can use a function's arguments in its evaluation: function television(size) = { diagonal = sqrt( size.x^2 + size.y^2); square(size); }; and then  you could talk about television(size).diagonal, which would evaluate the function, evaluate the object, evaluate the assignment and the geometry, return "diagonal" and the square, discard the square, and return "diagonal". In the discussion above I've concentrated on retrieving the values from the evaluation, and ignored the geometry.  Given o above, or the television() function, how would you get the geometry added to the model?  So far the best answer seems to be that the render() module would be extended to accept such an object as an argument, and would add it to the model.  (Furiously handwaving whether it would actually *render* the object, as it is defined in OpenSCAD, or just pass it through into the CSG tree.) It would be nice if you could take a module defined in the traditional way, and use it directly in an expression.  But that would introduce a number of problems, and I don't immediately see any real advantages.
JB
Jordan Brown
Sat, Sep 3, 2022 3:41 AM

On 9/2/2022 8:33 PM, Jordan Brown wrote:

  • Getting the value of "diagonal" requires evaluating the module's
    assignments.  I can't see doing that unless we also evaluate its
    module invocations.  (That does happen in a couple of obscure
    cases in OpenSCAD, but I wouldn't want to make it be a primary case.)

Clarification: when I say "That does happen", I mean evaluating a
block's assignments without evaluating its geometry.  (That's changed
since 2021.01; kudos if you can identify the case and the change.)

On 9/2/2022 8:33 PM, Jordan Brown wrote: > > * Getting the value of "diagonal" requires evaluating the module's > assignments.  I can't see doing that unless we also evaluate its > module invocations.  (That *does* happen in a couple of obscure > cases in OpenSCAD, but I wouldn't want to make it be a primary case.) > Clarification: when I say "That *does* happen", I mean evaluating a block's assignments without evaluating its geometry.  (That's changed since 2021.01; kudos if you can identify the case and the change.)
K
kwikius@yahoo.com
Sat, Sep 3, 2022 9:52 AM

I think the best thing to do is to try implementing  this a little more, to see what is possible and what isnt . So far I have implemented some (most?) ofthe syntax, but not done much on the evaluation.

I hope that some questions are answered by the example linked below

https://github.com/kwikius/openscad/blob/WIP-first-class-module/tests/data/scad/experimental/first_class_module/first_class2.scad

Note that the example is running in the fork executable, but passing syntax only. Evaluation is the next part to do some work on!

module television(diagonal, thickness) {
    // Ratio for a wide-screen TV
    nominal_h = 9;
    nominal_w = 16;
    nominal_diagonal = sqrt(nominal_h^2 + nominal_w^2);
    // Multiplier for *this* TV
    ratio = diagonal/nominal_diagonal;
    // Resulting height and width
    h = nominal_h * ratio;
    w = nominal_w * ratio;
    cube([w, thickness, h]);
    // add some feet
    translate([0,0,-1]) cube([1,thickness,1]);
    translate([w-1,0,-1]) cube([1,thickness,1]);
    echo(h=h, w=w, diagonal=diagonal);
}
// module_alias to television
m = module (d) television(d,5);
ratio = module m(20).ratio;
// module_alias to anonymous module 
m1 = module (params) { television(params.d, params.t); };
// module_alias to anonymous module
m2 = module (params, size) { television(params.d, params.t); credenza(size); };
// a module alias referring to an anonymous module
// containing a member module named member
m3 = module (params, size = [1,2] ) {
  params = params;
  size = size;
  telly = module m2(params,size);
  
  cube([size.x,params.d,size.y]);
  module member() { 
     c = module cube([1,2,3]); 
     x = 1;
  }
  f = function (a,b)  a+b;
};
m4 = module m3([20,20]);
diagonal = module m3([1,2]). module telly.diagonal;
telly = module m3([1,2]). module telly;
telly();
x1 = module m3([1,2]). module member.x;
c_x = module m3([1,2,3]). module member.c.x;
m3_member = module m3([1,2]). module member();
m3_member3 = module m3([1,2]). module member;
m4_size = module m4().size;
m4_sizex = module m4.size.x;
I think the best thing to do is to try implementing this a little more, to see what is possible and what isnt . So far I have implemented some (most?) ofthe syntax, but not done much on the evaluation. I hope that some questions are answered by the example linked below https://github.com/kwikius/openscad/blob/WIP-first-class-module/tests/data/scad/experimental/first_class_module/first_class2.scad Note that the example is running in the fork executable, but passing syntax only. Evaluation is the next part to do some work on! ``` module television(diagonal, thickness) { ``` ``` // Ratio for a wide-screen TV ``` ``` nominal_h = 9; ``` ``` nominal_w = 16; ``` ``` nominal_diagonal = sqrt(nominal_h^2 + nominal_w^2); ``` ``` // Multiplier for *this* TV ``` ``` ratio = diagonal/nominal_diagonal; ``` ``` // Resulting height and width ``` ``` h = nominal_h * ratio; ``` ``` w = nominal_w * ratio; ``` ``` cube([w, thickness, h]); ``` ``` // add some feet ``` ``` translate([0,0,-1]) cube([1,thickness,1]); ``` ``` translate([w-1,0,-1]) cube([1,thickness,1]); ``` ``` echo(h=h, w=w, diagonal=diagonal); ``` ``` } ``` ``` // module_alias to television ``` ``` m = module (d) television(d,5); ``` ``` ratio = module m(20).ratio; ``` ``` // module_alias to anonymous module ``` ``` m1 = module (params) { television(params.d, params.t); }; ``` ``` // module_alias to anonymous module ``` ``` m2 = module (params, size) { television(params.d, params.t); credenza(size); }; ``` ``` // a module alias referring to an anonymous module ``` ``` // containing a member module named member ``` ``` m3 = module (params, size = [1,2] ) { ``` ``` params = params; ``` ``` size = size; ``` ``` telly = module m2(params,size); ``` ``` ``` ``` cube([size.x,params.d,size.y]); ``` ``` module member() { ``` ``` c = module cube([1,2,3]); ``` ``` x = 1; ``` ``` } ``` ``` f = function (a,b) a+b; ``` ``` }; ``` ``` m4 = module m3([20,20]); ``` ``` diagonal = module m3([1,2]). module telly.diagonal; ``` ``` telly = module m3([1,2]). module telly; ``` ``` telly(); ``` ``` x1 = module m3([1,2]). module member.x; ``` ``` c_x = module m3([1,2,3]). module member.c.x; ``` ``` m3_member = module m3([1,2]). module member(); ``` ``` m3_member3 = module m3([1,2]). module member; ``` ``` m4_size = module m4().size; ``` ``` m4_sizex = module m4.size.x; ```