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

new release has customiser UI #61

Open
Neon22 opened this issue Jan 4, 2017 · 11 comments
Open

new release has customiser UI #61

Neon22 opened this issue Jan 4, 2017 · 11 comments

Comments

@Neon22
Copy link

Neon22 commented Jan 4, 2017

The customiser-style ui is now integrated into openSCAD.
So there is apane with variables that the user can fool with interactively.
IWBNI SolidPython could support this.

What this would mean (I think):

  • order of variables, = order shown in UI
  • highlight sections = groupings of adjustable parameters
  • ignore sectoin so all subsequent varaibles are not shown in inetractive UI.

Maybe thereis a way to tag a parameter (using @ decorator maybe?) as a group name, or interactive variable...

@etjones
Copy link
Contributor

etjones commented Jan 4, 2017

Excellent! I haven't looked into this yet, but I've been wanting some better interactivity for a long time. I'll see what I can figure out there, and if you've got any code suggestions, I'd love to hear them. Cheers!

@etjones
Copy link
Contributor

etjones commented Jan 4, 2017

For future reference: docs for customizer syntax here:
https://en.wikibooks.org/wiki/OpenSCAD_User_Manual/WIP#Customizer

@etjones
Copy link
Contributor

etjones commented Jan 4, 2017

Looks like this shouldn't be too hard to do. My initial thought is something like:

from solid import *

cube_width = ScadSlider(default=5, min=1, max=20, description="Cube Width Label")

c = cube(size=[cube_width, 5, 5,])

print(scad_render(c))

# yields:
'''
// Cube Width Label
cube_width = 5; // [1:20]

cube(size=[cube_width, 5, 5]);
'''

The following classes should cover what's in the current docs:

ScadComboBox( list_or_dict)
ScadSlider()
ScadCheckbox()
ScadSpinbox()
ScadTextBox()
ScadVector()

Not quite sure how to handle code that involves actions on these adjustable parameters, like:

cube_width = ScadSlider(default=5, min=1, max=20)
some_string = ScadTextBox('default value')

# How to deal with arithmetic on numeric values?
cube(size=[2*cube_width, cube_width/other_param, 5])

# Or transforms on strings or vectors?
text(text=some_string + 's')

Subclassing numbers.Number or String seems like overkill, and trying to parse source for strings we can use in an expression seems problematic. The easy way out might be to only allow un-altered Customizer objects as arguments (i.e. cube(size=cube_width) would be OK, but cube(size=2*cube_width) wouldn't be), but that seems tacky to me. Would welcome other approaches to this.

@Neon22
Copy link
Author

Neon22 commented Jan 4, 2017

How about - create a new special_named variable if there was any complex usage of a variable - then use that new name instead.

e.g. in above example - new variable UI_cube_width = 2*cube_width and cube(size=UI_cube_width)
but this involves lexical analysis.

There is a python program that does this called rope which can do code refactoring automagically.
If you did the refactoring in the python code before passing it to SolidPython then it might be quite simple to author. doc link below

Check out a related github project I started here for micropython (unfinished and languishing I admit)
https://github.com/Neon22/python-flavin It uses this approach to search all imports to find every function,etc that is actually used and copy them all into a new import so that only the code and all children that is actually referenced, is compiled into python.
using:

@aarchiba
Copy link

aarchiba commented May 30, 2017

screenshot from 2017-05-30 23-38-00

Some things just aren't possible - if n_sides is a customizer value, then there's no way to (say) let python loop up to n_sides without rerunning python every time the customizer is tweaked. Since SolidPython doesn't generate loops (it doesn't normally need to) this makes customizer variables fundamentally less useful in SolidPython. Nevertheless, basic support could be had by allowing OpenSCAD expressions to be formed: say a CustomizerExpression class so that doing 2 * x generates the OpenSCAD expression "2 * (whatever is in x)".

Fundamentally interactivity is going to require rerunning python code, so it's in the domain of making python code interactive. I know IPython notebooks provide some features to allow web controls in cells to set variables for python. With suitable hackery these could be connected up to generate OpenSCAD scripts, and the autoreload could handle updating the rendered object,
Edited to add: see attached screenshot for a simple demo.

@etjones
Copy link
Contributor

etjones commented Jun 9, 2017

I don't see any particular problem with running the python over and over and over again. Because SP is just doing some fancy string manipulation, it's rare for even big complex models to take more than a half- second or so to run. Not to say that it wouldn't be nice if we could avoid doing that, but changes like that would require a significant architectural change that's not entirely clear to me.

I think your IPython notebook example is a great way to go, @aarchiba; it would be cross-platform and wouldn't require much in the way of widget programming, which is appealing. Probably not going to get to that in the next couple months, but I think it would be a great addition to the project and I hope to have some time to spend on it in the autumn if nobody's beaten me to it

@jeff-dh
Copy link
Contributor

jeff-dh commented May 31, 2021

I would suggest the following:

#test.py
from solid import *

outer_radius = 5
inner_radius = 2
thickness = 3

def customized_washer():

    washer = cylinder(outer_radius, thickness) - cylinder(inner_radius, thickness + 1).down(0.5)

    return washer
#some customizer interface (-> python shell for now ;)
>>> import test
>>> test.outer_radius=20
>>> test.inner_radius=16
>>> test.thickness = 4
>>> test.customized_washer()
difference() {
        cylinder(h = 4, r = 20);
        translate(v = [0, 0, -0.5000000000]) {
                cylinder(h = 5, r = 16);
        }
}
>>> test.customized_washer().save_as_scad() #updates OpenSCAD preview
'/home/xxx/xxx/SolidPython/solid/examples/customize/expsolid_out.scad'
>>> test.outer_radius=8
>>> test.inner_radius=4
>>> test.thickness = 2
>>> test.customized_washer()
difference() {
        cylinder(h = 2, r = 8);
        translate(v = [0, 0, -0.5000000000]) {
                cylinder(h = 3, r = 4);
        }
}
>>> test.customized_washer().save_as_scad() #updates OpenSCAD preview
'/home/xxx/xxx/SolidPython/solid/examples/customize/expsolid_out.scad'
>>> 

(This was tested with expsolid (https://github.com/jeff-dh/SolidPython/tree/exp_solid) but I assume it should work the same way with the regular SolidPython)

This is the command line version of a customizer. If you want a GUI, you could implement a solid-customizer-gui that might use inspect and do exactly what I did from the shell in a generic pattern. If you want sliders and stuff like that you could probably do it somehow with "annotations" or wrapping functions:

outer_radius = 5 #slider[1, 20,1]
#or
outer_radius = customizerGUI.getSliderValue("outer_radius", min=1, max=20, step=1)

or similar, I think this should be possible to implement.

As far as I understand the whole system I don't think it's possible to interact between OpenSCAD and (Solid)Python on a code-level (sharing variables). Therefor I see OpenSCAD only as a "backend" for SolidPython which could actually be replaced (by ImplicitCAD or any csg library). As such I think if SolidPython wants to provide features like a customizer or animated scenes it would have to implement them on it's own and keep using OpenSCAD as backend for processing the resulting csg tree (actually that's how SolidPython uses OpenSCAD and render_animate does this too).

I guess(!) -- any way of trying to overcome this barrier will become a hassle and will not be scalable properly. You will for example have a lot of restrictions. With this solution you can do anything, you can pass the customized variables into any python functions and do whatever you.

This solution is perfectly scalable to really large projects. You could have a config.py which configures a whole printer design (that's how Joseph Prusa did it for the I3, c.f. https://github.com/josefprusa/Prusa3/blob/master/box_frame/configuration.scad.dist).

#config.py
outer_radius = 5
inner_radius = 2
thickness = 3
#test.py
from solid import *
import config

def customized_washer():

    washer = cylinder(config.outer_radius, config.thickness) - cylinder(config.inner_radius, config.thickness + 1).down(0.5)

    return washer
#some customizer interface (-> python shell for now ;)
>>> import config
>>> import test
>>> config.outer_radius = 10
>>> config.inner_radius = 8
>>> config.thickness = 3
>>> test.customized_washer()
difference() {
        cylinder(h = 3, r = 10);
        translate(v = [0, 0, -0.5000000000]) {
                cylinder(h = 4, r = 8);
        }
}

PS: You might get the GUI more or less for free if you use xml configuration files, because I assume there are xml editors out there that can do what you would need and you just have to add a "execute solidpython script" button ;)

@etjones
Copy link
Contributor

etjones commented May 31, 2021

I think what I want really is a native OpenSCAD implementation of the Customizer syntax, so that generated OpenSCAD code can be changed dynamically in the OpenSCAD viewer, and so that projects created with SolidPython could be customized on Thingiverse.

I have a working prototype for OpenSCAD's sliders and dropdown boxes, and I should have checkbox & spinbox variants shortly. Because the OpenSCAD customizer syntax is so minimal, that part was simple. I had to make a custom subclass of float to work nicely with customizer variable math, and that took a little more doing, but I think it should be pretty low impact

@jeff-dh
Copy link
Contributor

jeff-dh commented Jun 1, 2021

What do you think about this:

from solid import *

# ======================================
# = this could be done in some library =
# ======================================
class CustomizerInterface:
    def __init__(self):
        self.header = ""

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

    def get(self, name):
        return scad_inline(name)
# ======================================

customizer = CustomizerInterface()

#register all the custom variables you want to use
customizer.register("objects", "4", "[2, 4, 6]")
customizer.register("side", "4")
customizer.register("cube_pos", "[5, 5, 5]")
customizer.register("cube_size", "5")
customizer.register("text", '"customize me!"' ,' ["customize me!", "Thank you!"]')

#use scad_inline to use them
scene = scad_inline("""
                    for (i = [1:objects]){
                        translate([2*i*side,0,0]){
                            cube(side);
                        }
                    }
                    """)

#use the customizer.get function to use them as parameters
scene += translate(customizer.get("cube_pos")) (
            cube(customizer.get("cube_size")))

scene += translate([0, -20, 0]) (
            text(customizer.get("text")))

scad_render_to_file(scene, file_header = customizer.header)

😃

Works great with expsolid and I think it will also work for the master. You need the scad_inline function from #178.

@jeff-dh
Copy link
Contributor

jeff-dh commented Jun 1, 2021

py_factor = 2
cube_size = customizer.get(f"sin(cube_size) * {py_factor}")

scene += translate(customizer.get("cube_pos")) (
            cube(cube_size))

@jeff-dh
Copy link
Contributor

jeff-dh commented Jun 1, 2021

cube_size = customizer.get(f"cube_size - cube_pos[0] * {py_factor}")

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants