SolidCode / SolidPython

A python frontend for solid modelling that compiles to OpenSCAD
1.13k stars 177 forks source link

ScadInterface #181

Open jeff-dh opened 3 years ago

jeff-dh commented 3 years ago
#scad_interface.py
from .solidpython import OpenSCADObject
class ConstantOpenSCADObject(OpenSCADObject):
    def __init__(self, code):
        super().__init__("not valid openscad code !?!?!", {})
        self.code = code

    def _render(self, render_holes=42):
        return self.code

class ScadInterface:
    def __init__(self):
        self.header = ''

    def register_customizer_var(self, name, value, options=''):
        self.header += f'{name} = {value}; //{options}\n'

    def set_global_var(self, name, value):
        self.header += f'{name} = {value};\n'

    def get_header_str(self):
        return self.header

    def register_font(self, filename):
        self.header += f'use <{filename}>\n'

    @staticmethod
    def get(name):
        return self.inline(name)

    @staticmethod
    def inline(code):
        return scad_inline(code)

def scad_inline(code):
    return OpenSCADConstant(code)

#scad_render
def scad_render(root, file_header = '', scad_interface=None):
    [...]
    if scad_interface != None:
        file_header += scad_interface.get_header_str()

    return file_header + includes + scad_body \
                       + extensions_footer_str

These 50 lines are able to close #180, #178, #165 and #61.

If possible I would not let OpenSCADConstant inherit from OpenSCADObject and use duck typing instead. But since the master is full of "typing-stuff" I'm afraid it'll crash it.

Example usage of the Interface: customizer support fonts animation

This can also be used to set global $fn, $fa, $fs.

And I guess it would make the header parameter for scad_render obsolete, right? Any thing else you would like to inject into the header can be done trough the ScadInterface.

etjones commented 3 years ago

It looks like this small amount of code enables a bunch of Python-SCAD bridging.

This seems to involve a some conceptual hopping back and forth between Python & OpenSCAD, and I'm wondering if there are some ways we can automate that, or at least not require someone to be so precised about the somewhat inscrutable OpenSCAD syntax. For instance, the differences between a Customizer slider, dropdown box, and spinbox are all quite small variations in the annotations that we supply to register_customizer_var(). Likewise, the $vpt, $vpr, and $vpd animation variables are all effective, but require a user to know all these cryptic abbreviations. I don't like that style in the OpenSCAD language, and while I don't want to disable use of those variables, it's always been my design goal to offer more obvious and Pythonic options in SolidPython. So I'm interested in some more wrappers around these specifics, even if the generated OpenSCAD code would be identical.

My other goal in the Customizer work has also been to maximize interoperability with the rest of the Python code. What we see in the customizer example allows us to use the customizer values, but only by using scad_inline(), and without enabling any other use of native Python variables. I've kept working on this project because I don't like the OpenSCAD language very much, and I'm hesitant to force people to have two active languages in their minds while they're working on something.

In this branch (some example code here) I've put my approach to all of this. It does allow some greater interoperability with Python code by subclassing float and doing a lot of ugly work. But... what you've got here is much shorter, more direct, and quite graceful. I'm going to think a bit about whether the extra interoperability and explicitness are worth that much more machinery in the code.

jeff-dh commented 3 years ago

I'm a veeeeeerrryyyyyyy big fan of KISS.

What I do like about this interface is that even though you are hopping between OpenSCAD and Python it is obvious what you are doing and what you can do. It's pretty tricky -- at least for me -- to wrap my hat around what is actually going on when you are hopping back and forth and what is where and how possible. This solution shows you are more or less straight line what is possible and what is not possibe. OpenSCADConstants are basically strings and you can't (and the interpreter will tell you I guess) add an integer to it or calculate sin(<customized_var>) in python code and I think that's really good, because I think you can\t interpolate between the two worlds more than passing values from python to openscad, that's all. I think(!) technically impossible unless you have a python2openscad compiler which is more or less impossible because blablabla.....

But go for whatever you like........

Concerning the customizer specification issue: You could also add more methods to the ScadInterface that are nicer like:

class ScadInterface:
    def register_customizer_slider_value(self, name, default, min, max, step):
         #translate the parameters to the openscad syntax

Ouh while typing one should check what happens if you do OpenSCADConstant() + cube(1) I guess this will not complain because OpenSCADConstant is a subclass of OpenSCADObject and that's not good. So remove the inheritance and see whether it crashes in some special case.....

jeff-dh commented 3 years ago

(I did not study every single line of your code, if you have a magic trick somewhere in there, let me know ;)

Your solution looks nice for the user, but for my "policies" I guess I can break it to easy.

I assume:

from math import sin
from numpy import fancy_math_function

sin(customized_variable) #?
fancy_math_function(customized_variable) #?

a = int()
a *= customized_variable
if not isinstance(a, int):
    print("This is true, right?")

sin(a) #crash, right?

I guess this can be really really confusing for the user if you can't distinguish which variable belongs to which world. The buttom line -- I think -- is you can't mix those further than "passing values into strings" + using variables and functions from the OpenSCAD world.

jeff-dh commented 3 years ago

What we see in the customizer example allows us to use the customizer values, but only by using scad_inline(), and without enabling any other use of native Python variables.

did you noticed py_factor?

py_factor = 2
cube_size = scad.inline(f"cube_size - cube_pos[0] * {py_factor}")
jeff-dh commented 3 years ago
a_numbers_dropdown = CustomizerDropdownNumber('a_numbers_dropdown', 4, [2, 4, 12, 14])
text(f"sin({a_numbers_dropdown}) == {sin(a_numbers_dropdown)}")

If I understand your code correctly, I guess this will "break" it. There should be no compile errors or warnings but the output should not be what you would expect if you change the a_numbers_dropdown in the customizer, right?

etjones commented 3 years ago

This is some of what I’ve been struggling with. I’ve been wanting to maintain the Python interoperability, but the boundary of what things are or aren’t updatable with the Customizer is a little fuzzy. Arithmetic will work, but string interpolation will not, as in your example, and non-OpenSCAD function arguments like sin() also will not.

I’m not in love with the mental model of doing all your customization in OpenSCAD literals. I’m even less pleased by having to calculate what things are or aren’t going to work despite being valid Python, using vague rules, though.

Your register_customizer_slider_value( ) suggestion above may be a good middle ground. I’m going to think on this some more.

jeff-dh commented 3 years ago

I’m not in love with the mental model of doing all your customization in OpenSCAD literals.

Well but that's what they are! You won't get around it. No way (I think! Unless you come up with a flux capacitor... but until than.... ;). I'm pretty sure it's technically impossible to get the value of a customized OpenSCAD variable in python, you won't get further than the OpenSCAD literal. And therefor all you can do is manipulate those literals. (unless you come up with a python2openscad compiler, but even than you would actually only manipulate literals (with the compiler output)).

If it is more or less obvious that those variables are OpenSCAD literals and are inlined scad code I can get a clue what I can do with them. I think this is probably the most transparent and least confusing way to enable "hopping" between the worlds. (Even though I expect that even this will confuse quite a lot people at the first sight and should be pointed out explicitly).

Check this out:

a_numbers_dropdown = CustomizerDropdownNumber('a_numbers_dropdown', 4, [2, 4, 12, 14])
assert(a_numbers_dropdown == 4) # this assert will never break!
text(f"{a_numbers_dropdown}") # even though the output of this might be 2, 4, 12, or 14
#edit: ah, not sure about the text line, but this should create cubes of size 2, 4, 12, or 14
cube(a_numbers_dropdown)

#try to figure out the possible values of strange_stuff...
strange_stuff = a_numbers_dropdown + abs(a_numbers_dropdown)

assert(a_numbers_dropdown > 0)
assert(strange_stuff == 2 * a_numbers_dropdown) #this will crash as soon as you customize a_numbers_dropdown

This should be valid python code and should compile and execute just fine but the semantics are way of from what everyone is used to............ this breaks the basic arithmetic laws.......

Your variables will have different values in the two different worlds and now you try to start mixing those worlds. I pretty sure this will cause pure confusion and I think this is the boundary we should not touch but respect and try to make it as transparent as possible.

jeff-dh commented 3 years ago

I would suggest: if you like, you can grab #182 and shape it as you like.

That's my intentionally explicit proposal for the issue (at least for now)...

jeff-dh commented 3 years ago

I guess this is what you want.....

74caa68206c82dff8a83a03e79396fb0445efd54 https://github.com/jeff-dh/SolidPython/tree/greedy_scad_interface

PS: I'm still afraid this might cause confusion.........

Neon22 commented 1 year ago

some of these links don't work anymore. I see from other threads that this all may be being resolved as a new 2.0 version here. https://github.com/jeff-dh/SolidPython/issues/2

Personally I'm a fan of the customiser interface for presenting my code for people to adjust.

jeff-dh commented 1 year ago

Well... probably the answer is yes.

From my point of view, the "2.xx" branch is ready to be used, maintained and I would call it stable. So, I would suggest to jump over and use it.

https://github.com/jeff-dh/SolidPython/tree/master-2.0.0-beta-dev

I guess we both lost the drive "bringing it back' and now it just sits around the corner...... (but I think is already in use by several people and seems to work)