665 lines
30 KiB
Python
665 lines
30 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Created on Wed Apr 26 17:11:57 2023
|
|
|
|
@author: Edward Middleton-Smith
|
|
|
|
Braille 3D Model Product Creation
|
|
|
|
Plaintext message translation into Braille for 3D modelling
|
|
|
|
Procedural OpenSCAD Generation
|
|
Braille Scrabble Pieces
|
|
"""
|
|
|
|
# CLASSES
|
|
# ATTRIBUTE DECLARATION
|
|
# METHODS
|
|
# FUNCTION
|
|
# ARGUMENTS
|
|
# ARGUMENT VALIDATION
|
|
# ATTRIBUTE + VARIABLE INSTANTIATION
|
|
# METHODS
|
|
# RETURNS
|
|
|
|
# NORMAL METHODS
|
|
# FUNCTION
|
|
# ARGUMENTS
|
|
# ARGUMENT VALIDATION
|
|
# VARIABLE INSTANTIATION
|
|
# METHODS
|
|
# RETURNS
|
|
|
|
|
|
import openpyscad as ops
|
|
import numpy as np
|
|
import argument_validation as av
|
|
from openscad_objects import gen_openscad_colours
|
|
from typing import Optional
|
|
from prettytable import PrettyTable
|
|
from enum import Enum
|
|
|
|
|
|
class elem_visibility(Enum):
|
|
EMBOSSED = -1
|
|
HIDDEN = 0
|
|
VISIBLE = 1
|
|
def mini():
|
|
# FUNCTION
|
|
# Get minimum value in enumerator
|
|
# RETURNS
|
|
return min([e.value for e in elem_visibility])
|
|
def maxi():
|
|
# FUNCTION
|
|
# Get maximum value in enumerator
|
|
# RETURNS
|
|
return max([e.value for e in elem_visibility])
|
|
|
|
class style():
|
|
# ATTRIBUTE DECLARATION
|
|
braille: elem_visibility
|
|
letter: elem_visibility
|
|
number: elem_visibility
|
|
|
|
def __new__(cls, braille, letter, number):
|
|
_m = 'style.__new__'
|
|
av.val_type(braille, "<enum 'elem_visibility'>", 'braille', _m)
|
|
av.val_type(braille, "<enum 'elem_visibility'>", 'braille', _m)
|
|
av.val_type(braille, "<enum 'elem_visibility'>", 'braille', _m)
|
|
return super(style, cls).__new__(cls)
|
|
|
|
def __init__(self, braille, letter, number):
|
|
self.braille = braille
|
|
self.letter = letter
|
|
self.number = number
|
|
|
|
def gen_filetxt(mystyle):
|
|
# FUNCTION
|
|
# generate filename text for style settings
|
|
# ARGUMENTS
|
|
# style mystyle
|
|
# ARGUMENT VALIDATION
|
|
av.val_type(mystyle, "<class 'translate_braille_2_scad.style'>", 'mystyle', 'get_style_filetxt')
|
|
# VARIABLE INSTANTIATION
|
|
style_txt = '_0b' if (mystyle.braille == elem_visibility.HIDDEN) else ''
|
|
style_txt += '_0l' if (mystyle.letter == elem_visibility.HIDDEN) else ''
|
|
style_txt += '__l' if (mystyle.letter == elem_visibility.EMBOSSED) else ''
|
|
style_txt += '_0n' if (mystyle.number == elem_visibility.HIDDEN) else ''
|
|
# RETURNS
|
|
return style_txt
|
|
|
|
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]);
|
|
|
|
|
|
class scrabble_dimensions:
|
|
# ATTRIBUTE DECLARATION
|
|
name: str
|
|
dp: float # distance between dots
|
|
hblock: float # height of
|
|
s: float # spacing between keys on keyboard
|
|
base_height: float # height of
|
|
hcyl: float # height of Braille dots
|
|
rcyl: float # base radius of Braille dots
|
|
rsphere: float # top radius of Braille dots
|
|
|
|
# METHODS
|
|
def __new__(cls, name, dp, hblock, s, base_height, hcyl, rcyl, rsphere):
|
|
# FUNCTION
|
|
# Initialise class object
|
|
# ARGUMENTS
|
|
# str name - name size scheme
|
|
# float dp - distance between braille pins
|
|
# float hblock - height of base of each key
|
|
# float s - spacing
|
|
# float base_height - height of base block - board
|
|
# float hcyl - height of braille pins
|
|
# float rcyl - base radius of braille pins
|
|
# float rsphere - tip radius of braille pins
|
|
# ARGUMENT VALIDATION
|
|
_m = 'scrabble_dimensions.__new__'
|
|
v_arg_type = 'class attribute'
|
|
av.val_str(name, 'name', _m, 1, v_arg_type = v_arg_type)
|
|
av.full_val_float(dp, 'dp', _m, 0, v_arg_type = v_arg_type)
|
|
av.full_val_float(hblock, 'hblock', _m, v_arg_type = v_arg_type)
|
|
av.full_val_float(s, 's', _m, 0, v_arg_type = v_arg_type)
|
|
av.full_val_float(base_height, 'base_height', _m, 0, v_arg_type = v_arg_type)
|
|
av.full_val_float(hcyl, 'hcyl', _m, 0, v_arg_type = v_arg_type)
|
|
av.full_val_float(rcyl, 'rcyl', _m, 0, v_arg_type = v_arg_type)
|
|
av.full_val_float(rsphere, 'rsphere', _m, 0, v_arg_type = v_arg_type)
|
|
# RETURNS
|
|
return super(scrabble_dimensions, cls).__new__(cls)
|
|
|
|
def __init__(self, name, dp, hblock, s, base_height, hcyl, rcyl, rsphere):
|
|
# FUNCTION
|
|
# Construct class object
|
|
# ARGUMENTS
|
|
# str name
|
|
# float dp
|
|
# float hblock
|
|
# float s
|
|
# float base_height
|
|
# float hcyl
|
|
# float rcyl
|
|
# float rsphere
|
|
# ARGUMENT VALIDATION
|
|
# see __new__()
|
|
# ATTRIBUTE INSTANTIATION
|
|
self.name = name
|
|
self.dp = dp
|
|
self.hblock = hblock
|
|
self.s = s
|
|
self.base_height = base_height
|
|
self.hcyl = hcyl
|
|
self.rcyl = rcyl
|
|
self.rsphere = rsphere
|
|
|
|
def __repr__(self):
|
|
# FUNCTION
|
|
# Convert object to str representation
|
|
# RETURNS
|
|
return f"name = {self.name}, dp = {self.dp}, hblock = {self.hblock}, s = {self.s}, base_height = {self.base_height}, hcyl = {self.hcyl}, rcyl = {self.rcyl}, rsphere = {self.rsphere}"
|
|
|
|
def as_dict(self):
|
|
# FUNCTION
|
|
# Convert object attribute, value pairs to dictionary representation
|
|
# RETURNS
|
|
return {'name': self.name, 'dp': self.dp, 'hblock': self.hblock, 's': self.s, 'base_height': self.base_height, 'hcyl': self.hcyl, 'rcyl': self.rcyl, 'rsphere': self.rsphere}
|
|
|
|
|
|
# colour themes: # ToDo: include in arguments to generator!!
|
|
class export_colour_theme:
|
|
# ATTRIBUTE DECLARATION
|
|
name: str
|
|
coltop: str
|
|
colbase: str
|
|
|
|
# METHODS
|
|
def __new__(cls, name, coltop, colbase, openscad_colours):
|
|
# FUNCTION
|
|
# Initialise class object
|
|
# ARGUMENTS
|
|
# str name - user reference to colour theme
|
|
# str coltop - a valid openSCAD colour e.g. '#010101'
|
|
# str colbase - a valid openSCAD colour e.g. 'blue'
|
|
# list[str] openscad_colours
|
|
# ARGUMENT VALIDATION
|
|
_m = 'export_colour_theme.__new__'
|
|
v_arg_type = 'class attribute'
|
|
av.val_str(name, 'name', _m, 1, v_arg_type = v_arg_type)
|
|
av.val_list(openscad_colours, 'openscad_colours', _m, "<class 'str'>", 1, v_arg_type = v_arg_type)
|
|
if not validate_openscad_colour(coltop, openscad_colours):
|
|
raise ValueError(f"Invalid export_colour_theme attribute coltop. Type = {str(type(coltop))}. Value = {coltop}")
|
|
if not validate_openscad_colour(colbase, openscad_colours):
|
|
raise ValueError(f"Invalid export_colour_theme attribute colbase. Type = {str(type(colbase))}. Value = {colbase}")
|
|
# RETURNS
|
|
return super(export_colour_theme, cls).__new__(cls)
|
|
|
|
def __init__(self, name, coltop, colbase, openscad_colours):
|
|
# FUNCTION
|
|
# Initialise class object
|
|
# ARGUMENTS
|
|
# str name - user reference to colour theme
|
|
# str coltop - a valid openSCAD colour e.g. '#010101'
|
|
# str colbase - a valid openSCAD colour e.g. 'blue'
|
|
# list[str] openscad_colours # redundant in this function - used for argument validation
|
|
# ARGUMENT VALIDATION
|
|
# see __new__()
|
|
# ATTRIBUTE INSTANTIATION
|
|
self.name = name
|
|
self.top = coltop
|
|
self.base = colbase
|
|
|
|
def __repr__(self):
|
|
# FUNCTION
|
|
# Convert object to str representation
|
|
# RETURNS
|
|
return f"name = {self.name}, colour top = {self.top}, colour base = {self.base}" # , openscad_colours = {self.openscad_colours}
|
|
|
|
def as_dict(self):
|
|
# FUNCTION
|
|
# Convert object attribute, value pairs to dictionary representation
|
|
# RETURNS
|
|
return {'name': self.name, 'colour top': self.top, 'colour base': self.base} # , 'openscad_colours': self.openscad_colours
|
|
|
|
def as_row(self):
|
|
# FUNCTION
|
|
# Convert object values to list representation
|
|
# RETURNS
|
|
return [self.name, self.top, self.base] # , 'openscad_colours': self.openscad_colours
|
|
|
|
|
|
def gen_scrabble_sizes(s: Optional[float] = 0.5):
|
|
# default scrabble sizes
|
|
# s = 1/2
|
|
# def __init__(self, name, dp, hblock, s, base_height, hcyl, rcyl, rsphere):
|
|
k = 2.3622
|
|
szs_scrabble = []
|
|
szs_scrabble.append(scrabble_dimensions('sz_1', 2.5, 2*s, s, 4*s, 1.2, 2/3, 1/2))
|
|
szs_scrabble.append(scrabble_dimensions('sz_2', 4, 1.28, s, 4*s, 1.2, 3/2, 1))
|
|
szs_scrabble.append(scrabble_dimensions('sz_3', 8, 2.5, s, 6*s, 1.2, 3/2, 1))
|
|
szs_scrabble.append(scrabble_dimensions('sz_4', 10, 3, 1, 6*s, 1.2, 3/2, 1))
|
|
szs_scrabble.append(scrabble_dimensions('sz_5', 10, 1, 1, 6*s, 1.2, 3/2, 1))
|
|
szs_scrabble.append(scrabble_dimensions('keyboard', 2.5, 0.8, 0.5, 4, 1.2, 2/3, 0.5))
|
|
szs_scrabble.append(scrabble_dimensions('poster', 2.5, 0.8, 0.5, 2, 1.2, 2/3, 0.5))
|
|
szs_scrabble.append(scrabble_dimensions('poster_big', 2.5*k, 0.8, 0.5*k, 2, 1.2, 2/3*k, 0.5*k))
|
|
# print('szs_scrabble')
|
|
# print(szs_scrabble)
|
|
return szs_scrabble # pd.DataFrame([x.as_dict() for x in szs_scrabble])
|
|
|
|
|
|
def input_product_size(szs_product, v_size = 2):
|
|
# FUNCTION
|
|
# input valid product size from user
|
|
# ARGUMENTS
|
|
# list[scrabble_dimensions] szs_product
|
|
# int v_size
|
|
# ARGUMENT VALIDATION
|
|
_m = 'get_product_size'
|
|
av.val_list(szs_product, 'szs_product', _m, "<class 'translate_braille_2_scad.scrabble_dimensions'>")
|
|
av.val_int(v_size, 'v_size', _m)
|
|
# VARIABLE INSTANTIATION
|
|
n = len(szs_product)
|
|
# METHODS
|
|
if v_size > 0 and v_size <= n:
|
|
return szs_product[v_size - 1]
|
|
my_table = PrettyTable()
|
|
my_table.field_names = ['index', 'name', 'dp', 'hblock', 's', 'base_height', 'hcyl', 'rcyl', 'rsphere']
|
|
for i in range(n):
|
|
prod = szs_product[i]
|
|
my_table.add_row([i + 1, prod.name, prod.dp, prod.hblock, prod.s, prod.base_height, prod.hcyl, prod.rcyl, prod.rsphere])
|
|
print()
|
|
print("Please select product dimensions configuration from below:")
|
|
print(my_table)
|
|
# loopy = False
|
|
while True:
|
|
my_input = input("Product dimensions configuration (name or index): ")
|
|
if my_input == "#!ERRORCODE!#": exit
|
|
for i in range(n):
|
|
if validate_input_product_size(my_input, szs_product):
|
|
# loopy = True
|
|
# RETURNS
|
|
return get_product_size(my_input, szs_product)
|
|
|
|
|
|
def get_product_size(product_option, szs_product):
|
|
# FUNCTION
|
|
# get valid product size from list of scrabble_dimensions
|
|
# ARGUMENTS
|
|
# str/int product_option
|
|
# list[scrabble_dimensions] szs_product
|
|
# ARGUMENT VALIDATION
|
|
_m = 'get_product_size'
|
|
error_msg = av.error_msg_str(_m, product_option, 'product_option', 'scrabble_dimensions identifier')
|
|
if not (av.val_int(product_option, 'product_option', _m, suppress_errors = True, suppress_console_outputs = True) or av.val_str(product_option, 'product_option', _m, suppress_errors = True, suppress_console_outputs = True)):
|
|
raise ValueError(error_msg)
|
|
av.val_list(szs_product, 'szs_product', _m, "<class 'translate_braille_2_scad.scrabble_dimensions'>")
|
|
# VARIABLE INSTANTIATION
|
|
n = len(szs_product)
|
|
# METHODS
|
|
if av.full_val_int(product_option, 'product_option', _m, suppress_errors = True):
|
|
product_option = av.input_int(product_option, 'product_option', _m)
|
|
return szs_product[product_option - 1]
|
|
for col_i in range(n):
|
|
my_product = szs_product[col_i]
|
|
if product_option == my_product.name:
|
|
return my_product
|
|
# RETURNS
|
|
raise ValueError(error_msg)
|
|
|
|
|
|
def validate_input_product_size(product_option, szs_product):
|
|
# FUNCTION
|
|
# evaluate if product_option relates to a szs_product
|
|
# ARGUMENTS
|
|
# str/int product_option
|
|
# list[scrabble_dimensions] szs_product
|
|
# ARGUMENT VALIDATION
|
|
_m = 'validate_input_product_size'
|
|
if not (av.val_int(product_option, 'product_option', _m, suppress_errors = True, suppress_console_outputs = True) or av.val_str(product_option, 'product_option', _m, suppress_errors = True, suppress_console_outputs = True)): return False
|
|
av.val_list(szs_product, 'szs_product', _m, "<class 'translate_braille_2_scad.scrabble_dimensions'>")
|
|
# VARIABLE INSTANTIATION
|
|
n = len(szs_product)
|
|
# METHODS
|
|
if av.full_val_int(product_option, 'product_option', _m, suppress_errors = True):
|
|
product_option = av.input_int(product_option, 'product_option', _m)
|
|
return (0 < product_option and product_option <= n)
|
|
for prod_i in range(n):
|
|
if product_option == szs_product[prod_i].name:
|
|
return True
|
|
# RETURNS
|
|
return False
|
|
|
|
|
|
def validate_openscad_colour(mycolour, openscad_colours):
|
|
# FUNCTION
|
|
# validate argument openscad colour as string
|
|
# ARGUMENTS
|
|
# str mycolour - to be validated
|
|
# list[str] openscad_colours - to search through
|
|
# ARGUMENT VALIDATION
|
|
_m = 'validate_openscad_colour'
|
|
av.val_str(mycolour, 'mycolour', _m)
|
|
av.val_list(openscad_colours, 'openscad_colours', _m, "<class 'str'>")
|
|
# VARIABLE INSTANTIATION
|
|
N = len(mycolour)
|
|
# METHODS
|
|
if (N == 7):
|
|
if (mycolour[0] == '#' and mycolour[0:5].isnumeric):
|
|
return True
|
|
for i in range(len(openscad_colours)):
|
|
if (mycolour == openscad_colours[i]):
|
|
return True
|
|
# RETURNS
|
|
return False
|
|
|
|
|
|
# inputs
|
|
# path_scad = "C:/Users/edwar/OneDrive/Documents/OpenSCAD"
|
|
# path_stl = "C:/Users/edwar/OneDrive/Documents/OpenSCAD/STL"
|
|
# filename = "Braille_Scrabble_"
|
|
# cum = [0, 1]
|
|
# letters = ["B"]
|
|
# braille = [[2]]
|
|
# braille = [[[1, 1, 0, 0, 0, 0]]]
|
|
# lmsg = 1
|
|
# N = 1
|
|
# n_row_keys = 1
|
|
# maxln = 2
|
|
|
|
def gen_col_themes():
|
|
# FUNCTION
|
|
# create colour themes for image export of 3D models
|
|
# REQUIRES
|
|
# class export_colour_theme
|
|
# function gen_openscad_colours
|
|
# VARIABLE INSTANTIATION
|
|
openscad_colours = gen_openscad_colours()
|
|
col_themes = []
|
|
col_themes.append(export_colour_theme("Blue", "#337AFF", "#337AFF", openscad_colours))
|
|
col_themes.append(export_colour_theme("Purple", "#8E44AD", "#8E44AD", openscad_colours))
|
|
col_themes.append(export_colour_theme("Red", "#F7322F", "#F7322F", openscad_colours))
|
|
col_themes.append(export_colour_theme("Black", "Black", "#17202A", openscad_colours))
|
|
col_themes.append(export_colour_theme("White", "White", "White", openscad_colours))
|
|
# RETURNS
|
|
# list[export_colour_theme] col_themes - colour themes with name, top colour, base colour
|
|
return col_themes
|
|
|
|
|
|
def input_colour_themes(colour_permutations = False, colour_option = None):
|
|
# FUNCTION
|
|
# get valid colour option using parameter, else user console input
|
|
# ARGUMENTS
|
|
# bool colour_permutations
|
|
# int colour_option
|
|
# ARGUMENT VALIDATION
|
|
_m = 'input_colour_themes'
|
|
av.val_bool(colour_permutations, 'colour_permutations', _m)
|
|
if not (av.val_int(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True) or av.val_str(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True) or str(type(colour_option)) == "<class 'NoneType'>"):
|
|
raise ValueError(av.error_msg_str(_m, 'colour_option', colour_option, "export_colour_theme identifier"))
|
|
# VARIABLE INSTANTIATION
|
|
colour_themes = gen_col_themes()
|
|
if colour_permutations: return colour_themes
|
|
n = len(colour_themes)
|
|
# METHODS
|
|
if not validate_input_colour(colour_option, colour_themes):
|
|
colour_table = PrettyTable(field_names=['index', 'name', 'colour top', 'colour base'])
|
|
for col_i in range(n):
|
|
colour_table.add_row([col_i + 1] + colour_themes[col_i].as_row()) # .insert(0, str(col_i + 1))
|
|
print(colour_table)
|
|
while not validate_input_colour(colour_option, colour_themes):
|
|
colour_option = input("Please input colour selection by name or index above.")
|
|
# RETURNS
|
|
return [get_colour_theme(colour_option, colour_themes)]
|
|
|
|
|
|
def validate_input_colour(colour_option, colour_themes):
|
|
# FUNCTION
|
|
# evaluate if colour_option relates to a colour_theme
|
|
# ARGUMENTS
|
|
# str/int colour_option
|
|
# list[export_colour_themes] colour_themes
|
|
# ARGUMENT VALIDATION
|
|
_m = 'validate_input_colour'
|
|
if not (av.val_int(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True) or av.val_str(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True)): return False
|
|
av.val_list(colour_themes, 'colour_themes', _m, "<class 'translate_braille_2_scad.export_colour_theme'>")
|
|
# VARIABLE INSTANTIATION
|
|
n = len(colour_themes)
|
|
# METHODS
|
|
if av.full_val_int(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True):
|
|
colour_option = av.input_int(colour_option, 'colour_option', _m)
|
|
return (0 < colour_option and colour_option <= n)
|
|
for col_i in range(n):
|
|
if colour_option == colour_themes[col_i].name:
|
|
return True
|
|
# RETURNS
|
|
return False
|
|
|
|
|
|
def get_colour_theme(colour_option, colour_themes):
|
|
# FUNCTION
|
|
# get valid colour_option from colour_themes
|
|
# ARGUMENTS
|
|
# str/int colour_option
|
|
# list[export_colour_themes] colour_themes
|
|
# ARGUMENT VALIDATION
|
|
_m = 'get_colour_theme'
|
|
error_msg = av.error_msg_str(_m, colour_option, 'colour_option', 'export_colour_theme identifier')
|
|
if not (av.val_int(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True) or av.val_str(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True)):
|
|
raise ValueError(error_msg)
|
|
av.val_list(colour_themes, 'colour_themes', _m, "<class 'translate_braille_2_scad.export_colour_theme'>")
|
|
# VARIABLE INSTANTIATION
|
|
n = len(colour_themes)
|
|
# METHODS
|
|
if av.full_val_int(colour_option, 'colour_option', _m, suppress_errors = True, suppress_console_outputs = True):
|
|
colour_option = av.input_int(colour_option, 'colour_option', _m)
|
|
return colour_themes[colour_option - 1]
|
|
for col_i in range(n):
|
|
my_colour = colour_themes[col_i]
|
|
if colour_option == my_colour.name:
|
|
return my_colour
|
|
# ERROR HANDLING
|
|
raise ValueError(error_msg)
|
|
|
|
|
|
def gen_path_braille_scrabble(sz_scheme, col_theme, my_style, path_file = "C:\\Users\\edwar\\OneDrive\\Documents\\Programming\\Python Scripts\\Braille_Scrabble", suffix = "stl", presuffix=""):
|
|
# FUNCTION
|
|
# generate file path for braille scrabble design
|
|
# ARGUMENTS
|
|
# scrabble_dimensions sz_scheme
|
|
# int letter - index in the alphabet with base 0
|
|
# export_colour_theme col_theme
|
|
# style my_style
|
|
# string path_file - path of folder + filename (message text)
|
|
# string suffix - file type
|
|
# string presuffix - text before file type
|
|
# ARGUMENT VALIDATION
|
|
_m = 'gen_path_braille_scrabble'
|
|
av.val_type(sz_scheme, "<class 'translate_braille_2_scad.scrabble_dimensions'>", 'sz_scheme', _m)
|
|
av.val_type(col_theme, "<class 'translate_braille_2_scad.export_colour_theme'>", 'col_theme', _m)
|
|
av.val_type(my_style, "<class 'translate_braille_2_scad.style'>", 'my_style', _m)
|
|
av.val_str(path_file, 'path_file', _m)
|
|
av.val_str(suffix, 'suffix', _m)
|
|
av.val_str(presuffix, 'presuffix', _m)
|
|
# RETURNS
|
|
return f"{path_file}_{sz_scheme.name}_{col_theme.name}{my_style.gen_filetxt()}.{suffix}"
|
|
|
|
|
|
def gen_openscad_braille(cum, letters, numbers, braille, n_keys, n_row_keys, n_rows, mydims, mystyle, col_theme, filename, path_file="C:/Users/edwar/OneDrive/Documents/Programming/Python Scripts/Braille", quality = 25):
|
|
# FUNCTION
|
|
# generate openscad model of Braille product
|
|
# ARGUMENTS
|
|
# list[int] cum
|
|
# list[str] letters - array of plaintext for output
|
|
# list[int] numbers - ordinality of translated keys in alphabet / number line (where appropriate)
|
|
# list[list[int]] braille - translated keys
|
|
# int n_keys - number of keys in Braille message
|
|
# int n_row_keys - number of Braille keys per row on keyboard
|
|
# int n_rows - number of rows of keys on keyboard
|
|
# scrabble_dimensions mydims - sizing parameters
|
|
# style mystyle
|
|
# export_colour_themes col_theme
|
|
# str filename - excl. path
|
|
# str path_file
|
|
# ARGUMENT VALIDATION
|
|
_m = 'gen_openscad_braille'
|
|
av.val_list(cum, 'cum', _m, "<class 'int'>")
|
|
av.val_list(letters, 'letters', _m, "<class 'str'>")
|
|
av.val_nested_list(numbers, 0, 1, 'numbers', _m, "<class 'int'>")
|
|
av.val_nested_list(braille, 0, 2, 'braille', _m, "<class 'int'>", -1, [-1, -1, 6], -1, [-1, -1, 6])
|
|
av.val_int(n_keys, 'n_keys', _m)
|
|
av.val_int(n_row_keys, 'n_row_keys', _m)
|
|
av.val_int(n_rows, 'n_rows', _m)
|
|
# av.val_int(show_braille, 'show_braille', _m)
|
|
# av.val_int(show_let, 'show_let', _m)
|
|
# av.val_int(show_num, 'show_num', _m)
|
|
# av.val_type(show_braille, "<enum 'elem_visibility'>", 'show_braille', _m)
|
|
# av.val_type(show_let, "<enum 'elem_visibility'>", 'show_let', _m)
|
|
# av.val_type(show_num, "<enum 'elem_visibility'>", 'show_num', _m)
|
|
av.val_type(mystyle, "<class 'translate_braille_2_scad.style'>", 'mystyle', _m)
|
|
# if not (av.val_type(col_theme, "<class 'export_colour_theme'>", 'col_theme', _m, True) or av.val_type(col_theme, "<class 'translate_braille_2_scad.export_colour_theme'>", 'col_theme', _m, True)):
|
|
# raise ValueError(av.error_msg_str(_m, 'col_theme', col_theme, "<class 'export_colour_theme'>"))
|
|
av.val_type(col_theme, "<class 'translate_braille_2_scad.export_colour_theme'>", 'col_theme', _m)
|
|
av.val_str(filename, 'filename', _m)
|
|
av.val_str(path_file, 'path_file', _m)
|
|
# VARIABLE INSTANTIATION
|
|
# integer boolean conversions for position
|
|
i_s_braille = int(mystyle.braille == elem_visibility.VISIBLE)
|
|
i_s_let = int(mystyle.letter == elem_visibility.VISIBLE)
|
|
i_s_num = int(mystyle.number == elem_visibility.VISIBLE)
|
|
print(f'i_s_b = {i_s_braille}\ni_s_let = {i_s_let}\ni_s_num = {i_s_num}\n')
|
|
# dimensions
|
|
dp = mydims.dp # horizontal and vertical spacing between dots = 2.2 - 2.8mm
|
|
h_block = mydims.hblock # block height
|
|
s = mydims.s # inter-block spacing
|
|
h_base = mydims.base_height # thickness of base block
|
|
# derived from: horizontal distance between corresponding braille dots in adjacent cells = 5.1 - 6.8mm
|
|
# and also conforms to: vertical distance between corresponding braille dots in adjacent cells = 10 - 15mm
|
|
hcyl = mydims.hcyl # braille dot height
|
|
rcyl = mydims.rcyl #3/2; # braille dot base-radius
|
|
rsphere = mydims.rsphere #3/2*4/5; # braille dot top-radius
|
|
xb = dp + 2 * s + rcyl * 2
|
|
R_base_letter = [xb, xb] # alphabet block dimensions [x, y]
|
|
R_base_braille = [xb, xb + dp] # braille block dimensions [x, y]
|
|
R_base_number = np.array([xb, xb - 2 * s]) * i_s_num # number block dimensions [x, y] ' adjust to scalar coefficient rather than linear translation
|
|
htxt = hcyl # height of text
|
|
stxt = xb - 4 * s
|
|
snum = stxt * 0.75
|
|
ntxtsfn = 0.65 # scale factor for shrinking number text to fit more letters in single char space
|
|
ntxtsfm = 0.75 # scale factor for shrinking message text to fit more letters in single char space
|
|
font = '"Arial:style=Bold"'
|
|
_fn = quality
|
|
|
|
# keyboard layout
|
|
#maxln = 10;
|
|
#nln = min(maxln, N); # renamed n_row_keys
|
|
# temp = 1 + (N - 1) % nln
|
|
|
|
# yn = (N + nln - temp) / nln # - 0 ** (temp)
|
|
# print(f"number of rows = {yn}")
|
|
|
|
R_board = [n_row_keys * R_base_letter[0] + (n_row_keys + 3) * s,
|
|
n_rows * (R_base_braille[1] * i_s_braille + R_base_letter[1] * i_s_let + R_base_number[1] * i_s_num + s * (i_s_braille + i_s_let + i_s_num)) + 3 * s, # - 3 * s,
|
|
h_base] # +R_base_number[1]
|
|
print(f"R_board = {R_board}")
|
|
obj_dif = ops.Difference()
|
|
obj_uni = ops.Union()
|
|
|
|
# obj_uni.append(base_fillet_cube(R_board[0], R_board[1], R_board[2], h_block, centre = "false").translate([-2 * s - rcyl, 2 * s + rcyl - R_board[1], -h_block - R_board[2]]).color(col_theme.base))
|
|
obj_uni.append(ops.Cube([R_board[0], R_board[1], R_board[2]], h_block, centre = "false").translate([-3 * s - rcyl, 3 * s + rcyl - R_board[1], -h_block - R_board[2]]).color(col_theme.base))
|
|
|
|
# METHODS
|
|
for ab_i in range(len(braille)): # alpha-braille iterator
|
|
char = braille[ab_i]
|
|
print(f"char = braille[{ab_i}]") # " = {char}")
|
|
for char_j in range(len(char)):
|
|
char_old = [0, 0, 0, 0, 0, 0] if (ab_i == 0 and char_j == 0) else braille[0][char_j - 1] if (char_j > 0) else braille[ab_i - 1][len(braille[ab_i - 1]) - 1]
|
|
char_new = char[char_j]
|
|
print(f"char_new = {char_new}")
|
|
ab_n = (cum[ab_i] + char_j) % n_row_keys
|
|
ab_len = ((cum[ab_i] + char_j) - ab_n) / n_row_keys
|
|
ab_p = [ab_n * (R_base_braille[0] + s), -ab_len * (R_base_braille[1] * i_s_braille + R_base_letter[1] * i_s_let + R_base_number[1] * i_s_num + s * (i_s_braille + i_s_let + i_s_num)), 0]
|
|
# Bases
|
|
if (letters[ab_i] != " "):
|
|
if (mystyle.braille == elem_visibility.VISIBLE):
|
|
obj_uni.append(ops.Cube([R_base_braille[0], R_base_braille[1], h_block]).color(col_theme.base).translate([-(s + rcyl), s + rcyl - R_base_braille[1], -h_block]).translate(ab_p))
|
|
if (mystyle.letter == elem_visibility.VISIBLE):
|
|
obj_uni.append(ops.Cube([R_base_letter[0], R_base_letter[1], h_block]).color(col_theme.base).translate([-(s + rcyl), s + rcyl -R_base_letter[1], 0]).translate([0, -(R_base_braille[1] + s) * i_s_braille, -h_block]).translate(ab_p)) # should this be: rcyl - R_base_letter[1] ?????!?!?!?!!!!
|
|
|
|
# Braille message
|
|
# Dots
|
|
if (mystyle.braille == elem_visibility.VISIBLE):
|
|
for dot_y in range(3):
|
|
for dot_x in range(2):
|
|
if (char_new[dot_y + 3 * dot_x] == 1):
|
|
obj_uni.append(ops.Cylinder(hcyl, None, rcyl, rsphere, _fn = _fn, center = 'true').color(col_theme.top).translate([dot_x * dp, -dot_y * dp, 0]).translate(ab_p))
|
|
# Text
|
|
if (mystyle.letter == elem_visibility.VISIBLE):
|
|
obj_uni.append(ops.Text(f'"{letters[ab_i]}"', stxt * ntxtsfm ** (len(letters[ab_i]) - 1), font, halign = '"center"', valign = '"center"', _fn = _fn).linear_extrude(htxt, center = "false").translate([R_base_letter[0] / 2, R_base_letter[1] / 2, 0]).color(col_theme.top).translate([-(s + rcyl), s + rcyl -R_base_letter[1], 0]).translate([0, - (R_base_braille[1] + s) * i_s_braille, 0]).translate(ab_p))
|
|
# Text ordinality
|
|
num_new = numbers[ab_i]
|
|
if (num_new != [-1] and mystyle.number == elem_visibility.VISIBLE):
|
|
print(f"num_new = {num_new}, type = {type(num_new)}")
|
|
for num_x in range(len(num_new)):
|
|
snum = (R_base_number[1] - s * (len(num_new) + 1)) / (len(num_new) ** ntxtsfn)
|
|
# Base
|
|
obj_uni.append(ops.Cube([R_base_number[0], R_base_number[1], h_block]).color(col_theme.base).translate([0, -R_base_number[1], 0]).translate([-(s + rcyl), s + rcyl -(R_base_letter[1] + s) * i_s_let, 0]).translate([0, -(R_base_braille[1] + s) * i_s_braille, -h_block]).translate(ab_p))
|
|
# Number (text)
|
|
obj_uni.append(ops.Text(f'"{num_new[num_x]}"', snum, font, halign = '"center"', valign = '"center"', _fn = _fn).linear_extrude(htxt, center = "true").color(col_theme.top).translate([R_base_number[0] / 2 if (len(num_new) == 1) else R_base_number[0] / 2 + 1.6 * snum * (num_x - (len(num_new) - 1) / 2), R_base_number[1] * 0.25, htxt / 2]).translate([-s, s -R_base_number[1], 0]).translate([0, -(R_base_letter[1] - s) * i_s_let, 0]).translate([s-(s + rcyl), s - (R_base_braille[1] + s) * i_s_braille, -h_block * 0]).translate(ab_p))
|
|
|
|
obj_dif.append(obj_uni)
|
|
# remove excess plastic if no numbers on final row regardless of mystyle.number
|
|
remove_num_space = True
|
|
for ab_i in range(n_row_keys):
|
|
remove_num_space &= numbers[len(numbers) - ab_i - 1] == [-1]
|
|
# Trimming tools for no numbers:
|
|
if remove_num_space:
|
|
obj_dif.append(ops.Cube([R_board[0], R_base_number[1] + s, R_board[2]], center = "true").translate([R_board[0] / 2 - 3 * s - rcyl, 3 * s + rcyl + (R_base_number[1] + s) / 2 - R_board[1], -R_board[2] / 2 - h_block]))
|
|
# obj_dif.append(triprism(h_block, R[0], centre = "true").rotate([0, 90, 0]).translate([R[0] / 2 - 1.6, (12+4) * s - R[1], -1/1000 - R[2] - h_block]))
|
|
|
|
# embossed characters
|
|
for ab_i in range(len(braille)):
|
|
char = braille[ab_i]
|
|
print(f"char = braille[{ab_i}]") # " = {char}")
|
|
for char_j in range(len(char)):
|
|
# what is the purpose of char_old?
|
|
char_old = [0, 0, 0, 0, 0, 0] if (ab_i == 0 and char_j == 0) else braille[0][char_j - 1] if (char_j > 0) else braille[ab_i - 1][len(braille[ab_i - 1]) - 1]
|
|
char_new = char[char_j]
|
|
ab_n = (cum[ab_i] + char_j) % n_row_keys
|
|
ab_len = ((cum[ab_i] + char_j) - ab_n) / n_row_keys
|
|
ab_p = [ab_n * (R_base_braille[0] + s), -ab_len * (R_base_braille[1] * i_s_braille + R_base_letter[1] * i_s_let + R_base_number[1] * i_s_num + s * (i_s_braille + i_s_let + i_s_num)), 0]
|
|
|
|
# Text
|
|
if (mystyle.letter == elem_visibility.EMBOSSED):
|
|
obj_dif.append(ops.Text(f'"{letters[ab_i]}"', stxt * ntxtsfm ** (len(letters[ab_i]) - 1) * 1.15, font, halign = '"center"', valign = '"center"', _fn = _fn).linear_extrude(htxt, center = "false").mirror([1, 0, 0]).translate([R_base_letter[0] / 2, R_base_letter[1] * 3 / 4, - R_board[2]]).color(col_theme.top).translate([0, -R_base_braille[1], 0]).translate([-(s + rcyl), s + rcyl - (R_base_braille[1] + s) * i_s_braille * 0, -h_block]).translate(ab_p))
|
|
|
|
# my_prefix = '_0b' if (mystyle.braille == elem_visibility.HIDDEN) else ''
|
|
# my_prefix += '_0l' if (mystyle.letter == elem_visibility.HIDDEN) else ''
|
|
# my_prefix += '__l' if (mystyle.letter == elem_visibility.EMBOSSED) else ''
|
|
# my_prefix += '_0n' if (mystyle.number == elem_visibility.HIDDEN) else ''
|
|
# my_prefix = mystyle.gen_filetxt()
|
|
|
|
path_png = gen_path_braille_scrabble(mydims, col_theme, mystyle, path_file, 'png')
|
|
path_scad = gen_path_braille_scrabble(mydims, col_theme, mystyle, path_file, 'scad')
|
|
path_stl = gen_path_braille_scrabble(mydims, col_theme, mystyle, path_file, 'stl')
|
|
# RETURNS
|
|
obj_dif.write(path_scad)
|
|
print("writing SCAD")
|
|
# obj_dif.write(path_png)
|
|
return path_png, path_scad, path_stl
|
|
|
|
|
|
# def get_shownum(braille):
|
|
# shownum = 1
|
|
# for n_x in range(len(braille)):
|
|
# shownum &= not (not braille[n_x])
|
|
# return 0 # 1 if shownum else 0
|