Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Discuss Function literals & Objects etc. #3088

Open
MichaelAtOz opened this issue Oct 8, 2019 · 41 comments

Comments

@MichaelAtOz
Copy link
Member

commented Oct 8, 2019

I'm raising this issue so the technical issues don't get bloated with discussion.
Function literals, @doug-moen's description, and initial discussion in @t-paul's implementation issue #3077, and Geometry Objects, Doug's description and tp's initial work #3087, are proposed as the next evolution of OpenSCAD.

Note:

Terminology!
We need an agreed term for 'a geometric thingi', so until a better term comes along, to avoid confusion I'll use Thingi, (note I did not use 'object'). 'script' may also need a better term, but will do for now - a section of OpenSCAD code.

[posting now so this OP remains as the overview, next post continues discussion]


Want to back this issue? Post a bounty on it! We accept bounties via Bountysource.

@MichaelAtOz

This comment has been minimized.

Copy link
Member Author

commented Oct 8, 2019

@nophead said:

But why? What is wrong with modules?
And how to I have a script to make a fan, that can also cut it holes and make fan assembly?

Nothing is wrong with modules, but as it stands you can't do object-oriented programming.

Having quickly reread Doug's doco, I need to go over it again, as I was interpreting some things with my own presumptions, so I need to think again about some of the fine print (like when the object is instantiated into a Thingi). I'm also not natively imbued with object-orientation.
But as a generalisation I see the adoption of these two changes as a means of better supporting libraries and class like containerisation. The namespace like 'fields' helps.

I'll re-read it today and comment further.

@MichaelAtOz MichaelAtOz referenced this issue Oct 8, 2019
9 of 10 tasks complete
@MichaelAtOz

This comment has been minimized.

Copy link
Member Author

commented Oct 8, 2019

@doug-moen wrote:

I think there is some confusion about OpenSCAD2.

A "script" is an element of syntax. It's a mixture of statements (that create geometry) and definitions (that define variables, functions and modules). An entire OpenSCAD program is a script. You can also enclose a script (a sequence of statements and definitions) in curly braces.

An "object" is what is returned by a module. So it is a run-time concept, not a grammatical concept. For example, in

module lollipop() {
  radius   = 10; // candy
  diameter = 3;  // stick
  height   = 50; // stick
  translate([0,0,height]) sphere(r=radius);
  cylinder(d=diameter,h=height);
}

then the thingy returned by lollipop() is an object, and the text between the curly braces is a script.

Consider that a script contains both statements (that generate geometry) and definitions. Program scripts are sometimes used to generate geometry and specify a 3D model. Other times, program scripts are used as libraries: it is the definitions that are important, and libraries do not usually contain any geometry.

In OpenSCAD2, objects are generalized. An object is a first class value that is constructed from a script. An object contains geometry (if any was specified), and it contains named fields that are initialized from the definitions, if any definitions were present. You can select fields from an object using dot notation: obj.field.

Just as program scripts are used either for 3D models or libraries, so OpenSCAD2 objects can be used either as shapes or as library namespaces. You can import a program script into memory using script("filename.scad"), and that will return an object. If the imported script is a model, then you can use the object as a shape. If the imported script is a library script, then you can use the object as a library namespace. So now we have the concept of namespaces.

In OpenSCAD2, the object returned by lollipop() has named fields like radius and diameter. So now models can carry around the parameters used to generate them, and those parameters can be programmatically accessed when the object is passed as an argument to another module.

There's more about library objects here: https://github.com/doug-moen/openscad2/blob/master/rfc/Library_Scripts.md

When @kintel reviewed the OpenSCAD2 design in 2015?, he suggested that the script() module should be called import() instead. Perhaps that would eliminate some confusion.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

I think the answer with the design so far is: it depends... on how things are defined inside the screws.scad. Right now I don't yet see how to handle lets call them "old style module definitions" imported from a script.

ext.scad

// data
a = 42;
b = a + 8;

// function literal
func = function(x) x * x;

// object literal
o = {
    r = 2;
    h = 10;
    data = [ 1, 2, 3, 4];
    nested_func = function(x) x + r;
    cylinder(r = r, h = h);
};

// old style function
function func_old(x) = x + x;

module mod_old(x) {
    cube([x, x, a]);
}
ext = script("ext.scad"); // or maybe ext = import("ext.scad"); ?
echo(ext.a); // 42
echo(ext.func(4)); // 16

ext.o(); // generate cube with r = 2 and h = 10;
ext.o(r = 4); // customization description sounds like the radius of the cylinder is still 2?

I don't see how we would access func_old and mod_old unless they are handled like with use<> and are imported into the scope of the calling module.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

I thought one of the mains point was to import into a namespace, so I don't think it is correct to put modules into the scope of the calling module. What is wrong with the dot notation to access everything inside the script?

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

Nothing wrong with that, I like it very much as it automatically produces namespaces when importing multiple libraries. But we need to consider backward compatibility and this makes things a bit more complicated for existing constructs. If we treat those objects imported via script("..."); similar to normal modules, they maybe can have their own legacy functions and modules.

That's the whole point of pushing the current prototype code into public to have that discussion and find a solution that both brings OpenSCAD into a much better future state while still maintaining compatibility with existing code.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

Are you expecting to phase out the use of old style functions and modules?

I would only use a new style function if I wanted the pass it, which is not often.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

I would expect both old and new style to co-exist for quite some time, maybe even forever. We still need to define a way how to handle things if both exist and declare what the preferred usage is.

Right now, with the existing logic

inc.scad

function f(x) = x * x;
module m(r) sphere(r);

main.scad

use <inc.scad>
echo(f(3));
m(10);

Both function and module definitions are imported into the function and module lists of main.scad (which is internally called a file module defined in FileModule.{h,cc}). As result both can just be called in that file at top level without any other qualifier.

Whatever the name of the new style import will be (script, import, ...) we need some definition of how to handle those old style functions/modules at that point. As it's a new feature, it can't break backward compatibility so we are relatively free with the definition. There's at least 3 possible options:

  1. both are not accessible at all
  2. same behavior like use<> so they would be imported in the enclosing module
  3. call via the object, e.g. echo(o.nested_func(4)) in the example above - which needs the shadowing defined if there's another o.nested_func variable is declared
@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 8, 2019

@nophead wrote:

So instead of

include <screws.scad>
screw(M3_cap_screw, 10);

I could do?

screws = script(screws.scad);
screws.screw(screws.M3_cap_screw, 10);

Can I also do?

screws = script(screws.scad);
screw = screws.screw;
the_screw = screws.M3_cap_screw;
screw(the_screw, 10);

That is correct. Or at least, that is the vision of how OpenSCAD2 is supposed to work. There are still details to work out about how this interacts with the 3 namespaces (variable/function/module), as @t-paul has noted.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

@t-paul I don't see why it needs to emulate use as in 2 because one can still use use if one wants that behaviour.

@doug-moen
Yes I often use screw = M3_cap_screw; screw(screw, 10);

That isn't going to work if screws.scad is imported with script() instead of use.

Also I was assuming that one can assign a module to a variable as well as an object literal, which seems more like a module literal to me.

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 8, 2019

@nophead said "Are you expecting to phase out the use of old style functions and modules?"

That is what the OpenSCAD2 proposal says should happen. Chris's objection to this (circa about 2015) is part of the reason why I dropped the proposal.

Today, I think that the 3 namespaces (variable/function/module) should be preserved. We should see if it's feasible to modify the OpenSCAD2 object proposal to be compatible with the 3 namespaces, without trying to deprecate them. The code fragments that Chris is posting should be taken as requirements that the new design needs to satisfy.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

All the new style features naturally end up in the variable namespace anyway. So it seems this would be mostly about retaining support for the legacy definitions? I guess we would not actively deprecate them but slowly move documentation to highlight new style variants.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

Object literals don't seem to be a replacement for modules unless I am missing something. When you define a module you specify the parameters. Anything inside the module is private. E.g. variables derived from the parameters and functions and modules defined inside it.

With an object literal it seems you can access anything inside it with dot and there doesn't seem to be anyway to distinguish parameters that can be overridden from derived values. Also no way to have a parameter that doesn't have a default.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

That came up in the IRC discussion too:

Although I still feel like there is a redundancy between modules and these objects. They are almost identical. Let me ask this question: Is there any feature of objects that is harmful to introduce to modules?
o = (x, y) { square(x, y); } // Possible syntax for parameters
Okay, but then it's a class, not an object.
So maybe:
C = module(x, y) { square(x, y); } o = C(5, 6);
Then all existing modules are basically already named classes, while assignable modules that are assigned to a variable that behaves like a class. If I have a named module m, o = m(5, 6); would be functionally equivalent to o = { m(5, 6); }; in terms of what is displayed. So then a variable assigned to an unnamed module is behaving identically to a named module.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

If you have:
C = module(x) { r = x / 2; sphere(r) } o = C(5);

As I understand it you can do ECHO(o.x) and get 5 but can you also do ECHO(o.r)?

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

I believe the idea with that was to have C as class definition and o as instance - which matches existing module behavior. That seems to collide with the currently described behavior of objects, at least I don't see that differentiation yet. So that needs to be sorted, specifically we need to specify what is evaluated at which point in time.

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 8, 2019

@nophead: "Object literals don't seem to be a replacement for modules unless I am missing something."
IRC person: "Although I still feel like there is a redundancy between modules and these objects. They are almost identical."

Objects already exist in OpenSCAD. cube(10) is an object. {cube(10);sphere(12);} is an object literal (a piece of syntax) that evaluates to an object. If I seem to be inventing new terminology, I'm sorry, but there isn't consistent terminology for these concepts. In the C++ source code, an object is represented by class AbstractNode, and the result of evaluating an object literal is represented by class GroupNode. The word 'object' is commonly used in the documentation and forum, so I picked that word.

Modules and objects are orthogonal. The 'children' argument of a module is an object. A module instantiation returns an object. Object literals are also used to form the 'children' argument to a module. In this statement:

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

the expression in curly braces is an object literal.

In OpenSCAD2, the redundancy is between modules and functions. In OpenSCAD2, objects are values, so it is now possible for a function to take an object as an argument, and return an object as a result. So functions can now do everything that modules do. One of the benefits is that it is now possible to write a function that takes an object as an argument, and returns numeric data as a result. There is currently no syntax for this, and there is a backlog of feature requests that are blocked because we don't have a syntax for this.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

I think the confusing thing is object literals can have values overridden like in the lollipops example. so then they are more like modules that get instantiated.

I must say I find this very confusing and I would class myself as experienced OpenSCAD user, not sure how easy a beginner would find it, whereas I found it very easy to learn OpenSCAD 1.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 8, 2019

Yes, that's the point where I also have not fully wrapped my head around. For consistency object literals must have lexical scoping too which seems clear enough when just looking at data, but I don't yet see how to handle the geometry instantiation.

o = {
    r = 5;
    sphere(r);
};

With lexical scoping that binds r passed to the sphere directly to the r = 5. If the sphere is also directly instantiated, how would o(r = 10); work?

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 8, 2019

@nophead: "object literals can have values overridden like in the lollipops example"

You should not try to implement the full OpenSCAD2 proposal. It's too big and complicated. The "object customization" feature that you refer to is not essential (nobody has ever asked for it) so just leave it out.

The aspects of the OpenSCAD2 object type that are valuable, and possibly worth implementing, are the ones that implement features that people have asked for on the forum and in the githhub repo:

  • library namespaces
  • adding arbitrary named metadata to a shape
  • functions that take a shape as an argument and return numeric data as a result
  • a dictionary-like data structure, similar to a Python dictionary or a Javascript object or a Ruby hash
@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2019

@t-paul "For consistency object literals must have lexical scoping too which seems clear enough when just looking at data, but I don't yet see how to handle the geometry instantiation."

I did implement a version of this in Curv. To do it in OpenSCAD, you need to preserve the parent lexical scope of the object literal in a data structure (using whatever data structure is used for function closures). And you need to preserve the unexecuted code of the object literal (probably as a parse tree, or using whatever data structure is used by function closures). When an object is customized, you re-execute the code to regenerate the geometry, using a lexical environment that overrides local definitions with the key/value bindings passed as arguments.

I'm still experimenting with the idea. However, based on my experience with Curv so far, I don't think this is a critical feature, and I don't think it should be prioritized.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 9, 2019

The lexical scoping itself is not a big problem anymore as I've implemented that for the function literals already. The function objects can now hold on to their lexical scope even after it's dropped from the dynamic evaluation scope stack. So that is basically all prepared. The part where I'm not clear about yet is how to handle the re-evaluation in a consistent way.

So for example

o1 = {
    a = 5;
    b = a + 2;
    c = a + 3;
    cube([a, b, c]);
}

would evaluate all variables but the cube() is not instantiated yet.

o1(); // instantiate cube([5, 7, 8]);
o2 = o1(a = 2); // customize data, but no geometry instantiation
o2(); // instantiate cube([2, ?, ?]);
o2(c = 1);

That would make a difference between statement and expression evaluation which is probably not intended.

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2019

In the OpenSCAD2 proposal, after executing

o1 = {
    a = 5;
    b = a + 2;
    c = a + 3;
    cube([a, b, c]);
}

then o1 is fully instantiated. It contains fields (a,b,c) and it contains geometry (cube([5,7,8])). There is no need to use o1() to instantiate it, because o1 is already an object.

So you just write o1; as a statement to add the geometry in o1 to some other object. Eg,

   translate([1,1,1]) o1;

When you write o1(a=3) then you are cloning a fully instantiated object to create another fully instantiated object. To do this requires re-executing the original script, except that the value of a is overridden to have the value 3. The definitions of b and c must be re-executed before the geometry is instantiated. The net result is the same as executing

{
    a = 3;
    b = a + 2;
    c = a + 3;
    cube([a, b, c]);
}

If this seems confusing, it's because o1 is simultaneously an object and it is also a module.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 9, 2019

Since OpenSCAD currently evaluates all expressions, then creates geometry, I don't see how any of this makes sense.  

What happens with:

translate([1,1,1]) o1;
o1.b = 7;

Or just:

o1(b = 7);

b is a derived variable from a, so it makes no sense to me it can be modified other than through changing a. In a conventional module this would be expressed and enforced by the argument list.

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2019

@nophead said "b is a derived variable from a, so it makes no sense to me it can be modified other than through changing a."

In the context of OpenSCAD, of course it makes sense. What happens if you put

    a = 3;
    b = a + 2;
    c = a + 3;
    cube([a, b, c]);

into o1.scad, and then you run openscad -Db=7 o1.scad? Same thing, right? Or what if you write

module obj(n) {
   include <o1.scad>
   b=n;
}

and then instantiate obj(7)? Same thing, right?

The reason I put this weird feature into OpenSCAD2 is because OpenSCAD already has this capability (see examples above), and some people on the forum were concerned that this capability should be preserved in future versions of the language.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 9, 2019

Yes you can do that with -D and the command line, but I don't do customisation of objects that way. I only use the command line defs to turn on BOM generation and explode views. I don't know what other people use it for.

However with the customiser, which is designed for customise objects in the GUI you can actually select which variables can be changed and in this example it could be just a. With the normal way of making customisable objects, i.e. modules you can't mess with their internal state.

I don't think the language should be extended to make bad practice easier.

@doug-moen

This comment has been minimized.

Copy link
Contributor

commented Oct 9, 2019

Is it crazy to allow customization of a dependent parameter like b, that depends on another parameter a? When I was writing this design document in 2015, I looked at Mendel90 to see if there was code like that, because I didn't want to leave out a feature that people were using in real projects.

Mendel90 does contain code that customizes dependent parameters. For example, scad/conf/config.scad defines this:

nut_trap_depth = nut_trap_depth(nut);

So nut_trap_depth is a dependent parameter that depends on nut. Elsewhere, this dependent parameter is customized. In scad/frame_edge_clamp.scad, we see:

include <conf/config.scad>
nut_trap_depth = screw_head_height(M3_hex_screw);

I'm not trying to be a dick here, this is actual research I did in 2015 that convinced me that OpenSCAD2 needed to support customization of dependent parameters.

Also, the purpose of this post is to explain the historical document. In 2019, I think OpenSCAD2 is too complicated, and needs to be simplified. The project died because it was too big and complicated. Anything that gets implemented needs to be simpler, easier to understand, easier to implement.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

scad/frame_edge_clamp.scad is used so it has its own namespace. nut_trap_depth is logically a new variable that is not related to the one in config.scad. It does shadow it but that is an accident rather by design.

The one in config is the default screw, which varies between machine size variants. It is used where the size doesn't matter in an attempt to keep the BOM smaller.

The one in frame clamp for fixing a camera is a specific size for the job. So a bad choice of names that clash. Should have been default_nut_trap_depth in config but I am lazy at naming and typing. Often OpenSCAD would give a weird error as the RHS expression has to be in scope in config.scad, and then I would have fixed the name clash but in this case it went unnoticed.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

Rather than adding object literals, which seem to lurch towards more weirdness I would rather see some more simple additions:

module literals to match function literals that can be assigned to variables. So:

mycube = module(a,b,c) cube([a, b, c]);
translate(...) mycube(1,2,3);

That allows polymorphism when drawing things like hot ends. They have some common properties, like length, but when it comes to drawing them I have to switch on manufacturer as there is nothing in common when drawing a J-Head versus an E3D.

Dictionaries e.g. dict = { 'a' : 3, 'b' : 1 }; echo(dict['a'], dict.a);

I use lists to represent objects in an OO way but with these mods they will look a lot more like objects.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

Agreed, what I'm trying to achieve at this time is pretty much the list @doug-moen mentioned in #3088 (comment) - it seems the current design of object literals goes in a different direction which I did not see (and maybe still don't).

I do like the part that allows structured data to be represented easily, including functions and the automatic namespace support. We will some way of importing the objects from another file.

Dictionaries are a separate topic that might be relatively trivial to implement once we have agreement on a syntax how to declare and create modified instances.

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

I think objects literals have taken the normal dictionary syntax and have a syntax to create modified instances. They seem to do everything but in a very confusing way.

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

Well, normal as in Python maybe 😄... yes, this might be the most common way, but there's still lots of other existing solutions.

For example Swift uses

var interestingNumbers = ["primes": [2, 3, 5, 7, 11, 13, 17],
                          "triangular": [1, 3, 6, 10, 15, 21, 28],
                          "hexagonal": [1, 6, 15, 28, 45, 66, 91]]

Which is not a bad fit either. It might collide with the range syntax though.

Scala uses arrow notation

var hashMapName = HashMap("key1"->"value1", "key2"->"value2", "key3"->"value3", ...) 

More examples in different languages: https://en.wikipedia.org/wiki/Comparison_of_programming_languages_(associative_array)

@nophead

This comment has been minimized.

Copy link
Member

commented Oct 10, 2019

Not a Java script programmer but it seems they use the Python syntax but you can use labels as keys and then use the .notation to access them, so they look more like objects, or perhaps they are, not sure.

For example I have:


var frequencies = {
   '5min'  : { major : 300,            minor : 60,            poll : 1},
   'day'   : { major : 3 * 3600,       minor : 3600,          poll : 60},
   'week'  : { major : 24 * 3600,      minor : 6 * 3600,      poll : 600},
   'month' : { major : 7 * 24 * 3600,  minor : 24 * 3600,     poll : 1440},
   'year'  : { major : 31 * 24 * 3600, minor : 7 * 24 * 3600, poll : 17280}
};

And can do frequencies[frequency].poll

I managed to write a web page that draws graphs without learning any Java at all. I just used Google.

@MichaelAtOz

This comment has been minimized.

Copy link
Member Author

commented Oct 10, 2019

Ooops, I though I had submitted this yesterday

The part where I'm not clear about yet is how to handle the re-evaluation in a consistent way.

So for example

o1 = {
    a = 5;
    b = a + 2;
    c = a + 3;
    cube([a, b, c]);
}

would evaluate all variables but the cube() is not instantiated yet.

o1(); // instantiate cube([5, 7, 8]);
o2 = o1(a = 2); // customize data, but no geometry instantiation
o2(); // instantiate cube([2, ?, ?]);
o2(c = 1);

That would make a difference between statement and expression evaluation which is probably not intended.

This is where I'm having concerns too.
That is why I used the term Thingi, I think you need to have 'object' (as in o1 above) but unlike Doug's view, it is not instantiated into a Thingi on creation, that needs to be specifcally done, not just by defining the object.
I can't see how you can have a class like object, when every time it pops more Thingi's into the result.
I was viewing the 'object' as virtual, where you can change stuff, copy it etc, until you want to instantiate it into an actual Thingi.
The 'shape's (as Doug uses in the RFC) e.g. lollipop[1] [== cylinder(3,50)], is a definition, or virtual Thingi, can be used, until finally being instantiated, perhaps as part of other objects, by some call, such as o1(); above.

Further - today.

Re the above, the RFC has

The First Class Values principle requires object literals.
An object literal is a script surrounded by brace brackets.

// define the lollipop object
lollipop = {
  radius   = 10; // candy
  diameter = 3;  // stick
  height   = 50; // stick

  translate([0,0,height]) sphere(r=radius);
  cylinder(d=diameter,h=height);
};

// now add some lollipops to our geometry list
translate([-50,0,0]) lollipop;
translate([50,0,0]) lollipop;

From that, I take the sphere & cylinder in the 'object' do not 'add...to our geometry' list, ie are not Thingi's, only the lollipop calls later do. This then can be utilised with either include or use, if that was one file, is the 'object' lollipop accessible with use which does not add the two lollipop's to our geometry list?

That conflicts with Doug's statement above

In the OpenSCAD2 proposal, after executing

o1 = {
a = 5;
b = a + 2;
c = a + 3;
cube([a, b, c]);
}

then o1 is fully instantiated. It contains fields (a,b,c) and it contains geometry (cube([5,7,8])). There is no need to use o1() to instantiate it, because o1 is already an object.

tp said:

Agreed, what I'm trying to achieve at this time is pretty much the list @doug-moen mentioned

Does that encompass class like include and/or use?

@nathanielstenzel

This comment was marked as off-topic.

Copy link

commented Oct 12, 2019

Is there a chance of getting something like rotate_extrude but around the edge of a 2D shape? This would be similar to the result of using a hand router with cabinetry bits to make the fancy edges around the edges.

@MichaelAtOz

This comment was marked as off-topic.

Copy link
Member Author

commented Oct 12, 2019

@nathanielstenzel that is off-topic, please raise a new issue for a feature request.

@nophead

This comment was marked as off-topic.

Copy link
Member

commented Oct 12, 2019

@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 12, 2019

I was thinking of something which would allow things like import() or text() to be used in a expression context and returning something which will have both raw data and also geometry to instantiate. That seems to be very similar to what object literals would produce, but not exactly the same? Something like

t = text("hallo", 14);
echo(t.width);
echo(t.chars[2].x);
t.text(); // or t.geometry() to actually instantiate the geometry
t(); // not sure if that would make sense on the top level
@MichaelAtOz

This comment has been minimized.

Copy link
Member Author

commented Oct 14, 2019

t.<something to instantiate the geometry>
A quick thesaurus of instantiate and render gives;
manifest, realise (realize - maybe not), embody, create, compose, generate, draw, solidify, publish, show, makeItSo, evoke, depict, make, build, assemble, construct, express, ...
I like t.build() or maybe just reuse a term t.render()?

That, above post, would be handy, presumably restricted (maybe initially) to things were that data is readily available, as opposed to having to render it. I believe the bounding box of text() is part of the API, and import() has the geometry so BB/max/min should be easy.
These types of data would be read only.

Don't know whether extending that to render() is good, e.g.

// actually build the 3d geometry in the object, but not adding it to the global geometry
ob = render() something(); 
echo(wide=(ob.bb.maxX-ob.bb.minX) );
translate([0,0,-ob.bb.minZ])  // bring object to z-axis
  ob.build();  // add pre-rendered geometry (thingi) to the global model/shape/collections-of-thingi
// maybe allow points=ob.points?
ob2=doSomething(ob.points);
oc2.build();
@MichaelAtOz

This comment has been minimized.

Copy link
Member Author

commented Oct 14, 2019

Maybe that last should be

newpoints=doSomething(ob.points);
polyhedron(points=newpoints);
@t-paul

This comment has been minimized.

Copy link
Member

commented Oct 14, 2019

Yes, something like that is certainly on the wishlist (e.g. #1713 and more specifically #301 (comment)). It would be great if we can get that part working, which is pretty much point 3 on the trimmed down list @doug-moen posted above. I don't think the full data access will be the first to be added as that is going to be another one of those API which will be very hard to change once put into the world. Things like bounding box might be easier to define in a way that does not need to be changed later.

One thing to note is that the t.text() in the example above is not calling some kind of member function of t. It would be: evaluate t.text which will result in some kind of value and then do a function call with no parameters on that.

For the function literals that is:

o = { a = 4; f = function(x) a + x; };
echo(o.f(10)); // ECHO: 14
func = o.f;
echo(func(12)); // ECHO 16

In this case o.f evaluates to a function-thingy value (a reference to the function literal including the captured lexical context which defines variable a). This value can be passed around just like a string value. It does have the additional feature that it's possible to call it via (...).

What @nophead showed in #3088 (comment) would make that possible for geometry too. With the old style module definition, it's probably not possible without breaking backward compatibility (just as with functions).

o = { mycube = module(a,b,c) cube([a, b, c]); };
o.mycube();

This seems to overlap the object literals and I'm not sure how we could resolve the conflict there.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
5 participants
You can’t perform that action at this time.