diff --git a/generate.md b/generate.md index 069d314..3d9339f 100644 --- a/generate.md +++ b/generate.md @@ -8,7 +8,7 @@ SPDX-License-Identifier: CERN-OHL-S-2.0 ## Overview -Nimble hardware models are generated using Python. For CAD we used [CadQuery](https://cadquery.readthedocs.io/en/latest/intro.html). CadQuery scripts can be found in the `mechanical` directory, with individual components in the `mechanical/components/cadquery` directory. Our CadQuery scripts also our `nimble-builder` module of helper functions. +Nimble hardware models are generated using Python. For CAD we used [CadQuery](https://cadquery.readthedocs.io/en/latest/intro.html). CadQuery scripts can be found in the `mechanical` directory, with individual components in the `mechanical/components/cadquery` directory. Our CadQuery scripts also use the `nimble-builder` module of helper functions. Our preferred way to generate the models needed for a nimble configuration is via our orchestration module `nimble-orchestration`. This can be used to generate trays for networking components, nimble-rack components, and a final CAD assembly. The orchestration system uses [cq-cli](https://github.com/CadQuery/cq-cli) to execute CadQuery scripts, and [ExSource Tools](https://gitlab.com/gitbuilding/exsource-tools) to manage the process of turning scripts into useable models. The orchestration script will eventually use [GitBuilding](https://gitbuilding.io) to generate assembly manuals. @@ -32,7 +32,7 @@ Run: ## Running the code -As the code is very much a work in progress we do not have detailed documentation of how to it for custom configurations. +As the code is very much a work in progress we do not have detailed documentation of how to run it for custom configurations. For now the best way to run the code is with the two scripts in this repository. @@ -42,7 +42,7 @@ The script `generate-static.py` is used to create a number of example components To run this script, run the following command: - python generate-static.py + python generate_static.py This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `stl` files that can be 3D printed. @@ -53,7 +53,7 @@ It also creates a number of files that are used by the orchestration script. If you don't want to run this locally you can see a [hosted version of the output](https://wakoma.github.io/nimble/). -### Generate static +### Generate configuration The script `generate.py` is our test-bed script for generating a complete set of information for a nimble configuration. diff --git a/generate_static.py b/generate_static.py index 8cc8447..f95f7ef 100755 --- a/generate_static.py +++ b/generate_static.py @@ -66,7 +66,7 @@ def get_component_list(): ) ) - for (shelf_type, hole_count) in [ + for (shelf_type, height_in_u) in [ ("stuff", 3), ("stuff-thin", 3), ("nuc", 3), @@ -80,7 +80,7 @@ def get_component_list(): ("dual-ssd", 2), ("raspi", 2)]: - components.append(generate_shelf(shelf_type, hole_count)) + components.append(generate_shelf(shelf_type, height_in_u)) return components @@ -106,21 +106,21 @@ def generate_leg(length, mounting_holes_dia, name, out_file): ) -def generate_shelf(shelf_type, hole_count): +def generate_shelf(shelf_type, height_in_u): """ Helper function to generate a shelf/tray. Returns a GeneratedMechanicalComponent object which contains the data for the shelf. """ - out_file = f"./printed_components/shelf_6in_{shelf_type}u_{hole_count}.stl" + out_file = f"./printed_components/shelf_6in_{shelf_type}u_{height_in_u}.stl" source = os.path.join(REL_MECH_DIR, "components/cadquery/tray_6in.py") device_name = shelf_type.replace('-', ' ') return GeneratedMechanicalComponent( - key=f"{shelf_type}_{hole_count}u", + key=f"{shelf_type}_{height_in_u}u", name=f"Tray for {device_name}", - description=f"Tray for {device_name}, height = {hole_count}u", + description=f"Tray for {device_name}, height = {height_in_u}u", output_files=[out_file], source_files=[source], - parameters={'shelf_type': shelf_type, 'hole_count': hole_count}, + parameters={'shelf_type': shelf_type, 'height_in_u': height_in_u}, application="cadquery" ) diff --git a/lint_test.py b/lint_test.py index 4868e03..9f6b2a7 100755 --- a/lint_test.py +++ b/lint_test.py @@ -55,8 +55,6 @@ def lint(pylint_args): something needs doing in the future and you want to push to master then make an issue. """ - - output = Run(pylint_args, exit=False) diff --git a/mechanical/assembly_renderer.py b/mechanical/assembly_renderer.py index 0f991a0..424cc1a 100644 --- a/mechanical/assembly_renderer.py +++ b/mechanical/assembly_renderer.py @@ -1,7 +1,8 @@ """ cadquery module that takes an assembly-def.yaml file and generate an assembly from it. -The assembly-def.yaml file is a yaml file that contains a list of parts and their positions in the assembly. +The assembly-def.yaml file is a yaml file that contains a list of parts and their +positions in the assembly. Example file: assembly: parts: @@ -15,15 +16,13 @@ assembly-step: '2' """ -# parameters -# can be set in exsource-def.yaml file -assembly_definition_file = "assembly-def.yaml" import os from pathlib import Path import cadquery as cq import yaml +assembly_definition_file = "assembly-def.yaml" class PartDefinition: """ @@ -55,7 +54,7 @@ class AssemblyRederer: def __init__(self, assembly_def_file: str): - with open(assembly_def_file, "r") as f: + with open(assembly_def_file, "r", encoding="utf-8") as f: assembly_def = yaml.load(f, Loader=yaml.FullLoader) for part_def in assembly_def["assembly"]["parts"]: self._parts.append(PartDefinition(part_def)) @@ -71,20 +70,25 @@ def generate(self) -> cq.Assembly: cq_part = cq.importers.importStep(part.step_file) for tag in part.tags: cq_part = cq_part.tag(tag) - assembly.add(cq_part, name=part.name, loc=cq.Location(part.position)) + #Pylint appears to be confused by the multimethod __init__ used by cq.Location + assembly.add( + cq_part, + name=part.name, + loc=cq.Location(part.position) #pylint: disable=no-value-for-parameter + ) return assembly # Handle different execution environments, including ExSource-Tools -if "show_object" in globals() or __name__ == "__cqgi__": +if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals(): # CQGI should execute this whenever called assembly = AssemblyRederer(assembly_definition_file).generate() show_object(assembly) if __name__ == "__main__": # for debugging - folder = (Path(__file__).resolve().parent.parent) + folder = Path(__file__).resolve().parent.parent os.chdir(folder) - assembly = AssemblyRederer("assembly-def.yaml").generate() + assembly = AssemblyRederer(assembly_definition_file).generate() assembly.save("assembly.stl", "STL") diff --git a/mechanical/components/cadquery/base_plate.py b/mechanical/components/cadquery/base_plate.py index e9d41fe..dd667f3 100644 --- a/mechanical/components/cadquery/base_plate.py +++ b/mechanical/components/cadquery/base_plate.py @@ -2,9 +2,12 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later +""" +cq-cli script using cadscript to generate the baseplate of a nimble rack. +""" import cadscript as cad from nimble_builder.nimble_end_plate import create_end_plate - + # parameters to be set in exsource-def.yaml file width = 100 @@ -13,13 +16,15 @@ def create(width, depth, height): - # create end plate, turn it over and move it to the correct position + """ + create end plate, turn it over and move it to the correct position + """ plate = create_end_plate(width, depth, height) plate = plate.rotate("X", 180) plate = plate.move((0, 0, height)) return plate -if __name__ == "__cqgi__": +if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals(): result = create(width, depth, height) cad.show(result) # when run in cq-cli, will return result diff --git a/mechanical/components/cadquery/nimble_tray.py b/mechanical/components/cadquery/nimble_tray.py deleted file mode 100644 index 4505ff7..0000000 --- a/mechanical/components/cadquery/nimble_tray.py +++ /dev/null @@ -1,94 +0,0 @@ -# SPDX-FileCopyrightText: 2023 Andreas Kahler -# SPDX-FileCopyrightText: 2023 Ruslan Krenzler -# -# SPDX-License-Identifier: AGPL-3.0-or-later - -import cadquery as cq -import math - -height_in_hole_unites = 2 -tray_width = 115 -tray_depth = 115 - - -class Params: - def __init__(self): - # Default length unit is mm. - self.tray_width = 115 - self.tray_depth = 115 - self.leg_width = 20 - self.height_in_hole_unites = 2 - self.wall_thickness = 4 - self.hole_distance = 14 # Distance between the holes - self.hole_diameter = 3.6 - - @property - def inner_width(self): - return self.tray_width - self.wall_thickness * 2 - - @property - def inner_depth(self): - return self.tray_depth - self.wall_thickness * 2 - - @property - def tray_height(self): - return self.height_in_hole_unites * self.hole_distance - - @property - def inner_height(self): - return self.tray_height - self.wall_thickness - - @staticmethod - def build_for_inner_dims(width, height, depth): - p = Params() - p.tray_width = width + 2 * p.wall_thickness - p.tray_depth = depth + 2 * p.wall_thickness - # Convert hole into number of holes. - nh = math.ceil(height / p.hole_distance) - # The number of holes must be at least two - p.height_in_hole_unites = max(nh, 2) - return p - - -def _create_part(params): - # Create an empty tray as a box without a lid with a given wall thickness. - tray = cq.Workplane("XY").box(params.tray_width, params.tray_depth, params.tray_height, centered=False) - tray = tray.faces(">Z").workplane().moveTo(params.wall_thickness, params.wall_thickness).rect(params.inner_width, - params.inner_depth, - centered=False).cutBlind( - -params.inner_height) - # Add some mounting wings to fix the tray within a case. - wings = cq.Workplane("XY").moveTo(-params.leg_width, 0).box(params.tray_width + params.leg_width * 2, - params.wall_thickness, params.tray_height, - centered=False) - # Add four holes. - wings = wings.faces(">Y").workplane().moveTo(params.leg_width / 2, params.hole_distance / 2).circle( - params.hole_diameter / 2).cutThruAll() - wings = wings.faces(">Y").workplane().moveTo(params.leg_width / 2, - params.tray_height - params.hole_distance / 2).circle( - params.hole_diameter / 2).cutThruAll() - wings = wings.faces(">Y").workplane().moveTo(-params.tray_width - params.leg_width / 2, - params.hole_distance / 2).circle(params.hole_diameter / 2).cutThruAll() - wings = wings.faces(">Y").workplane().moveTo(-params.tray_width - params.leg_width / 2, - params.tray_height - params.hole_distance / 2).circle( - params.hole_diameter / 2).cutThruAll() - # Merge box and mounting wings. - result = tray.union(wings) - # Remove part of the wall from the front and from the back of the ray. - result = result.faces(">Y").workplane().moveTo(-params.tray_width / 2, -params.tray_height / 2).rect( - params.inner_width - params.wall_thickness, params.inner_height - params.wall_thickness).cutThruAll() - - return result - - -def create(number_of_units, tray_width, tray_depth): - params = Params() - params.height_in_hole_unites = number_of_units - params.tray_width = tray_width - params.tray_depth = tray_depth - return _create_part(params) - - -# CQGI should execute this whenever called -obj = create(height_in_hole_unites, tray_width, tray_depth) -show_object(obj) diff --git a/mechanical/components/cadquery/rack_leg.py b/mechanical/components/cadquery/rack_leg.py index aad1138..ca51fa7 100644 --- a/mechanical/components/cadquery/rack_leg.py +++ b/mechanical/components/cadquery/rack_leg.py @@ -1,4 +1,8 @@ -from math import e, floor +""" +cq-cli script using cadscript to generate the rack legs of a nimble rack. +""" + +from math import floor import cadscript as cad import nimble_builder @@ -6,15 +10,26 @@ # parameters to be set in exsource-def.yaml file length = 294.0 -hole_spacing = 14.0 long_axis_hole_dia = 4.6 mounting_holes_dia = 3.6 -def make_rack_leg(length, hole_spacing, long_axis_hole_dia, mounting_holes_dia) -> cad.Body: +def make_rack_leg( + length, + long_axis_hole_dia, + mounting_holes_dia, + rack_params=None + ) -> cad.Body: + """ + Create the rack legs of given length. + """ + + if not rack_params: + rack_params = nimble_builder.RackParameters() + # Construct the overall shape - leg = cad.make_box(nimble_builder.beam_width, nimble_builder.beam_width, length) - leg = leg.fillet("|Z", nimble_builder.corner_fillet) + leg = cad.make_box(rack_params.beam_width, rack_params.beam_width, length) + leg = leg.fillet("|Z", rack_params.corner_fillet) # Long-axis hole for connecting multiple leg sections together long_axis_hole = cad.make_sketch() @@ -22,22 +37,26 @@ def make_rack_leg(length, hole_spacing, long_axis_hole_dia, mounting_holes_dia) leg = leg.cut_extrude(">Z", long_axis_hole, -length) # Calculate the count of the holes in the Y direction based on the total length - number_of_holes = int(floor(length / hole_spacing)) + number_of_holes = int(floor(length / rack_params.mounting_hole_spacing)) # Mounting holes - mount_hole_ptn = cad.pattern_grid(count_x=1, count_y=number_of_holes, spacing_y=hole_spacing) + mount_hole_ptn = cad.pattern_grid( + count_x=1, + count_y=number_of_holes, + spacing_y=rack_params.mounting_hole_spacing + ) sketch = cad.make_sketch() sketch.add_circle(diameter=mounting_holes_dia, positions=mount_hole_ptn) - leg.cut_extrude(" # # SPDX-License-Identifier: AGPL-3.0-or-later - +""" +cq-cli script using cadscript to generate the topplate of a nimble rack. +As of yet this cannot hold an instrument. +""" import cadscript as cad from nimble_builder.nimble_end_plate import create_end_plate @@ -13,9 +16,11 @@ def create(width, depth, height): - # just create end plate, not further changes needed + """ + just create end plate, not further changes needed + """ return create_end_plate(width, depth, height) - -result = create(width, depth, height) -cad.show(result) # when run in cq-cli, will return result +if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals(): + result = create(width, depth, height) + cad.show(result) # when run in cq-cli, will return result diff --git a/mechanical/components/cadquery/tray_6in.py b/mechanical/components/cadquery/tray_6in.py index 756a544..c88cd82 100644 --- a/mechanical/components/cadquery/tray_6in.py +++ b/mechanical/components/cadquery/tray_6in.py @@ -2,14 +2,21 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later +""" +This module provides many different nimble shelves created using +the nimble_builder ShelfBuilder. +""" + import cadscript as cad -from nimble_builder import shelf_builder +import nimble_builder +from nimble_builder.shelf_builder import ShelfBuilder, ziptie_shelf # parameters to be set in exsource-def.yaml file # shelf types -# "stuff" - for general stuff -# "stuff-thin" - for general stuff, thin version +# "generic" - a generic cable tie shelf +# "stuff" - for general stuff such as wires. No access to the front +# "stuff-thin" - a thin version of above # "nuc" - for Intel NUC # "usw-flex" - for Ubiquiti USW-Flex # "usw-flex-mini" - for Ubiquiti Flex Mini @@ -19,148 +26,246 @@ # "hdd35" - for 3.5" HDD # "dual-ssd" - for 2x 2.5" SSD # "raspi" - for Raspberry Pi -shelf_type = "stuff" -hole_count = 2 - - -def get_builder(hole_count) -> shelf_builder.ShelfBuilder: - shelf = shelf_builder.ShelfBuilder(hole_count, width="6inch") - return shelf - - -def create_6in_shelf(shelf_type, hole_count) -> cad.Body: - if shelf_type == "stuff": - b = get_builder(hole_count) - b.make_front(front_type="w-pattern", bottom_type="closed") - b.make_tray(width="broad", depth="standard", sides="w-pattern", back="open") - return b.get_body() - if shelf_type == "stuff-thin": - b = get_builder(hole_count) - b.make_front(front_type="w-pattern", bottom_type="closed") - b.make_tray(width="standard", depth="standard", sides="w-pattern", back="open") - return b.get_body() - if shelf_type == "nuc": - b = get_builder(hole_count) - b.make_front(front_type="full", bottom_type="closed") - b.cut_opening("Y", 30, offset_y=b.bottom_thickness, depth=10) - b.add_mounting_hole_to_side(y_pos=59, z_pos=b._height / 2, hole_type="M3-tightfit", side="both") - b.add_mounting_hole_to_back(x_pos=-75 / 2, z_pos=b._height / 2, hole_type="M3-tightfit") - b.add_mounting_hole_to_back(x_pos=+75 / 2, z_pos=b._height / 2, hole_type="M3-tightfit") - return b.get_body() - if shelf_type == "anker-powerport5": - return create_ziptie_shelf(hole_count, width=56, length=90.8, height=25, cutout_width=53) - if shelf_type == "anker-atom3slim": - return create_ziptie_shelf(hole_count, width=86.5, length=90, height=20, cutout_width=71) - if shelf_type == "anker-a2123": - # 99 x 70 x 26 mm - # use height = 25, max for cage on 2 hole shelf - return create_ziptie_shelf(hole_count, width=70, length=99, height=25, cutout_width=65) - if shelf_type == "hdd35": - width = 102.8 # 101.6 + 1.2 clearance - screw_pos1 = 77.3 # distance from front - screw_pos2 = screw_pos1 + 41.61 - screw_y = 7 # distance from bottom plane - b = get_builder(hole_count) - b.make_front(front_type="w-pattern", bottom_type="closed") - b.make_tray(width="standard", depth="standard", sides="slots", back="open") - mount_sketch = cad.make_sketch() - mount_sketch.add_rect((width / 2, b._inner_width / 2 + b.side_wall_thickness), 21, - pos=[(0, screw_pos1), (0, screw_pos2)]) - mount_sketch.chamfer(" cad.Body: + """ + This is the top level function called when the script + is called. It uses the `shelf_type` string to decide + which of the defined shelf functions to call. + """ + + # Dictionary of with key as shelf type and value as tuple + # of (function, keyword-arguments) + shelf_functions = { + "generic": (generic_shelf, {}), + "stuff": (stuff_shelf, {}), + "stuff-thin": (stuff_shelf, {"thin":True}), + "nuc": (nuc_shelf, {}), + "usw-flex": (usw_flex_shelf, {}), + "usw-flex-mini": (usw_flex_mini_shelf, {}), + "anker-powerport5": (anker_shelf, { + "internal_width": 56, + "internal_depth": 90.8, + "internal_height": 25, + "front_cutout_width": 53 + }), + "anker-a2123": (anker_shelf, { + "internal_width": 86.5, + "internal_depth": 90, + "internal_height": 20, + "front_cutout_width": 71 + }), + "anker-atom3slim": (anker_shelf, { + "internal_width": 70, + "internal_depth": 99, + #should be 26 high but this height create interference of the shelf + "internal_height": 25, + "front_cutout_width": 65 + }), + "hdd35": (hdd35_shelf, {}), + "dual-ssd": (dual_ssd_shelf, {}), + "raspi": (raspi_shelf, {}) + } + + if shelf_type in shelf_functions: + shelf_func = shelf_functions[shelf_type][0] + kwargs = shelf_functions[shelf_type][1] + return shelf_func(height_in_u, **kwargs) raise ValueError(f"Unknown shelf type: {shelf_type}") +def generic_shelf(height_in_u) -> cad.Body: + """ + A generic cable tie shelf + """ + return ziptie_shelf(height_in_u) + +def stuff_shelf(height_in_u, thin=False) -> cad.Body: + """ + A shelf for general stuff such as wires. No access to the front + """ + width = "broad" if not thin else "standard" + builder = ShelfBuilder( + height_in_u, width=width, depth="standard", front_type="w-pattern" + ) + builder.make_tray(sides="w-pattern", back="open") + return builder.get_body() + +def nuc_shelf(height_in_u) -> cad.Body: + """ + A shelf for an Intel NUC + """ + builder = ShelfBuilder( + height_in_u, width="broad", depth="standard", front_type="full" + ) + builder.cut_opening(" cad.Body: + """ + A shelf for a Ubiquiti USW-Flex + """ + builder = ShelfBuilder( + height_in_u, width="standard", depth=119.5, front_type="full" + ) + builder.cut_opening(" cad.Body: + """ + A shelf for a for Ubiquiti Flex Mini + """ + rack_params = nimble_builder.RackParameters(tray_side_wall_thickness=3.8) + builder = ShelfBuilder( + height_in_u, width="standard", depth=73.4, front_type="full", rack_params=rack_params + ) + builder.cut_opening("Y", 30, offset_y=builder.rack_params.tray_bottom_thickness, depth=10) + builder.add_mounting_hole_to_side( + y_pos=59, z_pos=builder.height / 2, hole_type="M3-tightfit", side="both" + ) + builder.add_mounting_hole_to_back( + x_pos=-75 / 2, z_pos=builder.height / 2, hole_type="M3-tightfit" + ) + builder.add_mounting_hole_to_back( + x_pos=+75 / 2, z_pos=builder.height / 2, hole_type="M3-tightfit" + ) + return builder.get_body() + +def anker_shelf( + height_in_u, + internal_width, + internal_depth, + internal_height, + front_cutout_width + ) -> cad.Body: + """ + A shelf for an Anker PowerPort 5, Anker 360 Charger 60W (a2123), or Anker PowerPort Atom + III Slim (AK-194644090180) + """ + return ziptie_shelf( + height_in_u, + internal_width=internal_width, + internal_depth=internal_depth, + internal_height=internal_height, + front_cutout_width=front_cutout_width + ) + +def hdd35_shelf(height_in_u) -> cad.Body: + """ + A shelf for an 3.5" HDD + """ + width = 102.8 # 101.6 + 1.2 clearance + screw_pos1 = 77.3 # distance from front + screw_pos2 = screw_pos1 + 41.61 + screw_y = 7 # distance from bottom plane + builder = ShelfBuilder( + height_in_u, width="standard", depth="standard", front_type="w-pattern" + ) + builder.make_tray(sides="slots", back="open") + mount_sketch = cad.make_sketch() + mount_sketch.add_rect( + (width / 2, builder.inner_width / 2 + builder.rack_params.tray_side_wall_thickness), + 21, + pos=[(0, screw_pos1), (0, screw_pos2)], + ) + mount_sketch.chamfer(" cad.Body: + """ + A shelf for atwo 2.5" SSDs + """ + rack_params = nimble_builder.RackParameters() + width = 70 + screw_pos1 = 12.5 # distance from front + screw_pos2 = screw_pos1 + 76 + screw_y1 = 6.6 # distance from bottom plane + screw_y2 = screw_y1 + 11.1 + builder = ShelfBuilder( + height_in_u, + width=width + 2 * rack_params.tray_side_wall_thickness, + depth=111, + front_type="w-pattern", + base_between_beam_walls="none", + beam_wall_type="none", + ) + builder.make_tray(sides="slots", back="open") + for x, y in [ + (screw_pos1, screw_y2), + (screw_pos2, screw_y2), + (screw_pos1, screw_y1), + (screw_pos2, screw_y1), + ]: + builder.add_mounting_hole_to_side( + y_pos=x, + z_pos=y + rack_params.tray_bottom_thickness, + hole_type="M3-tightfit", + side="both", + base_diameter=11, + ) + return builder.get_body() + +def raspi_shelf(height_in_u) -> cad.Body: + """ + A shelf for a Raspberry Pi + """ + screw_dist_x = 49 + screw_dist_y = 58 + dist_to_front = 23.5 + offset_x = -13 + builder = ShelfBuilder(height_in_u, width="standard", depth=111, front_type="full") + builder.cut_opening(" # # SPDX-License-Identifier: AGPL-3.0-or-later +""" +A generic function for the end plates of the rack (top and bottom plates. +This currently only generates the flat plates, not ones that can hold and +instrument on top. +""" import cadscript as cad import nimble_builder -hole_dia = 4.7 -hole_countersink_dia = 10 -rail_width = 5 -rail_height = 3 -star_width = 9 +def create_end_plate(width, depth, height, rack_params=None): + """ + Create the top and bottom of the rack. + """ + if not rack_params: + rack_params = nimble_builder.RackParameters() -def create_end_plate(width, depth, height): - - rail_length = width - nimble_builder.beam_width - hole_countersink_dia - rail_offset = (width - nimble_builder.beam_width) / 2 + rail_length = width - rack_params.beam_width - rack_params.end_plate_hole_countersink_dia + rail_offset = (width - rack_params.beam_width) / 2 # Make the main body plate = cad.make_box(width, depth, height, center="XY") - plate.fillet("|Z", nimble_builder.corner_fillet) + plate.fillet("|Z", rack_params.corner_fillet) # add star pattern to save material cutout = cad.make_sketch() - cutout.add_rect(width - 2 * nimble_builder.beam_width, depth - 2 * nimble_builder.beam_width) + cutout.add_rect(width - 2 * rack_params.beam_width, depth - 2 * rack_params.beam_width) for i in range(4): - cutout.cut_rect(star_width, width + depth, angle=(i * 45)) + cutout.cut_rect(rack_params.end_plate_star_width, width + depth, angle=i*45) plate.cut_extrude(">Z", cutout, -height) # Add the corner mounting holes with countersinks - hole_positions = cad.pattern_rect(width - nimble_builder.beam_width, depth - nimble_builder.beam_width) - plate.cut_hole(">Z", d=hole_dia, d2=hole_countersink_dia, countersink_angle=90, pos=hole_positions) + hole_positions = cad.pattern_rect( + width - rack_params.beam_width, + depth - rack_params.beam_width + ) + plate.cut_hole( + ">Z", + d=rack_params.end_plate_hole_dia, + d2=rack_params.end_plate_hole_countersink_dia, + countersink_angle=90, + pos=hole_positions + ) # add "rails" between the holes # basic shape with chamfers - rail = cad.make_box(rail_length, rail_width, height + rail_height, center="XY") - rail.chamfer(">Z and |Y", rail_height) + rail = cad.make_box( + rail_length, + rack_params.end_plate_rail_width, + height + rack_params.end_plate_rail_height, + center="XY" + ) + rail.chamfer(">Z and |Y", rack_params.end_plate_rail_height) # add 4 instances rail.move((0, rail_offset, 0)) for _ in range(4): @@ -47,6 +65,6 @@ def create_end_plate(width, depth, height): # for debugging purposes -if __name__ == "main": +if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals(): result = create_end_plate(155, 155, 3) cad.show(result) diff --git a/nimble_builder/shelf_builder.py b/nimble_builder/shelf_builder.py index fed87a4..ac8fb6d 100644 --- a/nimble_builder/shelf_builder.py +++ b/nimble_builder/shelf_builder.py @@ -2,30 +2,29 @@ # # SPDX-License-Identifier: AGPL-3.0-or-later +""" +Shelf_builder provides classes and methods for creating any numble shelf, with +very little code. +""" + from typing import Literal, Optional, Union import cadscript as cad from cadscript.interval import Interval2D, Interval1D -from .helpers import cut_slots, cut_w_pattern - - +from nimble_builder.helpers import cut_slots, cut_w_pattern +import nimble_builder -# standard sizes for the shelf -beam_width = 20 # width and depth of the beams that the front panels are attached to -width_6in = 155 # full width (front panel) of the 6 inch nimble rack -width_10in = 254 # full width (front panel) of the 10 inch rack -width_10in_reduced = 250 # front panel width for 10 inch rack, reduced to fit into a 250mm wide printer -# standard distances between the holes in the front panels (6 inch nimble rack) -hole_dist_y = 14 - -no_slots = False # speedup for debugging +NO_SLOTS = False # speedup for debugging class ShelfBuilder: """ - A class to build a variety of shelf types + A class to build a variety of shelf types. + + On initialising the front of the shelf is made. Class methods can be used to + add the base the cutouts and mounting. origin lies x: center of front panel @@ -33,198 +32,317 @@ class ShelfBuilder: z: bottom of shelf """ - hole_diameter = 3.8 # diameter of the holes in the front panels for rack mounting - panel_thickness = 4 # thickness of the front panels - bottom_thickness = 2 # thickness of the lower plate of the shelf - side_wall_thickness = 2.5 # thickness of the side walls of the shelf - back_wall_thickness = 2.5 # thickness of the back wall of the shelf - side_wall_offset = 16 # distance between side walls to the bouding box of the rack + _rack_params: nimble_builder.RackParameters _shelf: cad.Body - _vertical_hole_count: int - _width_type: Literal["6inch", "10inch", "10inch_reduced"] - _width: float - _height: float - _inner_width: float # inside walls between the beams + _height_in_u: int + + def __init__( + self, + height_in_u: int, + width: Union[Literal["standard", "broad"], float], + depth: Union[Literal["standard"], float], + front_type: Literal["full", "open", "w-pattern", "slots"], + base_between_beam_walls: Literal["none", "front-open", "closed"] = "closed", + beam_wall_type: Literal["none", "standard", "ramp"] = "standard", + rack_params=None, + ) -> None: + """ + Initialize the shelf builder this makes the front of the shelf. + """ + self._height_in_u = height_in_u + self._width = width + self._depth = depth + self._front_type = front_type + self._front_type = front_type + + self._beam_wall_type = beam_wall_type + self._base_between_beam_walls = base_between_beam_walls + if not rack_params: + rack_params = nimble_builder.RackParameters() + self._rack_params = rack_params + self._make_front() + + @property + def plate_width(self): + """ + Derived property which is the width of the main plate (base). + """ + # basic size + + if self._width == "standard": + if self._rack_params.nominal_rack_width == "6inch": + # could be a bit larger, use this for backwards compatibility + return 115 + return self.inner_width + 2 * self.rack_params.tray_side_wall_thickness - _hole_dist_x: float - _hole_offset_y: float + if self._width == "broad": + return self._rack_params.rack_width - 2 * self.rack_params.broad_tray_clearance + if isinstance(self._width, (float, int)) and self._width > 0: + return self._width + raise ValueError(f"The value {self._width} is not a valid shelf width") - def __init__(self, - vertical_hole_count: int, - width: Literal["6inch", "10inch", "10inch_reduced"], - ) -> None: + @property + def plate_depth(self): """ - Initialize the shelf builder + Derived property which is the depth of the main plate (base) """ - self._vertical_hole_count = vertical_hole_count - self._width_type = width - self._width = width_6in if width == "6inch" else width_10in if width == "10inch" else width_10in_reduced - self.init_values() + if self._depth == "standard": + return 136 + + if isinstance(self._depth, (float, int)) and self._depth > 0: + return self._depth + raise ValueError(f"The value {self._depth} is not a valid shelf depth") - def init_values(self): - self._height = self._vertical_hole_count * hole_dist_y - self._hole_dist_x = self._width - beam_width - self._hole_offset_y = hole_dist_y / 2 - self._inner_width = self._width - 2 * beam_width - 2 * self.side_wall_thickness - self._front_depth = beam_width + 2.75 # the size of the front panel part in y direction - self._padding_front = self._front_depth # the space between the front panel and where slots etc can start at the try bottom + @property + def rack_params(self): + """ + Returns the RackParameters() object that configures the shelf. + """ + return self._rack_params - def make_front(self, - front_type: Literal["full", "open", "w-pattern", "slots"], - bottom_type: Literal["none", "front-open", "closed"], - beam_wall_type: Literal["none", "standard", "ramp"] = "standard" - ) -> None: + @property + def height(self): """ - Make the front panel of the shelf + Derived property which is the height of the shelf. + """ + return self._height_in_u * self._rack_params.mounting_hole_spacing + + @property + def hole_dist_x(self): + """ + Derived property which is the horizontal separation between the mounting holes. + """ + return self._rack_params.rack_width - self._rack_params.beam_width + + @property + def hole_offset_z(self): + """ + Derived property which is the vertical ditance between the top/bottom of the shelf + and the mounting hole. + """ + return self._rack_params.mounting_hole_spacing / 2 + + @property + def inner_width(self): + """ + The internal width of the tray area. + """ + return ( + self._rack_params.rack_width + - 2 * self._rack_params.beam_width + - 2 * self._rack_params.tray_side_wall_thickness + ) + + @property + def front_depth(self): + """ + The length of the beam walls that sit behind front panel. + """ + return self._rack_params.beam_width + 2.75 + + @property + def front_of_tray(self): + """ + Front position of the tray. + """ + if self._base_between_beam_walls == "none": + return 0 + return self.front_depth + + @property + def padding_front(self): + """ + The space between the front panel and where slots etc can start at the tray bottom. + """ + if self._base_between_beam_walls != "front-open": + return self.front_depth / 4 + return self.front_depth + + def _make_front(self) -> None: + """ + Make the front panel of the shelf. This happens on initialization. """ # sketch as viewed from top sketch = cad.make_sketch() # front panel - sketch.add_rect(self._width, (-self.panel_thickness, 0), center="X") + sketch.add_rect( + self._rack_params.rack_width, + (-self._rack_params.tray_front_panel_thickness, 0), + center="X", + ) # wall next to the beam - if beam_wall_type != "none": - sketch.add_rect((self._inner_width / 2, self._width / 2 - beam_width), - self._front_depth, center=False) + if self._beam_wall_type != "none": + sketch.add_rect( + ( + self.inner_width / 2, + self._rack_params.rack_width / 2 - self._rack_params.beam_width, + ), + self.front_depth, + center=False, + ) sketch.mirror("X") # front panel with holes - front = cad.make_extrude("XY", sketch, self._height) - pattern_holes = cad.pattern_rect(self._hole_dist_x, (self._hole_offset_y, self._height - self._hole_offset_y), center="X") - front.cut_hole("Y and >Z and |X", min(self._height, beam_width)) + front.cut_hole( + "Y and >Z and |X", min(self.height, self._rack_params.beam_width)) + + # We now have a solid front panel with short walls behind self._shelf = front - # front types + self._pattern_front() + self._add_base_between_beam_walls() - if front_type == "open": - self.cut_opening(" None: + """ + Depending on the "front_type" made cuts to pattern the front + """ - if front_type == "w-pattern": + if self._front_type == "open": + self.cut_opening(" None: + if self._base_between_beam_walls == "closed": # a full-material base between the 2 rack legs bottom_sketch = cad.make_sketch() - bottom_sketch.add_rect(self._inner_width, (-0.1, self._front_depth), center="X") - bottom = cad.make_extrude("XY", bottom_sketch, self.bottom_thickness) + bottom_sketch.add_rect(self.inner_width, (-0.1, self.front_depth), center="X") + bottom = cad.make_extrude("XY", bottom_sketch, self._rack_params.tray_bottom_thickness) self._shelf.add(bottom) - self._padding_front *= 0.25 # can be smaller in this case - if bottom_type == "front-open": + if self._base_between_beam_walls == "front-open": # a base with a cutout on the front side for e.g. cable handling bottom_sketch = cad.make_sketch() - bottom_sketch.add_rect(self._inner_width, (self._front_depth - 2, self._front_depth), center="X") - bottom_sketch.add_polygon([(self._inner_width / 2, 0), - (self._inner_width / 2, self._front_depth), - (self._inner_width / 2 - 5, self._front_depth), - ]) + bottom_sketch.add_rect( + self.inner_width, (self.front_depth - 2, self.front_depth), center="X" + ) + bottom_sketch.add_polygon( + [ + (self.inner_width / 2, 0), + (self.inner_width / 2, self.front_depth), + (self.inner_width / 2 - 5, self.front_depth), + ] + ) bottom_sketch.mirror("X") - bottom = cad.make_extrude("XY", bottom_sketch, self.bottom_thickness) + bottom = cad.make_extrude("XY", bottom_sketch, self._rack_params.tray_bottom_thickness) self._shelf.add(bottom) - def make_tray(self, - width: Union[Literal["standard", "broad"], float], - depth: Union[Literal["standard"], float], - sides: Literal["full", "open", "w-pattern", "slots", "ramp"], - back: Literal["full", "open", "w-pattern", "slots"], - bottom_type: Literal["full", "slots", "slots-large"] = "slots-large", - wall_height: Optional[float] = None, - padding_front: Optional[float] = None, - ) -> None: + def make_tray( + self, + sides: Literal["full", "open", "w-pattern", "slots", "ramp"], + back: Literal["full", "open", "w-pattern", "slots"], + bottom_type: Literal["full", "slots", "slots-large"] = "slots-large", + wall_height: Optional[float] = None, + ) -> None: """ - Make the tray of the shelf + Make the tray part of the shelf. This is most of the base (except the area between + the beam walls) and the walls at the sides and back of the shelf. """ - # basic size - plate_width = 0 if not isinstance(width, (float, int)) else width - if width == "standard": - if self._width_type == "6inch": - plate_width = 115 # could be a bit larger, use this for backwards compatibility - else: - plate_width = self._inner_width + 2 * self.side_wall_thickness - elif width == "broad": - plate_width = self._width - 2 * self.side_wall_offset - if not isinstance(plate_width, (float, int)) and plate_width <= 0: - raise ValueError("Invalid width") - self.plate_width = plate_width - - plate_depth = 0 if not isinstance(depth, (float, int)) else depth - if depth == "standard": - plate_depth = 136 - if not isinstance(plate_depth, float) and plate_depth <= 0: - raise ValueError("Invalid depth") - self.plate_depth = plate_depth - - # base plate + + if not wall_height: + wall_height = self.height + + # create the base plate plate_sketch = cad.make_sketch() - plate_sketch.add_rect(plate_width, (self._front_depth, plate_depth), center="X") - plate = cad.make_extrude("XY", plate_sketch, self.bottom_thickness) + plate_sketch.add_rect(self.plate_width, (self.front_of_tray, self.plate_depth), center="X") + plate = cad.make_extrude("XY", plate_sketch, self._rack_params.tray_bottom_thickness) if sides in ["open", "ramp"] and back == "open": plate.fillet(">Y and |Z", 3) self._shelf.add(plate) + self._make_joining_walls(sides) + self._pattern_tray_base(bottom_type) + self._tray_walls(sides, back, wall_height) - # for broad trays, add walls behind beams - # for thin trays connect the walls + def _make_joining_walls(self, sides: Literal["full", "open", "w-pattern", "slots", "ramp"]): + """ + Make the walls between the front walls and the tray walls. For broad trays, + these are walls behind beams. For thin trays this connects the walls. + """ - if sides != "open" and self._front_depth > 0: - if plate_width > self._width - 2 * beam_width: + if sides != "open" and self._base_between_beam_walls != "none": + if self.plate_width > self._rack_params.rack_width - 2 * self._rack_params.beam_width: # broad tray - left = self._inner_width / 2 - right = plate_width / 2 + left = self.inner_width / 2 + right = self.plate_width / 2 else: # thin tray - left = plate_width / 2 - self.side_wall_thickness - right = self._inner_width / 2 + left = self.plate_width / 2 - self._rack_params.tray_side_wall_thickness + right = self.inner_width / 2 if abs(left - right) > 0.5: extra_sketch = cad.make_sketch() - extra_sketch.add_rect((left, right), - (beam_width + 0.25, self._front_depth)) + extra_sketch.add_rect( + (left, right), (self._rack_params.beam_width + 0.25, self.front_of_tray) + ) extra_sketch.mirror("X") - extra_walls = cad.make_extrude("XY", extra_sketch, self._height) + extra_walls = cad.make_extrude("XY", extra_sketch, self.height) self._shelf.add(extra_walls) + def _pattern_tray_base(self, bottom_type: Literal["full", "slots", "slots-large"]): # add slots to base plate padding_x = 8 padding_y = 8 - space_at_front = self._padding_front if padding_front is None else padding_front - - if not no_slots: - if bottom_type in ["slots", "slots-large"]: - cut_slots(self._shelf, ">Z", - plate_width, (space_at_front, plate_depth), - padding_x, padding_y, - slot_type="large" if bottom_type == "slots-large" else "standard" - ) - - # add side walls and back wall - wall_height = self._height if wall_height is None else wall_height - dim_sides = Interval2D(plate_width / 2 - self.side_wall_thickness, plate_width / 2, self._front_depth, plate_depth) - dim_back = cad.helpers.get_dimensions_2d([plate_width, (plate_depth - self.back_wall_thickness, plate_depth)], center="X") + if bottom_type in ["slots", "slots-large"] and not NO_SLOTS: + cut_slots( + self._shelf, + ">Z", + self.plate_width, + (self.padding_front, self.plate_depth), + padding_x, + padding_y, + slot_type="large" if bottom_type == "slots-large" else "standard", + ) + + def _tray_walls( + self, + sides: Literal["full", "open", "w-pattern", "slots", "ramp"], + back: Literal["full", "open", "w-pattern", "slots"], + wall_height: float, + ): + + dim_sides = Interval2D( + self.plate_width / 2 - self._rack_params.tray_side_wall_thickness, + self.plate_width / 2, + self.front_of_tray, + self.plate_depth, + ) + dim_back = cad.helpers.get_dimensions_2d( + [ + self.plate_width, + (self.plate_depth - self._rack_params.tray_back_wall_thickness, self.plate_depth), + ], + center="X", + ) have_walls = False wall_sketch = cad.make_sketch() if sides not in ["open", "ramp"]: @@ -235,42 +353,58 @@ def make_tray(self, wall_sketch.add_rect(dim_back.tuple_x, dim_back.tuple_y) have_walls = True - padding_side = 8 padding_top = 5 padding_top_w = 6 if have_walls: walls = cad.make_extrude("XY", wall_sketch, wall_height) if sides == "w-pattern": - cut_w_pattern(walls, ">X", dim_sides.tuple_y, (0, wall_height), padding_side, padding_top_w, cut_depth=self._width + 1) - if sides == "slots" and not no_slots: - cut_slots(walls, ">X", dim_sides.tuple_y, (0, wall_height), padding_side, padding_top) + cut_w_pattern( + walls, + ">X", + dim_sides.tuple_y, + (0, wall_height), + padding_side, + padding_top_w, + cut_depth=self._rack_params.rack_width + 1, + ) + if sides == "slots" and not NO_SLOTS: + cut_slots( + walls, ">X", dim_sides.tuple_y, (0, wall_height), padding_side, padding_top + ) if back == "w-pattern": - cut_w_pattern(walls, ">Y", dim_back.tuple_x, (0, wall_height), padding_side, padding_top_w) - if back == "slots" and not no_slots: - cut_slots(walls, ">Y", dim_back.tuple_x, (0, wall_height), padding_side, padding_top) + cut_w_pattern( + walls, ">Y", dim_back.tuple_x, (0, wall_height), padding_side, padding_top_w + ) + if back == "slots" and not NO_SLOTS: + cut_slots( + walls, ">Y", dim_back.tuple_x, (0, wall_height), padding_side, padding_top + ) self._shelf.add(walls) if sides == "ramp": - ramp_width = self._height * 1.5 - ramp_width = min(ramp_width, plate_depth - self._front_depth) + ramp_width = self.height * 1.5 + ramp_width = min(ramp_width, self.plate_depth - self.front_of_tray) ramp_sketch = cad.make_sketch() - ramp_sketch.add_polygon([(self._front_depth, 0), - (self._front_depth + ramp_width, 0), - (self._front_depth, self._height) - ]) + ramp_sketch.add_polygon( + [ + (self.front_of_tray, 0), + (self.front_of_tray + ramp_width, 0), + (self.front_of_tray, self.height), + ] + ) ramp = cad.make_extrude("YZ", ramp_sketch, dim_sides.tuple_x).mirror("X") self._shelf.add(ramp) - - def cut_opening(self, - face: str, - size_x: cad.DimensionDefinitionType, - offset_y: float = 0, - size_y: Optional[cad.DimensionDefinitionType] = None, - depth: float = 999, - ) -> None: + def cut_opening( + self, + face: str, + size_x: cad.DimensionDefinitionType, + offset_y: float = 0, + size_y: Optional[cad.DimensionDefinitionType] = None, + depth: float = 999, + ) -> None: """ Cut an opening into a plate """ @@ -283,13 +417,14 @@ def cut_opening(self, sketch.add_rect(size_x, dim_y.tuple, center="X") self._shelf.cut_extrude(face, sketch, -depth) - def add_mounting_hole_to_bottom(self, - x_pos: float, - y_pos: float, - base_thickness: float, - hole_type: Literal["M3cs", "M3-tightfit", "base-only"], - base_diameter: float = 15 - ) -> None: + def add_mounting_hole_to_bottom( + self, + x_pos: float, + y_pos: float, + base_thickness: float, + hole_type: Literal["M3cs", "M3-tightfit", "base-only"], + base_diameter: float = 15, + ) -> None: """ Add a mounting hole to the shelf """ @@ -305,20 +440,28 @@ def add_mounting_hole_to_bottom(self, else: raise ValueError(f"Unknown hole type: {hole_type}") - def add_mounting_hole_to_side(self, - y_pos: float, - z_pos: float, - hole_type: Literal["M3-tightfit", "HDD"], - side: Literal["left", "right", "both"], - base_diameter: float = 8, - ) -> None: + def add_mounting_hole_to_side( + self, + y_pos: float, + z_pos: float, + hole_type: Literal["M3-tightfit", "HDD"], + side: Literal["left", "right", "both"], + base_diameter: float = 8, + ) -> None: """ Add a mounting hole to the shelf """ base_sketch = cad.make_sketch() base_sketch.add_circle(diameter=base_diameter, pos=(y_pos, z_pos)) base_sketch.add_rect(base_diameter, (0, z_pos), center="X", pos=(y_pos, 0)) - base = cad.make_extrude("YZ", base_sketch, (self.plate_width / 2 - self.side_wall_thickness, self.plate_width / 2)) + base = cad.make_extrude( + "YZ", + base_sketch, + ( + self.plate_width / 2 - self._rack_params.tray_side_wall_thickness, + self.plate_width / 2, + ), + ) if side == "both": base.mirror("X") else: @@ -331,11 +474,12 @@ def add_mounting_hole_to_side(self, else: raise ValueError(f"Unknown hole type: {hole_type}") - def add_mounting_hole_to_back(self, - x_pos: float, - z_pos: float, - hole_type: Literal["M3-tightfit"], - ) -> None: + def add_mounting_hole_to_back( + self, + x_pos: float, + z_pos: float, + hole_type: Literal["M3-tightfit"], + ) -> None: """ Add a mounting hole to the shelf """ @@ -343,76 +487,99 @@ def add_mounting_hole_to_back(self, base_sketch = cad.make_sketch() base_sketch.add_circle(diameter=base_diameter, pos=(x_pos, z_pos)) base_sketch.add_rect(base_diameter, (0, z_pos), center="X", pos=(x_pos, 0)) - base = cad.make_extrude("XZ", base_sketch, (-self.plate_depth, -(self.plate_depth - self.back_wall_thickness))) + base = cad.make_extrude( + "XZ", + base_sketch, + (-self.plate_depth, -(self.plate_depth - self._rack_params.tray_back_wall_thickness)), + ) self._shelf.add(base) if hole_type == "M3-tightfit": - self._shelf.cut_hole(">Y", d=2.9, pos=(-x_pos, z_pos), depth=self.back_wall_thickness + 1) + self._shelf.cut_hole( + ">Y", + d=2.9, + pos=(-x_pos, z_pos), + depth=self._rack_params.tray_back_wall_thickness + 1, + ) else: raise ValueError(f"Unknown hole type: {hole_type}") - def add_cage(self, - inner_width: float, - inner_depth: float, - height: float = 0, - back_cutout_width: float = 0, - add_ziptie_channels: bool = True, - ): + def add_cage( + self, + internal_width: float, + internal_depth: float, + internal_height: float = 0, + rear_cutout_width: float = 0, + add_ziptie_channels: bool = True, + ): + """ + Add a cage around shelf. The cage is specified by the internal space. + The cage adds ziptie channels as default. + """ wall_thickness = 2.5 offset_top = 2 - offset_bottom = 2 - channel_width = 5 - guide_width = 8 - guide_radius = 6 - ziptie_pos_y1 = inner_depth * 0.25 - ziptie_pos_y1 = max(ziptie_pos_y1, self._front_depth + channel_width / 2) - ziptie_pos_y2 = inner_depth * 0.75 - if height == 0: - height = self._height - cage_height = height + self.bottom_thickness - offset_top + + if internal_height == 0: + internal_height = self.height + cage_height = internal_height + self._rack_params.tray_bottom_thickness - offset_top # basic cage sketch = cad.make_sketch() - sketch.add_rect(inner_width + wall_thickness * 2, inner_depth + wall_thickness, center="X") - sketch.cut_rect(inner_width, inner_depth, center="X") - if back_cutout_width > 0: - sketch.cut_rect(back_cutout_width, inner_depth + wall_thickness + 1, center="X") + sketch.add_rect( + internal_width + wall_thickness * 2, internal_depth + wall_thickness, center="X" + ) + sketch.cut_rect(internal_width, internal_depth, center="X") + if rear_cutout_width > 0: + sketch.cut_rect(rear_cutout_width, internal_depth + wall_thickness + 1, center="X") cage = cad.make_extrude("XY", sketch, cage_height) self._shelf.add(cage) + if add_ziptie_channels: + self._cable_tie_channels(internal_width, internal_depth, cage_height) - # cable tie channels - - if not add_ziptie_channels: - return + def _cable_tie_channels(self, internal_width, internal_depth, cage_height): + channel_width = 5 + offset_bottom = 2 + guide_width = 8 + guide_radius = 6 + ziptie_pos_y1 = max(internal_depth * 0.25, self.front_of_tray + channel_width / 2) + ziptie_pos_y2 = internal_depth * 0.75 sketch_channel_guide = cad.make_sketch() - sketch_channel_guide.add_rect(inner_width + guide_radius * 2, guide_width, - pos=[(0, ziptie_pos_y1), (0, ziptie_pos_y2)]) - sketch_channel_guide.cut_rect(inner_width, 999) + sketch_channel_guide.add_rect( + internal_width + guide_radius * 2, + guide_width, + pos=[(0, ziptie_pos_y1), (0, ziptie_pos_y2)], + ) + sketch_channel_guide.cut_rect(internal_width, 999) channel_guide = cad.make_extrude_z(sketch_channel_guide, cage_height) channel_guide.fillet(">Z and >X and |Y", guide_radius / 2) channel_guide.fillet(">Z and cad.Body: """ @@ -420,10 +587,46 @@ def get_body(self) -> cad.Body: """ return self._shelf - -# for development and debugging -if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals(): - b = ShelfBuilder(vertical_hole_count=3, width="10inch") - b.make_front(front_type="slots", bottom_type="closed") - b.make_tray(width="standard", depth=80, sides="ramp", back="open") - cad.show(b._shelf) +def ziptie_shelf( + height_in_u: int, + internal_width: Optional[float] = None, + internal_depth: Optional[float] = None, + internal_height: Optional[float] = None, + front_cutout_width: Optional[float] = None, + rear_cutout_width: Optional[float] = None, + rack_params: nimble_builder.RackParameters = None, +): + """ + Return a ziptie shelf. The height in units must be set. + The other sizes are determined from the internal tray dimensions to + make it simple to create a shelf for a device of known size. + """ + if not rack_params: + rack_params = nimble_builder.RackParameters() + if not internal_width: + internal_width = rack_params.tray_width - 12 + if not internal_depth: + internal_depth = 115 + if not internal_height: + internal_height = rack_params.tray_height(height_in_u) - 3 + if not rear_cutout_width: + front_cutout_width = internal_width - 10 + if not rear_cutout_width: + rear_cutout_width = internal_width - 20 + + builder = ShelfBuilder( + height_in_u, + width=internal_width + 10, + depth=internal_depth + 3, + front_type="full", + beam_wall_type="ramp", + ) + builder.cut_opening( + " -# -# SPDX-License-Identifier: AGPL-3.0-or-later - -import cadquery as cq -import cadscript - -def export_svg(part, filename): - ''' - Export a part to SVG. - ''' - - if isinstance(part, cadscript.Body): - part = part.cq() - - cq.exporters.export(part, - str(filename), - opt={ - "width": 300, - "height": 300, - "marginLeft": 10, - "marginTop": 10, - "showAxes": False, - "projectionDir": (1, 1, 1), - "strokeWidth": 0.8, - "strokeColor": (0, 0, 0), - "hiddenColor": (0, 0, 255), - "showHidden": False, - },) - -def export_step(part, filename): - ''' - Export a part as STEP. - ''' - - if isinstance(part, cadscript.Body): - part = part.cq() - - cq.exporters.export(part, str(filename), exportType="STEP") - - -def export_stl(part, filename): - ''' - Export a part as STL. - ''' - - if isinstance(part, cadscript.Body): - part = part.cq() - - cq.exporters.export(part, str(filename), exportType="STL") \ No newline at end of file diff --git a/nimble_orchestration/exsource_def_generator.py b/nimble_orchestration/exsource_def_generator.py index b7eca68..a527679 100644 --- a/nimble_orchestration/exsource_def_generator.py +++ b/nimble_orchestration/exsource_def_generator.py @@ -40,7 +40,7 @@ import pathlib import yaml -from nimble_orchestration.yaml_cleaner import YamlCleaner +from nimble_orchestration import yaml_cleaner class ExsourceDefGenerator: @@ -86,4 +86,4 @@ def save(self, output_file: str | pathlib.Path): "exports": self._parts } with open(output_file, "w", encoding="utf-8") as f: - yaml.dump(YamlCleaner.clean(data), f, sort_keys=False) + yaml.dump(yaml_cleaner.clean(data), f, sort_keys=False) diff --git a/nimble_orchestration/yaml_cleaner.py b/nimble_orchestration/yaml_cleaner.py index 24cfa09..b2b2366 100644 --- a/nimble_orchestration/yaml_cleaner.py +++ b/nimble_orchestration/yaml_cleaner.py @@ -1,37 +1,42 @@ +""" +A module containting functions to clean up dictionaries before writing to yaml file. +""" + import pathlib -class YamlCleaner: +def clean(data: dict) -> dict: """ - Clean up data before writing to yaml file. + Clean the input dictionary (recursively). Removing any keys where the value is + none, changing pathlib.Path to strings and converting tuples to strings. + + The input is a single dictionary, the output is a cleaned dictionary. """ - @classmethod - def clean(cls, data: dict) -> dict: - # iterate over entries - keys_to_delete = [] - for key, value in data.items(): - # remove empty entries - if value is None: - keys_to_delete.append(key) - else: - data[key] = cls.clean_object(value) - # delete empty entries - for key in keys_to_delete: - del data[key] - return data + # iterate over entries + keys_to_delete = [] + for key, value in data.items(): + # remove empty entries + if value is None: + keys_to_delete.append(key) + else: + data[key] = _clean_object(value) + # delete empty entries + for key in keys_to_delete: + del data[key] + return data + - @classmethod - def clean_object(cls, obj: object) -> object: - # clean up lists - if isinstance(obj, list): - return [cls.clean_object(x) for x in obj] - # clean up dicts - if isinstance(obj, dict): - return cls.clean(obj) - if isinstance(obj, tuple): - # convert to string like "(1,2,3)" - return str(obj) - if isinstance(obj, pathlib.Path): - # convert to string - return str(obj) - return obj \ No newline at end of file +def _clean_object(obj: object) -> object: + # clean up lists + if isinstance(obj, list): + return [_clean_object(x) for x in obj] + # clean up dicts + if isinstance(obj, dict): + return clean(obj) + if isinstance(obj, tuple): + # convert to string like "(1,2,3)" + return str(obj) + if isinstance(obj, pathlib.Path): + # convert to string + return str(obj) + return obj diff --git a/server/nimble_server.py b/server/nimble_server.py index e1db575..1ff71ac 100644 --- a/server/nimble_server.py +++ b/server/nimble_server.py @@ -213,8 +213,9 @@ def read_item(number_of_units: float = 2, tray_width: float = 115, tray_depth: f import cadquery as cq + ## NOTE that tray_width and tray_depth are no longer used after moving to tray_6in.py # Run the script with customized parameters - tray = cqgi_model_script("nimble_tray.py", {"height_in_hole_unites": number_of_units, "tray_width": tray_width, "tray_depth": tray_depth}) + tray = cqgi_model_script("tray_6in.py", {"height_in_u": number_of_units, "shelf_type" = "generic"}) # In case there was an error if (type(tray).__name__ == "HTMLResponse"):