SolidCode / SolidPython

A python frontend for solid modelling that compiles to OpenSCAD
1.1k stars 171 forks source link

Feature Request: Some way to inline literal OpenSCAD code #178

Open etjones opened 3 years ago

etjones commented 3 years ago

With the impending arrival (fingers crossed) of Customizer support for #61, some features of SolidPython won't play nicely with customizer values. For example:

from solid import cube, scad_render_to_file
from solid.utils import right
from solid.customizer import CustomizerSliderFloat
object_count = CustomizerSlider('objects', 2, 1, 10, step=1)
side = 2
objs = [right(2*i*side)(cube(side)) for i in range(object_count)] # we want this to use a reactive slider value but it can't :-/
all_objs = union()(objs)
scad_render_to_file(all_objs, 'customizer_fail.scad')

This will create an OpenSCAD file with a slider, objects from 1 to 10. But, because the list comprehension (objs = [right(...]) happens only in Python code, the SCAD file will only use the initial value of the slider no matter how a user changes the slider in OpenSCAD.

If we want a row of cubes that reacts to the OpenSCAD slider, we need to run the object connection loop in OpenSCAD itself. One way to do this would be a dedicated scad_loop() function we could add. But we might also add a scad_inline() function that would just paste an OpenSCAD string directly into the output .scad file without translating at all. That should make us able to use Customizer values anywhere, however we like.

Proposed fix:

from solid import cube, scad_render_to_file, scad_inline
from solid.utils import right
from solid.customizer import CustomizerSliderFloat
object_count = CustomizerSlider('objects', 2, 1, 10, step=1)
side = 2
objs = scad_inline('''
for (i = [1:objects]){
    translate([2*i*side,0,0]){
        cube(side);
    }
}
''')

all_objs = union()(objs)
scad_render_to_file(all_objs, 'customizer_fail.scad')

I'm not sure how I should build this yet, but I don't think it should take too much work

jeff-dh commented 3 years ago
class ConstantOpenSCADObject(OpenSCADObject):
    def __init__(self, code):
        super().__init__("not reander able", {})
        self.code = code

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

def scad_inline(code):
    return ConstantOpenSCADObject(code)

You should be able to just plug this into your code in "user space" and use it.

This is not 100% clean, because OpenSCADObject contains a lot of stuff not needed for ConstantOpenSCADObject but since there's no other base class...... You might also leave the inheritance away and just plug a duck typing class with _render into it. (set_modifier or similar will not work with tihs, but I think it also makes not a lot sense to make it work)

Another solution could be to use py2openscad. When ever you render a child, don't call _render, but call py2openscad. If it's a OpenSCADObject it calls _render anyway but if it's just a string it return's it......... this should also work.

But I don't think you'll gain a lot with this..... I'm not really into these customizer stuff but mixing OpenSCAD and SolidPython code -- I think -- will never work properly.

Isn't this the same as if you would create a .scad file that contains:

side = customizer_stuff(....)
objects = customizer_stuff(..)

module customized_boxes()
for (i = [1:objects]){
    translate([2*i*side,0,0]){
        cube(side);
    }
}

import it with SolidPython and call customized_boxes ?

jeff-dh commented 2 years ago

Update:

The scad_inline posted above has a minor issue.

Here's a working (but therefor hacky version):

from solid import *

class ConstantOpenSCADObject(IncludedOpenSCADObject):
    def __init__(self, code):
        self._get_include_path = lambda x : ''
        super().__init__("not reander able", {}, "blablub")
        self.include_string = ''
        self.code = " " + code + " "

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

def scad_inline(code):
    return ConstantOpenSCADObject(code)