Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Final Assembly Rendering #109

Merged
merged 8 commits into from
Jan 13, 2025
6 changes: 6 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,9 @@ jobs:
uses: coactions/setup-xvfb@v1
with:
run: pytest

- name: Upload rendered images as artifacts for inspection
uses: actions/upload-artifact@v3
with:
name: rendered-images
path: renders/*.png
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,5 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

renders/
2 changes: 1 addition & 1 deletion generate.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Currently the configuration is just a list of the networking components which ar

To generate run the following:

cadorchestrator generate '["NUC10i5FNH", "Raspberry_Pi_4B", "Raspberry_Pi_4B"]'
julianstirling marked this conversation as resolved.
Show resolved Hide resolved
cadorchestrator generate '{"device-ids": ["NUC10i5FNH", "Raspberry_Pi_4B", "Raspberry_Pi_4B"]}'

This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `stl` files that can be 3D printed. It will also contain `step` files for each of these components.

Expand Down
27 changes: 27 additions & 0 deletions mechanical/assembly_renderer.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,10 @@

from nimble_build_system.cad.shelf import create_shelf_for

from nimble_build_system.cad.rack_assembly import RackAssembly

assembly_definition_file = "../build/assembly-def.yaml"
rack_parts_definition_file = "../build/empty_rack-pars.yaml"
render_destination = os.path.join(os.getcwd(), "renders")

class PartDefinition:
Expand Down Expand Up @@ -55,6 +58,7 @@ class AssemblyRenderer:
"""

_parts: list[PartDefinition] = []
_assembly_parts: list[PartDefinition] = []


def __init__(self, assembly_def_file: str):
Expand All @@ -64,6 +68,14 @@ def __init__(self, assembly_def_file: str):
for part_def in assembly_def["assembly"]["parts"]:
self._parts.append(PartDefinition(part_def))

# Load the rack parts definition file
# Check to see if the _assembly_parts list is empty
if len(self._assembly_parts) == 0:
with open(rack_parts_definition_file, "r", encoding="utf-8") as f:
rack_parts_def = yaml.load(f, Loader=yaml.FullLoader)
for part_def in rack_parts_def["assembly"]["parts"]:
self._assembly_parts.append(PartDefinition(part_def))



def generate(self) -> cq.Assembly:
Expand Down Expand Up @@ -101,11 +113,26 @@ def generate(self) -> cq.Assembly:
return assembly


def generate_assembly_process_renders(self):
"""
Generate renders of the assembly steps.
"""

# Create a union of _assembly_parts and _parts
all_parts = self._parts + self._assembly_parts

# Create a rack assembly that will handle putting parts together and exporting them to PNG
rack_assembly = RackAssembly(all_parts)
rack_assembly.generate_renders(render_destination=render_destination)


# Handle different execution environments, including ExSource-Tools
if __name__ == "__main__" or __name__ == "__cqgi__" or "show_object" in globals():
def_file = Path(assembly_definition_file)
folder = def_file.resolve().parent
os.chdir(folder)
# CQGI should execute this whenever called
assembly = AssemblyRenderer(def_file.name).generate()
AssemblyRenderer(def_file.name).generate_assembly_process_renders()

show_object(assembly)
139 changes: 139 additions & 0 deletions nimble_build_system/cad/fasteners.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
Holds fastener classes containing the information needed to generate CAD models of fasteners.
"""

import cadquery as cq
from cq_warehouse.fastener import ButtonHeadScrew, CounterSunkScrew, PanHeadScrew

class Fastener:
"""
Class that defines a generic fastener that can be used in the assembly of a device and/or rack.
Expand All @@ -12,6 +15,9 @@ class Fastener:
_size = "M3-0.5"
_fastener_type = "iso7380_1"
_direction_axis = "-Z"
_rotation = ((0, 0, 1), 0)
_face_selector = ">X"
_fastener_model = None

def __init__(
self,
Expand Down Expand Up @@ -95,6 +101,41 @@ def direction_axis(self):
"""
return self._direction_axis

@property
def rotation(self):
"""
Getter for the rotation of the fastener.
"""
return self._rotation

@rotation.setter
def rotation(self, rotation):
"""
Setter for the rotation of the fastener.
"""
self._rotation = rotation

@property
def face_selector(self):
"""
Getter for the face selector of the fastener.
"""
return self._face_selector

@face_selector.setter
def face_selector(self, face_selector):
"""
Setter for the face selector of the fastener.
"""
self._face_selector = face_selector

@property
def fastener_model(self):
"""
Getter for the fastener model of the fastener.
"""
return self._fastener_model

def _gen_human_name(self):
return f"{self.size} {self.fastener_type}"

Expand Down Expand Up @@ -123,6 +164,26 @@ def __init__(

self._length = length

julianstirling marked this conversation as resolved.
Show resolved Hide resolved
# Handle rotation based on the direction axis
if axis == "X":
self._rotation = ((0, 1, 0), 90)
self._face_selector = ">X"
elif axis == "-X":
self._rotation = ((0, 1, 0), -90)
self._face_selector = ">X"
elif axis == "Y":
self._rotation = ((1, 0, 0), 90)
self._face_selector = "<Y"
elif axis == "-Y":
self._rotation = ((1, 0, 0), -90)
self._face_selector = ">Y"
elif axis == "Z":
self._rotation = ((0, 1, 0), 0)
self._face_selector = "<Z"
elif axis == "-Z":
self._rotation = ((0, 1, 0), 180)
self._face_selector = ">Z"

super().__init__(
name,
position=position,
Expand All @@ -133,6 +194,32 @@ def __init__(
human_name=human_name
)

# Generate the CadQuery model for this fastener
if self._fastener_type == "iso10642":
# Create the counter-sunk screw model
self._fastener_model = cq.Workplane(CounterSunkScrew(size=self._size,
fastener_type=self._fastener_type,
length=self._length,
simple=True).cq_object)
elif self._fastener_type == "asme_b_18.6.3":
# Create the cheesehead screw model
self._fastener_model = cq.Workplane(PanHeadScrew(size=self._size,
fastener_type=self._fastener_type,
length=self._length,
simple=True).cq_object)
elif self._fastener_type == "iso7380_1":
# Create a button head screw model
self._fastener_model = cq.Workplane(ButtonHeadScrew(size=self._size,
fastener_type=self._fastener_type,
length=self._length,
simple=True).cq_object)
else:
raise ValueError("Unknown screw type.")

# Make sure assembly lines are present with each fastener
self._fastener_model.faces(self._face_selector).tag("assembly_line")


@property
def length(self):
"""
Expand Down Expand Up @@ -195,6 +282,26 @@ def __init__(self,
self._length = length
self._width = float(size)

# Handle the rotation and face selector based on the direction axis
if axis == "X":
self._rotation = ((0, 0, 1), -90)
self._face_selector = ">Z"
elif axis == "-X":
self._rotation = ((0, 0, 1), 90)
self._face_selector = ">Z"
elif axis == "Y":
self._rotation = ((0, 0, 1), 0)
self._face_selector = ">Z"
elif axis == "-Y":
self._rotation = ((0, 0, 1), 180)
self._face_selector = ">Z"
elif axis == "Z":
self._rotation = ((1, 0, 0), 0)
self._face_selector = ">X"
elif axis == "-Z":
self._rotation = ((1, 0, 0), 180)
self._face_selector = ">X"

super().__init__(name,
position=position,
explode_translation=explode_translation,
Expand All @@ -203,6 +310,38 @@ def __init__(self,
direction_axis=axis,
human_name=human_name)

# Generate the CadQuery model for this fastener
# Create the ziptie spine
self._fastener_model = cq.Workplane().box(self._width,
self._length,
self._thickness)

# Create the ziptie head
self._fastener_model = (self._fastener_model.faces(">Z")
.workplane(invert=True)
.move(0.0, self._length / 2.0)
.rect(self._width + 2.0, self._width + 2.0)
.extrude(self._thickness + 3.0))

# Chamfer the insertion end of the ziptie
self._fastener_model = (self._fastener_model.faces(">Y")
.edges(">X and |Z")
.chamfer(length=self._width / 4.0,
length2=self._width * 2.0))
self._fastener_model = (self._fastener_model.faces(">Y")
.edges("<X and |Z")
.chamfer(length=self._width / 4.0,
length2=self._width * 2.0))

# Add the slot in the head for insertion of the tail
self._fastener_model = (self._fastener_model.faces(">Z")
.workplane(invert=True)
.move(0.0, -(self._length / 2.0))
.rect(self._width, self._thickness)
.cutThruAll())

# Make sure assembly lines are present with each fastener
self._fastener_model.faces(self._face_selector).tag("assembly_line")

@property
def length(self):
Expand Down
Loading
Loading