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

Greedy scad interface #183

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions solid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# from solid import *
# from solid.utils import *
from .solidpython import scad_render, scad_render_to_file
from .greedy_scad_interface import *
from .solidpython import scad_render_animated, scad_render_animated_file
from .solidpython import OpenSCADObject, IncludedOpenSCADObject
from .objects import *
Expand Down
61 changes: 61 additions & 0 deletions solid/examples/greedy_scad_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# ======================================================
# = add relative path to the solid package to sys.path =
# ======================================================
import sys
from pathlib import Path
solidPath = Path(__file__).absolute().parent.parent.parent.as_posix()
sys.path.insert(0, solidPath)
#======================================================
from solid import *

set_global_fn(32)
set_global_viewport_distance(abs(sin(get_animation_time() * 360)) * 10 + 5)
set_global_viewport_translation([0, -1, 0])
set_global_viewport_rotation([63, 0, get_animation_time() * 360])
set_global_viewport_fov(25)

def funny_cube():
customized_color = CustomizerDropdownVariable(name = "cube_color",
default_value = "blue",
options = ["red", "green", "blue"],
label = "The color of the cube",
tab="Colors")

customized_animation_factor = CustomizerSliderVariable(name = "anim_factor",
default_value = 1,
min_ = 1,
max_ = 10,
step = 0.5,
label = "Animation speed factor",
tab = "Animation")

return color(customized_color) (
cube(abs(sin(get_animation_time() * 360 * customized_animation_factor)), center=True)
)

def funny_sphere():
customized_color = ScadValue("cube_color")
customized_animation_factor = ScadValue("anim_factor")

return translate([0, -2, 0]) (
color(customized_color) (
sphere(r = abs(sin(get_animation_time() * 360 * customized_animation_factor - 90)))
)
)

def do_nots():
customized_color = ScadValue("cube_color")
customized_animation_factor = ScadValue("anim_factor")

#if customized_color == "blue":
# print("This causes a python runtime error!")

#for i in range(customized_animation_factor):
# print("This causes a python runtime error!")

#f = 1.0
#f *= customized_animation_factor
#for i in range(f):
# print("This causes a python runtime error! (and this is why it is called greedy)")

scad_render_to_file(funny_cube() + funny_sphere())
4 changes: 4 additions & 0 deletions solid/greedy_scad_interface/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from .customizer_widgets import *
from .scad_variable import *
from .scad_interface import *

31 changes: 31 additions & 0 deletions solid/greedy_scad_interface/customizer_widgets.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from .scad_variable import ScadVariable

class CustomizerDropdownVariable(ScadVariable):
def __init__(self, name, default_value, options='', label='', tab=''):
if isinstance(options, list):
options_str = '[' + ", ".join(map(str, options)) + ']'

if isinstance(options, dict):
reverse_options = [ f'{options[k]} : "{k}"' for k in options.keys()]
options_str = f'[{", ".join(reverse_options)}]'

super().__init__(name, default_value, options_str, label=label, tab=tab)

class CustomizerSliderVariable(ScadVariable):
def __init__(self, name, default_value, min_, max_, step='', label='', tab=''):
options_str = '['
options_str += min_ and str(min_) + ':'
options_str += step and str(step) + ':'
options_str += str(max_) + ']'

super().__init__(name, default_value, options_str, label=label, tab=tab)

class CustomizerCheckboxVariable(ScadVariable):
def __init__(self, name, default_value, label='', tab=''):
super().__init__(name, default_value, label=label, tab=tab)

class CustomizerTextboxVariable(ScadVariable):
def __init__(self, name, default_value, max_length='', label='', tab=''):
options_str = max_length and str(max_length)
super().__init__(name, default_value, options_str, label=label, tab=tab)

30 changes: 30 additions & 0 deletions solid/greedy_scad_interface/scad_interface.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from .scad_variable import ScadVariable, ScadValue

def get_animation_time():
return ScadValue("$t")

def set_global_fn(_fn):
ScadVariable("$fn", _fn)

def set_global_fa(_fa):
ScadVariable("$fa", _fa)

def set_global_fs(_fs):
ScadVariable("$fs", _fs)

def set_global_viewport_translation(trans):
ScadVariable("$vpt", trans)

def set_global_viewport_rotation(rot):
ScadVariable("$vpr", rot)

def set_global_viewport_fov(fov):
ScadVariable("$vpf", fov)

def set_global_viewport_distance(d):
ScadVariable("$vpd", d)

def get_scad_header():
base_str = "\n\n".join(ScadVariable.registered_variables.values())
return f'{base_str}\n'

87 changes: 87 additions & 0 deletions solid/greedy_scad_interface/scad_variable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import math

sin = lambda x: ScadValue(f'sin({x})') if isinstance(x, ScadValue) else math.sin(x)
cos = lambda x: ScadValue(f'cos({x})') if isinstance(x, ScadValue) else math.cos(x)
tan = lambda x: ScadValue(f'tan({x})') if isinstance(x, ScadValue) else math.tan(x)
asin = lambda x: ScadValue(f'asin({x})') if isinstance(x, ScadValue) else math.asin(x)
acos = lambda x: ScadValue(f'acos({x})') if isinstance(x, ScadValue) else math.acos(x)
atan = lambda x: ScadValue(f'atan({x})') if isinstance(x, ScadValue) else math.atan(x)
sqrt = lambda x: ScadValue(f'sqrt({x})') if isinstance(x, ScadValue) else math.sqrt(x)
not_ = lambda x: ScadValue(f'!{x}')

class ScadValue:
def __init__(self, value):
self.value = value

def __repr__(self):
return f'{self.value}'

def __operator_base__(self, op, other):
return ScadValue(f'({self} {op} {other})')

def __unary_operator_base__(self, op):
return ScadValue(f'({op}{self})')

def __illegal_operator__(self):
raise Exception("You can't compare a ScadValue with something else, " +\
"because we don't know the customized value at " +\
"SolidPythons runtime because it might get customized " +\
"at OpenSCAD runtime.")

#basic operators +, -, *, /, %, **
def __add__(self, other): return self.__operator_base__("+", other)
def __sub__(self, other): return self.__operator_base__("-", other)
def __mul__(self, other): return self.__operator_base__("*", other)
def __mod__(self, other): return self.__operator_base__("%", other)
def __pow__(self, other): return self.__operator_base__("^", other)
def __truediv__(self, other): return self.__operator_base__("/", other)

def __radd__(self, other): return self.__operator_base__("+", other)
def __rsub__(self, other): return self.__operator_base__("-", other)
def __rmul__(self, other): return self.__operator_base__("*", other)
def __rmod__(self, other): return self.__operator_base__("%", other)
def __rpow__(self, other): return self.__operator_base__("^", other)
def __rtruediv__(self, other): return self.__operator_base__("/", other)

#unary operators
def __neg__(self): return self.__unary_operator_base__("-")

#other operators
def __abs__(self): return ScadValue(f'abs({self})')

#"illegal" operators
def __eq__(self, other): return self.__illegal_operator__()
def __ne__(self, other): return self.__illegal_operator__()
def __le__(self, other): return self.__illegal_operator__()
def __ge__(self, other): return self.__illegal_operator__()
def __lt__(self, other): return self.__illegal_operator__()
def __gt__(self, other): return self.__illegal_operator__()

#do not allow to evaluate to bool
def __bool__(self):
raise Exception("You can't use scad variables as truth statement because " +\
"we don't know the value of a customized variable at " +\
"SolidPython runtime.")

class ScadVariable(ScadValue):
registered_variables = {}

def __init__(self, name, default_value, options_str='', label='', tab=''):
super().__init__(name)

if name in self.registered_variables.keys():
raise ValueError("Multiple instances of ScadVariable with the same name.")

def_str = self.get_definition(name, default_value, options_str, label, tab)
self.registered_variables.update({name : def_str})

def get_definition(self, name, default_value, options_str, label, tab):
from ..solidpython import py2openscad

tab = tab and f'/* [{tab}] */\n'
label = label and f'//{label}\n'
options_str = options_str and f' //{options_str}'
default_value = py2openscad(default_value)

return f'{tab}{label}{name} = {default_value};{options_str}'

4 changes: 3 additions & 1 deletion solid/solidpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import pkg_resources
import re

from . import greedy_scad_interface

PathStr = Union[Path, str]
AnimFunc = Callable[[Optional[float]], 'OpenSCADObject']
# These are features added to SolidPython but NOT in OpenSCAD.
Expand Down Expand Up @@ -427,7 +429,7 @@ def scad_render(scad_object: OpenSCADObject, file_header: str = '') -> str:
if file_header and not file_header.endswith('\n'):
file_header += '\n'

return file_header + includes + scad_body
return file_header + greedy_scad_interface.get_scad_header() + includes + scad_body

def scad_render_animated(func_to_animate: AnimFunc,
steps: int =20,
Expand Down