From 8a7be7a68d97b4582dcb99014f9dd428d301c912 Mon Sep 17 00:00:00 2001 From: babayaga Date: Fri, 4 Jul 2025 18:06:29 +0200 Subject: [PATCH] freecad fuckery :) --- .../freecad_box_generator.cpython-311.pyc | Bin 0 -> 8636 bytes cad/drawers/tools/box_folding_tool.FCStd | Bin 40521 -> 0 bytes cad/drawers/tools/cl.md | 25 -- cad/drawers/tools/cp.md | 27 -- cad/drawers/tools/cutlist.md | 21 -- cad/drawers/tools/freecad_box_generator.py | 187 ++++++++++ cad/drawers/tools/my_final_box.FCStd | Bin 0 -> 9764 bytes cad/drawers/tools/run_freecad_generator.sh | 31 ++ cad/drawers/tools/test/box_folding_tool.scad | 348 ++++++++++++++++++ 9 files changed, 566 insertions(+), 73 deletions(-) create mode 100644 cad/drawers/tools/__pycache__/freecad_box_generator.cpython-311.pyc delete mode 100644 cad/drawers/tools/box_folding_tool.FCStd delete mode 100644 cad/drawers/tools/cl.md delete mode 100644 cad/drawers/tools/cp.md delete mode 100644 cad/drawers/tools/cutlist.md create mode 100644 cad/drawers/tools/freecad_box_generator.py create mode 100644 cad/drawers/tools/my_final_box.FCStd create mode 100644 cad/drawers/tools/run_freecad_generator.sh create mode 100644 cad/drawers/tools/test/box_folding_tool.scad diff --git a/cad/drawers/tools/__pycache__/freecad_box_generator.cpython-311.pyc b/cad/drawers/tools/__pycache__/freecad_box_generator.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..87a2650686978aeec358abebd3793c5d0f4351ce GIT binary patch literal 8636 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 literal 0 HcmV?d00001 diff --git a/cad/drawers/tools/box_folding_tool.FCStd b/cad/drawers/tools/box_folding_tool.FCStd deleted file mode 100644 index 58fce831fb7aa0ac462f5463b4664232d295e488..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40521 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 F@i$*v3XcE) diff --git a/cad/drawers/tools/cl.md b/cad/drawers/tools/cl.md deleted file mode 100644 index bb7f7e8..0000000 --- a/cad/drawers/tools/cl.md +++ /dev/null @@ -1,25 +0,0 @@ -# Saw Operator Cut Plan -## Board Dimensions: `500 x 500 mm` - -## Specifications -* **V-Groove Spec:** Depth `5 mm` -* **Slot Spec:** Width `8 mm`, Depth `5 mm` - -## Blade Setup -* **For V-Grooves:** Set saw blade angle to **45 degrees**. -* **For Slots:** Set saw blade angle to **0 degrees** (straight up). - -## SETUP 1: VERTICAL CUTS -### 1. Reference from LEFT edge: -* Set fence to: `80 mm` -- for **V-GROOVE** -### 2. Reference from RIGHT edge (FLIP board 180 deg): -* Set fence to: `80 mm` -- for **V-GROOVE** - -## SETUP 2: HORIZONTAL CUTS -### 1. Rotate board 90 degrees CCW, reference from NEW LEFT edge (original BOTTOM edge): -* Set fence to: `80 mm` -- for **V-GROOVE** -### 2. Reference from NEW RIGHT edge (original TOP edge, FLIP board 180 deg): -* Set fence to: `80 mm` -- for **V-GROOVE** - ---- -*End of Plan* diff --git a/cad/drawers/tools/cp.md b/cad/drawers/tools/cp.md deleted file mode 100644 index 2db285e..0000000 --- a/cad/drawers/tools/cp.md +++ /dev/null @@ -1,27 +0,0 @@ -# Saw Operator Cut Plan -## Board Dimensions: `500 x 500 mm` - -## Specifications -* **V-Groove Spec:** Depth `5 mm` -* **Slot Spec:** Width `8 mm`, Depth `5 mm` - -## Blade Setup -* **For V-Grooves:** Set saw blade angle to **45 degrees**. -* **For Slots:** Set saw blade angle to **0 degrees** (straight up). - -## SETUP 1: VERTICAL CUTS -All distances are from the LEFT edge. Flip board for cuts > 50%. - -* `80 mm` -- **V-GROOVE** -* `254 mm` -- **SLOT** -* `420 mm` -- **V-GROOVE** - -## SETUP 2: HORIZONTAL CUTS -Rotate board 90 deg. All distances from the NEW LEFT edge. - -* `80 mm` -- **V-GROOVE** -* `254 mm` -- **SLOT** -* `420 mm` -- **V-GROOVE** - ---- -*End of Plan* diff --git a/cad/drawers/tools/cutlist.md b/cad/drawers/tools/cutlist.md deleted file mode 100644 index 7875a37..0000000 --- a/cad/drawers/tools/cutlist.md +++ /dev/null @@ -1,21 +0,0 @@ -# Saw Operator Cut Plan -## Board Dimensions: `500 x 500 mm` - -## Specifications -* **V-Groove Spec:** Depth `5 mm` -* **Slot Spec:** Width `8 mm`, Depth `5 mm` - -## Blade Setup -* **For V-Grooves:** Set saw blade angle to **45 degrees**. -* **For Slots:** Set saw blade angle to **0 degrees** (straight up). - -## SETUP 1: VERTICAL CUTS -### 1. Reference from LEFT edge: -### 2. Reference from RIGHT edge (FLIP board 180 deg): - -## SETUP 2: HORIZONTAL CUTS -### 1. Rotate board 90 degrees CCW, reference from NEW LEFT edge (original BOTTOM edge): -### 2. Reference from NEW RIGHT edge (original TOP edge, FLIP board 180 deg): - ---- -*End of Plan* diff --git a/cad/drawers/tools/freecad_box_generator.py b/cad/drawers/tools/freecad_box_generator.py new file mode 100644 index 0000000..b764a9c --- /dev/null +++ b/cad/drawers/tools/freecad_box_generator.py @@ -0,0 +1,187 @@ +# freecad_box_generator.py +# +# A Python script to generate a parametric box natively in FreeCAD. +# +# --- How to use (GUI) --- +# 1. Open FreeCAD and ensure you have a new, empty document open. +# 2. Paste this script into the Python console and press Enter. +# +# --- How to use (Command Line) --- +# FreeCADCmd.exe freecad_box_generator.py [output_file.FCStd] + +import FreeCAD +import Part +import sys + +# --- Customizable Parameters --- +# (Mirrors the parameters from the OpenSCAD file) + +# Overall dimensions +TOTAL_WIDTH = 500 +TOTAL_LENGTH = 500 +BOX_HEIGHT = 80 + +# Material and slot properties +WALL_THICKNESS = 8 + +# Internal grid configuration +NUM_BOXES_U = 2 # Along X-axis (width) +NUM_BOXES_V = 2 # Along Y-axis (length) + + +def create_box_assembly(doc): + """ + Generates the box assembly as native FreeCAD objects in the specified document. + This function does not save the file. + """ + + # --- Create a Group for organization --- + box_group = doc.addObject("App::DocumentObjectGroup", "BoxAssembly") + + # --- Create the Base Plate --- + base = Part.makeBox(TOTAL_WIDTH, TOTAL_LENGTH, WALL_THICKNESS) + base_obj = doc.addObject("Part::Feature", "Base") + base_obj.Shape = base + box_group.addObject(base_obj) + + # --- Calculate Inner Dimensions --- + inner_width = TOTAL_WIDTH - (2 * WALL_THICKNESS) + inner_length = TOTAL_LENGTH - (2 * WALL_THICKNESS) + + # --- Create Outer Walls --- + # South Wall (along the front X-axis) + wall_south = Part.makeBox(TOTAL_WIDTH, WALL_THICKNESS, BOX_HEIGHT) + wall_south.translate(FreeCAD.Vector(0, 0, WALL_THICKNESS)) # Position it on top of the base + wall_south_obj = doc.addObject("Part::Feature", "Wall_South") + wall_south_obj.Shape = wall_south + box_group.addObject(wall_south_obj) + + # North Wall (along the back X-axis) + wall_north = Part.makeBox(TOTAL_WIDTH, WALL_THICKNESS, BOX_HEIGHT) + wall_north.translate(FreeCAD.Vector(0, TOTAL_LENGTH - WALL_THICKNESS, WALL_THICKNESS)) + wall_north_obj = doc.addObject("Part::Feature", "Wall_North") + wall_north_obj.Shape = wall_north + box_group.addObject(wall_north_obj) + + # West Wall (along the left Y-axis, fits between N/S walls) + wall_west = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT) + wall_west.translate(FreeCAD.Vector(0, WALL_THICKNESS, WALL_THICKNESS)) + wall_west_obj = doc.addObject("Part::Feature", "Wall_West") + wall_west_obj.Shape = wall_west + box_group.addObject(wall_west_obj) + + # East Wall (along the right Y-axis, fits between N/S walls) + wall_east = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT) + wall_east.translate(FreeCAD.Vector(TOTAL_WIDTH - WALL_THICKNESS, WALL_THICKNESS, WALL_THICKNESS)) + wall_east_obj = doc.addObject("Part::Feature", "Wall_East") + wall_east_obj.Shape = wall_east + box_group.addObject(wall_east_obj) + + # --- Create Internal Dividers --- + if NUM_BOXES_U > 1: + # --- Vertical Dividers (along Y-axis) --- + compartment_width = inner_width / NUM_BOXES_U + for i in range(1, NUM_BOXES_U): + # Create the main body of the divider + x_pos = (i * compartment_width) + divider_v_body = Part.makeBox(WALL_THICKNESS, inner_length, BOX_HEIGHT) + + # --- Create Notches for Horizontal Dividers (top-down) --- + if NUM_BOXES_V > 1: + # Create all notch cutting tools first + notch_tools = [] + notch_cutout = Part.makeBox(WALL_THICKNESS, WALL_THICKNESS, BOX_HEIGHT / 2) + for j in range(1, NUM_BOXES_V): + compartment_length = inner_length / NUM_BOXES_V + y_pos = (j * compartment_length) - (WALL_THICKNESS / 2) + notch_tools.append(notch_cutout.translated(FreeCAD.Vector(0, y_pos, BOX_HEIGHT / 2))) + # Fuse them into a single cutting tool + cutting_compound = Part.makeCompound(notch_tools) + # Perform a single, efficient cut + divider_v_body = divider_v_body.cut(cutting_compound) + + # Position the final divider and add to document + divider_v_body.translate(FreeCAD.Vector(x_pos, WALL_THICKNESS, WALL_THICKNESS)) + divider_v_obj = doc.addObject("Part::Feature", f"Divider_V_{i}") + divider_v_obj.Shape = divider_v_body + box_group.addObject(divider_v_obj) + + if NUM_BOXES_V > 1: + # --- Horizontal Dividers (along X-axis) --- + compartment_length = inner_length / NUM_BOXES_V + for i in range(1, NUM_BOXES_V): + # Create the main body of the divider + y_pos = (i * compartment_length) + divider_h_body = Part.makeBox(inner_width, WALL_THICKNESS, BOX_HEIGHT) + + # --- Create Notches for Vertical Dividers (bottom-up) --- + if NUM_BOXES_U > 1: + # Create all notch cutting tools first + notch_tools = [] + notch_cutout = Part.makeBox(WALL_THICKNESS, WALL_THICKNESS, BOX_HEIGHT / 2) + for j in range(1, NUM_BOXES_U): + compartment_width = inner_width / NUM_BOXES_U + x_pos = (j * compartment_width) - (WALL_THICKNESS / 2) + notch_tools.append(notch_cutout.translated(FreeCAD.Vector(x_pos, 0, 0))) + # Fuse them into a single cutting tool + cutting_compound = Part.makeCompound(notch_tools) + # Perform a single, efficient cut + divider_h_body = divider_h_body.cut(cutting_compound) + + # Position the final divider and add to document + divider_h_body.translate(FreeCAD.Vector(WALL_THICKNESS, y_pos, WALL_THICKNESS)) + divider_h_obj = doc.addObject("Part::Feature", f"Divider_H_{i}") + divider_h_obj.Shape = divider_h_body + box_group.addObject(divider_h_obj) + + # --- Finalize --- + doc.recompute() + print("Box assembly generated.") + + +def main_cli(): + """Function to run when script is executed from the command line.""" + output_file = "box.FCStd" # Default filename + + # Look for the output file as a positional argument after the script name + # This is a robust way to handle arguments when FreeCAD's parser is unpredictable. + try: + script_index = [i for i, arg in enumerate(sys.argv) if 'freecad_box_generator.py' in arg][0] + if script_index + 1 < len(sys.argv): + output_file = sys.argv[script_index + 1] + except IndexError: + # This case handles running the script directly without FreeCAD, for debugging. + pass + + doc = FreeCAD.newDocument("Box") + create_box_assembly(doc) + + try: + doc.saveAs(output_file) + print(f"Successfully saved box assembly to: {output_file}") + except Exception as e: + print(f"Error saving file: {e}") + + FreeCAD.closeDocument(doc.Name) + # Ensure the command-line application exits cleanly + sys.exit() + +def main_gui(): + """Function to run when script is executed from the FreeCAD GUI.""" + doc = FreeCAD.activeDocument() + if not doc: + doc = FreeCAD.newDocument("Box") + + create_box_assembly(doc) + + if FreeCAD.GuiUp: + FreeCAD.Gui.activeDocument().activeView().viewAxonometric() + FreeCAD.Gui.SendMsgToActiveView("ViewFit") + +# --- Main Execution Block --- +# This determines if the script is running in the GUI or from the command line +# and calls the appropriate main function. +if FreeCAD.GuiUp: + main_gui() +else: + main_cli() \ No newline at end of file diff --git a/cad/drawers/tools/my_final_box.FCStd b/cad/drawers/tools/my_final_box.FCStd new file mode 100644 index 0000000000000000000000000000000000000000..46042fa411529b847621a9c9e1b25945de26a816 GIT binary patch literal 9764 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 + +# --- Argument Validation --- +if [ -z "$1" ]; then + echo "Usage: $0 " + echo "Error: Output file not specified." + exit 1 +fi + +OUTPUT_FILE="$1" + +# --- Execute FreeCAD --- +# We pass the python script to the FreeCAD executable, followed by the +# output filename as a simple positional argument. + +echo "Executing FreeCAD generator script..." +FreeCADCmd.exe -c freecad_box_generator.py "$OUTPUT_FILE" + +if [ $? -ne 0 ]; then + echo "" + echo "Error: FreeCAD script execution failed." + exit 1 +fi + +echo "Script executed successfully." \ No newline at end of file diff --git a/cad/drawers/tools/test/box_folding_tool.scad b/cad/drawers/tools/test/box_folding_tool.scad new file mode 100644 index 0000000..ac1f28a --- /dev/null +++ b/cad/drawers/tools/test/box_folding_tool.scad @@ -0,0 +1,348 @@ +// Customizable parameters for the box folding tool +// These values are based on the provided screenshot. + +// Overall dimensions +TotalWidth = 500; // [100:1000] +TotalLength = 500; // [100:1000] +Height = 80; // [20:200] + +// Material and slot properties +// Updated to reflect the V-groove cutting example +TotalThickness = 8; // [1:20] +BaseThickness = 3; // [0.5:19] +Slot_Width_Walls = 8; // [1:20] for internal dividers + +// Internal grid configuration +Nb_Boxes_U = 2; // [1:10] +Nb_Boxes_V = 2; // [1:10] + +// View control +Folded_View = false; // [true, false] +// Use "export" to render all parts separately for STEP conversion +view_mode = "preview"; // ["preview", "export", "dxf", "cutplan"] + +// Note: InnerBox_Width from the screenshot (100) is not used directly. +// Instead, the compartment widths are calculated based on TotalWidth, Height, and Nb_Boxes_U/V. +// This ensures the design remains consistent with the overall dimensions. + +// --------------------------------------------------------------------- +// Main module to generate the box folding pattern +// --------------------------------------------------------------------- +module unfolded_pattern() { + GrooveDepth = TotalThickness - BaseThickness; + + // To prevent export errors, we perform the cuts sequentially. + // This is more stable than a single, complex difference(). + difference() { + // Start with the result of the first cut... + difference() { + // 1. Create the base plate + cube([TotalWidth, TotalLength, TotalThickness], center = true); + + // 2. Cut the V-Grooves for folding the main box walls + translate([0, 0, TotalThickness/2]) { + wall_grooves(); + } + } + + // 3. ...and then cut the internal rectangular slots from that result. + if (Nb_Boxes_U > 1 || Nb_Boxes_V > 1) { + translate([0,0,TotalThickness/2 - GrooveDepth/2]) + internal_grooves(); + } + } +} + +// --------------------------------------------------------------------- +// Helper modules +// --------------------------------------------------------------------- + +// Module for creating the grooves for the outer walls +// Reverted to simple rectangular slots as a final workaround to bypass +// a persistent, unfixable geometry kernel bug with V-grooves. +module wall_grooves() { + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + GrooveDepth = TotalThickness - BaseThickness; + + // For a folding groove, the width should be slightly less than the material thickness + // to leave some material at the corners. We'll use the depth as a proxy. + FoldingSlotWidth = GrooveDepth; + + // Grooves parallel to Y-axis (vertical) + translate([-InnerWidth/2, 0, 0]) + cube([FoldingSlotWidth, TotalLength, GrooveDepth], center=true); + translate([InnerWidth/2, 0, 0]) + cube([FoldingSlotWidth, TotalLength, GrooveDepth], center=true); + + // Grooves parallel to X-axis (horizontal) + translate([0, -InnerLength/2, 0]) + cube([TotalWidth, FoldingSlotWidth, GrooveDepth], center=true); + translate([0, InnerLength/2, 0]) + cube([TotalWidth, FoldingSlotWidth, GrooveDepth], center=true); +} + +// Module for creating a V-shaped groove for folding (45-degree cuts) +module v_groove(length) { + // This module is no longer used but is kept for historical reference. +} + +// Module for creating the grooves for the internal compartments +module internal_grooves() { + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + GrooveDepth = TotalThickness - BaseThickness; + + CompartmentWidth = (InnerWidth - (Nb_Boxes_U - 1) * Slot_Width_Walls) / Nb_Boxes_U; + CompartmentLength = (InnerLength - (Nb_Boxes_V - 1) * Slot_Width_Walls) / Nb_Boxes_V; + + // Internal vertical grooves + if (Nb_Boxes_U > 1) { + for (i = [1 : Nb_Boxes_U - 1]) { + x_pos = -InnerWidth/2 + i * CompartmentWidth + (i - 1/2) * Slot_Width_Walls; + translate([x_pos, 0, 0]) + cube([Slot_Width_Walls, InnerLength, GrooveDepth], center = true); + } + } + + // Internal horizontal grooves + if (Nb_Boxes_V > 1) { + for (i = [1 : Nb_Boxes_V - 1]) { + y_pos = -InnerLength/2 + i * CompartmentLength + (i - 1/2) * Slot_Width_Walls; + translate([0, y_pos, 0]) + cube([InnerWidth, Slot_Width_Walls, GrooveDepth], center = true); + } + } +} + +// --------------------------------------------------------------------- +// Modules for Folded View +// --------------------------------------------------------------------- + +// Generates the fully assembled 3D box +module folded_box() { + // Inner dimensions of the box + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + + // Material thickness for all parts + wall_thickness = TotalThickness; + + // 1. Base Plate + translate([0, 0, wall_thickness / 2]) + cube([InnerWidth, InnerLength, wall_thickness], center = true); + + // 2. Outer Walls + // South Wall (bottom) - Full Width + translate([0, -InnerLength/2, Height/2 + wall_thickness]) + rotate([90, 0, 0]) + cube([InnerWidth, Height, wall_thickness], center=true); + + // North Wall (top) - Full Width + translate([0, InnerLength/2, Height/2 + wall_thickness]) + rotate([-90, 0, 0]) + cube([InnerWidth, Height, wall_thickness], center=true); + + // West Wall (left) - Shortened to fit between North/South walls + translate([-InnerWidth/2, 0, Height/2 + wall_thickness]) + rotate([0, 90, 0]) + cube([Height, InnerLength - 2 * wall_thickness, wall_thickness], center=true); + + // East Wall (right) - Shortened to fit between North/South walls + translate([InnerWidth/2, 0, Height/2 + wall_thickness]) + rotate([0, -90, 0]) + cube([Height, InnerLength - 2 * wall_thickness, wall_thickness], center=true); + + // 3. Internal Dividers + internal_dividers_folded(); +} + +// Generates the interlocking internal dividers for the folded view +module internal_dividers_folded() { + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + divider_thickness = Slot_Width_Walls; // Use slot width as the divider material thickness + + // Calculate compartment sizes + Compartment_U = (InnerWidth + divider_thickness) / Nb_Boxes_U; + Compartment_V = (InnerLength + divider_thickness) / Nb_Boxes_V; + + // Vertical dividers (U-direction) + for (i = [1 : Nb_Boxes_U - 1]) { + x_pos = -InnerWidth/2 + i * Compartment_U - divider_thickness/2; + difference() { + // Main divider piece + translate([x_pos, 0, Height/2 + TotalThickness]) + cube([divider_thickness, InnerLength, Height], center=true); + // Slots for horizontal dividers (top-down) + for (j = [1 : Nb_Boxes_V - 1]) { + y_pos = -InnerLength/2 + j * Compartment_V - divider_thickness/2; + translate([x_pos, y_pos, Height * 0.75 + TotalThickness]) + cube([divider_thickness + 0.1, divider_thickness, Height/2], center=true); + } + } + } + + // Horizontal dividers (V-direction) + for (j = [1 : Nb_Boxes_V - 1]) { + y_pos = -InnerLength/2 + j * Compartment_V - divider_thickness/2; + difference() { + // Main divider piece + translate([0, y_pos, Height/2 + TotalThickness]) + cube([InnerWidth, divider_thickness, Height], center=true); + // Slots for vertical dividers (bottom-up) + for (i = [1 : Nb_Boxes_U - 1]) { + x_pos = -InnerWidth/2 + i * Compartment_U - divider_thickness/2; + translate([x_pos, y_pos, Height * 0.25 + TotalThickness]) + cube([divider_thickness, divider_thickness + 0.1, Height/2], center=true); + } + } + } +} + +// --------------------------------------------------------------------- +// Module for exporting all parts separately +// --------------------------------------------------------------------- +module export_layout() { + wall_thickness = TotalThickness; + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + + // 1. Base Plate + translate([0, 0, -TotalThickness/2]) + cube([InnerWidth, InnerLength, wall_thickness], center = true); + + // Spacing for laying out parts + spacing = TotalWidth; + + // 2. Outer Walls + translate([spacing, 0, 0]) + cube([InnerWidth, Height, wall_thickness], center=true); // South + translate([spacing, Height + 10, 0]) + cube([InnerWidth, Height, wall_thickness], center=true); // North + + translate([spacing + InnerWidth + 10, 0, 0]) + cube([InnerLength - 2 * wall_thickness, Height, wall_thickness], center=true); // West + translate([spacing + InnerWidth + 10, Height + 10, 0]) + cube([InnerLength - 2 * wall_thickness, Height, wall_thickness], center=true); // East + + // 3. Internal Dividers + divider_thickness = Slot_Width_Walls; + Compartment_U = (InnerWidth + divider_thickness) / Nb_Boxes_U; + Compartment_V = (InnerLength + divider_thickness) / Nb_Boxes_V; + + // Place all vertical dividers in a row + for (i = [1 : Nb_Boxes_U - 1]) { + translate([2 * spacing + (i-1)*(divider_thickness+10), 0, 0]) + internal_divider_vertical_export(InnerLength, Compartment_V); + } + + // Place all horizontal dividers in a row + for (j = [1 : Nb_Boxes_V - 1]) { + translate([2 * spacing, (j-1)*(Height+10) + Height + 10, 0]) + internal_divider_horizontal_export(InnerWidth, Compartment_U); + } +} + +// Helper modules for export layout (without difference operations) +module internal_divider_vertical_export(length, compartment_v_size) { + divider_thickness = Slot_Width_Walls; + difference() { + cube([divider_thickness, length, Height], center=true); + // Notches for horizontal dividers (bottom-up) + for (i = [1 : Nb_Boxes_V - 1]) { + y_pos = -length/2 + i * compartment_v_size - divider_thickness/2; + translate([0, y_pos, -Height/4]) + cube([divider_thickness + 0.1, divider_thickness, Height/2], center=true); + } + } +} + +module internal_divider_horizontal_export(length, compartment_u_size) { + divider_thickness = Slot_Width_Walls; + difference() { + cube([length, divider_thickness, Height], center=true); + // Notches for vertical dividers (top-down) + for (j = [1 : Nb_Boxes_U - 1]) { + x_pos = -length/2 + j * compartment_u_size - divider_thickness/2; + translate([x_pos, 0, Height/4]) + cube([divider_thickness, divider_thickness + 0.1, Height/2], center=true); + } + } +} + +// --------------------------------------------------------------------- +// Module to generate a markdown cut plan for the operator. +// This version avoids all list manipulation for maximum compatibility. +// --------------------------------------------------------------------- +module generate_cut_plan_text() { + // --- Calculations --- + InnerWidth = TotalWidth - 2 * Height; + InnerLength = TotalLength - 2 * Height; + GrooveDepth = TotalThickness - BaseThickness; + + // --- Markdown Output --- + echo("# Saw Operator Cut Plan"); + echo(str("## Board Dimensions: `", TotalWidth, " x ", TotalLength, " mm`")); + echo(""); + echo("## Specifications"); + echo(str("* **V-Groove Spec:** Depth `", GrooveDepth, " mm`")); + echo(str("* **Slot Spec:** Width `", Slot_Width_Walls, " mm`, Depth `", GrooveDepth, " mm`")); + echo(""); + echo("## Blade Setup"); + echo("* **For V-Grooves:** Set saw blade angle to **45 degrees**."); + echo("* **For Slots:** Set saw blade angle to **0 degrees** (straight up)."); + echo(""); + + // --- Setup 1: Vertical Cuts --- + echo("## SETUP 1: VERTICAL CUTS"); + echo("All distances are from the LEFT edge. Flip board for cuts > 50%."); + echo(""); + echo(str("* `", Height, " mm` -- **V-GROOVE**")); + if (Nb_Boxes_U > 1) { + for (i = [1 : Nb_Boxes_U - 1]) { + pos = Height + i * ((InnerWidth - (Nb_Boxes_U - 1) * Slot_Width_Walls) / Nb_Boxes_U) + i * Slot_Width_Walls; + echo(str("* `", pos, " mm` -- **SLOT**")); + } + } + echo(str("* `", TotalWidth - Height, " mm` -- **V-GROOVE**")); + echo(""); + + // --- Setup 2: Horizontal Cuts --- + echo("## SETUP 2: HORIZONTAL CUTS"); + echo("Rotate board 90 deg. All distances from the NEW LEFT edge."); + echo(""); + echo(str("* `", Height, " mm` -- **V-GROOVE**")); + if (Nb_Boxes_V > 1) { + for (i = [1 : Nb_Boxes_V - 1]) { + pos = Height + i * ((InnerLength - (Nb_Boxes_V - 1) * Slot_Width_Walls) / Nb_Boxes_V) + i * Slot_Width_Walls; + echo(str("* `", pos, " mm` -- **SLOT**")); + } + } + echo(str("* `", TotalLength - Height, " mm` -- **V-GROOVE**")); + + echo(""); + echo("---"); + echo("*End of Plan*"); + + // Generate a tiny invisible cube because OpenSCAD needs to produce some geometry. + cube(0.01); +} + +// --------------------------------------------------------------------- +// Render the final object +// --------------------------------------------------------------------- +if (view_mode == "export") { + export_layout(); +} else if (view_mode == "dxf") { + // Project the 2D unfolded pattern for DXF export + projection(cut = true) unfolded_pattern(); +} else if (view_mode == "cutplan") { + generate_cut_plan_text(); +} else { + if (Folded_View) { + folded_box(); + } else { + unfolded_pattern(); + } +} \ No newline at end of file