Add two-halves print option with alignment pins

- Add print_mode parameter: single, top_half, bottom_half, both_halves
- Create alignment pin system for precise half assembly
- Support 2-3 configurable alignment pins with adjustable diameter
- Add keychain_full() module for unified rendering
- Implement intersection-based splitting at configurable height
- Display assembly instructions in console for two-halves mode
- Update documentation with three assembly methods
- Add preview mode (both_halves) to check alignment
- Simplify NFC tag insertion with glued assembly option
This commit is contained in:
Claude 2026-01-13 03:48:27 +00:00
parent f275ff4f34
commit 7651e56d19
No known key found for this signature in database

View file

@ -9,10 +9,18 @@
* - Set keychain_shape = "square" for rectangular design with rounded corners
* - Set nfc_tag_hole = true/false to include/exclude NFC tag recess
*
* PRINT PAUSE FEATURE:
* - When you open this file, the CONSOLE displays the exact pause height
* - Set a pause in your slicer to insert the NFC tag mid-print
* - The tag gets sealed inside as printing continues
* THREE ASSEMBLY METHODS:
* 1. Mid-Print Pause: Single piece with pause to insert NFC tag
* - Console displays exact pause height
* - Tag gets sealed during print
*
* 2. Two-Halves Assembly: Print top and bottom separately
* - Set print_mode = "bottom_half" then "top_half"
* - Built-in alignment pins for perfect fit
* - Glue halves together with NFC tag inside
* - Easiest method for consistent results
*
* 3. Post-Print: Glue tag in after printing
*
* To use your own photo/logo:
* 1. Save your image file in the same directory as this .scad file
@ -33,7 +41,10 @@ tag_color = "#FFFFFF"; // color
// Keychain shape style
keychain_shape = "oval"; // [oval, square]
// Create a recessed hole for embedding NFC tag
// Print mode: single piece or two halves for gluing
print_mode = "single"; // [single, top_half, bottom_half, both_halves]
// Create a recessed hole for embedding NFC tag (only for single piece mode)
nfc_tag_hole = true;
/* [Logo 1 Options - Front Side] */
@ -144,6 +155,19 @@ square_corner_radius = 3;
// Distance from bottom to center of hanging hole (only used if keychain_shape = "square")
square_hole_distance = 43;
/* [Two-Halves Print Options] */
// Height at which to split the keychain (default is middle of total height)
split_height = 0; // 0 = auto-calculate to middle
// Add alignment pin holes for easier assembly
add_alignment_pins = true;
// Diameter of alignment pins
alignment_pin_diameter = 2;
// Number of alignment pins (2 or 3 recommended)
alignment_pin_count = 3;
// ============================================================
// MODULES - Main Construction Code
@ -299,80 +323,224 @@ module nfc_hole() {
}
}
/*
* Creates alignment pin holes for two-halves assembly
*/
module alignment_pins(pin_height, is_socket = false) {
if (add_alignment_pins) {
pin_radius = alignment_pin_diameter / 2;
// Clearance for socket holes
socket_radius = is_socket ? pin_radius + 0.2 : pin_radius;
for (i = [0:alignment_pin_count-1]) {
angle = 360 / alignment_pin_count * i;
// Position pins around the perimeter
rotate([0, 0, angle])
translate([0, diameter1/2 - 5, 0])
cylinder(h = pin_height, r = socket_radius, $fn=20);
}
}
}
/*
* Main keychain assembly (full or split)
*/
module keychain_full() {
difference() {
// Main body
union() {
if (keychain_shape == "oval") {
keychain_oval_with_ends();
} else if (keychain_shape == "square") {
keychain_square();
}
// Add alignment pins for bottom half
if (print_mode == "bottom_half" && add_alignment_pins) {
calculated_split = split_height > 0 ? split_height : (keychain_thickness + bevel_radius * 2) / 2;
translate([0, 0, 0])
alignment_pins(calculated_split, false);
}
}
// Subtract NFC hole (only in single piece mode)
if (print_mode == "single") {
nfc_hole();
}
// Add alignment pin sockets for top half
if (print_mode == "top_half" && add_alignment_pins) {
calculated_split = split_height > 0 ? split_height : (keychain_thickness + bevel_radius * 2) / 2;
translate([0, 0, 0])
alignment_pins(calculated_split, true);
}
}
}
// ============================================================
// RENDERING - Final Assembly
// ============================================================
// Calculate and display pause height for NFC tag insertion
// Calculate dimensions
pause_height = nfc_tag_height;
total_height = keychain_thickness + bevel_radius * 2;
calculated_split = split_height > 0 ? split_height : total_height / 2;
// Display pause information in console
// Display information in console
echo(str("=============================================="));
echo(str("PRINT PAUSE INFORMATION:"));
echo(str("=============================================="));
if (nfc_tag_hole) {
echo(str("✓ NFC tag recess is ENABLED"));
echo(str("Pause Height: ", pause_height, " mm"));
echo(str("Total Height: ", total_height, " mm"));
echo(str(""));
echo(str("SLICER SETUP INSTRUCTIONS:"));
echo(str("1. Slice this model normally"));
echo(str("2. Add a pause at height: ", pause_height, " mm"));
echo(str(" - PrusaSlicer: Right-click layer → Add pause print"));
echo(str(" - Cura: Extensions → Post Processing → Modify G-Code → Pause at height"));
echo(str(" - Bambu Studio: Right-click layer → Add pause"));
echo(str("3. When printer pauses, place NFC tag in recess"));
echo(str("4. Resume print to cover the tag"));
if (print_mode == "single") {
echo(str("PRINT MODE: Single Piece"));
echo(str("=============================================="));
if (nfc_tag_hole) {
echo(str("✓ NFC tag recess is ENABLED"));
echo(str("Pause Height: ", pause_height, " mm"));
echo(str("Total Height: ", total_height, " mm"));
echo(str(""));
echo(str("SLICER SETUP INSTRUCTIONS:"));
echo(str("1. Slice this model normally"));
echo(str("2. Add a pause at height: ", pause_height, " mm"));
echo(str(" - PrusaSlicer: Right-click layer → Add pause print"));
echo(str(" - Cura: Extensions → Post Processing → Modify G-Code → Pause at height"));
echo(str(" - Bambu Studio: Right-click layer → Add pause"));
echo(str("3. When printer pauses, place NFC tag in recess"));
echo(str("4. Resume print to cover the tag"));
} else {
echo(str("✗ NFC tag recess is DISABLED"));
echo(str("Total Height: ", total_height, " mm"));
}
} else {
echo(str("✗ NFC tag recess is DISABLED"));
echo(str("PRINT MODE: Two-Halves Assembly"));
echo(str("=============================================="));
echo(str("Split Height: ", calculated_split, " mm"));
echo(str("Total Height: ", total_height, " mm"));
echo(str("Alignment Pins: ", add_alignment_pins ? "ENABLED" : "DISABLED"));
if (add_alignment_pins) {
echo(str("Pin Count: ", alignment_pin_count));
echo(str("Pin Diameter: ", alignment_pin_diameter, " mm"));
}
echo(str(""));
echo(str("ASSEMBLY INSTRUCTIONS:"));
echo(str("1. Print both halves (set print_mode to 'bottom_half' then 'top_half')"));
echo(str("2. Place NFC tag in the bottom half"));
echo(str("3. Apply glue (CA glue or epoxy) to the mating surface"));
echo(str("4. Align the halves using the pins"));
echo(str("5. Press together and let cure"));
}
echo(str("=============================================="));
// Main keychain body with NFC tag recess
color(tag_color)
difference() {
// Render selected shape
if (keychain_shape == "oval") {
keychain_oval_with_ends();
} else if (keychain_shape == "square") {
keychain_square();
}
nfc_hole();
// Main rendering based on print mode
if (print_mode == "single") {
// Single piece with optional NFC recess
color(tag_color) {
keychain_full();
}
// Calculate logo Y offset based on shape (square needs vertical centering)
logo1YPosition = keychain_shape == "square"
? logo1OffsetY + square_height/2 - 5 // Center on square body
: logo1OffsetY;
// Add logos
logo1YPosition = keychain_shape == "square" ? logo1OffsetY + square_height/2 - 5 : logo1OffsetY;
logo1ZOffset = logo1Type == "svg" ? keychain_thickness + bevel_radius - logo1Thickness/2 : keychain_thickness + bevel_radius - logo1Thickness + 0.01;
// Front side logo (Logo 1)
logo1ZOffset = logo1Type == "svg"
? keychain_thickness + bevel_radius - logo1Thickness/2
: keychain_thickness + bevel_radius - logo1Thickness + 0.01;
color(logo1Color)
translate([0, 0, logo1ZOffset])
logo(logo1Type, logo1OffsetX, logo1YPosition, logo1Width, logo1Height, logo1Thickness, svgFile1, pngFile1, stlFile1);
color(logo1Color)
translate([0, 0, logo1ZOffset])
logo(logo1Type, logo1OffsetX, logo1YPosition, logo1Width, logo1Height,
logo1Thickness, svgFile1, pngFile1, stlFile1);
if(logo2Enabled) {
logo2YPosition = keychain_shape == "square" ? logo2OffsetY + square_height/2 - 5 : logo2OffsetY;
logo2ZOffset = logo2Type == "svg" ? -bevel_radius + logo2Thickness/2 : -bevel_radius - 0.01 + logo2Thickness;
color(logo2Color)
translate([0, 0, logo2ZOffset])
rotate([0, 180, 0])
logo(logo2Type, logo2OffsetX, logo2YPosition, logo2Width, logo2Height, logo2Thickness, svgFile2, pngFile2, stlFile2);
}
} else if (print_mode == "bottom_half") {
// Bottom half
color(tag_color) {
intersection() {
keychain_full();
translate([-100, -100, -0.1])
cube([200, 200, calculated_split + 0.1]);
}
}
// Back side logo (Logo 2) - Optional
if(logo2Enabled) {
// Calculate logo2 Y offset based on shape
logo2YPosition = keychain_shape == "square"
? logo2OffsetY + square_height/2 - 5
: logo2OffsetY;
// Add logo to bottom half (back side)
if(logo2Enabled) {
logo2YPosition = keychain_shape == "square" ? logo2OffsetY + square_height/2 - 5 : logo2OffsetY;
logo2ZOffset = logo2Type == "svg" ? -bevel_radius + logo2Thickness/2 : -bevel_radius - 0.01 + logo2Thickness;
intersection() {
color(logo2Color)
translate([0, 0, logo2ZOffset])
rotate([0, 180, 0])
logo(logo2Type, logo2OffsetX, logo2YPosition, logo2Width, logo2Height, logo2Thickness, svgFile2, pngFile2, stlFile2);
translate([-100, -100, -0.1])
cube([200, 200, calculated_split + 0.1]);
}
}
} else if (print_mode == "top_half") {
// Top half
color(tag_color) {
intersection() {
keychain_full();
translate([-100, -100, calculated_split])
cube([200, 200, total_height]);
}
}
logo2ZOffset = logo2Type == "svg"
? -bevel_radius + logo2Thickness/2
: -bevel_radius - 0.01 + logo2Thickness;
// Add logo to top half (front side)
logo1YPosition = keychain_shape == "square" ? logo1OffsetY + square_height/2 - 5 : logo1OffsetY;
logo1ZOffset = logo1Type == "svg" ? keychain_thickness + bevel_radius - logo1Thickness/2 : keychain_thickness + bevel_radius - logo1Thickness + 0.01;
color(logo2Color)
translate([0, 0, logo2ZOffset])
rotate([0, 180, 0])
logo(logo2Type, logo2OffsetX, logo2YPosition, logo2Width, logo2Height,
logo2Thickness, svgFile2, pngFile2, stlFile2);
intersection() {
color(logo1Color)
translate([0, 0, logo1ZOffset])
logo(logo1Type, logo1OffsetX, logo1YPosition, logo1Width, logo1Height, logo1Thickness, svgFile1, pngFile1, stlFile1);
translate([-100, -100, calculated_split])
cube([200, 200, total_height]);
}
} else if (print_mode == "both_halves") {
// Show both halves side by side for preview
spacing = keychain_shape == "oval" ? diameter1 + 5 : square_width + 5;
// Bottom half
translate([-spacing/2, 0, 0]) {
color(tag_color) {
intersection() {
keychain_full();
translate([-100, -100, -0.1])
cube([200, 200, calculated_split + 0.1]);
}
}
if(logo2Enabled) {
logo2YPosition = keychain_shape == "square" ? logo2OffsetY + square_height/2 - 5 : logo2OffsetY;
logo2ZOffset = logo2Type == "svg" ? -bevel_radius + logo2Thickness/2 : -bevel_radius - 0.01 + logo2Thickness;
intersection() {
color(logo2Color)
translate([0, 0, logo2ZOffset])
rotate([0, 180, 0])
logo(logo2Type, logo2OffsetX, logo2YPosition, logo2Width, logo2Height, logo2Thickness, svgFile2, pngFile2, stlFile2);
translate([-100, -100, -0.1])
cube([200, 200, calculated_split + 0.1]);
}
}
}
// Top half
translate([spacing/2, 0, 0]) {
color(tag_color) {
intersection() {
keychain_full();
translate([-100, -100, calculated_split])
cube([200, 200, total_height]);
}
}
logo1YPosition = keychain_shape == "square" ? logo1OffsetY + square_height/2 - 5 : logo1OffsetY;
logo1ZOffset = logo1Type == "svg" ? keychain_thickness + bevel_radius - logo1Thickness/2 : keychain_thickness + bevel_radius - logo1Thickness + 0.01;
intersection() {
color(logo1Color)
translate([0, 0, logo1ZOffset])
logo(logo1Type, logo1OffsetX, logo1YPosition, logo1Width, logo1Height, logo1Thickness, svgFile1, pngFile1, stlFile1);
translate([-100, -100, calculated_split])
cube([200, 200, total_height]);
}
}
}
// ============================================================
@ -384,20 +552,32 @@ if(logo2Enabled) {
* SELECT YOUR VERSION:
* Version 1 - Oval with NFC recess:
* keychain_shape = "oval"
* print_mode = "single"
* nfc_tag_hole = true
*
* Version 2 - Oval without NFC recess:
* keychain_shape = "oval"
* print_mode = "single"
* nfc_tag_hole = false
*
* Version 3 - Square with NFC recess:
* keychain_shape = "square"
* print_mode = "single"
* nfc_tag_hole = true
*
* Version 4 - Square without NFC recess:
* keychain_shape = "square"
* print_mode = "single"
* nfc_tag_hole = false
*
* Version 5 - Two-Halves Assembly (Oval):
* keychain_shape = "oval"
* print_mode = "bottom_half" or "top_half" or "both_halves"
*
* Version 6 - Two-Halves Assembly (Square):
* keychain_shape = "square"
* print_mode = "bottom_half" or "top_half" or "both_halves"
*
* 1. PREPARE YOUR IMAGE:
* - For photos: Convert to SVG using online tools like:
* * vectorizer.io
@ -419,11 +599,12 @@ if(logo2Enabled) {
* - Enable supports if needed for hanging hole
* - Consider pause at layer for multi-color printing
*
* 5. NFC TAG INSERTION (IMPORTANT!):
* 5. NFC TAG INSERTION (THREE METHODS):
*
* METHOD 1 - Mid-Print Pause (Best for single piece):
* When you open this file in OpenSCAD, check the CONSOLE window.
* It will display the exact pause height for your settings.
*
* TO INSERT NFC TAG DURING PRINT:
* a) In your slicer, add a pause at the height shown in console
* - PrusaSlicer: Right-click on the layer "Add pause print (M601)"
* - Cura: Extensions Post Processing Modify G-Code "Pause at height"
@ -431,7 +612,21 @@ if(logo2Enabled) {
* b) When the print pauses, place your NFC tag in the recess
* c) Resume printing - the remaining layers will seal the tag inside
*
* ALTERNATIVE METHOD:
* METHOD 2 - Two-Halves Assembly (Easiest alignment):
* a) Set print_mode = "bottom_half" and export/print
* b) Set print_mode = "top_half" and export/print
* c) Place NFC tag in the bottom half
* d) Apply CA glue (super glue) or epoxy to the mating surface
* e) Align using the built-in alignment pins
* f) Press together firmly and let cure (60 seconds for CA glue)
*
* TIPS for Two-Halves:
* - Set print_mode = "both_halves" to preview alignment
* - Alignment pins ensure perfect registration
* - Use thin CA glue for fastest assembly
* - Apply glue sparingly to avoid squeeze-out
*
* METHOD 3 - Post-Print Glue:
* - Print the keychain normally without pause
* - Glue NFC tag into recess after printing
* - Cover with thin layer of epoxy or clear nail polish if desired