SolidCode / SolidPython

A python frontend for solid modelling that compiles to OpenSCAD
1.11k stars 173 forks source link

Reference Object (generate a module) #76

Closed Jip-Hop closed 7 years ago

Jip-Hop commented 7 years ago

Started with OpenSCAD and SolidPython today for the sake of 3D printing (switching over from Blender). I build my model by unioning partly overlapping cubes based on some JSON data. Next step is to get an equal wall thickness for the unioned model. Came across this post with example code that works in my case.

// This example makes an object hollow with a specified wall thickness, 
// and cuts away the lower half (Z<0). The outer dimensions don't change. 
// The Z=0 plane must cross the object. 

Wall = 0.2;   // Wall thickness 
Box = 100;    // Box must be bigger than the object 

difference() { 
    MyObject(); 
    translate([0,0,-Box/2]) cube(Box,center=true); 
    difference() { 
        translate([0,0,Box/2-0.001]) cube(Box,center=true); 
        minkowski() { 
            cube(2*Wall,center=true);   
            difference() { 
                translate([0,0,Box/2]) cube(Box,center=true); 
                MyObject(); 
            } 
        } 
    } 
} 

module MyObject() { 
    $fn=12; 
    union() {   
        sphere(10); 
        translate([15,0,0]) sphere(10);   
        cube([3,30,6],center=true); 
    } 
}

But as far as I can tell there's no way to program this with SolidPython. How can I output my object as a module, which then can be referenced in the difference section?

It feels wrong to output the same OpenSCAD code twice, when there's a build in way to reference. Apart from making OpenSCAD code with redundant sections, I guess it would also be slower to render.

etjones commented 7 years ago

You're correct that there's no pure-python way to create & reuse OpenSCAD modules. To do this we'd need an actual lexer/parser, where SolidPython is really more or less a string manipulation hack. That said, I'd be curious to do the timing on reused objects to see how much much render speed you gain by using modules versus just repeating code. I've never done this for modules, but a for-loop, for example, is no faster than just inlining all the results.

There are a couple ways you might get around things. 1) Try using the render() statement to force OpenSCAD to cache a given shape's representation. I haven't done any timing on this

2) Use the use() function to import and reuse SCAD code. This will actually give you code very similar to your original SCAD code and should get any speed benefits you'd see in OpenSCAD. The downside is that any modules you wanted to reuse would have to be written in OpenSCAD, which often defeats the purpose of writing in Python.

Here's some example code that does things both ways. I haven't done anything besides verify they both work. If you want to do any timing there, I'd love to hear what kind of results you get!

filename: myObj.scad

module MyObject() { 
    $fn=12; 
    union() {   
        sphere(10); 
        translate([15,0,0]) sphere(10);   
        cube([3,30,6],center=true); 
    } 
}

filename: shell_demo.py; place in same directory as myObj.scad:

#!/usr/bin/env python
from solid import *
from solid.utils import *

# MyObj.scad must include the MyObject() module
use('MyObj.scad')

SEGMENTS = 12
WALL_THICKNESS = 0.2;   # Wall thickness 

def open_bottom_shell(obj, max_obj_dimension, wall_thickness):
    d = max_obj_dimension
    box = cube(d, center=True)
    a = difference()(
        obj, 
        down(d/2)(box),
        difference()(
            up(d/2 - EPSILON)( box),
            minkowski()(
                cube(2*wall_thickness, center=True),
                difference()(
                    difference()(
                        up(d/2)(box),
                        obj
                    )
                )
            )
        ) 
    )
    return a

def pyMyObject():
    return render()(
        union()(
            sphere(10),
            right(15)(sphere(10)),
            cube([3,30,6], center=True)
        )
    )

shell = open_bottom_shell(MyObject(), max_obj_dimension=50, wall_thickness=WALL_THICKNESS)
scad_render_to_file(shell, filepath='scad_obj.scad', file_header='$fn = %s;' % SEGMENTS)  

py_shell = open_bottom_shell(pyMyObject(), max_obj_dimension=50, wall_thickness=WALL_THICKNESS)
scad_render_to_file(py_shell, filepath='py_obj.scad',  file_header='$fn = %s;' % SEGMENTS)  

execute with python shell_demo.py in the same directory where both files are. Load generated files scad_obj.scad and py_obj.scad in OpenSCAD and see how long the CGAL compiles take. They both take comparable time (~22seconds) to load on my late 2016 MacBook, but that's all I've got.

Jip-Hop commented 7 years ago

Thanks a lot for replying and providing me with code so I can run some tests myself. Your examples takes about 23 seconds on my MacBook Pro (Retina, 15 inch, medio 2014). So no difference there.

I've done the same test with a more complex model of my own with similar results:

With module re-use
Generating:
Total rendering time: 0 hours, 5 minutes, 36 seconds
Rendering:
Total rendering time: 0 hours, 0 minutes, 16 seconds

With redundant code
Generating:
Total rendering time: 0 hours, 5 minutes, 48 seconds
Rendering:
Total rendering time: 0 hours, 0 minutes, 17 seconds

I've been using my computer during the test, so this may have influenced the timing. Though this experiment doesn't provide any evidence to believe reusing objects saves time.

Perhaps another experiment will give different insights but for now I'll happily output repeated code. Thanks for replying so quickly!