saw cut list gen

This commit is contained in:
babayaga 2025-07-04 14:25:51 +02:00
parent 64f676a716
commit 76c3a3cdff
9 changed files with 1886 additions and 120 deletions

View File

@ -7,9 +7,10 @@ TotalLength = 500; // [100:1000]
Height = 80; // [20:200]
// Material and slot properties
TotalThickness = 5; // [1:20]
// Updated to reflect the V-groove cutting example
TotalThickness = 8; // [1:20]
BaseThickness = 3; // [0.5:19]
Slot_Width_Walls = 8; // [1:20]
Slot_Width_Walls = 8; // [1:20] for internal dividers
// Internal grid configuration
Nb_Boxes_U = 2; // [1:10]
@ -18,7 +19,7 @@ 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", "cutlist"]
view_mode = "preview"; // ["preview", "export", "dxf"]
// 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.
@ -30,20 +31,25 @@ view_mode = "preview"; // ["preview", "export", "dxf", "cutlist"]
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() {
// 1. Create the base plate
cube([TotalWidth, TotalLength, TotalThickness], center = true);
// Start with the result of the first cut...
difference() {
// 1. Create the base plate
cube([TotalWidth, TotalLength, TotalThickness], center = true);
// 2. Create the cutting grooves
translate([0, 0, TotalThickness/2 - GrooveDepth/2]) {
// Grooves for the main box walls
wall_grooves();
// Grooves for internal compartments
if (Nb_Boxes_U > 1 || Nb_Boxes_V > 1) {
internal_grooves();
// 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();
}
}
}
@ -52,22 +58,33 @@ module unfolded_pattern() {
// ---------------------------------------------------------------------
// 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([Slot_Width_Walls, TotalLength, GrooveDepth], center=true);
cube([FoldingSlotWidth, TotalLength, GrooveDepth], center=true);
translate([InnerWidth/2, 0, 0])
cube([Slot_Width_Walls, TotalLength, GrooveDepth], center=true);
cube([FoldingSlotWidth, TotalLength, GrooveDepth], center=true);
// Grooves parallel to X-axis (horizontal)
translate([0, -InnerLength/2, 0])
cube([TotalWidth, Slot_Width_Walls, GrooveDepth], center=true);
cube([TotalWidth, FoldingSlotWidth, GrooveDepth], center=true);
translate([0, InnerLength/2, 0])
cube([TotalWidth, Slot_Width_Walls, GrooveDepth], center=true);
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
@ -254,60 +271,6 @@ module internal_divider_horizontal_export(length, compartment_u_size) {
}
}
// ---------------------------------------------------------------------
// Module to generate a cut list for the operator
// ---------------------------------------------------------------------
module generate_cutlist() {
// --- Calculations ---
InnerWidth = TotalWidth - 2 * Height;
InnerLength = TotalLength - 2 * Height;
// --- Header ---
echo("--- Cut List for Saw Operator ---");
echo(str("Board Dimensions: ", TotalWidth, " x ", TotalLength, " mm"));
echo("All distances are for the centerline of the slots.");
echo("---");
// --- Vertical Cuts (Parallel to Y-axis) ---
echo("VERTICAL CUTS (Distances from left edge):");
// Outer walls
v_outer_1 = Height;
v_outer_2 = TotalWidth - Height;
echo(str(" Outer Wall 1: ", v_outer_1, " mm"));
echo(str(" Outer Wall 2: ", v_outer_2, " mm"));
// Internal dividers
if (Nb_Boxes_U > 1) {
CompartmentWidth = (InnerWidth - (Nb_Boxes_U - 1) * Slot_Width_Walls) / Nb_Boxes_U;
for (i = [1 : Nb_Boxes_U - 1]) {
x_pos_abs = Height + i * CompartmentWidth + i * Slot_Width_Walls;
echo(str(" Internal Divider ", i, ": ", x_pos_abs, " mm"));
}
}
echo("---");
// --- Horizontal Cuts (Parallel to X-axis) ---
echo("HORIZONTAL CUTS (Distances from bottom edge):");
// Outer walls
h_outer_1 = Height;
h_outer_2 = TotalLength - Height;
echo(str(" Outer Wall 1: ", h_outer_1, " mm"));
echo(str(" Outer Wall 2: ", h_outer_2, " mm"));
// Internal dividers
if (Nb_Boxes_V > 1) {
CompartmentLength = (InnerLength - (Nb_Boxes_V - 1) * Slot_Width_Walls) / Nb_Boxes_V;
for (i = [1 : Nb_Boxes_V - 1]) {
y_pos_abs = Height + i * CompartmentLength + i * Slot_Width_Walls;
echo(str(" Internal Divider ", i, ": ", y_pos_abs, " mm"));
}
}
echo("--- End of List ---");
// Generate a tiny invisible cube because OpenSCAD needs to produce some geometry.
cube(0.01);
}
// ---------------------------------------------------------------------
// Render the final object
// ---------------------------------------------------------------------
@ -316,8 +279,6 @@ if (view_mode == "export") {
} else if (view_mode == "dxf") {
// Project the 2D unfolded pattern for DXF export
projection(cut = true) unfolded_pattern();
} else if (view_mode == "cutlist") {
generate_cutlist();
} else {
if (Folded_View) {
folded_box();

BIN
cad/drawers/tools/box_folding_tool_false.png (Stored with Git LFS) Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -57,6 +57,150 @@ LINE
21
-250
0
LINE
8
0
10
-165
20
-4
11
-165
21
4
0
LINE
8
0
10
-165
20
4
11
-4
21
4
0
LINE
8
0
10
-4
20
4
11
-4
21
165
0
LINE
8
0
10
-4
20
165
11
4
21
165
0
LINE
8
0
10
4
20
165
11
4
21
4
0
LINE
8
0
10
4
20
4
11
165
21
4
0
LINE
8
0
10
165
20
4
11
165
21
-4
0
LINE
8
0
10
165
20
-4
11
4
21
-4
0
LINE
8
0
10
4
20
-4
11
4
21
-165
0
LINE
8
0
10
4
20
-165
11
-4
21
-165
0
LINE
8
0
10
-4
20
-165
11
-4
21
-4
0
LINE
8
0
10
-4
20
-4
11
-165
21
-4
0
ENDSEC
0
SECTION

View File

@ -0,0 +1,101 @@
import argparse
def generate_cut_plan(total_width, total_length, height, base_thickness, total_thickness, slot_width, u_boxes, v_boxes):
"""
Calculates and prints a markdown cut plan for the operator.
"""
# --- Calculations ---
inner_width = total_width - 2 * height
inner_length = total_length - 2 * height
groove_depth = total_thickness - base_thickness
# --- Collect all cut positions ---
vertical_cuts = []
if u_boxes > 1:
compartment_width = (inner_width - (u_boxes - 1) * slot_width) / u_boxes
for i in range(1, u_boxes):
pos = height + (i * compartment_width) + (i * slot_width)
vertical_cuts.append(pos)
vertical_cuts.extend([height, total_width - height])
horizontal_cuts = []
if v_boxes > 1:
compartment_length = (inner_length - (v_boxes - 1) * slot_width) / v_boxes
for i in range(1, v_boxes):
pos = height + (i * compartment_length) + (i * slot_width)
horizontal_cuts.append(pos)
horizontal_cuts.extend([height, total_length - height])
# --- Markdown Output ---
print("# Saw Operator Cut Plan")
print(f"## Board Dimensions: `{total_width} x {total_length} mm`")
print("")
print("## Specifications")
print(f"* **V-Groove Spec:** Depth `{groove_depth:.2f} mm`")
print(f"* **Slot Spec:** Width `{slot_width} mm`, Depth `{groove_depth:.2f} mm`")
print("")
print("## Blade Setup")
print("* **For V-Grooves:** Set saw blade angle to **45 degrees**.")
print("* **For Slots:** Set saw blade angle to **0 degrees** (straight up).")
print("")
# --- Setup 1: Vertical Cuts ---
print("## SETUP 1: VERTICAL CUTS")
print("### 1. Reference from LEFT edge:")
left_cuts = sorted([dist for dist in vertical_cuts if dist <= total_width / 2])
for dist in left_cuts:
cut_type = "V-GROOVE" if dist == height else "SLOT"
print(f"* Set fence to: `{dist:.2f} mm` -- for **{cut_type}**")
print("### 2. Reference from RIGHT edge (FLIP board 180 deg):")
right_cuts = sorted([total_width - dist for dist in vertical_cuts if dist > total_width / 2])
for dist in right_cuts:
abs_pos = total_width - dist
cut_type = "V-GROOVE" if abs_pos == (total_width - height) else "SLOT"
print(f"* Set fence to: `{dist:.2f} mm` -- for **{cut_type}**")
print("")
# --- Setup 2: Horizontal Cuts ---
print("## SETUP 2: HORIZONTAL CUTS")
print("### 1. Rotate board 90 degrees CCW, reference from NEW LEFT edge (original BOTTOM edge):")
bottom_cuts = sorted([dist for dist in horizontal_cuts if dist <= total_length / 2])
for dist in bottom_cuts:
cut_type = "V-GROOVE" if dist == height else "SLOT"
print(f"* Set fence to: `{dist:.2f} mm` -- for **{cut_type}**")
print("### 2. Reference from NEW RIGHT edge (original TOP edge, FLIP board 180 deg):")
top_cuts = sorted([total_length - dist for dist in horizontal_cuts if dist > total_length / 2])
for dist in top_cuts:
abs_pos = total_length - dist
cut_type = "V-GROOVE" if abs_pos == (total_length - height) else "SLOT"
print(f"* Set fence to: `{dist:.2f} mm` -- for **{cut_type}**")
print("")
print("---")
print("*End of Plan*")
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Generate a cut plan for the poly-mech box.")
parser.add_argument('--total-width', type=float, default=500, help='Total width of the board')
parser.add_argument('--total-length', type=float, default=500, help='Total length of the board')
parser.add_argument('--height', type=float, default=80, help='Height of the box walls')
parser.add_argument('--base-thickness', type=float, default=3, help='Remaining thickness after V-groove')
parser.add_argument('--total-thickness', type=float, default=8, help='Total thickness of the material')
parser.add_argument('--slot-width', type=float, default=8, help='Width of the slots for internal dividers')
parser.add_argument('--u-boxes', type=int, default=2, help='Number of boxes along the width (U-axis)')
parser.add_argument('--v-boxes', type=int, default=2, help='Number of boxes along the length (V-axis)')
args = parser.parse_args()
generate_cut_plan(
args.total_width,
args.total_length,
args.height,
args.base_thickness,
args.total_thickness,
args.slot_width,
args.u_boxes,
args.v_boxes
)

25
cad/drawers/tools/cl.md Normal file
View File

@ -0,0 +1,25 @@
# 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*

View File

@ -0,0 +1,21 @@
# 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*

View File

@ -1,14 +0,0 @@
--- Cut List for Saw Operator ---
Board Dimensions: 500 x 500 mm
All distances are for the centerline of the slots.
---
VERTICAL CUTS (Distances from left edge):
Outer Wall 1: 80 mm
Outer Wall 2: 420 mm
Internal Divider 1: 254 mm
---
HORIZONTAL CUTS (Distances from bottom edge):
Outer Wall 1: 80 mm
Outer Wall 2: 420 mm
Internal Divider 1: 254 mm
--- End of List ---

View File

@ -1,51 +1,34 @@
#!/bin/bash
# --- generate_cutlist.sh ---
# Generates a cut list of slot distances from an OpenSCAD file.
# Generates a markdown cut plan by calling an external Python script.
# This bypasses a bug in some OpenSCAD versions that prevents the echo
# module from functioning correctly.
#
# Usage: ./generate_cutlist.sh <source.scad> [output.txt]
# If output file is not provided, prints to console.
# Usage: ./generate_cutlist.sh [output.md]
# All parameters are taken from the defaults in the python script.
# For custom dimensions, edit calculate_cuts.py or call it directly.
# --- Input Validation ---
if [ -z "$1" ]; then
echo "Usage: $0 <source.scad> [output.txt]"
echo "Error: Source file not specified."
exit 1
fi
OUTPUT_FILE="$1"
SOURCE_FILE="$1"
OUTPUT_FILE="$2"
DUMMY_OUTPUT="cutlist_dummy.stl" # A dummy file to force command-line execution
# Run the python script to generate the markdown content.
# The script can be modified to change box parameters.
CUTLIST_CONTENT=$(python calculate_cuts.py)
# --- OpenSCAD Command ---
# We force a dummy output file with -o to prevent the GUI from launching.
# OpenSCAD prints echo() statements to stderr. We redirect stderr to stdout (2>&1),
# then use grep to filter for only the lines starting with "ECHO:",
# and then use sed to remove the prefix and surrounding quotes for a clean output.
CUTLIST_CONTENT=$(openscad \
-o "$DUMMY_OUTPUT" \
-D "view_mode=\"cutlist\"" \
"$SOURCE_FILE" \
2>&1 | grep "ECHO:" | sed 's/ECHO: "//;s/"$//')
# Check the exit status of the openscad command.
if [ ${PIPESTATUS[0]} -ne 0 ]; then
# Check the exit status of the python script.
if [ $? -ne 0 ]; then
echo ""
echo "Error: OpenSCAD command failed."
rm -f "$DUMMY_OUTPUT" # Clean up dummy file on failure
echo "Error: Python script 'calculate_cuts.py' failed."
exit 1
fi
# Clean up the dummy file
rm -f "$DUMMY_OUTPUT"
# --- Output the result ---
if [ -z "$OUTPUT_FILE" ]; then
echo ""
echo "--- Generated Cut List ---"
echo "--- Generated Cut Plan (Markdown) ---"
echo "$CUTLIST_CONTENT"
echo "--------------------------"
echo "-------------------------------------"
else
echo "$CUTLIST_CONTENT" > "$OUTPUT_FILE"
echo "Cut list successfully saved to '$OUTPUT_FILE'."
echo "Cut plan successfully saved to '$OUTPUT_FILE'."
fi