316 lines
9.1 KiB
Python
316 lines
9.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Created on Thu Apr 27 12:03:30 2023
|
|
|
|
@author: Edward Middleton-Smith
|
|
|
|
Procedural OpenSCAD Generation
|
|
https://github.com/taxpon/openpyscad/blob/develop/openpyscad/base.py
|
|
"""
|
|
#import argument_validation as av
|
|
from colour_theme_braille_character import Colour_Theme_Character_Braille
|
|
|
|
# import openpyscad as ops
|
|
from openpyscad import Union
|
|
from typing import Optional
|
|
from pydantic import BaseModel, Field, validator
|
|
# from pydantic.fields import ModelField
|
|
from abc import ABC, abstractmethod
|
|
import os
|
|
|
|
def bool_2_str(mybool):
|
|
# FUNCTION
|
|
# Convert boolean to lowercase string for openscad
|
|
# ARGUMENTS
|
|
# bool mybool
|
|
# ARGUMENT VALIDATION
|
|
if not av.val_bool(mybool):
|
|
return f'Error converting boolean to string.\nInput: {mybool}'
|
|
# RETURNS
|
|
return str(mybool).lower()
|
|
|
|
|
|
def neg_fillet(L, r, centre = True, q = 25):
|
|
# FUNCTION
|
|
# return negative-space fillet object for use with boolean operation
|
|
# ARGUMENTS
|
|
# int L - cylinder height
|
|
# double r - fillet radius
|
|
# bool centre - is fillet centred vertically?
|
|
# int q - quality / discretisation - number of points per edge
|
|
# ARGUMENT VALIDATION
|
|
if not (av.val_double(L, 0) and av.val_double(r, 0) and av.val_bool(centre) and av.val_int(q, 0)):
|
|
return None
|
|
# VARIABLE INSTANTIATION
|
|
fillet = ops.Difference()
|
|
# METHODS
|
|
fillet.append(ops.Cube([r, r, L], center = "false", _fn = q))
|
|
fillet.append(ops.Cylinder(L, r, center = "false"))
|
|
fillet.mirror([1, 1, 0])
|
|
fillet.translate([r, r, -L/2 if centre else 0])
|
|
# RETURNS
|
|
return fillet
|
|
|
|
|
|
def base_fillet_cube(a, b, c, f, q, centre = True):
|
|
# FUNCTION
|
|
# return openpyscad object of cube with base edges and corners filleted with radius f
|
|
# ARGUMENTS
|
|
# double a, b, c - cube dimensions x, y, z
|
|
# double f - fillet radius
|
|
# int q - quality / discretisation - number of points per edge
|
|
# bool centre - is base-filleted cube centred?
|
|
# ARGUMENT VALIDATION
|
|
if not (av.val_double(a, 0) and av.val_double(a, 0) and av.val_double(a, 0)):
|
|
return None
|
|
if not (av.val_double(f, 0, min(a, b, c)) and av.val_int(q, 1) and av.val_bool(centre)):
|
|
return None
|
|
# VARIABLE INSTANTIATION
|
|
my_dif = ops.Difference()
|
|
# METHODS
|
|
my_dif.append(ops.Cube([a, b, c], center = "true", _fn = q).translate([0, 0, c/2]))
|
|
my_dif.append(neg_fillet(a, f, True, q).rotate([90, 0, 0]))
|
|
my_dif.append(neg_fillet(a, f, True, q).rotate([90, 0, 0]).mirror([0, 1, 0]).translate([f + a, 0, 0]))
|
|
my_dif.append(neg_fillet(a, f, True, q).rotate([0, 90, 0]))
|
|
my_dif.append(neg_fillet(a, f, True, q).rotate([0, 90, 0]).mirror([1, 0, 0]).translate([0, f + b, 0]))
|
|
# RETURNS
|
|
return my_dif.translate([0 if centre else a/2, 0 if centre else b/2, -c/2 if centre else 0])
|
|
|
|
|
|
def triprism(a, L, centre = True):
|
|
# FUNCTION
|
|
# Triangular-based prism
|
|
# ARGUMENTS
|
|
# double a - triangle side-length
|
|
# double L - depth in Z-direction
|
|
# bool centre - is triprism centred by volume?
|
|
# ARGUMENT VALIDATION
|
|
if not (av.val_double(a, 0) and av.val_double(L, 0) and av.val_bool(centre)):
|
|
return None
|
|
# RETURNS
|
|
return ops.Polygon([[0,0], [0, a], [a, 0]]).linear_extrude(L, center = bool_2_str(centre)).translate([-a/2 if centre else 0, -a/3 if centre else 0, 0])
|
|
|
|
def get_openscad_colours():
|
|
return [ # openscad_colours = [
|
|
"Lavender",
|
|
"Thistle",
|
|
"Plum",
|
|
"Violet",
|
|
"Orchid",
|
|
"Fuchsia",
|
|
"Magenta",
|
|
"MediumOrchid",
|
|
"MediumPurple",
|
|
"BlueViolet",
|
|
"DarkViolet",
|
|
"DarkOrchid",
|
|
"DarkMagenta",
|
|
"Purple",
|
|
"Indigo",
|
|
"DarkSlateBlue",
|
|
"SlateBlue",
|
|
"MediumSlateBlue",
|
|
"Pink",
|
|
"LightPink",
|
|
"HotPink",
|
|
"DeepPink",
|
|
"MediumVioletRed",
|
|
"PaleVioletRed",
|
|
"Aqua",
|
|
"Cyan",
|
|
"LightCyan",
|
|
"PaleTurquoise",
|
|
"Aquamarine",
|
|
"Turquoise",
|
|
"MediumTurquoise",
|
|
"DarkTurquoise",
|
|
"CadetBlue",
|
|
"SteelBlue",
|
|
"LightSteelBlue",
|
|
"PowderBlue",
|
|
"LightBlue",
|
|
"SkyBlue",
|
|
"LightSkyBlue",
|
|
"DeepSkyBlue",
|
|
"DodgerBlue",
|
|
"CornflowerBlue",
|
|
"RoyalBlue",
|
|
"Blue",
|
|
"MediumBlue",
|
|
"DarkBlue",
|
|
"Navy",
|
|
"MidnightBlue",
|
|
"IndianRed",
|
|
"LightCoral",
|
|
"Salmon",
|
|
"DarkSalmon",
|
|
"LightSalmon",
|
|
"Red",
|
|
"Crimson",
|
|
"FireBrick",
|
|
"DarkRed",
|
|
"GreenYellow",
|
|
"Chartreuse",
|
|
"LawnGreen",
|
|
"Lime",
|
|
"LimeGreen",
|
|
"PaleGreen",
|
|
"LightGreen",
|
|
"MediumSpringGreen",
|
|
"SpringGreen",
|
|
"MediumSeaGreen",
|
|
"SeaGreen",
|
|
"ForestGreen",
|
|
"Green",
|
|
"DarkGreen",
|
|
"YellowGreen",
|
|
"OliveDrab",
|
|
"Olive",
|
|
"DarkOliveGreen",
|
|
"MediumAquamarine",
|
|
"DarkSeaGreen",
|
|
"LightSeaGreen",
|
|
"DarkCyan",
|
|
"Teal",
|
|
"LightSalmon",
|
|
"Coral",
|
|
"Tomato",
|
|
"OrangeRed",
|
|
"DarkOrange",
|
|
"Orange",
|
|
"Gold",
|
|
"Yellow",
|
|
"LightYellow",
|
|
"LemonChiffon",
|
|
"LightGoldenrodYellow",
|
|
"PapayaWhip",
|
|
"Moccasin",
|
|
"PeachPuff",
|
|
"PaleGoldenrod",
|
|
"Khaki",
|
|
"DarkKhaki",
|
|
"Cornsilk",
|
|
"BlanchedAlmond",
|
|
"Bisque",
|
|
"NavajoWhite",
|
|
"Wheat",
|
|
"BurlyWood",
|
|
"Tan",
|
|
"RosyBrown",
|
|
"SandyBrown",
|
|
"Goldenrod",
|
|
"DarkGoldenrod",
|
|
"Peru",
|
|
"Chocolate",
|
|
"SaddleBrown",
|
|
"Sienna",
|
|
"Brown",
|
|
"Maroon",
|
|
"White",
|
|
"Snow",
|
|
"Honeydew",
|
|
"MintCream",
|
|
"Azure",
|
|
"AliceBlue",
|
|
"GhostWhite",
|
|
"WhiteSmoke",
|
|
"Seashell",
|
|
"Beige",
|
|
"OldLace",
|
|
"FloralWhite",
|
|
"Ivory",
|
|
"AntiqueWhite",
|
|
"Linen",
|
|
"LavenderBlush",
|
|
"MistyRose",
|
|
"Gainsboro",
|
|
"LightGrey",
|
|
"Silver",
|
|
"DarkGray",
|
|
"Gray",
|
|
"DimGray",
|
|
"LightSlateGray",
|
|
"SlateGray",
|
|
"DarkSlateGray",
|
|
"Black"
|
|
]
|
|
|
|
"""
|
|
def base_fillet_cube(a, b, c, f, q, centre = "true"):
|
|
# fillets removed as they print horribly
|
|
# now just cube
|
|
my_dif = ops.Difference();
|
|
my_dif.append(ops.Cube([a, b, c], center = "true", _fn = q));
|
|
return my_dif.translate([0 if (centre == "true") else a/2, 0 if (centre == "true") else b/2, 0 if (centre == "true") else c/2])
|
|
|
|
|
|
def triprism(a, L, centre = "true"):
|
|
return ops.Polygon([[0,0], [0, a], [a, 0]]).linear_extrude(L, center = "true").translate([0, 0, 0 if (centre == "true") else L/2]);
|
|
"""
|
|
|
|
"""
|
|
def __get_pydantic_core_schema__(cls, handler):
|
|
return handler.generate_schema(ops.Union)
|
|
|
|
ops.Union.__get_pydantic_core_schema__ = classmethod(__get_pydantic_core_schema__)
|
|
class Union_Field(ModelField):
|
|
def __init__(self, **kwargs):
|
|
super().__init__(**kwargs)
|
|
@classmethod
|
|
def __get_validators__(cls):
|
|
yield cls.validate
|
|
@classmethod
|
|
def validate(cls, value):
|
|
if not isinstance(value, Union):
|
|
raise ValueError(f'Union_Field: {value} is not a valid Union. Type: {type(value)}')
|
|
return value
|
|
"""
|
|
|
|
class ObjectOpenSCAD(BaseModel, ABC):
|
|
path_dir: str
|
|
filename: str
|
|
|
|
def __init__(self, **kwargs): # , path_dir, filename
|
|
# print(f'ObjectOpenSCAD: {path_dir}, {filename}')
|
|
# BaseModel.__init__(self, path_dir=path_dir, filename=filename)
|
|
# super().__init__(path_dir=path_dir, filename=filename, **kwargs)
|
|
BaseModel.__init__(self, **kwargs)
|
|
|
|
@validator('path_dir')
|
|
def validate_path_dir(cls, value):
|
|
if not os.path.exists(value):
|
|
raise ValueError("Path not found: " + value)
|
|
return value
|
|
|
|
@abstractmethod
|
|
def write(self):
|
|
pass
|
|
|
|
class ModelOpenSCAD(ObjectOpenSCAD): # , BaseModel):
|
|
colour_theme: Colour_Theme_Character_Braille
|
|
fn: int = 25
|
|
|
|
model: Optional[Union] = None #= Field(default_factory=Union) # : Optional[ops.Union] = None
|
|
|
|
def __init__(self, **kwargs): # , path_dir, filename, colour_theme, fn):
|
|
# print(f'ModelOpenSCAD: {path_dir}, {filename}, {colour_theme}, {fn}')
|
|
ObjectOpenSCAD.__init__(self, **kwargs) # , path_dir=path_dir, filename=filename)
|
|
BaseModel.__init__(self, **{**kwargs, 'model': None}) # , colour_theme=colour_theme, fn=fn)
|
|
self.model = Union()
|
|
|
|
def write(self):
|
|
self.model.write(self.path_dir + self.filename + '.scad')
|
|
|
|
class Config:
|
|
arbitrary_types_allowed = True
|
|
|
|
class AssemblyOpenSCAD(ObjectOpenSCAD): # , BaseModel):
|
|
dx: int
|
|
dy: int
|
|
max_models_per_row: int
|
|
fn: int = Field(ge=0)
|
|
|
|
def __init__(self, path_dir, filename, dx, dy, max_models_per_row, fn=25):
|
|
ObjectOpenSCAD.__init__(self, path_dir, filename)
|
|
BaseModel.__init__(self, dx, dy, max_models_per_row, fn)
|
|
self.assembly = ops.Assembly() |