refresh
18
README.md
@ -6,7 +6,7 @@
|
|||||||
- ***Mount Anything:*** Perfect for organizing SBCs, mini PCs, small switches, power hubs, etc.
|
- ***Mount Anything:*** Perfect for organizing SBCs, mini PCs, small switches, power hubs, etc.
|
||||||
- ***Fully customizable:*** Fully written in OpenSCAD. Everything, from the dimensions of the rack, to the roundness of the corners, can be modified with a simple code change.
|
- ***Fully customizable:*** Fully written in OpenSCAD. Everything, from the dimensions of the rack, to the roundness of the corners, can be modified with a simple code change.
|
||||||
- ***Printable from home:*** Designed to be printed with conventional FDM printers. Requires minimal supports when printing, and final assembly needs only a few easy-to-source parts.
|
- ***Printable from home:*** Designed to be printed with conventional FDM printers. Requires minimal supports when printing, and final assembly needs only a few easy-to-source parts.
|
||||||
- ***No cage nuts!*** Sliding hex nut design for the front rails allows one to easily mount items without dealing with cage nuts.
|
- ***No cage nuts!*** Sliding hex nut design for the front rails allows one to easily mount items, without dealing with cage nuts.
|
||||||
- ***Stackable:*** Individual racks can be easily stacked and fastened together. Mix and match different color and design combinations!
|
- ***Stackable:*** Individual racks can be easily stacked and fastened together. Mix and match different color and design combinations!
|
||||||
|
|
||||||
## Assembly
|
## Assembly
|
||||||
@ -51,9 +51,9 @@ Please see [the assembly README here](./assembly-guide)
|
|||||||
| [Feet](./rack/print/feet_P.scad) (optional) | 2 |
|
| [Feet](./rack/print/feet_P.scad) (optional) | 2 |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#### Notes:
|
#### Notes:
|
||||||
|
- Before printing the actual parts. It's recommended to print this evaluation part: [eval_P](./rack/print/eval_P.scad) to test tolerances. If you find the fits too tight/loose, you can adjust them [here](./config/slack.scad).
|
||||||
|
- Please also adjust [this file](./config/slicer.scad) to match your slicer settings.
|
||||||
- Omitted actual plastic for printing. Any conventional 3d printing plastic should do (PLA, PETG, ABS),
|
- Omitted actual plastic for printing. Any conventional 3d printing plastic should do (PLA, PETG, ABS),
|
||||||
but beware of PLA's thermal limits. Higher infill is recommended for all parts.
|
but beware of PLA's thermal limits. Higher infill is recommended for all parts.
|
||||||
- For joining two racks, you will need to print 4 [stackConnectorDuals](./rack/print/stackConnectorDual_P.scad), as well as 8 M3 hex nuts, and 8 M3x12 FHCS.
|
- For joining two racks, you will need to print 4 [stackConnectorDuals](./rack/print/stackConnectorDual_P.scad), as well as 8 M3 hex nuts, and 8 M3x12 FHCS.
|
||||||
@ -73,18 +73,10 @@ Generate all project files for the `micro` profile:
|
|||||||
|
|
||||||
`python3 rbuild.py -b all -c micro`
|
`python3 rbuild.py -b all -c micro`
|
||||||
|
|
||||||
This will build all the required STLs for a micro rack in the `stl/custom/` directory.
|
This will build all the parts defined in [rack/print](./rack/print), and put the STLs in [stl/micro](./stl/micro).
|
||||||
|
|
||||||
For generating a specific part:
|
For generating a specific part:
|
||||||
|
|
||||||
`python3 rbuild.py -b yBar -c micro -t custom`
|
`python3 rbuild.py -b yBar -c micro -t custom`
|
||||||
|
|
||||||
Generated stls are put into the `stl/` directories. The actual variable values for different profiles can be found in
|
`rbuild.py` also support an optional `--nightly` flag, which means the build script will use the `openscad-nightly` command, instead of `openscad`.
|
||||||
[rack/profiles.scad](config/rackFrame.scad).
|
|
||||||
|
|
||||||
`rbuild.py` also support an optional `--nightly` flag, which means the build script will use the `openscad-nightly` command, instead of `openscad`.
|
|
||||||
|
|
||||||
We recommend you start by printing the `eval_P.stl` file first, just to determine if the default slack/layer height
|
|
||||||
configurations work for you. If parts are too tight/loose please take a look at
|
|
||||||
[config/slack.scad](config/slack.scad). Please also adjust [config/printing.scad](config/slicer.scad) to match your
|
|
||||||
slicer settings.
|
|
||||||
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 137 KiB |
|
Before Width: | Height: | Size: 145 KiB After Width: | Height: | Size: 144 KiB |
|
Before Width: | Height: | Size: 325 KiB After Width: | Height: | Size: 326 KiB |
|
Before Width: | Height: | Size: 190 KiB After Width: | Height: | Size: 190 KiB |
|
Before Width: | Height: | Size: 134 KiB After Width: | Height: | Size: 131 KiB |
|
Before Width: | Height: | Size: 240 KiB After Width: | Height: | Size: 240 KiB |
|
Before Width: | Height: | Size: 59 KiB After Width: | Height: | Size: 58 KiB |
|
Before Width: | Height: | Size: 80 KiB After Width: | Height: | Size: 80 KiB |
@ -1,8 +0,0 @@
|
|||||||
include <../../rack/sharedVariables.scad>
|
|
||||||
use <../frontBoxHolder.scad>
|
|
||||||
use <../sideRail.scad>
|
|
||||||
|
|
||||||
|
|
||||||
module boxHolderHelper() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -5,12 +5,12 @@ frontBoxHolder(
|
|||||||
u=2,
|
u=2,
|
||||||
plateThickness=3,
|
plateThickness=3,
|
||||||
cutoutOffsetX=(rackMountScrewWidth-147)/2,
|
cutoutOffsetX=(rackMountScrewWidth-147)/2,
|
||||||
cutoutOffsetY=2,
|
cutoutOffsetY=4.25,
|
||||||
cutoutX=147,
|
cutoutX=147,
|
||||||
cutoutY=26,
|
cutoutY=21.5,
|
||||||
support=true,
|
support=true,
|
||||||
supportedZ = 27.5,
|
supportedZ = 26.5,
|
||||||
supportWidth=120,
|
supportWidth=120,
|
||||||
supportDepth=5,
|
supportDepth=5,
|
||||||
supportRailBaseThickness=1.25
|
supportRailBaseThickness=1.75
|
||||||
);
|
);
|
||||||
|
|||||||
@ -1,3 +1,3 @@
|
|||||||
use <../sideRail.scad>
|
use <../sideRail.scad>
|
||||||
|
|
||||||
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.5, supportedY=101.5, supportedX=159);
|
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.2, supportedY=101.5, supportedX=159);
|
||||||
@ -1,4 +1,4 @@
|
|||||||
use <../sideRail.scad>
|
use <../sideRail.scad>
|
||||||
|
|
||||||
mirror(v=[1,0,0])
|
mirror(v=[1,0,0])
|
||||||
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.5, supportedY=101.5, supportedX=159);
|
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.2, supportedY=101.5, supportedX=159);
|
||||||
@ -3,7 +3,7 @@ include <../config/common.scad>
|
|||||||
include <../rack/sharedVariables.scad>
|
include <../rack/sharedVariables.scad>
|
||||||
include <./common.scad>
|
include <./common.scad>
|
||||||
|
|
||||||
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.5, supportedY=101.5, supportedX=159);
|
sideSupportRailBase(u=2, double=true, top=true, baseThickness=1.5, sideThickness=4, backThickness=2, supportedZ=27.2, supportedY=101.5, supportedX=159);
|
||||||
|
|
||||||
// distance between front and back main rail screw mounts
|
// distance between front and back main rail screw mounts
|
||||||
sideRailScrewMountDist = yBarDepth - 2*(frontScrewSpacing + railFrontThickness + railSlotToXZ);
|
sideRailScrewMountDist = yBarDepth - 2*(frontScrewSpacing + railFrontThickness + railSlotToXZ);
|
||||||
@ -13,18 +13,22 @@ module sideSupportRailBase(u=2, double=true, top=true, baseThickness=2, sideThic
|
|||||||
mountBlockHeight = 10;
|
mountBlockHeight = 10;
|
||||||
mountBlockDepth = 10;
|
mountBlockDepth = 10;
|
||||||
screwMountGlobalDz = screwDiff / 2.0; // vertical distance between local origin and main rail screw mount
|
screwMountGlobalDz = screwDiff / 2.0; // vertical distance between local origin and main rail screw mount
|
||||||
railLength = max(sideRailScrewMountDist + frontScrewSpacing + mountBlockDepth, supportedY+backThickness);
|
sideRailScrewToMainRailFrontDx = frontScrewSpacing+railFrontThickness;
|
||||||
|
railLength = max(sideRailScrewMountDist + sideRailScrewToMainRailFrontDx + mountBlockDepth/2, supportedY+backThickness);
|
||||||
railBaseThickness = baseThickness;
|
railBaseThickness = baseThickness;
|
||||||
railSideThickness = sideThickness;
|
railSideThickness = sideThickness;
|
||||||
railBaseWidth = 15;
|
railBaseWidth = 15;
|
||||||
railSideHeight = supportedZ + railBaseThickness*2;
|
railSideHeight = supportedZ + railBaseThickness*2 + overhangSlack;
|
||||||
frontMountPad = frontScrewSpacing;
|
|
||||||
|
frontMountPad = (sideRailScrewToMainRailFrontDx-mountBlockDepth/2)-xySlack;
|
||||||
|
|
||||||
applyMainRailMounts()
|
applyMainRailMounts()
|
||||||
sideSupportRailBase();
|
sideSupportRailBase();
|
||||||
|
|
||||||
module applyMainRailMounts() {
|
echo(frontScrewSpacing);
|
||||||
|
echo(sideRailScrewToMainRailFrontDx);
|
||||||
|
|
||||||
|
module applyMainRailMounts() {
|
||||||
mountBlockExtension = (railSupportsDx - supportedX)/2 - railSideThickness;
|
mountBlockExtension = (railSupportsDx - supportedX)/2 - railSideThickness;
|
||||||
assert(mountBlockExtension >= 10);
|
assert(mountBlockExtension >= 10);
|
||||||
|
|
||||||
|
|||||||
45
rbuild.py
@ -7,24 +7,27 @@ import os
|
|||||||
PATH_TO_OPENSCAD = '/usr/bin/openscad'
|
PATH_TO_OPENSCAD = '/usr/bin/openscad'
|
||||||
PATH_TO_OPENSCAD_NIGHTLY = '/snap/bin/openscad-nightly'
|
PATH_TO_OPENSCAD_NIGHTLY = '/snap/bin/openscad-nightly'
|
||||||
|
|
||||||
|
|
||||||
# For actual dimensions, please see profiles.scad.
|
# For actual dimensions, please see profiles.scad.
|
||||||
class BuildSizeConfig:
|
class BuildSizeConfig:
|
||||||
NANO = 'nano'
|
NANO = 'nano'
|
||||||
MINI = 'mini'
|
MINI = 'mini'
|
||||||
MICRO = 'micro'
|
MICRO = 'micro'
|
||||||
|
|
||||||
RACK_BUILD_DIR = './rack/print'
|
|
||||||
RACK_MOUNT_BUILD_DIR = './rack-mount/print'
|
|
||||||
|
|
||||||
BUILD_PARENT_DIR = './stl'
|
FILE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
|
||||||
|
RACK_BUILD_DIR = os.path.join(FILE_DIR, 'rack/print')
|
||||||
|
RACK_MOUNT_BUILD_DIR = os.path.join(FILE_DIR, 'rack-mount/print')
|
||||||
|
BUILD_PARENT_DIR = os.path.join(FILE_DIR, 'stl')
|
||||||
|
|
||||||
RACK_BUILD_TARGET_SUB_DIR = 'rack'
|
RACK_BUILD_TARGET_SUB_DIR = 'rack'
|
||||||
RACK_MOUNT_BUILD_TARGET_SUB_DIR = 'rack-mount'
|
RACK_MOUNT_BUILD_TARGET_SUB_DIR = 'rack-mount'
|
||||||
|
|
||||||
ASSEMBLY_GIF_DIR = './rack/assembly'
|
ASSEMBLY_GIF_DIR = os.path.join(FILE_DIR, 'rack/assembly')
|
||||||
ASSEMBLY_GIF_BUILD_DIR = './assembly-guide/gifs'
|
ASSEMBLY_GIF_BUILD_DIR = os.path.join(FILE_DIR, 'assembly-guide/gifs')
|
||||||
ASSEMBLY_GIF_TEMP_DIR = ASSEMBLY_GIF_BUILD_DIR + '/tmp'
|
ASSEMBLY_GIF_TEMP_DIR = os.path.join(ASSEMBLY_GIF_BUILD_DIR, 'tmp')
|
||||||
BUILD_GIF_FROM_PNG_SCRIPT = './misc/animate.sh'
|
BUILD_GIF_FROM_PNG_SCRIPT = os.path.join(FILE_DIR, 'misc/animate.sh')
|
||||||
|
|
||||||
ASSEMBLY_STEPS = [
|
ASSEMBLY_STEPS = [
|
||||||
('slideHexNutsIntoYBar.scad', 24),
|
('slideHexNutsIntoYBar.scad', 24),
|
||||||
@ -44,10 +47,11 @@ ASSEMBLY_STEPS = [
|
|||||||
('attachXYPlates.scad', 16)
|
('attachXYPlates.scad', 16)
|
||||||
]
|
]
|
||||||
|
|
||||||
def main():
|
|
||||||
|
|
||||||
|
def main():
|
||||||
if not assertOpenscadExists():
|
if not assertOpenscadExists():
|
||||||
print("Could not find OpenSCAD binary. Please make sure it's configured in rbuild.py. Currently only Darwin and Linux have been tested to work.")
|
print(
|
||||||
|
"Could not find OpenSCAD binary. Please make sure it's configured in rbuild.py. Currently only Darwin and Linux have been tested to work.")
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(
|
parser = argparse.ArgumentParser(
|
||||||
prog='rbuild',
|
prog='rbuild',
|
||||||
@ -101,7 +105,6 @@ def main():
|
|||||||
|
|
||||||
|
|
||||||
def run_build(args):
|
def run_build(args):
|
||||||
|
|
||||||
build_var = args.b
|
build_var = args.b
|
||||||
config_var = args.c
|
config_var = args.c
|
||||||
target_var = args.t
|
target_var = args.t
|
||||||
@ -111,11 +114,11 @@ def run_build(args):
|
|||||||
|
|
||||||
if (build_var is not None) == (build_gifs is True):
|
if (build_var is not None) == (build_gifs is True):
|
||||||
print("Please either provide the build (-b) variable, or the build-gifs option (--build-assembly-gifs)")
|
print("Please either provide the build (-b) variable, or the build-gifs option (--build-assembly-gifs)")
|
||||||
quit()
|
return
|
||||||
|
|
||||||
if build_gifs:
|
if build_gifs:
|
||||||
build_assembly_gifs(config_var, dz, nightly)
|
build_assembly_gifs(config_var, dz, nightly)
|
||||||
quit()
|
return
|
||||||
|
|
||||||
if target_var != "":
|
if target_var != "":
|
||||||
final_target_directory_name = target_var
|
final_target_directory_name = target_var
|
||||||
@ -123,7 +126,8 @@ def run_build(args):
|
|||||||
final_target_directory_name = config_var
|
final_target_directory_name = config_var
|
||||||
|
|
||||||
rackBuildDirFull = os.path.join(BUILD_PARENT_DIR, final_target_directory_name, RACK_BUILD_TARGET_SUB_DIR)
|
rackBuildDirFull = os.path.join(BUILD_PARENT_DIR, final_target_directory_name, RACK_BUILD_TARGET_SUB_DIR)
|
||||||
rackMountBuildDirFull = os.path.join(BUILD_PARENT_DIR, final_target_directory_name, RACK_MOUNT_BUILD_TARGET_SUB_DIR)
|
rackMountBuildDirFull = os.path.join(BUILD_PARENT_DIR, final_target_directory_name,
|
||||||
|
RACK_MOUNT_BUILD_TARGET_SUB_DIR)
|
||||||
|
|
||||||
if not os.path.exists(rackBuildDirFull):
|
if not os.path.exists(rackBuildDirFull):
|
||||||
os.makedirs(rackBuildDirFull)
|
os.makedirs(rackBuildDirFull)
|
||||||
@ -136,7 +140,8 @@ def run_build(args):
|
|||||||
build_single(RACK_BUILD_DIR, rackBuildDirFull, dir_file, config_var, dz, nightly)
|
build_single(RACK_BUILD_DIR, rackBuildDirFull, dir_file, config_var, dz, nightly)
|
||||||
|
|
||||||
for dir_file in os.listdir(RACK_MOUNT_BUILD_DIR):
|
for dir_file in os.listdir(RACK_MOUNT_BUILD_DIR):
|
||||||
build_single(RACK_MOUNT_BUILD_DIR, rackMountBuildDirFull, dir_file, config_var, dz, nightly)
|
build_single(RACK_MOUNT_BUILD_DIR, rackMountBuildDirFull, dir_file, config_var, dz,
|
||||||
|
nightly)
|
||||||
return
|
return
|
||||||
|
|
||||||
filename_rack = find_rack(build_var)
|
filename_rack = find_rack(build_var)
|
||||||
@ -152,11 +157,13 @@ def run_build(args):
|
|||||||
if filename_rack_mount:
|
if filename_rack_mount:
|
||||||
build_single(RACK_MOUNT_BUILD_DIR, rackMountBuildDirFull, filename_rack, config_var, dz, nightly)
|
build_single(RACK_MOUNT_BUILD_DIR, rackMountBuildDirFull, filename_rack, config_var, dz, nightly)
|
||||||
|
|
||||||
|
|
||||||
def build_single(build_dir, target_dir, filename, config, dz, nightly):
|
def build_single(build_dir, target_dir, filename, config, dz, nightly):
|
||||||
print('Building:', filename, 'from', build_dir, 'to', target_dir)
|
print('Building:', filename, 'from', build_dir, 'to', target_dir)
|
||||||
openscad_args = construct_openscad_args(build_dir, target_dir, filename, config, dz)
|
openscad_args = construct_openscad_args(build_dir, target_dir, filename, config, dz)
|
||||||
run_openscad(openscad_args, nightly)
|
run_openscad(openscad_args, nightly)
|
||||||
|
|
||||||
|
|
||||||
def build_assembly_gifs(config, dz, nightly):
|
def build_assembly_gifs(config, dz, nightly):
|
||||||
print('Building assembly-gifs. Source Dir:', ASSEMBLY_GIF_DIR, '| Target:', ASSEMBLY_GIF_BUILD_DIR)
|
print('Building assembly-gifs. Source Dir:', ASSEMBLY_GIF_DIR, '| Target:', ASSEMBLY_GIF_BUILD_DIR)
|
||||||
|
|
||||||
@ -168,11 +175,10 @@ def build_assembly_gifs(config, dz, nightly):
|
|||||||
run_openscad(openscad_args, nightly)
|
run_openscad(openscad_args, nightly)
|
||||||
build_gif_from_png(fileName)
|
build_gif_from_png(fileName)
|
||||||
|
|
||||||
|
|
||||||
def build_gif_from_png(fileName):
|
def build_gif_from_png(fileName):
|
||||||
script_dir = os.path.dirname(os.path.abspath(__file__))
|
|
||||||
script_path = os.path.join(script_dir, BUILD_GIF_FROM_PNG_SCRIPT)
|
|
||||||
try:
|
try:
|
||||||
subprocess.run(["bash", script_path, fileName], check=True)
|
subprocess.run(["bash", BUILD_GIF_FROM_PNG_SCRIPT, fileName], check=True)
|
||||||
except subprocess.CalledProcessError as e:
|
except subprocess.CalledProcessError as e:
|
||||||
print(f"Error calling shell script: {e}")
|
print(f"Error calling shell script: {e}")
|
||||||
|
|
||||||
@ -209,7 +215,6 @@ def construct_openscad_animation_args(build_dir, target_dir, filename, config, d
|
|||||||
return openscad_args
|
return openscad_args
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def find_rack(filename):
|
def find_rack(filename):
|
||||||
return find_scad_file(RACK_BUILD_DIR, filename)
|
return find_scad_file(RACK_BUILD_DIR, filename)
|
||||||
|
|
||||||
@ -236,7 +241,6 @@ def find_scad_file(directory, filename):
|
|||||||
|
|
||||||
|
|
||||||
def run_openscad(options, nightly):
|
def run_openscad(options, nightly):
|
||||||
|
|
||||||
if nightly:
|
if nightly:
|
||||||
command = [PATH_TO_OPENSCAD_NIGHTLY, '--enable', 'all']
|
command = [PATH_TO_OPENSCAD_NIGHTLY, '--enable', 'all']
|
||||||
else:
|
else:
|
||||||
@ -250,8 +254,11 @@ def run_openscad(options, nightly):
|
|||||||
print('OpenSCAD command not found! '
|
print('OpenSCAD command not found! '
|
||||||
'Please make sure that you have the OpenSCAD binary configured in rbuild.py.'
|
'Please make sure that you have the OpenSCAD binary configured in rbuild.py.'
|
||||||
'(Currently needs Linux/Mac for this)')
|
'(Currently needs Linux/Mac for this)')
|
||||||
|
|
||||||
|
|
||||||
def assertOpenscadExists():
|
def assertOpenscadExists():
|
||||||
return os.path.exists(PATH_TO_OPENSCAD)
|
return os.path.exists(PATH_TO_OPENSCAD)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
main()
|
main()
|
||||||
|
|||||||