From ebd90083e760c4fbb2cc3557c37db248bcf22859 Mon Sep 17 00:00:00 2001 From: Dennis Gunia Date: Thu, 11 Sep 2025 21:57:16 +0200 Subject: [PATCH] initial commit --- configs/flap_config_1.yaml | 36 +++++ fillament_presets/bl.json | 35 +++++ fillament_presets/wt.json | 35 +++++ generator.py | 177 +++++++++++++++++++++++++ media/dimensions.png | Bin 0 -> 27113 bytes readme.md | 81 +++++++++++ result.json | 7 + templates/flap_generator_w_svg.scad | 120 +++++++++++++++++ templates/svgs/calendar-blank-icon.svg | 1 + templates/svgs/plane-icon.svg | 49 +++++++ 10 files changed, 541 insertions(+) create mode 100644 configs/flap_config_1.yaml create mode 100644 fillament_presets/bl.json create mode 100644 fillament_presets/wt.json create mode 100755 generator.py create mode 100644 media/dimensions.png create mode 100644 readme.md create mode 100644 result.json create mode 100644 templates/flap_generator_w_svg.scad create mode 100755 templates/svgs/calendar-blank-icon.svg create mode 100755 templates/svgs/plane-icon.svg diff --git a/configs/flap_config_1.yaml b/configs/flap_config_1.yaml new file mode 100644 index 0000000..6e49527 --- /dev/null +++ b/configs/flap_config_1.yaml @@ -0,0 +1,36 @@ +defaults: # default values for all flaps. can be overwritten in 'flaps' + font: "TW Cen MT Condensed:style=Bold" + font_size: 40 + font_y_scale: 1.3 + svg_scale: 0.8 + string: ' ' + svg: '' + +globals: + flap_width: 42.8 # flap width + flap_height: 35 # flap height + flap_thickness: 1 # flap thickness + flap_recess: 18 # recessed area in mm + flap_recess_width: 2 # recessed area width + flap_tap_width: 1 # flap tap width (shoud be smaller than d) + flap_tap_offset: 1 # margin to flap top + +output_path: "./out" +scad_path: "/usr/bin/openscad" +scad_file: "./templates/flap_generator_w_svg.scad" +flaps: + - string: "A" + - string: "B" + - string: "C" + - svg: "svgs/calendar-blank-icon.svg" + svg_scale: 0.8 + - svg: "svgs/plane-icon.svg" + svg_scale: 0.8 + - string: " " + +combine: # not implemented + enable: true + orca_slicer_exec: "/home/dennisgunia/Downloads/OrcaSlicer_Linux_AppImage_V2.3.0.AppImage" + fillament: + background: "./fillament_presets/bl.json" + foreground: "./fillament_presets/wt.json" diff --git a/fillament_presets/bl.json b/fillament_presets/bl.json new file mode 100644 index 0000000..5e584c7 --- /dev/null +++ b/fillament_presets/bl.json @@ -0,0 +1,35 @@ +{ + "type": "filament", + "name": "Bambu PLA Basic @base", + "inherits": "fdm_filament_pla", + "from": "system", + "filament_id": "GFA00", + "instantiation": "false", + "filament_cost": [ + "24.99" + ], + "filament_density": [ + "1.26" + ], + "filament_flow_ratio": [ + "0.98" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_vendor": [ + "Bambu Lab" + ], + "filament_scarf_seam_type": [ + "none" + ], + "filament_scarf_height":[ + "10%" + ], + "filament_scarf_gap":[ + "0%" + ], + "filament_scarf_length":[ + "10" + ] +} \ No newline at end of file diff --git a/fillament_presets/wt.json b/fillament_presets/wt.json new file mode 100644 index 0000000..5e584c7 --- /dev/null +++ b/fillament_presets/wt.json @@ -0,0 +1,35 @@ +{ + "type": "filament", + "name": "Bambu PLA Basic @base", + "inherits": "fdm_filament_pla", + "from": "system", + "filament_id": "GFA00", + "instantiation": "false", + "filament_cost": [ + "24.99" + ], + "filament_density": [ + "1.26" + ], + "filament_flow_ratio": [ + "0.98" + ], + "filament_max_volumetric_speed": [ + "12" + ], + "filament_vendor": [ + "Bambu Lab" + ], + "filament_scarf_seam_type": [ + "none" + ], + "filament_scarf_height":[ + "10%" + ], + "filament_scarf_gap":[ + "0%" + ], + "filament_scarf_length":[ + "10" + ] +} \ No newline at end of file diff --git a/generator.py b/generator.py new file mode 100755 index 0000000..f128ac5 --- /dev/null +++ b/generator.py @@ -0,0 +1,177 @@ +#!/bin/python3 +import yaml +from pathlib import Path +import os +import subprocess +import sys + +config_file = sys.argv[1] +scad_exec_path = "" +scad_out_path = "" +scad_input_path = "" + +# prepare config +config = {} +with open(config_file) as conf_stream: + try: + config = yaml.safe_load(conf_stream) + except yaml.YAMLError as exc: + print(exc) + +# count flaps +flap_count = len(config['flaps']) +print(f"Found {flap_count} flaps:") + +# fill in default values +for flap in config['flaps']: + for default_key in config['defaults'].keys(): + if (default_key not in flap.keys()): + flap[default_key] = config['defaults'][default_key] + print(flap) + +def generate_mesh(): + # check if openscad is installed + scad_exec = Path(config['scad_path']) + try: + scad_exec_path = scad_exec.resolve(strict=True) + except FileNotFoundError as exc: + print(exc) + else: + print(f"Using openscad from {scad_exec_path}") + + # check if output directory exists + scad_out = Path(config['output_path']) + try: + scad_out_path = scad_out.resolve(strict=True) + except FileNotFoundError as exc: + print(exc) + else: + print(f"Save flaps to {scad_out_path}") + + # clear out path + print("Clear output directory") + [f.unlink() for f in scad_out.glob("*") if f.is_file()] + + + # check if input file exists + scad_input = Path(config['scad_file']) + try: + scad_input_path = scad_input.resolve(strict=True) + except FileNotFoundError as exc: + print(exc) + else: + print(f"Use scad file {scad_input_path}") + + ## now start generating flaps + for ix, flap in enumerate(config['flaps']): + this_flap = flap + prev_flap = config['flaps'][(ix -1)%flap_count] + print (f"Generate flap {ix}") + print(this_flap) + print(prev_flap) + + cmd_1 = [f"{scad_exec}"] + cmd_2 = [f"{scad_exec}"] + + cmd_vals = [] + cmd_vals.append(f"char_top=\"{prev_flap['string']}\"") + cmd_vals.append(f"char_bottom=\"{this_flap['string']}\"") + cmd_vals.append(f"svg_top=\"{prev_flap['svg']}\"") + cmd_vals.append(f"svg_bottom=\"{this_flap['svg']}\"") + + cmd_vals.append(f"font_top=\"{prev_flap['font']}\"") + cmd_vals.append(f"font_top_size={prev_flap['font_size']}") + cmd_vals.append(f"font_top_y_scale={prev_flap['font_y_scale']}") + cmd_vals.append(f"svg_top_scale={prev_flap['svg_scale']}") + cmd_vals.append(f"font_bottom=\"{this_flap['font']}\"") + cmd_vals.append(f"font_bottom_size={this_flap['font_size']}") + cmd_vals.append(f"font_bottom_y_scale={this_flap['font_y_scale']}") + cmd_vals.append(f"svg_bottom_scale={this_flap['svg_scale']}") + + # pass globals + for global_var in config['globals'].keys(): + cmd_vals.append(f"{global_var}={config['globals'][global_var]}") + + outfile_a = os.path.join(scad_out, f"flap_{ix}_a.stl") + outfile_b = os.path.join(scad_out, f"flap_{ix}_b.stl") + + cmd_1.append("-o") + cmd_1.append(outfile_a) + cmd_2.append("-o") + cmd_2.append(outfile_b) + + # add to cmd + for cmd_val in cmd_vals: + cmd_1.append("-D") + cmd_1.append(f"{cmd_val}") + cmd_2.append("-D") + cmd_2.append(f"{cmd_val}") + + + cmd_1.append("-D") + cmd_1.append("exp_step=0") + cmd_1.append(str(scad_input_path)) + cmd_2.append("-D") + cmd_2.append("exp_step=1") + cmd_2.append(str(scad_input_path)) + # print out cli command + print (cmd_1) + # generate both parts + subprocess.run(cmd_1) + subprocess.run(cmd_2) + +def run_combine(): # not yet implemented + # combine stls into 3mf + + if config['combine']['enable']: + orca_exec = Path(config['combine']['orca_slicer_exec']) + try: + orca_exec_path = orca_exec.resolve(strict=True) + except FileNotFoundError as exc: + print(exc) + else: + print(f"Using orca slicer from {orca_exec_path}") + # check if output directory exists + scad_out = Path(config['output_path']) + try: + scad_out_path = scad_out.resolve(strict=True) + except FileNotFoundError as exc: + print(exc) + else: + print(f"Use meshes from {scad_out_path}") + + outfile_final = os.path.join(scad_out, f"flap_combine.3mf") + cmd_final = [ + str(orca_exec_path), + "--export-3mf", f"{outfile_final}", + "--debug", "1", + "--allow-multicolor-oneplate" + ] + + for ix, flap in enumerate(config['flaps']): + outfile_a = os.path.join(scad_out, f"flap_{ix}_a.stl") + outfile_b = os.path.join(scad_out, f"flap_{ix}_b.stl") + outfile = os.path.join(scad_out, f"flap_{ix}_multi.3mf") + cmd_final.append(outfile) + cmd = [ + str(orca_exec_path), + "--arrange", "1", + "--export-3mf", f"{outfile}", + "--debug", "1", + "--assemble", + "--ensure-on-bed", + "--allow-multicolor-oneplate", + "--load-filaments", f"{config['combine']['fillament']['background']};{config['combine']['fillament']['foreground']}", + "--load-filament-ids", "1,2", + outfile_a, outfile_b + ] + print(' '.join(cmd)) + subprocess.run(cmd) + # combine into final file + + print(' '.join(cmd_final)) + subprocess.run(cmd_final) + + +#generate_mesh() +run_combine() \ No newline at end of file diff --git a/media/dimensions.png b/media/dimensions.png new file mode 100644 index 0000000000000000000000000000000000000000..06b5f2eafe511099f9b4fd3a2568ac36233aa4b1 GIT binary patch literal 27113 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 +``` + +You can find an example config in `./configs/` + +## Config +The config is stored in a yaml format and contains the following sections: +### `globals` +Contains all global settings defining the dimensions of a single flap. You can change these values to fit your specific project: + +![dimensions drawing](media\dimensions.png) + +### `defaults` +This object contains all default values for a flap. These can be overwritten for each flap. If a specific property is not defined on a flap, this default setting is used. + +- `font`: Font used for text +- `font_size`: Default font size +- `font_y_scale`: Stretches font on y axis +- `svg_scale`: Scale for svg +- `string`: default text. Rcommended to use ' ', even for svg. +- `svg`: absolute path to svg file. If empty, the text is generated. This can also be r path relative to the location of `scad_file` + +### `flaps` +Array of each flap. you can specify each of the above mentioned parameters per flap. Every parameter set here overwerites the default parameters. + +For example: +``` +flaps: + - string: "A" + - string: "B" + - string: "C" + - svg: "C:\\\\Users\\\\DG\\\\Downloads\\\\calendar-blank-icon.svg" + svg_scale: 0.8 +``` + +### `output_path` +This is the string to the output directory. The `.stl` files are stored here. +### `scad_path` +Absolute path to the OpenSCAD executable on your system. For linux you can get it by running `which openscad` +### `scad_file` +Absolute path to the OpenSCAD file used to generate the mesh. Examples located in `./templates/` + +## Printing results +All files are stored in the directory specified by `output_path`. +- The `*_a.stl` file contains the flap itself. This should be printed in black. +- The `*_b.stl` files contains the text or graphics. It should be printed in the foreground color. + +If you use Orca Slicer or Bambu Studio, you can drag both files simultaneously into the slice. It will group them, which makes handling easier. You still have to manually select the fillaments for both parts (foregournd and background). + +## Generate 3mf from stl files +I am working on a way to combine the stl files into one 3mf file. +This requires you to install the Orca slicer from https://orca-slicer.com/#download + +You need to specify it in the config under `combine.orca_slicer_exec`. + +The settings are made under the `combine`section in the config file. + +- `enable`: enables combining the files +- `orca_slicer_exec`: path to the orca slider executable (NOTE: this only works on linux and mac.) +- `fillament`: map of fillament settings to use. You can keep those settings. They only force orca to treat them as two different fillaments. You can change them later manually. \ No newline at end of file diff --git a/result.json b/result.json new file mode 100644 index 0000000..ef8c95f --- /dev/null +++ b/result.json @@ -0,0 +1,7 @@ +{ + "error_string": "Unsupported 3MF version. Please make sure the 3MF file was created with the official version of Bambu Studio, not a beta version.", + "export_time": 105869091762224, + "plate_index": 0, + "prepare_time": 41, + "return_code": -24 +} diff --git a/templates/flap_generator_w_svg.scad b/templates/flap_generator_w_svg.scad new file mode 100644 index 0000000..a40df8c --- /dev/null +++ b/templates/flap_generator_w_svg.scad @@ -0,0 +1,120 @@ +// +// Parametric Split-Flap Generator V1.0 +// (c) Dennis Gunia 2025 - www.dennisgunia.de +// +// Generates printable 3d multi-material model for each flap! +// +flap_width = 42.8*1; // flap width +flap_height = 35; // flap height +flap_thickness = 1; // flap thickness +flap_recess = 18; // recessed area in mm +flap_recess_width = 2; // recessed area width +flap_tap_width = 1; // flap tap width (shoud be smaller than d) +flap_tap_offset = 1; // margin to flap top + +char_bottom = "A"; // char for front (top) +char_top = "W"; // char for back (previous) + +// alternate SVG +svg_top = ""; +svg_top_scale = 0.8; +svg_bottom = ""; +svg_bottom_scale = 0.8; + + +// Font settings +font_bottom = "TW Cen MT Condensed:style=Bold"; +font_bottom_size = 40; +font_bottom_y_scale = 1.3; + +font_top = "TW Cen MT Condensed:style=Bold"; +font_top_size = 40; +font_top_y_scale = 1.3; + +// export step +exp_step = 0; + +// Generate single sided text +module text2d(t, size, font, rotate180=false, y_scale){ + rotate(rotate180 ? 180 : 0) + scale([1,y_scale]) + text(t, size=size, font=font, halign="center", valign="center"); +} + +// Generate base +module base_plate(){ + color([0.1,0.1,0.1]) + translate([0,flap_height/-2,0]){ + union(){ + linear_extrude(flap_thickness) + square([flap_width - flap_recess_width*2, flap_height], center=true); + //generate tab + translate([(flap_width/2)*-1,flap_height/2-(flap_tap_offset+flap_tap_width),0]){ + linear_extrude(flap_thickness) + square([flap_width, flap_tap_width]); + }; + // generate lower part + translate([(flap_width/2)*-1,(flap_height/2)*-1,0]){ + linear_extrude(flap_thickness) + square([flap_width, flap_height-flap_recess]); + }; + } + } +} + +// Generate and translate Text +module text_component(str_bottom, str_top,oversize){ + if (len(svg_bottom) == 0){ + // generate bottom text + translate([0,0,-oversize]){ + mirror([1,0,0]) + linear_extrude(height=0.4+oversize) + text2d(str_bottom,font_top_size,font_bottom,false,font_bottom_y_scale); + } + } else { + // generate bottm shape from svg + translate([0,0,-oversize]){ + mirror([1,0,0]) + linear_extrude(height=0.4+oversize) + scale([svg_bottom_scale,svg_bottom_scale]) + import(svg_bottom, center=true); + } + } + if (len(svg_top) == 0){ + // generate top text + translate([0,0,flap_thickness-0.4+oversize]){ + linear_extrude(height=0.4+oversize) + text2d(str_top,font_top_size,font_top,true,font_top_y_scale); + } + } else { + // generate bottm shape from svg + translate([0,0,flap_thickness-0.4+oversize]){ + rotate(180) + linear_extrude(height=0.4+oversize) + scale([svg_top_scale,svg_top_scale]) + import(svg_top, center=true); + } + } +} + +// assemble final flap +module base(){ + difference(){ + base_plate(); + text_component(char_bottom,char_top,0.1); + } +} +module txt1(){ + difference(){ + color("white") + text_component(char_bottom,char_top,0.0); + translate([flap_width/-2,0,-0.1]) cube([flap_width,flap_height,flap_thickness+0.2]); + } +} +if (exp_step == 0){ + group("base") + base(); +} else { + group("text") + txt1(); +} diff --git a/templates/svgs/calendar-blank-icon.svg b/templates/svgs/calendar-blank-icon.svg new file mode 100755 index 0000000..7ec2075 --- /dev/null +++ b/templates/svgs/calendar-blank-icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/svgs/plane-icon.svg b/templates/svgs/plane-icon.svg new file mode 100755 index 0000000..f885576 --- /dev/null +++ b/templates/svgs/plane-icon.svg @@ -0,0 +1,49 @@ + + + + + + + + +