# -*- 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, "", 'braille', _m) av.val_type(braille, "", 'braille', _m) av.val_type(braille, "", '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, "", '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, "", 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, "") 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, "") # 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, "") # 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, "") # 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)) == ""): 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, "") # 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, "") # 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, "", 'sz_scheme', _m) av.val_type(col_theme, "", 'col_theme', _m) av.val_type(my_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, "") av.val_list(letters, 'letters', _m, "") av.val_nested_list(numbers, 0, 1, 'numbers', _m, "") av.val_nested_list(braille, 0, 2, 'braille', _m, "", -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, "", 'show_braille', _m) # av.val_type(show_let, "", 'show_let', _m) # av.val_type(show_num, "", 'show_num', _m) av.val_type(mystyle, "", 'mystyle', _m) # if not (av.val_type(col_theme, "", 'col_theme', _m, True) or av.val_type(col_theme, "", 'col_theme', _m, True)): # raise ValueError(av.error_msg_str(_m, 'col_theme', col_theme, "")) av.val_type(col_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