17 Commits

Author SHA1 Message Date
kellervater
de92bfc408 chore: defaults 2025-04-27 01:03:02 +02:00
kellervater
839ea944e9 feat: got it. Growing algorithm works finally 2025-04-27 01:00:17 +02:00
kellervater
ae9b343b40 feat(flexmount): single mounting bridge 2025-04-26 20:46:40 +02:00
kellervater
51aa349be5 feat: raw flexmount 2025-04-26 15:17:53 +02:00
kellervater
44ed1746f9 docs: updated links & anchors 2025-04-26 12:32:12 +02:00
kellervater
a5bad40082 feat(shelf): python exporter 2025-04-26 12:31:27 +02:00
kellervater
bbb742f271 chore: pre-commit config for python 2025-04-26 12:31:27 +02:00
Patrick Pötz
9dd9add861 docs: updated ToDos 2025-04-19 11:56:35 +02:00
kellervater
bc80d1e65f fix: logo transparency 2025-04-13 11:22:44 +02:00
kellervater
34cd659177 fix: logo details 2025-04-13 11:21:05 +02:00
kellervater
3a7f1ebb1b feat: add overlay logo 2025-04-13 11:03:38 +02:00
kellervater
631ef6e837 fix: transparent favicon 2025-04-13 10:47:49 +02:00
kellervater
91a43c8259 fix: favicon 2025-04-13 10:43:03 +02:00
kellervater
e91dffa6cc feat: favicon 2025-04-13 10:40:33 +02:00
kellervater
1ad8a68308 docs: try fixing links once more 2025-04-12 20:58:29 +02:00
kellervater
9ed1685b80 docs: fixed links 2025-04-12 20:55:28 +02:00
Patrick Pötz
3e935d4f3d docs: Initial Version (#9)
This is the initial version which I'm happy with. Took me almost the entire day to write.
Proof-read by ChatGPT because I'm bad in phrasing things.
2025-04-12 20:27:18 +02:00
33 changed files with 3286 additions and 40 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
*.3mf *.3mf
*.stl *.stl
.vscode

11
.lint/pylint/.pylintrc Normal file
View File

@@ -0,0 +1,11 @@
[BASIC]
good-names=i,j,k,ex,Run,_,pk,x,y
[FORMAT]
max-line-length=121
[MESSAGES CONTROL]
disable=import-error,logging-fstring-interpolation,missing-module-docstring,missing-function-docstring,missing-class-docstring,duplicate-code

View File

@@ -14,4 +14,12 @@ repos:
- id: renovate-config-validator - id: renovate-config-validator
args: ["--strict"] args: ["--strict"]
language_version: 20.18.0 # workaround till https://github.com/renovatebot/pre-commit-hooks/issues/2460 is fixed language_version: 20.18.0 # workaround till https://github.com/renovatebot/pre-commit-hooks/issues/2460 is fixed
- repo: https://github.com/PyCQA/pylint
rev: v3.3.6
hooks:
- id: pylint
args:
[
"--rcfile=.lint/pylint/.pylintrc"
]
... ...

276
README.md
View File

@@ -1,13 +1,275 @@
# 🏗️ HomeRacker - The universal modular rack building system [📚 What's it for?](#-use-cases) | [⚙️ How does it work?](#-features) | [🌐 Free & OpenSource](#-open-specs)
[![forthebadge](data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNTcuOTUzMTM2NDQ0MDkxOCIgaGVpZ2h0PSIzNSIgdmlld0JveD0iMCAwIDE1Ny45NTMxMzY0NDQwOTE4IDM1Ij48cmVjdCB3aWR0aD0iMTA2LjYyNTAwNzYyOTM5NDUzIiBoZWlnaHQ9IjM1IiBmaWxsPSIjZjZhYTRhIi8+PHJlY3QgeD0iMTA2LjYyNTAwNzYyOTM5NDUzIiB3aWR0aD0iNTEuMzI4MTI4ODE0Njk3MjY2IiBoZWlnaHQ9IjM1IiBmaWxsPSIjZmY4ZjAwIi8+PHRleHQgeD0iNTMuMzEyNTAzODE0Njk3MjY2IiB5PSIyMS41IiBmb250LXNpemU9IjEyIiBmb250LWZhbWlseT0iJ1JvYm90bycsIHNhbnMtc2VyaWYiIGZpbGw9IiNGRkZGRkYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGxldHRlci1zcGFjaW5nPSIyIj5ET1dOTE9BRDwvdGV4dD48dGV4dCB4PSIxMzIuMjg5MDcyMDM2NzQzMTYiIHk9IjIxLjUiIGZvbnQtc2l6ZT0iMTIiIGZvbnQtZmFtaWx5PSInTW9udHNlcnJhdCcsIHNhbnMtc2VyaWYiIGZpbGw9IiNGRkZGRkYiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZvbnQtd2VpZ2h0PSI5MDAiIGxldHRlci1zcGFjaW5nPSIyIj5GM0Q8L3RleHQ+PC9zdmc+)](https://makerworld.com/en/@kellervater)
![HomeRacker Assembly](./img/assembly_basics_4.png) ![HomeRacker Assembly](./img/assembly_basics_4.png)
HomeRacker is a fully modular rack building system for virtually any "racking needs" (Server Rack, shoe rack, book shelf, you name it). HomeRacker is a fully modular 3D-printable rack-building system for virtually any racking needs (server rack, shoe rack, bookshelf, you name it).
This repo contains the respective `scad` files for all fully customizable models as well as the documentation for the entire project which seeds the page https://homeracker.org. You can find all parametric and non-parametric models, as well as the `.f3d` files (like the `HomeRacker - Core`), on [Makerworld](https://makerworld.com/en/@kellervater).
You can find all parametric and non-parametric models as well as the `f3d` files (like the `HomeRacker - Core`) on [Makerworld](https://makerworld.com/en/@kellervater)). The parametric models are available in the [HomeRacker GitHub Repository](https://github.com/kellervater/homeracker/tree/main/models).
> 💡 **Note**
> The basic HomeRacker system is also referred to as `HomeRacker - Core`. Free for everyone to use, remix, and reshare.
# 📑 Table of Contents
- [🔧 Use Cases](#-use-cases)
- [✨ Features](#-features)
- [⚙️ How it works](#%EF%B8%8F-how-it-works)
- [🛠️ Assembly Basics](#%EF%B8%8F-assembly-basics)
- [💡 Assembly Tips](#-assembly-tips)
- [🖨️ Printing Tips](#%EF%B8%8F-printing-tips)
- [📐 Tech Specs](#-tech-specs)
- [🧱 Supports](#-supports)
- [🔗 Connectors](#-connectors)
- [📏 Lock Pins](#-lock-pins)
- [🌍 Open Specs](#-open-specs)
- [❓ Why the name?](#-why-the-name)
- [📜 Licensing](#-licensing)
- [🧪 Tests](#-tests)
- [⚠️ Disclaimer](#%EF%B8%8F-disclaimer)
- [🔬 How I tested](#-how-i-tested)
- [📋 Todos](#-todos)
# 🔧 Use Cases
I created HomeRacker because I was dissatisfied with the existing solutions available online.
Many designs were too specific—accommodating only certain devices owned by their creators. Others supported only the 10" standard, with no flexibility for deviations, often requiring additional adapters.
As I began my homelab journey in April 2025, I wanted a modular solution that could adapt and grow with my evolving needs. This would eliminate the need to purchase larger racks or completely change concepts when the original design no longer met my requirements.
![xkcd: The General Problem](https://imgs.xkcd.com/comics/the_general_problem.png)
As it is my nature to overengineer everything, I came up with a more generic solution to serve ANY racking need. Be it a small rack for a few Raspberry Pis, a 10" standard rack for homelabs, or even a 19" standard rack (still working on that though). You can even create bookshelves, shoe racks—or combine all of the above into an abomination of a rack.
To give you an idea of how this may look (10" rack, half-constructed Pi mini-rack, bookshelf):
![Real Life Example](./img/real_life_example.jpg)
Aside from the basic [building blocks](#-tech-specs), the rack above also contains the following parts:
* **10" Rack**
* [HomeRacker - 10" Rackmount Kit](https://makerworld.com/en/models/1353730-modular-10-server-rack#profileId-1396904) for standard-height units
* [Raspi 5 Mount Kit](https://makerworld.com/en/models/1324096-pi-5-snapcase-for-homeracker#profileId-1360937):
* Vertical Mount Adapter for HomeRacker
* Front panel for 10" racks
* Rackmount ears for the switch. These are fully customizable rackmount ears I created as an [OpenSCAD file](https://github.com/kellervater/homeracker/blob/main/models/rackmount_ears/rackmount_ears.scad). You can customize it directly [here](https://makerworld.com/en/models/1259227-fully-customizable-rackmount-ears#profileId-1283271).
* [HomeRacker Airflow Kit](https://makerworld.com/en/models/1353730-modular-10-server-rack#profileId-1396904) (currently part of the 10" rack model), which consists of:
* Front/back panels
* Side panels
* Bottom/top panels with air intake/exhaust grids and bores for standard fans (80/92/120mm)
* **Shelf** (Build any shelf configuration you like.)
# ✨ Features
The `HomeRacker - Core` features:
* **Fully modular** Thanks to the support-connector system, you can scale in any direction. The only limits are material strength—and how much money, space, and time you have.
* **3D-printable** The entire core system is printable, and no tools are required for assembly.
* **No supports needed** Not a single part of the core system needs printed supports.
* **OpenSource** Build your own adapters and use the system in personal or commercial projects (see [🌍 Open Specs](#-open-specs) and [📜 Licensing](#-licensing) for details).
## ⚙️ How it works
> **tl;dr** Think of a rack shape you want to build, download the model (insert link), print it, assemble it, and add your own mounts/adapters/whatever-you-like.
I might post a YouTube video here to show how it works.
### 🛠️ Assembly Basics
![Assembly Basics 4 White](./img/assembly_basics_4_white.png)
Assembly is straightforward and requires no tools:
1. **Prepare the components**: Download the HomeRacker - Core (❗Todo: insert link), and print all required parts. Clean off any debris.
2. **Connect supports and connectors**: Attach connectors to supports based on your desired configuration.
3. **Secure with Lock Pins**: Use Lock Pins to lock parts in place. They can be inserted horizontally or vertically thanks to their square profile.
4. **Add features**: Attach panels, shelves, and other accessories as needed.
### 💡 Assembly Tips
> 💡 **Pro Tip**: I created a sample 10" Cyberpunk-themed 3D model on MakerWorld (❗Todo: insert link) for inspiration.
1. Plan ahead! Otherwise, you'll end up with an army of unused parts like me:
![Army of Parts](./img/homeracker_army.jpg)
Make a parts list:
* How many supports of what lengths (in `base units`)?
* How many connectors of each type (pull-throughs, feet, regular)?
* Print a ton of Lock Pins. Youll need more than you might think. The model (❗Todo: insert link) on Makerworld should include a 100-pin plate and a [Gridfinity](https://gridfinity.xyz/) box for storage.
2. Build layer by layer: Start with the base frame, add vertical supports, then stack intermediate/top frames.
3. Make sure Lock Pins are fully inserted—gentle force might be needed.
### 🖨️ Printing Tips
1. If bed adhesion is sketchy: Add a brim to the supports. The small contact surface on connectors can cause print failures.
2. Prevent warping: Keep the print bed clean and oil-free. Even a fingerprint can cause issues.
3. When printing new filament: Make sure to calibrate the material flow!
> 💡 **Pro Tip**: If you need to disassemble and the pin is stuck, push it from the other side with another pin to release it.
## 📐 Tech Specs
> 💡 **Note** - For actual dimensions, check out the original Fusion `.f3d` files on Makerworld. All designs are fully parameterized for easy scaling.
The system is based on 4 core measurements:
1. **15mm** The `base_unit`. Each support has 15mm x/y dimensions, with z being a multiple of 15mm.
2. **4mm** Side length of Lock Pins and matching holes.
3. **2mm** Wall thickness of connectors.
4. **0.2mm** Tolerance added to connector interiors for print/material variances.
> These values (except tolerance) are arbitrary—just made sense during design.
### 🧱 Supports
Supports are the structural spine of HomeRacker.
![Core Support 3D Transparent](./img/core_support_3d_transparent.png)
Height is a multiple of 15mm (base units).
E.g.: A 3-unit support = 45mm tall, a 17-unit = 255mm.
Each unit height includes a 4mm hole for a Lock Pin.
* Holes match Lock Pin dimensions (no tolerance).
* Holes are convex on x and z axes for multi-directional insertion.
Schematics:
**Front View**
![Support Front View](./img/core_support_front_profile.png)
**Side View**
![Support Side View](./img/core_support_side_profile.png)
**Top View**
![Support Top View](./img/core_support_top_profile.png)
### 🔗 Connectors
Connectors join supports in 1 to 3 dimensions.
From straight extenders to 6-way junctions.
![3D Shot of All Connectors](./img/3d_shot_all_connectors.png)
Types:
* **Standard** Solid center; best for load-bearing.
* **Pull-Through** Open center for complex builds (e.g. 10" rack).
* **Feet** Solid end pieces; used as rack feet.
> ❗ **Important**
> Connector centers are always 1 `base_unit` in height. No offsets, no fluff.
> So: 2 × 3-unit supports + 1 connector = exactly 7 base units (105mm).
Schematics:
**Top View Outer**
![Connector Outer Measurements](./img/core_connector_outer_measurement.png)
**Top View Inner**
![Connector Inner Measurements](./img/core_connector_inner_measurement.png)
### 📏 lock Pins
Lock Pins hold the system together.
![lock Pin 3D](./img/core_lock_pin_3d.png)
They rely on tension from their convex shape to stay in place.
Schematics:
**Top View**
![lock Pin Top](./img/core_lock_pin_top.png)
**Side View**
> Dont ask why the height is 3.791mm—it works. I left it as-is.
![lock Pin Side](./img/core_lock_pin_side.png)
> I may tweak the pin grip in future versions for to make it easier to pull-out again. But the base dimensions will remain the same for compatibility.
## 🌍 Open Specs
I created `HomeRacker - Core` to be an open spec that any maker can build on, with (almost) no strings attached.
(See [📜 Licensing](#-licensing) for more.)
I encourage you to make your own models based on HomeRacker!
Let me know, and Ill feature your work on this page and cross-link it on Makerworld (subject to my "very objective" approval 😄).
Just [create an issue](https://github.com/kellervater/homeracker/issues/new) if you want to be featured.
# ❓ Why the name?
After ~4 hours of research, I found all my original ideas (UniRack, OpenRack, etc.) were taken.
So, "HomeRacker" was born—part practical, part tongue-in-cheek. It fits the homelab theme, but also hints at the "home-wrecking" time sink this can become.
# 📜 Licensing # 📜 Licensing
* The source code in this repository is licensed under the `MIT License` (see [LICENSE](./LICENSE)).
* All 3D models and creative assets (in /models/) are licensed under the `CC BY-NC 4.0 License` (see [/models/LICENSE](./models/LICENSE)). > 💡 **tl;dr**
> Use it for ANY purpose (even commercial), but credit me and share alike!
* Source code: `MIT License` ([LICENSE](https://github.com/kellervater/homeracker/blob/main/LICENSE))
* 3D models & creative assets (`/models/`): `CC BY 4.0 License` ([/models/LICENSE](https://github.com/kellervater/homeracker/blob/main/models/LICENSE))
These licenses apply to the `HomeRacker - Core` system and customizable rackmount ears.
> ❗ **Important**
> Other models I publish may have more restrictive licenses. This will be stated clearly on Makerworld.
HomeRacker is an unregistered trademark of Patrick Pötz (kellervater), first used publicly on 12.04.2025.
# 🧪 Tests
Of course I tested stuff... It took ~4 months from idea to this release.
Look at all the prototypes:
![Photo showing a variety of HomeRacker prototypes lined up](./img/prototypes.jpg)
## ⚠️ Disclaimer
> ⚠️ **Warning**
> This project is provided “as is,” without any warranty. Use at your own risk. Im not responsible for damage, injury, or loss caused by using this system or its parts.
Aside from the scary warning above, I need to mention, that due to the high modularity of this system combined with limited time and resources I was of course not able to test every combination of filaments, printers, print-settings, room conditions (temperature, humidity) or to do extensive load-bearing tests.
What I want to say:
I feel like the model turned out to be really nice and versatile. That's why I shared it in the first place.
But since I do not have control over the manufacturing conditions of any consumer of this model, I cannot give any guarantees on how your specific print will turn out in the end. There are just too much variables which not even the best model design can compensate for. (Writing this feels a bit like an upfront apology... seems like I'm a people pleaser)
## 🔬 How I tested
My setup is as follows:
* a room temperature between 17 and 25°C
* Humidity levels between 29% and 36% (depends on when I'm doing my laundry)
* A BambuLab X1C printer
* Exclusively BambuLab filament (haven't tried others yet)
* PLA Matte (I love the charcoal color. Looks so silky)
* PLA Basic
* ABS
* mostly I used the Textured PEI plate. It just works (provided you regularly clean it using Isopropyl alcohol). For the rest of the time I tried out the Cold Plate Super Track (it's nice but very hard to get your prints of the plates when it cools)
All above's filament types can be be combined in any possible way (just make sure you do flow calibration before using new filaments. First ABS print turned out horribly just because I forgot to click the calibration checkbox).
E.g.: you could print a connector in ABS, a support in PLA Matte and a Lock Pin in PLA and they will just fit when being assembled.
> 🛠️ **Btw:** I am not affiliated with Bambu in any way besides uploading my models to MakerWorld and occasionally making use of their Exclusive Model program. But they don't pay me for naming their products anywhere else (I wish 😉).
# 📋 Todos
* [x] Rename Building blocks in f3d (did bad translations from german to english there)
* [x] Release models on MakerLab
* [x] HomeRacker - Core (under above's license, non-exclusive)
* [x] HomeRacker - 10" Rackmount Kit (exclusive)
* [x] HomeRacker - Pi5 Mount Kit (exclusive)
* [x] Customizable Rackmount Ears
* [x] HomeRacker - Airflow Kit (exclusive) -> under 10" Rack
* [x] HomeRacker - Shelf
* [ ] Contributing.md stub?
* [ ] Quickstart Guide
* [ ] Parts Catalog
* [ ] Link (maybe via table) example models
# Logo
I asked ChatGPT to create a logo, and I think it turned out great — so well stick with it for now. This logo will also be used as an overlay image for the thumbnails of all my 3D models that are compatible with HomeRacker.
I encourage you to do the same if you create models for HomeRacker. That way, itll be immediately visible to users that a model is designed to be mounted on a HomeRacker system.
![HomeRacker Logo](./img/homeracker_logo.png)

View File

@@ -1,4 +1,4 @@
remote_theme: pages-themes/architect@v0.2.0 remote_theme: pages-themes/architect@v0.2.0
title: HomeRacker title: HomeRacker
description: "The universal modular rack building system" description: "A printable modular rack building system"
author: "kellervater" author: "kellervater"

BIN
favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 164 KiB

View File

Before

Width:  |  Height:  |  Size: 1.0 MiB

After

Width:  |  Height:  |  Size: 1.0 MiB

BIN
img/core_connector_1d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

View File

Before

Width:  |  Height:  |  Size: 128 KiB

After

Width:  |  Height:  |  Size: 128 KiB

View File

Before

Width:  |  Height:  |  Size: 115 KiB

After

Width:  |  Height:  |  Size: 115 KiB

BIN
img/core_lock_pin_3d.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

BIN
img/core_lock_pin_side.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
img/core_lock_pin_top.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 79 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

BIN
img/homeracker_army.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

BIN
img/homeracker_logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

BIN
img/homeracker_logo.xcf Normal file

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 KiB

BIN
img/prototypes.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

BIN
img/real_life_example.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

View File

@@ -1,4 +1,4 @@
Attribution-NonCommercial-ShareAlike 4.0 International Attribution-ShareAlike 4.0 International
======================================================================= =======================================================================
@@ -54,18 +54,18 @@ exhaustive, and do not form part of our licenses.
======================================================================= =======================================================================
Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International Creative Commons Attribution-ShareAlike 4.0 International Public
Public License License
By exercising the Licensed Rights (defined below), You accept and agree By exercising the Licensed Rights (defined below), You accept and agree
to be bound by the terms and conditions of this Creative Commons to be bound by the terms and conditions of this Creative Commons
Attribution-NonCommercial-ShareAlike 4.0 International Public License Attribution-ShareAlike 4.0 International Public License ("Public
("Public License"). To the extent this Public License may be License"). To the extent this Public License may be interpreted as a
interpreted as a contract, You are granted the Licensed Rights in contract, You are granted the Licensed Rights in consideration of Your
consideration of Your acceptance of these terms and conditions, and the acceptance of these terms and conditions, and the Licensor grants You
Licensor grants You such rights in consideration of benefits the such rights in consideration of benefits the Licensor receives from
Licensor receives from making the Licensed Material available under making the Licensed Material available under these terms and
these terms and conditions. conditions.
Section 1 -- Definitions. Section 1 -- Definitions.
@@ -84,7 +84,7 @@ Section 1 -- Definitions.
and Similar Rights in Your contributions to Adapted Material in and Similar Rights in Your contributions to Adapted Material in
accordance with the terms and conditions of this Public License. accordance with the terms and conditions of this Public License.
c. BY-NC-SA Compatible License means a license listed at c. BY-SA Compatible License means a license listed at
creativecommons.org/compatiblelicenses, approved by Creative creativecommons.org/compatiblelicenses, approved by Creative
Commons as essentially the equivalent of this Public License. Commons as essentially the equivalent of this Public License.
@@ -108,7 +108,7 @@ Section 1 -- Definitions.
g. License Elements means the license attributes listed in the name g. License Elements means the license attributes listed in the name
of a Creative Commons Public License. The License Elements of this of a Creative Commons Public License. The License Elements of this
Public License are Attribution, NonCommercial, and ShareAlike. Public License are Attribution and ShareAlike.
h. Licensed Material means the artistic or literary work, database, h. Licensed Material means the artistic or literary work, database,
or other material to which the Licensor applied this Public or other material to which the Licensor applied this Public
@@ -122,15 +122,7 @@ Section 1 -- Definitions.
j. Licensor means the individual(s) or entity(ies) granting rights j. Licensor means the individual(s) or entity(ies) granting rights
under this Public License. under this Public License.
k. NonCommercial means not primarily intended for or directed towards k. Share means to provide material to the public by any means or
commercial advantage or monetary compensation. For purposes of
this Public License, the exchange of the Licensed Material for
other material subject to Copyright and Similar Rights by digital
file-sharing or similar means is NonCommercial provided there is
no payment of monetary compensation in connection with the
exchange.
l. Share means to provide material to the public by any means or
process that requires permission under the Licensed Rights, such process that requires permission under the Licensed Rights, such
as reproduction, public display, public performance, distribution, as reproduction, public display, public performance, distribution,
dissemination, communication, or importation, and to make material dissemination, communication, or importation, and to make material
@@ -138,13 +130,13 @@ Section 1 -- Definitions.
public may access the material from a place and at a time public may access the material from a place and at a time
individually chosen by them. individually chosen by them.
m. Sui Generis Database Rights means rights other than copyright l. Sui Generis Database Rights means rights other than copyright
resulting from Directive 96/9/EC of the European Parliament and of resulting from Directive 96/9/EC of the European Parliament and of
the Council of 11 March 1996 on the legal protection of databases, the Council of 11 March 1996 on the legal protection of databases,
as amended and/or succeeded, as well as other essentially as amended and/or succeeded, as well as other essentially
equivalent rights anywhere in the world. equivalent rights anywhere in the world.
n. You means the individual or entity exercising the Licensed Rights m. You means the individual or entity exercising the Licensed Rights
under this Public License. Your has a corresponding meaning. under this Public License. Your has a corresponding meaning.
@@ -158,10 +150,9 @@ Section 2 -- Scope.
exercise the Licensed Rights in the Licensed Material to: exercise the Licensed Rights in the Licensed Material to:
a. reproduce and Share the Licensed Material, in whole or a. reproduce and Share the Licensed Material, in whole or
in part, for NonCommercial purposes only; and in part; and
b. produce, reproduce, and Share Adapted Material for b. produce, reproduce, and Share Adapted Material.
NonCommercial purposes only.
2. Exceptions and Limitations. For the avoidance of doubt, where 2. Exceptions and Limitations. For the avoidance of doubt, where
Exceptions and Limitations apply to Your use, this Public Exceptions and Limitations apply to Your use, this Public
@@ -229,9 +220,7 @@ Section 2 -- Scope.
Rights, whether directly or through a collecting society Rights, whether directly or through a collecting society
under any voluntary or waivable statutory or compulsory under any voluntary or waivable statutory or compulsory
licensing scheme. In all other cases the Licensor expressly licensing scheme. In all other cases the Licensor expressly
reserves any right to collect such royalties, including when reserves any right to collect such royalties.
the Licensed Material is used other than for NonCommercial
purposes.
Section 3 -- License Conditions. Section 3 -- License Conditions.
@@ -276,6 +265,7 @@ following conditions.
reasonable to satisfy the conditions by providing a URI or reasonable to satisfy the conditions by providing a URI or
hyperlink to a resource that includes the required hyperlink to a resource that includes the required
information. information.
3. If requested by the Licensor, You must remove any of the 3. If requested by the Licensor, You must remove any of the
information required by Section 3(a)(1)(A) to the extent information required by Section 3(a)(1)(A) to the extent
reasonably practicable. reasonably practicable.
@@ -287,7 +277,7 @@ following conditions.
1. The Adapter's License You apply must be a Creative Commons 1. The Adapter's License You apply must be a Creative Commons
license with the same License Elements, this version or license with the same License Elements, this version or
later, or a BY-NC-SA Compatible License. later, or a BY-SA Compatible License.
2. You must include the text of, or the URI or hyperlink to, the 2. You must include the text of, or the URI or hyperlink to, the
Adapter's License You apply. You may satisfy this condition Adapter's License You apply. You may satisfy this condition
@@ -307,8 +297,7 @@ apply to Your use of the Licensed Material:
a. for the avoidance of doubt, Section 2(a)(1) grants You the right a. for the avoidance of doubt, Section 2(a)(1) grants You the right
to extract, reuse, reproduce, and Share all or a substantial to extract, reuse, reproduce, and Share all or a substantial
portion of the contents of the database for NonCommercial purposes portion of the contents of the database;
only;
b. if You include all or a substantial portion of the database b. if You include all or a substantial portion of the database
contents in a database in which You have Sui Generis Database contents in a database in which You have Sui Generis Database
@@ -415,6 +404,7 @@ Section 8 -- Interpretation.
that apply to the Licensor or You, including from the legal that apply to the Licensor or You, including from the legal
processes of any jurisdiction or authority. processes of any jurisdiction or authority.
======================================================================= =======================================================================
Creative Commons is not a party to its public Creative Commons is not a party to its public

View File

@@ -0,0 +1,5 @@
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>

View File

@@ -0,0 +1,204 @@
// import BOSL2
include <BOSL2/std.scad>
$fn=100;
/* [Hidden] */
// constants which shouldn't be changed
// 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
/* [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] */
// Width of the device in mm. Will determine how far apart the actual mounts are in width.
device_width=100 // [20:0.1:500]
; // TODO test for zero cube
// Depth of the device in mm. Will determine how far apart the actual mounts are in depth.
device_depth=99; // [54:0.1:500]
// 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]
/* [Bracket] */
// Defines the bracket thickness on top of the device.
bracket_strength_top=7.5; // [4:0.1:20]
bracket_strength_sides=7.5; // [4:0.1:20]
// 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);
WIDTH_DIFF = modulo_width==15 ? 0 : modulo_width;
echo("Diff Width: ", WIDTH_DIFF);
mount_gap_filler_start=(device_width+TOLERANCE)/2;
// echo("mount_gap_filler_start: ", mount_gap_filler_start);
// echo("effective mount distance: ", device_width+WIDTH_DIFF+TOLERANCE);
DEPTH_DIFF=device_depth % BASE_UNIT;
echo("Diff Depth: ", DEPTH_DIFF);
mount_offset_depth=(device_depth-DEPTH_DIFF-BASE_UNIT)/2;
echo("mount_offset_depth*2: ", mount_offset_depth*2);
/* Code */
// creates a bracket around the device with TOLERANCE as additional space and BASE_STRENGTH as the strength of the bracket
module bracket(width,depth,height) {
// Bracket
outer_width=width+BASE_STRENGTH*2+TOLERANCE;
outer_depth=depth+BASE_STRENGTH*2+TOLERANCE;
outer_height=height+BASE_STRENGTH;
intersection(){
difference() {
// Outer
cuboid(
size=[outer_width,outer_depth,outer_height],
anchor=BOTTOM,
chamfer=CHAMFER, edges=[TOP,FRONT,BACK,LEFT,RIGHT], except=[BOTTOM]
);
// Top Skeleton
cuboid(
size=[width-bracket_strength_top,depth-bracket_strength_top,outer_height],
anchor=BOTTOM,
chamfer=-CHAMFER, edges=[TOP]
);
// Bottom Recess
cuboid(
size=[outer_width,outer_depth,height-bracket_strength_sides],
anchor=BOTTOM
);
}
translate([0,0,outer_height])
cuboid(
size=[outer_width,outer_depth,bracket_strength_sides],
anchor=TOP,
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
module mount(){
depth=BASE_UNIT+BASE_STRENGTH*2+TOLERANCE;
gap_filler_width=(WIDTH_DIFF)/2;
// depth translation
translate([0,mount_offset_depth,0])
union(){
translate([mount_gap_filler_start,0,0])
// Mount
union(){
top_width=BASE_STRENGTH;
bottom_width=BASE_UNIT+gap_filler_width;
// Bridge
difference(){
cuboid_width = bottom_width;
cuboid_chamfer = bottom_width;
prismoid(
size1 = [bottom_width, depth], // Bottom face: width 30, depth 60
size2 = [top_width, depth], // Top face: becomes a line segment (width 0)
shift = [(-bottom_width+top_width)/2, 0], // Shift top edge center to X=+15 (aligns with right edge of base)
chamfer=CHAMFER,
h = device_height, // Height
//chamfer=CHAMFER/2,
anchor = BOTTOM+LEFT // Anchor bottom left
);
translate([0,0,BASE_STRENGTH])
cuboid(
size=[cuboid_width,BASE_UNIT,device_height],
anchor=BOTTOM+LEFT,
chamfer=BASE_UNIT/2, edges=[BOTTOM], except=[RIGHT]
);
}
// Depth enforcement
depth_enforcement_x = BASE_STRENGTH;
depth_enforcemnt_y2 = BASE_UNIT*2;
prismoid(
size1 = [depth_enforcement_x, depth],
size2 = [depth_enforcement_x, depth_enforcemnt_y2],
shift = [0, (depth-depth_enforcemnt_y2)/2],
h = device_height,
chamfer=[CHAMFER,0,0,CHAMFER],
anchor = BOTTOM+LEFT
);
// Mount Rail with holes
difference(){
// Mount Rail
difference(){
height=BASE_UNIT+TOLERANCE/2;
// outer
cuboid(
size=[bottom_width,depth,height],
anchor=TOP+LEFT,
chamfer=CHAMFER, edges=[BOTTOM,RIGHT], except=[TOP]
);
// support recess
cuboid(
size=[bottom_width,BASE_UNIT+TOLERANCE,height],
anchor=TOP+LEFT
);
}
// holes
translate([bottom_width-LOCK_PIN_EDGE_DISTANCE,0,-LOCK_PIN_EDGE_DISTANCE-TOLERANCE/2])
cuboid(
size=[LOCK_PIN_SIDE,depth,LOCK_PIN_SIDE],
anchor=TOP+RIGHT
);
}
}
}
}
module mirror_mount_x_plane(){
mount();
x_mirror_plane = [1,0,0];
mirror(x_mirror_plane) {
mount();
}
}
// Assembly
rotate([180,0,0])
union() {
difference() {
bracket(device_width,device_depth,device_height);
device(device_width,device_depth,device_height);
}
// Mount Right
mirror_mount_x_plane();
y_mirror_plane = [0,1,0];
mirror(y_mirror_plane) {
mirror_mount_x_plane();
}
}

2
scripts/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
.env
.vscode

View File

@@ -0,0 +1,12 @@
{
"autodeskProduct": "Fusion",
"type": "script",
"author": "kellervater (Patrick Pötz)",
"description": {
"": "Exports all common dimensions of the shelf."
},
"version": "",
"supportedOS": "windows|mac",
"editEnabled": true,
"iconFilename": "ScriptIcon.svg"
}

View File

@@ -0,0 +1,176 @@
#Fusion360API Python Script
#Description: Exports STEP files. Exports "Support" once per depth,
# and "Shelf" for each width/depth combination.
import adsk.core, adsk.fusion, adsk.cam, traceback, os
def run(context):
ui = None
try:
# --- Basic Fusion 360 setup ---
app = adsk.core.Application.get()
ui = app.userInterface
design = app.activeProduct
if not design:
ui.messageBox('No active Fusion 360 design', 'Export Script Error')
return
# --- Get Document Version ---
doc = app.activeDocument
version_str = "v_unknown" # Default in case of error
try:
if doc.dataFile:
version_number = doc.dataFile.versionNumber
version_str = f"v{version_number}"
else:
version_str = "v_unsaved"
ui.messageBox('The current document has not been saved to the cloud yet.\n'
'Filenames will use "v_unsaved" instead of a version number.',
'Unsaved Document Warning')
except Exception as e:
ui.messageBox(f'Could not retrieve document version:\n{e}\n'
f'Filenames will use "{version_str}".', 'Version Error')
# --- End Get Document Version ---
# --- Configuration
widthParamName = "shelf_width_units"
depthParamName = "shelf_depth_units"
minWidth = 4
maxWidth = 15
minDepth = 7
maxDepth = 15
baseFileName = "homeracker_shelf"
shelfBodyName = "Shelf"
supportBodyName = "Support"
# --- End Configuration ---
# Get the root component
rootComp = design.rootComponent
# Get User Parameters collection
userParams = design.userParameters
if not userParams:
ui.messageBox('Could not access user parameters.', 'Export Script Error')
return
# Find the specific parameters
widthParam = userParams.itemByName(widthParamName)
depthParam = userParams.itemByName(depthParamName)
if not widthParam or not depthParam:
ui.messageBox(f'Could not find parameters: "{widthParamName}" or "{depthParamName}". Check names.', 'Param Error')
return
# --- Find the bodies to control visibility (once before loops) ---
shelfBody = None
supportBody = None
try:
shelfBody = rootComp.bRepBodies.itemByName(shelfBodyName)
supportBody = rootComp.bRepBodies.itemByName(supportBodyName)
if not shelfBody:
ui.messageBox(f'Body "{shelfBodyName}" not found. Aborting...', 'Body Not Found Warning')
return
if not supportBody:
ui.messageBox(f'Body "{supportBodyName}" not found. Aborting...', 'Body Not Found Warning')
return
except Exception as e:
ui.messageBox(f'Error finding bodies:\n{traceback.format_exc()}', 'Error Finding Bodies')
return
# --- Get Export Directory from User ---
folderDialog = ui.createFolderDialog()
folderDialog.title = "Select Folder to Save STEP Files"
dialogResult = folderDialog.showDialog()
if dialogResult == adsk.core.DialogResults.DialogOK:
exportFolder = folderDialog.folder
else:
ui.messageBox('Export cancelled by user.')
return
# --- Get Export Manager ---
exportMgr = design.exportManager
# --- Progress Bar Setup ---
num_depths = (maxDepth - minDepth + 1)
num_widths = (maxWidth - minWidth + 1)
# Total exports = one support per depth + one shelf per width*depth
total_exports = num_depths + (num_depths * num_widths)
progressDialog = ui.createProgressDialog()
progressDialog.isCancelEnabled = False
progressDialog.show(f'Exporting {baseFileName}', 'Initializing...', 0, total_exports)
exported_count = 0
# --- Loop through parameter combinations ---
for d in range(minDepth, maxDepth + 1):
try:
# --- Set Depth Parameter ---
depthParam.value = float(d)
adsk.doEvents() # Allow update
progressDialog.progressValue = exported_count
progressDialog.message = f'Exporting Support: D={d} ({exported_count + 1}/{total_exports})'
adsk.doEvents()
# Set visibility: Support ONLY
supportBody.isLightBulbOn = True
shelfBody.isLightBulbOn = False # Hide shelf if it exists
adsk.doEvents() # Allow visibility change
# Construct filename & path for Support
supportFileName = f"{baseFileName}_Support_D{d}_{version_str}"
supportFullPath = os.path.join(exportFolder, supportFileName + ".step")
# Export Support
supportStepOptions = exportMgr.createSTEPExportOptions(supportFullPath, rootComp)
exportMgr.execute(supportStepOptions)
exported_count += 1
# --- End Support Export ---
except Exception:
progressDialog.hide() # Hide progress on error
ui.messageBox(f'Failed during processing or Support export for Depth={d}:\n{traceback.format_exc()}', 'Outer Loop Error')
return
# --- INNER LOOP: Width (for Shelf Export) ---
for w in range(minWidth, maxWidth + 1):
try:
progressDialog.progressValue = exported_count
progressDialog.message = f'Exporting Shelf: W={w}, D={d} ({exported_count + 1}/{total_exports})'
adsk.doEvents()
# --- Set Width Parameter ---
widthParam.value = float(w)
adsk.doEvents() # Allow update
# --- Set Visibility for Shelf Export: Shelf ONLY ---
shelfBody.isLightBulbOn = True
supportBody.isLightBulbOn = False # Hide support if it exists
adsk.doEvents() # Allow visibility change
# --- Construct Filename & Path for Shelf ---
shelfFileName = f"{baseFileName}_Shelf_W{w}_D{d}_{version_str}"
shelfFullPath = os.path.join(exportFolder, shelfFileName + ".step")
# --- Export Shelf ---
shelfStepOptions = exportMgr.createSTEPExportOptions(shelfFullPath, rootComp)
exportMgr.execute(shelfStepOptions)
exported_count += 1
except Exception as e_inner:
# Log error for this specific shelf, but continue loop
ui.messageBox(f'Failed exporting Shelf W={w}, D={d}:\n{traceback.format_exc()}\nSkipping this combination.', 'Inner Loop Error')
# Optionally add logging here instead of message box spam
continue # Continue to next width value
# --- Clean up ---
progressDialog.hide()
if exported_count > 0:
ui.messageBox(f'Successfully exported {exported_count} STEP files to:\n{exportFolder}', 'Export Complete')
else:
ui.messageBox('No files were exported. Check script configuration or logs for errors.', 'Export Result')
except Exception as e:
if ui:
ui.messageBox('Script Failed Unexpectedly:\n{}'.format(traceback.format_exc()))
if 'progressDialog' in locals() and progressDialog.isShowing:
progressDialog.hide()

View File

@@ -0,0 +1,6 @@
This script is intended to be used with the Fusion360 file `HomeRacker - Shelf`.
It exports all sane dimensions of the shelf to avoid tedious manual work.
It is ready to go and just needs to be run from within Fusion360 (`Shift + S`).
> [!NOTE]
> Since I bootstrapped the script from within Fusion, idk how to import it. Maybe you need to create a new one and just import the contents. Just try it and let me know how it works.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 85 KiB