Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
099b754156 | ||
|
|
021cf7b78d | ||
|
|
d9eac222e4 | ||
|
|
4c7c8f2dc3 | ||
|
|
27213d9d50 | ||
|
|
a1c8d11b6a | ||
|
|
b5508af9d3 | ||
|
|
beb5f39722 | ||
|
|
a1a6777aea | ||
|
|
ebeb258d60 | ||
|
|
98e19efa60 |
161
.github/copilot-instructions.md
vendored
Normal file
161
.github/copilot-instructions.md
vendored
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# HomeRacker Copilot Instructions
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
HomeRacker is a fully modular 3D-printable rack-building system designed for versatile "racking needs" including server racks, shoe racks, bookshelves, and more. The project consists of:
|
||||||
|
|
||||||
|
- **HomeRacker Core**: Open-spec modular building system (MIT License)
|
||||||
|
- **3D Models**: Parametric and non-parametric models in OpenSCAD and Fusion 360 formats (CC BY 4.0)
|
||||||
|
- **Automation Scripts**: Fusion 360 export scripts for batch model generation
|
||||||
|
- **GitHub Pages Site**: Documentation and showcase website
|
||||||
|
|
||||||
|
## Core Technologies & Frameworks
|
||||||
|
- **3D Modeling**: OpenSCAD (parametric), Fusion 360 (.f3d files)
|
||||||
|
- **Languages**: SCAD (OpenSCAD), Python (Fusion 360 scripts)
|
||||||
|
- **Dependencies**: BOSL2 library for OpenSCAD
|
||||||
|
- **Web**: GitHub Pages with Jekyll (Architect theme)
|
||||||
|
- **Standards**: 10" and 19" rack specifications, custom HomeRacker dimensions
|
||||||
|
|
||||||
|
## Key Components Architecture
|
||||||
|
### Building Blocks
|
||||||
|
1. **Supports**: Vertical structural elements with standardized connection points
|
||||||
|
2. **Connectors**: 6-way connection system for supports (front/back, left/right, up/down)
|
||||||
|
3. **Lock Pins**: Securing mechanism for assembled connections
|
||||||
|
4. **Rebar**: Horizontal structural reinforcement elements
|
||||||
|
5. **Splints**: Additional reinforcement components
|
||||||
|
|
||||||
|
### Models Structure
|
||||||
|
- `/models/rackmount_ears/`: Fully customizable rackmount ears (OpenSCAD)
|
||||||
|
- `/models/gridfinity/`: Gridfinity-compatible base plates
|
||||||
|
- `/models/flexmount/`: Flexible mounting solutions
|
||||||
|
- `/models/shelf/`: Parametric shelf components
|
||||||
|
- `/models/wallmount/`: Wall mounting brackets
|
||||||
|
|
||||||
|
## Development Guidelines
|
||||||
|
|
||||||
|
### OpenSCAD Best Practices
|
||||||
|
- Use BOSL2 library for advanced geometric operations
|
||||||
|
- Set `$fn=100` for smooth curves in production models
|
||||||
|
- Implement parametric designs with customizer-friendly variables
|
||||||
|
- Group parameters logically with `/* [Section Name] */` comments
|
||||||
|
- Provide sensible defaults and value ranges for sliders
|
||||||
|
- Include sanity checks for critical parameters
|
||||||
|
- Use descriptive variable names following `CONSTANT_CASE` for constants
|
||||||
|
|
||||||
|
### File Organization
|
||||||
|
- Keep `.scad` files in appropriate `/models/` subdirectories
|
||||||
|
- Include example images showing customization options
|
||||||
|
- Maintain `README.md` files explaining each model's purpose
|
||||||
|
|
||||||
|
### Documentation Standards
|
||||||
|
- Use emoji headers for visual organization (🔧, ✨, 📐, etc.)
|
||||||
|
- Include clear assembly instructions with diagrams
|
||||||
|
- Provide printing tips and material recommendations
|
||||||
|
- Document all customizable parameters
|
||||||
|
- Include real-world use case examples
|
||||||
|
|
||||||
|
### Code Quality
|
||||||
|
- Comment complex geometric calculations
|
||||||
|
- Use meaningful module names
|
||||||
|
- Separate configuration from implementation
|
||||||
|
- Test parameter ranges for edge cases
|
||||||
|
- Validate dimensional accuracy against rack standards
|
||||||
|
|
||||||
|
## Project-Specific Context
|
||||||
|
|
||||||
|
### HomeRacker Core Dimensional Standards
|
||||||
|
- **Base Unit**: 15mm (core measurement for all HomeRacker components)
|
||||||
|
- **Lock Pin Side**: 4mm (square profile for pins and matching holes)
|
||||||
|
- **Wall Thickness**: 2mm (standard connector wall thickness)
|
||||||
|
- **Standard Tolerance**: 0.2mm (added to connector interiors for print variances)
|
||||||
|
- **Support Dimensions**: 15mm x 15mm base, height = multiples of 15mm
|
||||||
|
- **Connector Centers**: Always 1 base unit (15mm) in height
|
||||||
|
- **Lock Pin Edge Distance**: 5.5mm from support edges
|
||||||
|
- **Standard Chamfer**: 1mm for printability
|
||||||
|
|
||||||
|
### Licensing Requirements
|
||||||
|
- Source code: MIT License (commercial use allowed)
|
||||||
|
- 3D models & assets: CC BY 4.0 (attribution required, share-alike)
|
||||||
|
- Always include proper license headers in new files
|
||||||
|
- Credit original work when modifying existing models
|
||||||
|
|
||||||
|
### Brand Guidelines
|
||||||
|
- Use "HomeRacker" (not "Home Racker" or "home-racker")
|
||||||
|
- Include HomeRacker logo overlay on compatible model thumbnails
|
||||||
|
- Maintain consistent visual style across documentation
|
||||||
|
- Reference Makerworld links for published models
|
||||||
|
|
||||||
|
## Common Tasks & Patterns
|
||||||
|
|
||||||
|
### Creating New Models
|
||||||
|
1. Start with parametric design in OpenSCAD
|
||||||
|
2. Use BOSL2 library for complex operations
|
||||||
|
3. Include customizer parameters with proper grouping
|
||||||
|
4. Test with common device dimensions
|
||||||
|
5. Export STL files for immediate use
|
||||||
|
6. Document parameters and use cases
|
||||||
|
7. Add example images showing variations
|
||||||
|
|
||||||
|
### Fusion 360 Scripts
|
||||||
|
- Place scripts in `/scripts/` with proper manifest files
|
||||||
|
- Include usage instructions in README.md
|
||||||
|
- Test export functionality across parameter ranges
|
||||||
|
- Validate output file naming conventions
|
||||||
|
|
||||||
|
### Documentation Updates
|
||||||
|
- Update main README.md for new features
|
||||||
|
- Include visual examples with actual photos
|
||||||
|
- Link to Makerworld models where applicable
|
||||||
|
- Maintain table of contents structure
|
||||||
|
|
||||||
|
## Self-Improvement Instructions
|
||||||
|
|
||||||
|
### When Encountering Wrong Turns
|
||||||
|
If you find yourself making incorrect assumptions or heading down the wrong path during development assistance:
|
||||||
|
|
||||||
|
1. **Pause and Reassess**: Stop the current approach and explicitly state what went wrong
|
||||||
|
2. **Gather Context**: Re-read project documentation, examine existing code patterns, and understand the specific use case better
|
||||||
|
3. **Ask Clarifying Questions**: Request specific details about requirements, constraints, or expected outcomes
|
||||||
|
4. **Document the Learning**: Add insights to these instructions for future reference
|
||||||
|
|
||||||
|
### Instruction Enhancement Protocol
|
||||||
|
When updating these instructions based on new learnings:
|
||||||
|
|
||||||
|
1. **Preserve Core Structure**: Maintain the organized sections and GitHub best practices format
|
||||||
|
2. **Add Specific Examples**: Include concrete code snippets or configuration examples that caused confusion
|
||||||
|
3. **Update Technology Stack**: Keep dependencies, tools, and version requirements current
|
||||||
|
4. **Expand Edge Cases**: Document unusual scenarios or parameter combinations that need special handling
|
||||||
|
5. **Validate Changes**: Ensure new instructions don't conflict with existing project patterns
|
||||||
|
|
||||||
|
### Learning from Mistakes
|
||||||
|
Common areas for improvement:
|
||||||
|
- **Dimensional Accuracy**: Double-check rack standards and measurement conversions
|
||||||
|
- **Parameter Validation**: Ensure SCAD parameters have proper bounds and error checking
|
||||||
|
- **File Naming**: Follow established conventions for exports and derivatives
|
||||||
|
- **License Compliance**: Verify all new content uses appropriate licensing
|
||||||
|
- **Cross-Platform Compatibility**: Consider Windows/Linux/macOS differences in file paths and tools
|
||||||
|
|
||||||
|
### Feedback Integration
|
||||||
|
When receiving feedback about incorrect suggestions:
|
||||||
|
1. Acknowledge the specific error made
|
||||||
|
2. Explain the corrected approach
|
||||||
|
3. Update relevant sections of these instructions
|
||||||
|
4. Test the corrected approach before suggesting it again
|
||||||
|
|
||||||
|
## Troubleshooting Common Issues
|
||||||
|
|
||||||
|
### OpenSCAD Problems
|
||||||
|
- **Rendering Issues**: Check for overlapping geometry, invalid meshes, or extreme parameter values
|
||||||
|
- **BOSL2 Errors**: Verify library installation and include statements
|
||||||
|
- **Performance**: Reduce `$fn` for development, increase for final exports
|
||||||
|
|
||||||
|
### Export Script Issues
|
||||||
|
- **Fusion 360 API**: Check for API version compatibility and parameter access methods
|
||||||
|
- **File Paths**: Use cross-platform path handling in Python scripts
|
||||||
|
- **Batch Operations**: Implement proper error handling for automated exports
|
||||||
|
|
||||||
|
### Documentation Sync
|
||||||
|
- **Broken Links**: Validate all external references to Makerworld and GitHub
|
||||||
|
- **Image References**: Ensure all images exist and use correct relative paths
|
||||||
|
- **Version Mismatches**: Keep model versions synchronized between repository and published versions
|
||||||
|
|
||||||
|
Remember: HomeRacker is about modularity, openness, and practical maker solutions. Always consider how suggestions support these core principles while maintaining professional engineering standards.
|
||||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
*.3mf
|
*.3mf
|
||||||
*.stl
|
*.stl
|
||||||
.vscode
|
.vscode
|
||||||
|
bin/OpenSCAD-*/
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
Fully customizable mount to be used with HomeRacker.
|
|
||||||
It's intended for non-standard devices you want to mount on HomeRacker.
|
|
||||||
Non-standard being devices which aren't originally meant to be mounted in 10 or 19" racks.
|
|
||||||
Devices might be PSU's for Raspberry Pis or small smart switches like on these pictures:
|
|
||||||
<TODO: insert pics here>
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
// import BOSL2
|
// import BOSL2
|
||||||
include <BOSL2/std.scad>
|
include <BOSL2/std.scad>
|
||||||
|
|
||||||
$fn=100;
|
|
||||||
|
|
||||||
/* [Hidden] */
|
/* [Hidden] */
|
||||||
// constants which shouldn't be changed
|
// constants which shouldn't be changed
|
||||||
|
$fn=100;
|
||||||
|
|
||||||
// This is the HomeRacker base unit. don't change this!
|
// This is the HomeRacker base unit. don't change this!
|
||||||
BASE_UNIT=15; // mm
|
BASE_UNIT=15; // mm
|
||||||
@@ -19,23 +20,20 @@ LOCK_PIN_SIDE=4; // mm
|
|||||||
// lock pin edge distance
|
// lock pin edge distance
|
||||||
LOCK_PIN_EDGE_DISTANCE=5.5; // mm
|
LOCK_PIN_EDGE_DISTANCE=5.5; // mm
|
||||||
|
|
||||||
/* [Base] */
|
|
||||||
// If set to true, the mount will be fitted to align with the 10/19 inch HomRacker rackmount kit.
|
|
||||||
//fit_to_rack=true;
|
|
||||||
|
|
||||||
/* [Device Measurements] */
|
/* [Device Measurements] */
|
||||||
// Width of the device in mm. Will determine how far apart the actual mounts are in width.
|
// Width of the device in mm. Will determine how far apart the actual mounts are in width.
|
||||||
device_width=100 // [20:0.1:500]
|
device_width=100; // [20:0.1:500]
|
||||||
; // TODO test for zero cube
|
// TODO test for zero cube
|
||||||
// Depth of the device in mm. Will determine how far apart the actual mounts are in depth.
|
// Depth of the device in mm. Will determine how far apart the actual mounts are in depth.
|
||||||
device_depth=99; // [54:0.1:500]
|
device_depth=99; // [54:0.1:400]
|
||||||
// Height of the device in mm. Will determine how far apart the actual mounts are in height.
|
// Height of the device in mm. Will determine how far apart the actual mounts are in height.
|
||||||
device_height=25.5; // [15:0.1:500]
|
device_height=25.5; // [10:0.1:400]
|
||||||
|
|
||||||
/* [Bracket] */
|
/* [Bracket] */
|
||||||
// Defines the bracket thickness on top of the device.
|
// Defines the bracket thickness on top of the device.
|
||||||
bracket_strength_top=7.5; // [4:0.1:20]
|
bracket_strength_top=7.5; // [1:0.1:50]
|
||||||
bracket_strength_sides=7.5; // [4:0.1:20]
|
// Defines how much the bracket will overlap to the sides of the device.
|
||||||
|
bracket_strength_sides=7.5; // [2:0.1:50]
|
||||||
|
|
||||||
// diff_width resembles the gap between the device and the mount. This gap will be filled with a cuboid
|
// diff_width resembles the gap between the device and the mount. This gap will be filled with a cuboid
|
||||||
modulo_width=(BASE_UNIT - ( device_width + TOLERANCE ) % BASE_UNIT);
|
modulo_width=(BASE_UNIT - ( device_width + TOLERANCE ) % BASE_UNIT);
|
||||||
@@ -58,6 +56,10 @@ module bracket(width,depth,height) {
|
|||||||
outer_width=width+BASE_STRENGTH*2+TOLERANCE;
|
outer_width=width+BASE_STRENGTH*2+TOLERANCE;
|
||||||
outer_depth=depth+BASE_STRENGTH*2+TOLERANCE;
|
outer_depth=depth+BASE_STRENGTH*2+TOLERANCE;
|
||||||
outer_height=height+BASE_STRENGTH;
|
outer_height=height+BASE_STRENGTH;
|
||||||
|
inner_width=width-bracket_strength_top*2+TOLERANCE;
|
||||||
|
inner_depth=depth-bracket_strength_top*2+TOLERANCE;
|
||||||
|
bottom_recess_height=height-bracket_strength_sides;
|
||||||
|
|
||||||
intersection(){
|
intersection(){
|
||||||
difference() {
|
difference() {
|
||||||
// Outer
|
// Outer
|
||||||
@@ -66,36 +68,33 @@ module bracket(width,depth,height) {
|
|||||||
anchor=BOTTOM,
|
anchor=BOTTOM,
|
||||||
chamfer=CHAMFER, edges=[TOP,FRONT,BACK,LEFT,RIGHT], except=[BOTTOM]
|
chamfer=CHAMFER, edges=[TOP,FRONT,BACK,LEFT,RIGHT], except=[BOTTOM]
|
||||||
);
|
);
|
||||||
// Top Skeleton
|
// Top Skeleton (cut the middle to leave only a bracket to the sides)
|
||||||
cuboid(
|
cuboid(
|
||||||
size=[width-bracket_strength_top,depth-bracket_strength_top,outer_height],
|
size=[inner_width,inner_depth,outer_height],
|
||||||
anchor=BOTTOM,
|
anchor=BOTTOM,
|
||||||
chamfer=-CHAMFER, edges=[TOP]
|
chamfer=-CHAMFER, edges=[TOP]
|
||||||
);
|
);
|
||||||
// Bottom Recess
|
// Bottom Recess (cut the cube to leave only a top bracket)
|
||||||
cuboid(
|
cuboid(
|
||||||
size=[outer_width,outer_depth,height-bracket_strength_sides],
|
size=[outer_width,outer_depth,bottom_recess_height],
|
||||||
anchor=BOTTOM
|
anchor=BOTTOM
|
||||||
);
|
);
|
||||||
|
// Subtract Device to from bracket (cut the device into the bracket)
|
||||||
|
cuboid(
|
||||||
|
size=[width+TOLERANCE,depth+TOLERANCE,height+TOLERANCE/2],
|
||||||
|
anchor=BOTTOM
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
// chamfer intersection for the outer bottom chamfer of the bracket
|
||||||
translate([0,0,outer_height])
|
translate([0,0,outer_height])
|
||||||
cuboid(
|
cuboid(
|
||||||
size=[outer_width,outer_depth,bracket_strength_sides],
|
size=[outer_width,outer_depth,bracket_strength_sides+BASE_STRENGTH-TOLERANCE/2],
|
||||||
anchor=TOP,
|
anchor=TOP,
|
||||||
chamfer=CHAMFER, edges=[BOTTOM]
|
chamfer=CHAMFER, edges=[BOTTOM]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// device module adds TOLERANCE to the device size and creates a cuboid with the device size
|
|
||||||
// this is used to create the device in the mount and add TOLERANCE to the device size for spacing
|
|
||||||
module device(width,depth,height) {
|
|
||||||
// Device
|
|
||||||
cuboid(
|
|
||||||
size=[width+TOLERANCE,depth+TOLERANCE,height+TOLERANCE],
|
|
||||||
anchor=BOTTOM
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mount
|
// Mount
|
||||||
module mount(){
|
module mount(){
|
||||||
@@ -185,12 +184,8 @@ module mirror_mount_x_plane(){
|
|||||||
// Assembly
|
// Assembly
|
||||||
rotate([180,0,0])
|
rotate([180,0,0])
|
||||||
union() {
|
union() {
|
||||||
|
bracket(device_width,device_depth,device_height);
|
||||||
|
|
||||||
difference() {
|
|
||||||
bracket(device_width,device_depth,device_height);
|
|
||||||
device(device_width,device_depth,device_height);
|
|
||||||
}
|
|
||||||
// Mount Right
|
// Mount Right
|
||||||
mirror_mount_x_plane();
|
mirror_mount_x_plane();
|
||||||
|
|
||||||
|
|||||||
98
models/gridfinity/base.scad
Normal file
98
models/gridfinity/base.scad
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
// import BOSL2
|
||||||
|
include <BOSL2/std.scad>
|
||||||
|
|
||||||
|
/* [Hidden] */
|
||||||
|
// constants which shouldn't be changed
|
||||||
|
$fn=100;
|
||||||
|
|
||||||
|
// This is the HomeRacker base unit. don't change this!
|
||||||
|
BASE_UNIT=15; // mm
|
||||||
|
// Standard tolerance for the mount. This is a sane default.
|
||||||
|
TOLERANCE=0.2; // mm
|
||||||
|
// Base strength. This is a sane default.
|
||||||
|
BASE_STRENGTH=2; // mm
|
||||||
|
// Chamfer size. This is a sane default.
|
||||||
|
CHAMFER=2.5; // mm
|
||||||
|
// Lock Pin side length
|
||||||
|
LOCK_PIN_SIDE=3; // mm
|
||||||
|
// lock pin edge distance
|
||||||
|
LOCK_PIN_EDGE_DISTANCE=5.5; // mm
|
||||||
|
// lock pin chamfer
|
||||||
|
LOCK_PIN_CHAMFER=0.8; // mm
|
||||||
|
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH=42; // mm
|
||||||
|
GRIDFINITY_BASEPLATE_STRENGTH=8; // mm
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH_INNER=GRIDFINITY_BASEPLATE_SIDE_LENGTH-GRIDFINITY_BASEPLATE_STRENGTH/2; // mm
|
||||||
|
|
||||||
|
GRIDFINITY_INCLINE_1=0.7; // mm
|
||||||
|
GRIDFINITY_INCLINE_2=1.8; // mm
|
||||||
|
GRIDFINITY_INCLINE_3=2.15; // mm
|
||||||
|
GRIDFINITY_BASEPLATE_HEIGHT=GRIDFINITY_INCLINE_1+GRIDFINITY_INCLINE_2+GRIDFINITY_INCLINE_3; // mm
|
||||||
|
|
||||||
|
/* [Grdifinity] */
|
||||||
|
x_units=1; // [1:1:10]
|
||||||
|
y_units=1; // [1:1:10]
|
||||||
|
|
||||||
|
module recess(side_length,chamfer){
|
||||||
|
//chamfer
|
||||||
|
rotate([180,0,0])
|
||||||
|
prismoid(
|
||||||
|
size1=[side_length,side_length],
|
||||||
|
rounding=chamfer,
|
||||||
|
h=chamfer,
|
||||||
|
xang=45,
|
||||||
|
yang=45
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module gridfinity_unit(){
|
||||||
|
difference(){
|
||||||
|
//outer cube
|
||||||
|
cuboid(
|
||||||
|
size=[GRIDFINITY_BASEPLATE_SIDE_LENGTH,GRIDFINITY_BASEPLATE_SIDE_LENGTH,GRIDFINITY_BASEPLATE_HEIGHT],
|
||||||
|
anchor=CENTER+BOTTOM
|
||||||
|
);
|
||||||
|
//1st recess
|
||||||
|
|
||||||
|
translate([0,0,GRIDFINITY_BASEPLATE_HEIGHT])
|
||||||
|
recess(
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH,
|
||||||
|
GRIDFINITY_INCLINE_3);
|
||||||
|
//2nd recess
|
||||||
|
translate([0,0,GRIDFINITY_BASEPLATE_HEIGHT-GRIDFINITY_INCLINE_3-GRIDFINITY_INCLINE_2])
|
||||||
|
cuboid(
|
||||||
|
size=[
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH-GRIDFINITY_INCLINE_3*2,
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH-GRIDFINITY_INCLINE_3*2,
|
||||||
|
GRIDFINITY_INCLINE_2
|
||||||
|
],
|
||||||
|
rounding=GRIDFINITY_INCLINE_2,
|
||||||
|
except=[BOTTOM,TOP],
|
||||||
|
anchor=CENTER+BOTTOM
|
||||||
|
);
|
||||||
|
// //3rd recess
|
||||||
|
translate([0,0,GRIDFINITY_INCLINE_1+0.01])
|
||||||
|
recess(
|
||||||
|
GRIDFINITY_BASEPLATE_SIDE_LENGTH_INNER,
|
||||||
|
GRIDFINITY_INCLINE_1+0.01
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module grid(x,y){
|
||||||
|
intersection(){
|
||||||
|
grid_copies(spacing=[GRIDFINITY_BASEPLATE_SIDE_LENGTH,GRIDFINITY_BASEPLATE_SIDE_LENGTH],n=[x,y])
|
||||||
|
gridfinity_unit();
|
||||||
|
// roundings
|
||||||
|
cuboid(
|
||||||
|
size=[GRIDFINITY_BASEPLATE_SIDE_LENGTH*x,GRIDFINITY_BASEPLATE_SIDE_LENGTH*y,GRIDFINITY_BASEPLATE_HEIGHT*2],
|
||||||
|
anchor=CENTER+BOTTOM,
|
||||||
|
rounding=CHAMFER,
|
||||||
|
except=[BOTTOM,TOP]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
grid(x=2,y=3);
|
||||||
@@ -1,13 +1,28 @@
|
|||||||
// import BOSL2
|
// import BOSL2
|
||||||
include <BOSL2/std.scad>
|
include <BOSL2/std.scad>
|
||||||
|
|
||||||
|
/* [Hidden] */
|
||||||
$fn=100;
|
$fn=100;
|
||||||
|
CAGE_BOLT_DIAMETER=6.5;
|
||||||
|
RACK_BORE_DISTANCE_VERTICAL=15.875;
|
||||||
|
RACK_BORE_DISTANCE_TOP_BOTTOM=6.35;
|
||||||
|
RACK_MOUNT_SURFACE_WIDTH=15.875;
|
||||||
|
RACK_BORE_DISTANCE_HORIZONTAL=RACK_MOUNT_SURFACE_WIDTH/2;
|
||||||
|
RACK_HEIGHT_UNIT=44.5; // mm
|
||||||
|
|
||||||
|
RACK_WIDTH_10_INCH_INNER=222.25; // mm
|
||||||
|
RACK_WIDTH_10_INCH_OUTER=254; // mm
|
||||||
|
RACK_WIDTH_19_INCH=482.6; // mm
|
||||||
|
|
||||||
/* [Base] */
|
/* [Base] */
|
||||||
// automatically chooses the tightest fit for the rackmount ears based on the device width. If true, rack_size will be ignored.
|
// automatically chooses the tightest fit for the rackmount ears based on the device width. If true, rack_size will be ignored.
|
||||||
autosize=true;
|
autosize=true;
|
||||||
// rack size in inches. If autosize is true, this value will be ignored. Only 10 and 19 inch racks are supported.
|
// rack size in inches. If autosize is true, this value will be ignored. Only 10 and 19 inch racks are supported.
|
||||||
rack_size=10; // [10:10 inch,19:19 inch]
|
rack_size=10; // [10:10 inch,19:19 inch]
|
||||||
|
// Asymetry Slider. CAUTION: there's no sanity check for this slider!
|
||||||
|
asymetry=0; // [-150:0.1:150]
|
||||||
|
// shows the distance between the rackmount ears considering the device width.
|
||||||
|
show_distance=false;
|
||||||
|
|
||||||
// Width of the device in mm. Will determine the width of the rackmount ears depending on rack_size.
|
// Width of the device in mm. Will determine the width of the rackmount ears depending on rack_size.
|
||||||
device_width=201;
|
device_width=201;
|
||||||
@@ -38,44 +53,18 @@ device_bore_rows=2;
|
|||||||
// If true, the device will be aligned to the center of the rackmount ear. Otherwise it will be aligned to the bottom of the rackmount ear.
|
// If true, the device will be aligned to the center of the rackmount ear. Otherwise it will be aligned to the bottom of the rackmount ear.
|
||||||
center_device_bore_alignment=false;
|
center_device_bore_alignment=false;
|
||||||
|
|
||||||
|
/* [Derived] */
|
||||||
/* [CONSTANTS (shouldn't need to be changed)] */
|
|
||||||
CAGE_BOLT_DIAMETER=6.5;
|
|
||||||
CHAMFER=min(strength/3,0.5);
|
CHAMFER=min(strength/3,0.5);
|
||||||
RACK_BORE_DISTANCE_VERTICAL=15.875;
|
|
||||||
RACK_BORE_DISTANCE_TOP_BOTTOM=6.35;
|
|
||||||
RACK_MOUNT_SURFACE_WIDTH=15.875;
|
|
||||||
RACK_BORE_DISTANCE_HORIZONTAL=RACK_MOUNT_SURFACE_WIDTH/2;
|
|
||||||
RACK_BORE_WIDTH=RACK_MOUNT_SURFACE_WIDTH-2*max(strength,2);
|
RACK_BORE_WIDTH=RACK_MOUNT_SURFACE_WIDTH-2*max(strength,2);
|
||||||
RACK_HEIGHT_UNIT=44.5; // mm
|
|
||||||
RACK_HEIGHT_UNIT_COUNT=max(1,ceil(device_height/RACK_HEIGHT_UNIT));
|
RACK_HEIGHT_UNIT_COUNT=max(1,ceil(device_height/RACK_HEIGHT_UNIT));
|
||||||
RACK_HEIGHT=RACK_HEIGHT_UNIT_COUNT*RACK_HEIGHT_UNIT; // actual height calculated by height unit size x number of units
|
RACK_HEIGHT=RACK_HEIGHT_UNIT_COUNT*RACK_HEIGHT_UNIT; // actual height calculated by height unit size x number of units
|
||||||
RACK_BORE_COUNT=RACK_HEIGHT_UNIT_COUNT*3; // 3 holes for each units
|
RACK_BORE_COUNT=RACK_HEIGHT_UNIT_COUNT*3; // 3 holes for each units
|
||||||
RACK_WIDTH_10_INCH_INNER=222.25; // mm
|
|
||||||
RACK_WIDTH_10_INCH_OUTER=254; // mm
|
|
||||||
RACK_WIDTH_19_INCH=482.6; // mm
|
|
||||||
|
|
||||||
// Base assertions
|
|
||||||
module validate_params() {
|
|
||||||
valid_rack_sizes=[10,19];
|
|
||||||
if(autosize == false){
|
|
||||||
assert(rack_size == 10 || rack_size == 19, "Invalid rack_size. Only 10 and 19 inch racks are supported. Choose a valid one or set autosize to true.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
validate_params();
|
|
||||||
|
|
||||||
// Debug
|
// Debug
|
||||||
echo("Height: ", RACK_HEIGHT);
|
echo("Height: ", RACK_HEIGHT);
|
||||||
echo("Rack Bore Count: ", RACK_BORE_COUNT);
|
echo("Rack Bore Count: ", RACK_BORE_COUNT);
|
||||||
|
|
||||||
// Calculate the width of the ear
|
|
||||||
function get_ear_width(device_width) =
|
|
||||||
device_width > RACK_WIDTH_10_INCH_INNER || autosize == false && rack_size == 19 ?
|
|
||||||
(RACK_WIDTH_19_INCH - device_width) / 2 :
|
|
||||||
(RACK_WIDTH_10_INCH_OUTER - device_width) / 2
|
|
||||||
;
|
|
||||||
rack_ear_width = get_ear_width(device_width);
|
|
||||||
|
|
||||||
function get_bore_depth(device_bore_margin_horizontal,device_bore_columns) =
|
function get_bore_depth(device_bore_margin_horizontal,device_bore_columns) =
|
||||||
(device_bore_columns - 1) * device_bore_margin_horizontal
|
(device_bore_columns - 1) * device_bore_margin_horizontal
|
||||||
@@ -92,7 +81,7 @@ device_screw_alignment = [strength,depth/2,device_screw_alignment_vertical];
|
|||||||
module base_ear(width,strength,height) {
|
module base_ear(width,strength,height) {
|
||||||
union() {
|
union() {
|
||||||
// Front face
|
// Front face
|
||||||
cuboid([rack_ear_width,strength,height],anchor=LEFT+BOTTOM+FRONT,chamfer=CHAMFER);
|
cuboid([width,strength,height],anchor=LEFT+BOTTOM+FRONT,chamfer=CHAMFER);
|
||||||
// Side face
|
// Side face
|
||||||
cuboid([strength,depth,height],anchor=LEFT+BOTTOM+FRONT,chamfer=CHAMFER);
|
cuboid([strength,depth,height],anchor=LEFT+BOTTOM+FRONT,chamfer=CHAMFER);
|
||||||
}
|
}
|
||||||
@@ -110,15 +99,36 @@ module screws_countersunk(length, diameter_head, length_head, diameter_shaft) {
|
|||||||
|
|
||||||
|
|
||||||
// Assemble the rackmount ear
|
// Assemble the rackmount ear
|
||||||
difference() {
|
module rackmount_ear(asym=0){
|
||||||
|
ear_width_19_inch=(RACK_WIDTH_19_INCH - device_width) / 2 + asym;
|
||||||
|
ear_width_10_inch=(RACK_WIDTH_10_INCH_OUTER - device_width) / 2 + asym;
|
||||||
|
// Calculate the width of the ear
|
||||||
|
rack_ear_width = device_width > RACK_WIDTH_10_INCH_INNER || autosize == false && rack_size == 19 ?
|
||||||
|
ear_width_19_inch:
|
||||||
|
ear_width_10_inch
|
||||||
|
;
|
||||||
difference() {
|
difference() {
|
||||||
// Create the base of the ear
|
difference() {
|
||||||
base_ear(device_width,strength,RACK_HEIGHT);
|
// Create the base of the ear
|
||||||
// Create the holes for the device screws
|
base_ear(rack_ear_width,strength,RACK_HEIGHT);
|
||||||
screws_countersunk(length=strength,diameter_head=device_bore_hole_head_diameter,length_head=device_bore_hole_head_length,diameter_shaft=device_bore_hole_diameter);
|
// Create the holes for the device screws
|
||||||
|
screws_countersunk(length=strength,diameter_head=device_bore_hole_head_diameter,length_head=device_bore_hole_head_length,diameter_shaft=device_bore_hole_diameter);
|
||||||
|
}
|
||||||
|
// Create the holes for the rackmount screws
|
||||||
|
zcopies(spacing=RACK_HEIGHT_UNIT,n=RACK_HEIGHT_UNIT_COUNT,sp=[0,0,0])
|
||||||
|
zcopies(spacing=RACK_BORE_DISTANCE_VERTICAL,n=3,sp=[rack_ear_width-RACK_BORE_DISTANCE_HORIZONTAL,0,RACK_BORE_DISTANCE_TOP_BOTTOM])
|
||||||
|
cuboid([RACK_BORE_WIDTH,strength+1,CAGE_BOLT_DIAMETER], rounding=CAGE_BOLT_DIAMETER/2, edges=[TOP+LEFT,TOP+RIGHT,BOTTOM+LEFT,BOTTOM+RIGHT], anchor=FRONT);
|
||||||
}
|
}
|
||||||
// Create the holes for the rackmount screws
|
}
|
||||||
zcopies(spacing=RACK_HEIGHT_UNIT,n=RACK_HEIGHT_UNIT_COUNT,sp=[0,0,0])
|
|
||||||
zcopies(spacing=RACK_BORE_DISTANCE_VERTICAL,n=3,sp=[rack_ear_width-RACK_BORE_DISTANCE_HORIZONTAL,0,RACK_BORE_DISTANCE_TOP_BOTTOM])
|
// Ear distance
|
||||||
cuboid([RACK_BORE_WIDTH,strength+1,CAGE_BOLT_DIAMETER], rounding=CAGE_BOLT_DIAMETER/2, edges=[TOP+LEFT,TOP+RIGHT,BOTTOM+LEFT,BOTTOM+RIGHT], anchor=FRONT);
|
ear_distance = show_distance ? -device_width : -CAGE_BOLT_DIAMETER;
|
||||||
|
|
||||||
|
// Place the ears
|
||||||
|
rackmount_ear(asymetry);
|
||||||
|
|
||||||
|
x_mirror_plane = [1,0,0];
|
||||||
|
translate([ear_distance,0,0])
|
||||||
|
mirror(x_mirror_plane){
|
||||||
|
rackmount_ear(-asymetry);
|
||||||
}
|
}
|
||||||
|
|||||||
130
models/wallmount/wallmount.scad
Normal file
130
models/wallmount/wallmount.scad
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
// import BOSL2
|
||||||
|
include <BOSL2/std.scad>
|
||||||
|
|
||||||
|
/* [Hidden] */
|
||||||
|
// constants which shouldn't be changed
|
||||||
|
$fn=100;
|
||||||
|
|
||||||
|
// This is the HomeRacker base unit. don't change this!
|
||||||
|
BASE_UNIT=15; // mm
|
||||||
|
// Standard tolerance for the mount. This is a sane default.
|
||||||
|
TOLERANCE=0.2; // mm
|
||||||
|
// Base strength. This is a sane default.
|
||||||
|
BASE_STRENGTH=2; // mm
|
||||||
|
// Chamfer size. This is a sane default.
|
||||||
|
CHAMFER=1; // mm
|
||||||
|
// Lock Pin side length
|
||||||
|
LOCK_PIN_SIDE=4; // mm
|
||||||
|
// lock pin edge distance
|
||||||
|
LOCK_PIN_EDGE_DISTANCE=5.5; // mm
|
||||||
|
// lock pin chamfer
|
||||||
|
LOCK_PIN_CHAMFER=0.8; // mm
|
||||||
|
// Negative chamfer fix:
|
||||||
|
NEGATIVE_CHAMFER_TRANSLATE=0.01;
|
||||||
|
|
||||||
|
mount_units=2;
|
||||||
|
|
||||||
|
/* [Base] */
|
||||||
|
// TODO: enable mount units
|
||||||
|
// How many units shall the mount span across the support
|
||||||
|
//mount_units=2; //[2:1:10]
|
||||||
|
// Shall bores be countersunk or flathead
|
||||||
|
bore_type="flathead"; //[flathead,countersunk]
|
||||||
|
// Bore Shaft Diameter in mm
|
||||||
|
bore_shaft_diameter=4;
|
||||||
|
// Bore Head Diameter in mm (only relevant if bore_type=countersunk)
|
||||||
|
bore_head_diameter=8.5;
|
||||||
|
|
||||||
|
|
||||||
|
/* [Finetuning] */
|
||||||
|
// Defines the angle of the head rejuvenation. 90° is ISO standard. (only relevant if bore_type=countersunk)
|
||||||
|
countersunk_angle=90;
|
||||||
|
// Tolerance (in mm) for the bore holes. This is a sane default.
|
||||||
|
bore_tolerance=0.2;
|
||||||
|
|
||||||
|
// Calculate the head depth based on the countersunk angle.
|
||||||
|
head_depth = bore_head_diameter/2 - bore_shaft_diameter/2 * tan( countersunk_angle/2 );
|
||||||
|
|
||||||
|
|
||||||
|
echo("head_depth: ", head_depth);
|
||||||
|
|
||||||
|
|
||||||
|
connector_width_net=BASE_UNIT+TOLERANCE;
|
||||||
|
connector_width_gross=connector_width_net+BASE_STRENGTH*2;
|
||||||
|
//Wall Mount
|
||||||
|
mount_thickness=head_depth+BASE_STRENGTH;
|
||||||
|
mount_height=mount_units*BASE_UNIT;
|
||||||
|
mount_width=mount_units*2*BASE_UNIT+connector_width_gross;
|
||||||
|
|
||||||
|
|
||||||
|
// Support Interface
|
||||||
|
module mount(){
|
||||||
|
difference() {
|
||||||
|
union(){
|
||||||
|
cuboid([mount_width,mount_height,mount_thickness],anchor=CENTER+BOTTOM,chamfer=CHAMFER);
|
||||||
|
cuboid([connector_width_gross,mount_height,connector_width_gross],anchor=CENTER+BOTTOM,chamfer=CHAMFER);
|
||||||
|
}
|
||||||
|
translate([0,0,BASE_STRENGTH])
|
||||||
|
cuboid([connector_width_net,mount_height,connector_width_net],anchor=CENTER+BOTTOM);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lock pin Holes
|
||||||
|
module lock_pin_hole(){
|
||||||
|
// fix for negative chamfer. Otherwise the holes would not reach the surface
|
||||||
|
translate([0,0,-NEGATIVE_CHAMFER_TRANSLATE/2])
|
||||||
|
cuboid(
|
||||||
|
size=[LOCK_PIN_SIDE,LOCK_PIN_SIDE,connector_width_gross+0.01],
|
||||||
|
anchor=CENTER+BOTTOM,
|
||||||
|
chamfer=-LOCK_PIN_CHAMFER,edges=[TOP,BOTTOM]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
module lock_pin_holes(){
|
||||||
|
// ycopies
|
||||||
|
ycopies(BASE_UNIT,mount_units)
|
||||||
|
// Create cross
|
||||||
|
union(){
|
||||||
|
lock_pin_hole();
|
||||||
|
yrot(90,cp=[0,0,connector_width_gross/2])
|
||||||
|
lock_pin_hole();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//// Bores
|
||||||
|
// Shafts
|
||||||
|
module bore(){
|
||||||
|
bore_shaft_radius=(bore_shaft_diameter+bore_tolerance)/2;
|
||||||
|
bore_head_radius=(bore_head_diameter+bore_tolerance)/2;
|
||||||
|
if (bore_type=="flathead") {
|
||||||
|
cylinder(r=bore_shaft_radius,h=mount_thickness,anchor=CENTER+BOTTOM);
|
||||||
|
} else if (bore_type=="countersunk") {
|
||||||
|
// Countersunk
|
||||||
|
union() {
|
||||||
|
// Head
|
||||||
|
translate([0,0,mount_thickness])
|
||||||
|
cylinder(r1=bore_shaft_radius,r2=bore_head_radius,h=head_depth,anchor=CENTER+TOP);
|
||||||
|
// Shaft
|
||||||
|
cylinder(r=bore_shaft_radius,h=mount_thickness,anchor=CENTER+BOTTOM);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bore_position_x=BASE_STRENGTH+BASE_UNIT/2+TOLERANCE/2+BASE_UNIT;
|
||||||
|
echo("bore_position_x: ", bore_position_x);
|
||||||
|
// Assembly
|
||||||
|
translate([0,0,BASE_UNIT]) rotate([90,0,0])
|
||||||
|
union(){
|
||||||
|
difference() {
|
||||||
|
mount();
|
||||||
|
translate([-bore_position_x,0,0])
|
||||||
|
bore();
|
||||||
|
translate([bore_position_x,0,0])
|
||||||
|
bore();
|
||||||
|
|
||||||
|
// Lock Pin Holes
|
||||||
|
lock_pin_holes();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
BIN
png_creation/PXL_20250915_172044293.jpg
Normal file
BIN
png_creation/PXL_20250915_172044293.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
25
png_creation/create_png.py
Normal file
25
png_creation/create_png.py
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import google.generativeai as genai
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Configure with your API key
|
||||||
|
genai.configure(api_key="YOUR_API_KEY")
|
||||||
|
|
||||||
|
# The prompt from our refined prompt file
|
||||||
|
prompt_text = """
|
||||||
|
Analyze the attached image and generate a PNG file...
|
||||||
|
[...your full prompt text here...]
|
||||||
|
"""
|
||||||
|
|
||||||
|
# Load the image and prompt
|
||||||
|
model = genai.GenerativeModel('gemini-1.5-pro')
|
||||||
|
source_image = genai.upload_file(path="PXL_20250915_172025980.jpg")
|
||||||
|
|
||||||
|
# Make the API call
|
||||||
|
response = model.generate_content([prompt_text, source_image])
|
||||||
|
|
||||||
|
# Save the generated image data from the response
|
||||||
|
# (The exact syntax for saving the file may vary based on API response structure)
|
||||||
|
with open("output_silhouette.png", "wb") as f:
|
||||||
|
f.write(response.parts[0].blob.data)
|
||||||
|
|
||||||
|
print("Silhouette PNG created successfully!")
|
||||||
17
png_creation/png_creation.scad
Normal file
17
png_creation/png_creation.scad
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
/* [SVG Parameters] */
|
||||||
|
// Amount to "thicken" the SVG shape in mm. This helps to fill small gaps.
|
||||||
|
thicken_amount = 0.1; // [0:0.1:5]
|
||||||
|
|
||||||
|
// Height of the extrusion in mm
|
||||||
|
extrusion_height = 20; // [1:1:100]
|
||||||
|
|
||||||
|
/* [Global Settings] */
|
||||||
|
$fn = 100;
|
||||||
|
|
||||||
|
// --- Implementation ---
|
||||||
|
|
||||||
|
linear_extrude(height = extrusion_height) {
|
||||||
|
offset(delta = thicken_amount) {
|
||||||
|
import("spitzzangerl.svg", center = true);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
png_creation/prompt.md
Normal file
19
png_creation/prompt.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# --- Gemini Agent Prompt: Create Tool Silhouette ---
|
||||||
|
|
||||||
|
**ROLE:**
|
||||||
|
You are an expert image processing agent. Your sole function is to create a clean, solid black silhouette from a user-provided image of a tool.
|
||||||
|
|
||||||
|
**TASK:**
|
||||||
|
Analyze the attached image and generate a PNG file that meets the precise output requirements below. This PNG will be used as a source for a vector tracing program (Potrace), so precision is critical.
|
||||||
|
|
||||||
|
**INPUT:**
|
||||||
|
- An image file of a single tool lying on a high-contrast background.
|
||||||
|
|
||||||
|
**OUTPUT REQUIREMENTS:**
|
||||||
|
1. **Format:** PNG with a transparent background.
|
||||||
|
2. **Content:** A silhouette representing the **single, continuous, outermost contour only**. All internal holes, lines, and details must be completely filled in.
|
||||||
|
3. **Color:** The silhouette must be 100% solid black (`#000000`). No anti-aliasing or grey pixels.
|
||||||
|
4. **Cropping:** The final image must be tightly cropped around the silhouette with minimal transparent padding.
|
||||||
|
|
||||||
|
**ACTION:**
|
||||||
|
Process the attached photo and provide only the resulting PNG file as your output.
|
||||||
53
png_creation/spitzzangerl.svg
Normal file
53
png_creation/spitzzangerl.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 687 KiB |
6
renovate.json
Normal file
6
renovate.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"config:recommended"
|
||||||
|
]
|
||||||
|
}
|
||||||
12
scripts/ExportCoreComponents/ExportCoreComponents.manifest
Normal file
12
scripts/ExportCoreComponents/ExportCoreComponents.manifest
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"autodeskProduct": "Fusion",
|
||||||
|
"type": "script",
|
||||||
|
"author": "kellervater (Patrick Poetz)",
|
||||||
|
"description": {
|
||||||
|
"": "Exports\n* Supports in all sane dimensions\n* Lock Pin\n* All Bodies which can be found in the Group \"Connectors\" and its subgroups"
|
||||||
|
},
|
||||||
|
"version": "",
|
||||||
|
"supportedOS": "windows|mac",
|
||||||
|
"editEnabled": true,
|
||||||
|
"iconFilename": "ScriptIcon.svg"
|
||||||
|
}
|
||||||
219
scripts/ExportCoreComponents/ExportCoreComponents.py
Normal file
219
scripts/ExportCoreComponents/ExportCoreComponents.py
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
#Fusion360API Python Script
|
||||||
|
#Description: Exports Core components. Connectors identified by naming convention.
|
||||||
|
# - "Support" body (parametric by 'support_units')
|
||||||
|
# - "Lock Pin" body (single export)
|
||||||
|
# - "Connector" bodies (categorized by name, exported to subfolders)
|
||||||
|
#Version: 1.4.1 - Added 'pr' suffix for PullThrough connectors.
|
||||||
|
|
||||||
|
import adsk.core, adsk.fusion, adsk.cam, traceback, os
|
||||||
|
import re # Import regular expression module
|
||||||
|
|
||||||
|
def run(context):
|
||||||
|
ui = None
|
||||||
|
try:
|
||||||
|
# --- Basic Fusion 360 setup ---
|
||||||
|
app = adsk.core.Application.get()
|
||||||
|
ui = app.userInterface
|
||||||
|
# Using Design.cast for robustness
|
||||||
|
design = adsk.fusion.Design.cast(app.activeProduct)
|
||||||
|
if not design:
|
||||||
|
if ui: ui.messageBox('No active Fusion 360 design (or product is not a Design). Aborting script.', 'Export Script Error')
|
||||||
|
return
|
||||||
|
|
||||||
|
rootComp = design.rootComponent
|
||||||
|
doc = app.activeDocument
|
||||||
|
if not rootComp or not doc:
|
||||||
|
if ui: ui.messageBox('Could not retrieve root component or active document. Aborting script.', 'Export Script Error')
|
||||||
|
return
|
||||||
|
|
||||||
|
# --- Get Document Version (Simplified) ---
|
||||||
|
version_str = "v_unknown"
|
||||||
|
try:
|
||||||
|
if doc.dataFile:
|
||||||
|
version_number = doc.dataFile.versionNumber
|
||||||
|
version_str = f"v{version_number}" if version_number is not None else "v_no_version_num"
|
||||||
|
if version_number is None and ui: ui.messageBox(f"Cloud-saved, but version unavailable. Using '{version_str}'.", "Version Warning")
|
||||||
|
else:
|
||||||
|
version_str = "v_local_unversioned"
|
||||||
|
if ui: ui.messageBox(f"Document not cloud-saved. Using suffix '{version_str}'.", "Unsaved Document Info")
|
||||||
|
except Exception as e_ver:
|
||||||
|
version_str = "v_version_error"
|
||||||
|
if ui: ui.messageBox(f"Version retrieval error: {e_ver}\nUsing '{version_str}'.", "Version Retrieval Error")
|
||||||
|
# --- End Get Document Version ---
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
support_param_name = "support_units"
|
||||||
|
min_support_units, max_support_units = 2, 25
|
||||||
|
support_body_name = "Support" # Exact name of the Support BRepBody
|
||||||
|
lock_pin_body_name = "Lock Pin" # Exact name of the Lock Pin BRepBody
|
||||||
|
base_export_name_prefix = "Core" # Prefix for Support and LockPin files
|
||||||
|
connectors_output_base_folder_name = "Connectors" # Main output folder for all connector types
|
||||||
|
|
||||||
|
# Regex for connector names: extracts dimensions, optional ways, and optional type suffix
|
||||||
|
# Pattern: e.g., "3d", "2d4w", "3d5wp", "3d4wpr", "3d4wps", "1d2wf"
|
||||||
|
# Groups: 1=(\d+ dimensions), 2=(\d+ ways, optional), 3=(p|ps|pr|f suffix, optional)
|
||||||
|
connector_name_pattern = re.compile(r"^(\d+)d(?:(\d+)w)?(p|ps|pr|f)?$") # Updated for 'pr'
|
||||||
|
# --- End Configuration ---
|
||||||
|
|
||||||
|
userParams = design.userParameters
|
||||||
|
if userParams is None:
|
||||||
|
if ui: ui.messageBox('Could not access user parameters. Aborting.', 'Export Script Error')
|
||||||
|
return
|
||||||
|
|
||||||
|
support_param = userParams.itemByName(support_param_name)
|
||||||
|
support_body_object_for_parametric_export = rootComp.bRepBodies.itemByName(support_body_name)
|
||||||
|
lock_pin_body_object_for_export = rootComp.bRepBodies.itemByName(lock_pin_body_name)
|
||||||
|
|
||||||
|
all_export_tasks = []
|
||||||
|
|
||||||
|
# 1. Support Tasks
|
||||||
|
if support_body_object_for_parametric_export and support_body_object_for_parametric_export.isValid and \
|
||||||
|
support_param and support_param.isValid:
|
||||||
|
for i in range(min_support_units, max_support_units + 1):
|
||||||
|
all_export_tasks.append({
|
||||||
|
"type": "support", "units": i, "body_object": support_body_object_for_parametric_export,
|
||||||
|
"base_filename": f"{base_export_name_prefix}_{support_body_name}_x{i}"})
|
||||||
|
else:
|
||||||
|
missing_s_info = []
|
||||||
|
if not (support_body_object_for_parametric_export and support_body_object_for_parametric_export.isValid):
|
||||||
|
missing_s_info.append(f"Body '{support_body_name}'")
|
||||||
|
if not (support_param and support_param.isValid):
|
||||||
|
missing_s_info.append(f"Parameter '{support_param_name}'")
|
||||||
|
if missing_s_info and ui:
|
||||||
|
ui.messageBox(f"Support Export Warning: {', '.join(missing_s_info)} not found/valid. Skipping Support exports.", "Config Warning")
|
||||||
|
|
||||||
|
# 2. Lock Pin Task
|
||||||
|
if lock_pin_body_object_for_export and lock_pin_body_object_for_export.isValid:
|
||||||
|
all_export_tasks.append({
|
||||||
|
"type": "lock_pin", "body_object": lock_pin_body_object_for_export,
|
||||||
|
"base_filename": f"{base_export_name_prefix}_{lock_pin_body_name}"})
|
||||||
|
elif lock_pin_body_name and ui:
|
||||||
|
ui.messageBox(f"Lock Pin Export Warning: Body '{lock_pin_body_name}' not found/valid. Skipping Lock Pin export.", "Config Warning")
|
||||||
|
|
||||||
|
# 3. Connector Tasks (by Name Parsing)
|
||||||
|
connector_tasks_temp = []
|
||||||
|
found_connectors_by_name = 0
|
||||||
|
if rootComp.bRepBodies:
|
||||||
|
for body_iter in rootComp.bRepBodies:
|
||||||
|
if not body_iter.isValid or not body_iter.name:
|
||||||
|
continue
|
||||||
|
body_name_iter = body_iter.name
|
||||||
|
|
||||||
|
if body_name_iter == support_body_name or body_name_iter == lock_pin_body_name:
|
||||||
|
continue
|
||||||
|
|
||||||
|
match = connector_name_pattern.match(body_name_iter)
|
||||||
|
if match:
|
||||||
|
type_suffix = match.group(3)
|
||||||
|
|
||||||
|
target_subfolder_name = None
|
||||||
|
if type_suffix == "p" or type_suffix == "ps" or type_suffix == "pr": # Added 'pr'
|
||||||
|
target_subfolder_name = "PullThrough"
|
||||||
|
elif type_suffix == "f":
|
||||||
|
target_subfolder_name = "Feet"
|
||||||
|
elif type_suffix is None:
|
||||||
|
target_subfolder_name = "Standard"
|
||||||
|
|
||||||
|
if target_subfolder_name:
|
||||||
|
found_connectors_by_name += 1
|
||||||
|
connector_tasks_temp.append({
|
||||||
|
"type": "connector",
|
||||||
|
"body_object": body_iter,
|
||||||
|
"relative_export_path_dir": os.path.join(connectors_output_base_folder_name, target_subfolder_name),
|
||||||
|
"base_filename": body_name_iter
|
||||||
|
})
|
||||||
|
|
||||||
|
if connector_tasks_temp:
|
||||||
|
all_export_tasks.extend(connector_tasks_temp)
|
||||||
|
|
||||||
|
if found_connectors_by_name == 0 and ui:
|
||||||
|
ui.messageBox("Connector Export Warning: No bodies matching the defined connector naming patterns were found in the root component.", "Name Matching Warning")
|
||||||
|
|
||||||
|
if not all_export_tasks:
|
||||||
|
if ui: ui.messageBox("No export tasks were generated (possibly due to configuration or missing items). Aborting.", "Export Script Error")
|
||||||
|
return
|
||||||
|
|
||||||
|
folderDialog = ui.createFolderDialog()
|
||||||
|
folderDialog.title = "Select Folder to Save STEP Files"
|
||||||
|
dialogResult = folderDialog.showDialog()
|
||||||
|
if dialogResult == adsk.core.DialogResults.DialogOK: exportFolder = folderDialog.folder
|
||||||
|
else:
|
||||||
|
if ui: ui.messageBox('Export cancelled by user. Aborting script.')
|
||||||
|
return
|
||||||
|
|
||||||
|
exportMgr = design.exportManager
|
||||||
|
total_exports = len(all_export_tasks)
|
||||||
|
progressDialog = ui.createProgressDialog()
|
||||||
|
progressDialog.isCancelEnabled = False
|
||||||
|
progressDialog.show(f'Exporting {base_export_name_prefix} Items', 'Initializing...', 0, total_exports)
|
||||||
|
|
||||||
|
exported_count = 0
|
||||||
|
all_root_bodies_for_visibility_ctrl = [b for b in rootComp.bRepBodies if b.isValid] if rootComp.bRepBodies else []
|
||||||
|
|
||||||
|
if not all_root_bodies_for_visibility_ctrl and total_exports > 0 and ui:
|
||||||
|
ui.messageBox(f"Warning: No bodies retrieved from root for visibility control, but {total_exports} tasks are planned. Exports may fail or include unintended geometry.", "Visibility Control Warning")
|
||||||
|
|
||||||
|
for i, task in enumerate(all_export_tasks):
|
||||||
|
try:
|
||||||
|
target_body_for_export = task.get("body_object")
|
||||||
|
if not target_body_for_export or not target_body_for_export.isValid:
|
||||||
|
progressDialog.message = f'Skipping invalid task {i+1}/{total_exports}'
|
||||||
|
adsk.doEvents(); continue
|
||||||
|
|
||||||
|
progressDialog.progressValue = exported_count
|
||||||
|
task_desc = task["base_filename"]
|
||||||
|
if task["type"] == "support": task_desc += f" (Units: {task['units']})"
|
||||||
|
progressDialog.message = f'Exporting: {task_desc} ({exported_count + 1}/{total_exports})'
|
||||||
|
adsk.doEvents()
|
||||||
|
|
||||||
|
for body_in_root_list in all_root_bodies_for_visibility_ctrl:
|
||||||
|
body_in_root_list.isLightBulbOn = (body_in_root_list.entityToken == target_body_for_export.entityToken)
|
||||||
|
adsk.doEvents()
|
||||||
|
|
||||||
|
if task["type"] == "support":
|
||||||
|
if support_param and support_param.isValid:
|
||||||
|
support_param.expression = str(task["units"])
|
||||||
|
adsk.doEvents()
|
||||||
|
else:
|
||||||
|
if ui: ui.messageBox(f"Support parameter '{support_param_name}' became invalid during export. Skipping support task for {target_body_for_export.name}.", "Export Loop Error")
|
||||||
|
continue
|
||||||
|
|
||||||
|
filename_base = task['base_filename']
|
||||||
|
filename = f"{filename_base}_{version_str}.step"
|
||||||
|
|
||||||
|
if task["type"] == "connector":
|
||||||
|
current_export_dir = os.path.join(exportFolder, task["relative_export_path_dir"])
|
||||||
|
if not os.path.exists(current_export_dir): os.makedirs(current_export_dir)
|
||||||
|
full_path = os.path.join(current_export_dir, filename)
|
||||||
|
else:
|
||||||
|
full_path = os.path.join(exportFolder, filename)
|
||||||
|
|
||||||
|
stepSaveOptions = exportMgr.createSTEPExportOptions(full_path, rootComp)
|
||||||
|
exportMgr.execute(stepSaveOptions)
|
||||||
|
exported_count += 1
|
||||||
|
except Exception as e_loop:
|
||||||
|
if ui: ui.messageBox(f'Failed during export of: {task.get("base_filename", f"Task {i+1}")}\n'
|
||||||
|
f'Error: {traceback.format_exc()}\nSkipping this item.', 'Export Loop Error')
|
||||||
|
continue
|
||||||
|
|
||||||
|
progressDialog.hide()
|
||||||
|
|
||||||
|
if exported_count > 0:
|
||||||
|
success_msg = f'Successfully exported {exported_count} STEP files'
|
||||||
|
if exported_count < total_exports:
|
||||||
|
success_msg += f' (out of {total_exports} planned tasks).'
|
||||||
|
else:
|
||||||
|
success_msg += '.'
|
||||||
|
success_msg += f'\nFiles saved to:\n{exportFolder}'
|
||||||
|
if ui: ui.messageBox(success_msg, 'Export Complete')
|
||||||
|
elif total_exports > 0 and ui:
|
||||||
|
ui.messageBox(f'Export process ran for {total_exports} planned items, but 0 files were successfully exported. Please check warnings or naming conventions.', 'Export Result')
|
||||||
|
elif ui:
|
||||||
|
ui.messageBox('No files were exported as no tasks were identified or an early abort occurred.', 'Export Result')
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
if ui: ui.messageBox('Top-Level Script Failed Unexpectedly:\n{}'.format(traceback.format_exc()))
|
||||||
|
finally:
|
||||||
|
if 'progressDialog' in locals() and progressDialog and hasattr(progressDialog, 'isShowing') and progressDialog.isShowing:
|
||||||
|
try: progressDialog.hide()
|
||||||
|
except: pass
|
||||||
2569
scripts/ExportCoreComponents/ScriptIcon.svg
Normal file
2569
scripts/ExportCoreComponents/ScriptIcon.svg
Normal file
File diff suppressed because it is too large
Load Diff
|
After Width: | Height: | Size: 85 KiB |
Reference in New Issue
Block a user