SolidCode / SolidPython

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

Cannot pass OpenSCADObject as parameter #157

Closed ebak closed 3 years ago

ebak commented 3 years ago

Hi,

Thanks for creating SolidPython! It is a very useful tool for OpenSCAD. I have recently started to use its Importing OpenSCAD code feature (https://solidpython.readthedocs.io/en/latest/#importing-openscad-code), and luckily I started with a case which doesn't seem to be supported.

I had problem with the Round-Anything module's polyRound() function: https://kurthutten.com/blog/round-anything-api/

An example OpenSCAD code looks like:

radiiPoints=[[-4,0,1],[5,3,1.5],[0,7,0.1],[8,7,10],[20,20,0.8],[10,0,10]];
polygon(polyRound(radiiPoints,30));

Calling it from SolidPython would look like:

import solid as sd
rounder = sd.import_scad('polyround.scad')

rpoints = [
    (-10, -10, 3), (-10, 10, 3), (10, 10, 3), (10, -10, 3)]

pr = rounder.polyRound(radiipoints=rpoints, fn=32)  # It is an OpenSCADObject
poly = sd.polygon(points=pr)    # OpenSCADObject is passed as parameter

Unfortunately it doesn't work since the constructor of solid.polygon, which cannot take on OpenSCADObject parameters:

    def __init__(self, points: Points, paths: Indexes = None) -> None:
        if not paths:
            paths = [list(range(len(points)))]
        super().__init__('polygon',
                         {'points': _to_point2s(points), 'paths': paths})

It would be very nice to support OpenSCADObjects as paramters.

I have created a very ugly draft patch for myself to make it work:

import solid as sd
import solid.solidpython
from typing import Union, Iterable
from solid.objects import _to_point2s
from solid import OpenSCADObject, Points, Indexes

rounder = sd.import_scad('../SCAD.libs/Round-Anything-master/polyround.scad')

def patched_py2openscad(o: Union[bool, float, str, Iterable]) -> str:
    if isinstance(o, OpenSCADObject):
        return o._render()[:-1]     # PATCH
    if type(o) == bool:
        return str(o).lower()
    if type(o) == float:
        return f"{o:.10f}"  # type: ignore
    if type(o) == str:
        return f'\"{o}\"'  # type: ignore
    if type(o).__name__ == "ndarray":
        import numpy  # type: ignore
        return numpy.array2string(o, separator=",", threshold=1000000000)
    if hasattr(o, "__iter__"):
        s = "["
        first = True
        for i in o:  # type: ignore
            if not first:
                s += ", "
            first = False
            s += patched_py2openscad(i)
        s += "]"
        return s
    return str(o)

class patched_polygon(OpenSCADObject):

    def __init__(self, points: Points, paths: Indexes = None) -> None:
        if isinstance(points, OpenSCADObject):
            super().__init__(
                'polygon',
                {'points': points})
        else:
            if not paths:
                paths = [list(range(len(points)))]
            super().__init__('polygon',
                             {'points': _to_point2s(points), 'paths': paths})

solid.solidpython.py2openscad = patched_py2openscad     # PATCH
sd.polygon = patched_polygon                            # PATCH

rpoints = [
    (-10, -10, 3),
    (-10, 10, 3),
    (10, 10, 3),
    (10, -10, 3)]

pr = rounder.polyRound(radiipoints=rpoints, fn=32)  # It is an OpenSCADObject
poly = sd.polygon(points=pr)    # OpenSCADObject is passed as parameter

def save(sdobj, fname):
    scad = sd.scad_render(sdobj)
    with open(fname, "w") as f:
        f.write(pr.include_string + '\n')   # This is a very ugly workaround!
        f.write(scad)

save(poly, 'example.scad')

Would it be possible to implement a nice solution for this use-case?

Best Regards, Endre

etjones commented 3 years ago

Hi Endre - This issue has come up before, but I think this is a possible way forward that would allow us to use imported OpenSCAD code to make calculations as well as create geometry, which is how I wrote this in the first place.

Thanks for your code here! I think this has promise for dealing with polygon(), and I think we may be able to apply it in a few other parts of the code so that all the functions that could take non-geometry arguments could reasonably accept imported OpenSCAD calculations (instances of IncludedOpenSCADObject). More here soon...

etjones commented 3 years ago

Give the version released on PyPI as SolidPython 1.0.3 a try; it should have resolved this issue. In addition, any SolidPython object should be able to take imported OpenSCAD code as an argument to a function, so this should be a general solution to the problem.

There are, I think some complexities to this approach. If you did a set of nested calculations with imported OpenSCAD code, I'm not certain this would represent them correctly. BUT, for most purposes, which is "Somebody did some calculations I need in this OpenSCAD module-- can I just use them in SolidPython?", this should do the trick.

ebak commented 3 years ago

Hi,

I have verified this with the polygon stuffs. Works great. Thanks!