diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml new file mode 100644 index 0000000..9804bab --- /dev/null +++ b/.github/workflows/test.yaml @@ -0,0 +1,37 @@ +name: Test + +on: [push] + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - name: "Check out the repo" + uses: actions/checkout@v3 + with: + submodules: true + + - name: Fix checkout ownership + run: | + # HACK Workaround for bug: + # https://github.com/actions/runner/issues/2033 + mkdir -p /home/runner/work/_temp/_github_home + printf "[safe]\n\tdirectory = /github/workspace" \ + > /home/runner/work/_temp/_github_home/.gitconfig + + - uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install system dependencies + run: | + sudo apt update && sudo apt install -y libgl1-mesa-glx + + - name: Install nimble packages and dev tools + run: | + pip install .[dev] + + - name: Run tests + run: | + pytest diff --git a/nimble_build_system/cad/device_placeholder.py b/nimble_build_system/cad/device_placeholder.py index be26448..3b0bdfb 100644 --- a/nimble_build_system/cad/device_placeholder.py +++ b/nimble_build_system/cad/device_placeholder.py @@ -1,10 +1,16 @@ +""" +This module provides a function to generate a placeholder object for a device. Given the dimensions +of a device, this function will create a placeholder object that can be used to represent the +device in an assembly. The function will also try to imprint the name of the device on the resulting +model. +""" import cadquery as cq def generate_placeholder(device_name, length, depth, height): """ Generates a generalized placeholder object for a device. """ - print(length, depth, height) + # Save the smallest dimension for things like filleting and text smallest_dim = min(length, depth, height) @@ -15,6 +21,8 @@ def generate_placeholder(device_name, length, depth, height): placeholder = placeholder.edges().fillet(smallest_dim * 0.3) # Add the text of what the device is to the top - placeholder = placeholder.faces(">Z").workplane(centerOption="CenterOfBoundBox").text(device_name, fontsize=6, distance=-smallest_dim * 0.1) + placeholder = (placeholder.faces(">Z") + .workplane(centerOption="CenterOfBoundBox") + .text(device_name, fontsize=6, distance=-smallest_dim * 0.1)) - return placeholder \ No newline at end of file + return placeholder diff --git a/nimble_build_system/cad/shelf.py b/nimble_build_system/cad/shelf.py index ff9c24a..de62242 100644 --- a/nimble_build_system/cad/shelf.py +++ b/nimble_build_system/cad/shelf.py @@ -1,3 +1,10 @@ +""" +This module generates shelves for the nimble rack system. The shelves are matched with devices, and +the shelves are generated based on the device type. The shelves are generated using the +`ShelfBuilder` class from the `nimble_build_system.cad.shelf_builder` module. + +Rendering and documentation generation is also supported for the shelves. +""" import os import posixpath import warnings @@ -21,7 +28,21 @@ def create_shelf_for(device_id: str, color: str='dodgerblue1', rack_params: RackParameters|None = None, dummy_device_data:dict|None=None): - + """ + Create a shelf for a device based on the device id. The shelf is generated based on the device + type and dimensions. + + Parameters: + device_id (str): The id of the device that the shelf is for. + assembly_key (str): The key for the assembled shelf. + position (tuple[float, float, float]): The position of the shelf in the rack. + color (str): The color of the shelf, useful for rendering. + rack_params (RackParameters): The parameters for the rack that this shelf will be in. + dummy_device_data (dict): Data used if the device is a dummy device. + + Returns: + Shelf: A shelf object for the device, instantiating the correct Class. + """ if not rack_params: rack_params = RackParameters() @@ -99,6 +120,9 @@ def __init__(self, @property def height_in_u(self): + """ + Return the height of the shelf in standard rack units/increments. + """ return self._device.height_in_u def _generate_assembled_shelf(self, @@ -141,7 +165,7 @@ def name(self): Return the name of the shelf. This is the same name as the component. """ - return self.assembled_shelf.name + return self._assembled_shelf.name @property @@ -178,6 +202,11 @@ def generate_device_model(self): self._device.width, self._device.depth, self._device.height) + + # Once the device model has been generated once, save it so that it can be reused in + # assemblies and such + self._device_model = device + return device @@ -206,15 +235,13 @@ def generate_shelf_stl(self, shelf_model=None): def generate_assembly_model(self, shelf_model=None, - device_model=None, - with_fasteners=True, - exploded=False, - annotated=False): + device_model=None): """ Generates an CAD model of the shelf assembly showing assembly step between a device and a shelf. This can be optionally be exploded. It is generated solely based on the device ID. """ + #pylint: disable=unused-argument return NotImplemented @@ -225,7 +252,7 @@ def get_render(self, assy, camera_pos, image_format="png"): # TODO - Use the PNG functionality in CadQuery to generate a PNG render # TODO - Maybe also need other formats such as glTF - + #pylint: disable=unused-argument return NotImplemented @@ -258,6 +285,9 @@ def generate_docs(self): return md class StuffShelf(Shelf): + """ + A generic shelf for devices that do not have a specific shelf type. + """ ##TODO: Perhaps make a "dummy" device for "stuff"? def __init__(self, device: Device, @@ -282,6 +312,9 @@ def generate_shelf_model(self) -> cadscript.Body: return builder.get_body() class NUCShelf(Shelf): + """ + Shelf class for an Intel NUC device. + """ def generate_shelf_model(self) -> cadscript.Body: """ A shelf for an Intel NUC @@ -296,6 +329,9 @@ def generate_shelf_model(self) -> cadscript.Body: return builder.get_body() class USWFlexShelf(Shelf): + """ + Shelf class for a Ubiquiti USW-Flex device. + """ def generate_shelf_model(self) -> cadscript.Body: """ A shelf for a Ubiquiti USW-Flex @@ -315,6 +351,9 @@ def generate_shelf_model(self) -> cadscript.Body: return builder.get_body() class USWFlexMiniShelf(Shelf): + """ + Shelf class for a Ubiquiti Flex Mini device. + """ def generate_shelf_model(self) -> cadscript.Body: """ A shelf for a for Ubiquiti Flex Mini @@ -342,7 +381,9 @@ def generate_shelf_model(self) -> cadscript.Body: return builder.get_body() class AnkerShelf(Shelf): - + """ + Shelf class for an Anker PowerPort 5, Anker 360 Charger 60W (a2123), etc + """ def __init__(self, device: Device, assembly_key: str, @@ -374,6 +415,9 @@ def generate_shelf_model(self) -> cadscript.Body: ) class HDD35Shelf(Shelf): + """ + Shelf class for a 3.5" hard drive device. + """ def generate_shelf_model(self) -> cadscript.Body: """ A shelf for an 3.5" HDD @@ -410,6 +454,9 @@ def generate_shelf_model(self) -> cadscript.Body: return builder.get_body() class DualSSDShelf(Shelf): + """ + Shelf class for two 2.5" solid state drive devices. + """ def generate_shelf_model(self) -> cadscript.Body: """ A shelf for two 2.5" SSDs @@ -494,10 +541,7 @@ def generate_shelf_model(self): def generate_assembly_model(self, shelf_model=None, - device_model=None, - with_fasteners=True, - exploded=False, - annotated=False): + device_model=None): """ Generates an assembly showing the assembly step between a device and a shelf, optionally with fasteners. diff --git a/py.pylintrc b/py.pylintrc index e8576c2..a554116 100644 --- a/py.pylintrc +++ b/py.pylintrc @@ -452,8 +452,7 @@ timeout-methods=requests.api.delete,requests.api.get,requests.api.head,requests. # List of note tags to take in consideration, separated by a comma. notes=FIXME, - XXX, - TODO + XXX # Regular expression of note tags to take in consideration. notes-rgx= diff --git a/setup.py b/setup.py index a336dbc..8c42598 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,8 @@ extras_require={ 'dev': [ 'pylint', - 'colorama' + 'colorama', + 'pytest' ] }, entry_points={'console_scripts': ['gen_nimble_conf_options = nimble_build_system.utils.gen_nimble_conf_options:main']} diff --git a/tests/test_cad.py b/tests/test_cad.py index e901b6d..5ccff9b 100644 --- a/tests/test_cad.py +++ b/tests/test_cad.py @@ -2,14 +2,95 @@ from nimble_build_system.cad.shelf import Shelf, RaspberryPiShelf from nimble_build_system.orchestration.configuration import NimbleConfiguration -def test_generating_raspberry_pi_shelf(): +def test_shelf_generation(): """ Tests whether or not the functionality for a Raspberry Pi shelf is working as expected. This tests all of the CAD related functionality related to the Raspberry Pi shelf. """ + # This is a list of the shelf models that have potential red flags. + # In the cases where the depth seems small compared to the device, it + # seems to be related to the generic/zip-tie shelf. This needs more + # investigation. + broken_shelves = [] + broken_shelves.append("mANTBox_2_12s") # Shelf depth seems very small compared to the device + broken_shelves.append("mANTBox_ax_15s") # Missing height in units + broken_shelves.append("NetBox_5_ax") # Missing height in units + broken_shelves.append("OmniTIK_5") # Shelf depth seems very small compared to the device + broken_shelves.append("OmniTIK_5_ac") # Shelf depth seems very small compared to the device + broken_shelves.append("OmniTIK_5_PoE_ac") # Shelf depth seems very small compared to the device + broken_shelves.append("AC_Lite") # Missing device dimensions + broken_shelves.append("AC_Mesh_Pro") # Shelf depth seems very small compared to the device + broken_shelves.append("AC_Pro") # Missing device dimensions + broken_shelves.append("U6_Lite") # Missing device dimensions + broken_shelves.append("U6_Long_Range") # Missing device dimensions + broken_shelves.append("U6_Mesh") # Missing device dimensions + broken_shelves.append("U6_Pro") # Missing device dimensions + broken_shelves.append("U6+") # Missing device dimensions + broken_shelves.append("UniFi_AC_Mesh") # Shelf depth seems very small compared to the device + broken_shelves.append("hAP_ac") # Shelf depth seems smaller than the device + broken_shelves.append("hAP_lite") # Missing device dimensions + broken_shelves.append("RB951Ui-2HnD") # Shelf depth seems smaller than the device + broken_shelves.append("Hex_PoE") # Shelf depth seems smaller than the device + broken_shelves.append("L009UiGS-RM") # Missing height in units + broken_shelves.append("RB4011iGS+RM") # Shelf depth seems smaller than the device + broken_shelves.append("RB5009UG+S+IN") # Missing height in units + broken_shelves.append("RB5009UPr+S+IN") # Missing height in units + broken_shelves.append("EQ12_N100") # Missing height in units + broken_shelves.append("SEi10_1035G7") # Shelf depth seems smaller than the device + broken_shelves.append("SEi12_i7-12650H") # Missing height in units + broken_shelves.append("SER5_PRO_5700U") # Missing height in units + broken_shelves.append("Gigabyte_Brix_GB-BEi5-1240_(rev._1.0)") # Shelf depth seems smaller than the device + broken_shelves.append("Gigabyte_Brix_GB-BRi5-10210(E)") # Shelf depth seems smaller than the device + broken_shelves.append("Gigabyte_Brix_GB-BRi5H-10210(E)") # Shelf depth seems smaller than the device + broken_shelves.append("Gigabyte_Brix_GB-BRi5HS-1335") # Shelf depth seems smaller than the device + broken_shelves.append("Gigabyte_Brix_GB-BSi5-1135G7_(rev._1.0)") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCenter_M700") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCentre_M70q") # Missing height in units + broken_shelves.append("ThinkCentre_M70s") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCentre_M75s") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCentre_M80s") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCentre_M90q") # Shelf depth seems smaller than the device + broken_shelves.append("ThinkCentre_M90s") # Shelf depth seems smaller than the device + broken_shelves.append("CRS106-1C-5S") # Shelf depth seems smaller than the device + broken_shelves.append("CRS112-8G-4S-IN") # Shelf depth seems smaller than the device + broken_shelves.append("CRS112-8P-4S-IN") # Shelf depth seems smaller than the device + broken_shelves.append("CRS305-1G-4S+IN") # Shelf depth is the same as the device depth, which seems too tight + broken_shelves.append("CRS310-1G-5S-4S+IN") # Shelf depth seems smaller than the device + broken_shelves.append("CRS310-8G+2S+IN") # Shelf depth seems smaller than the device + broken_shelves.append("CSS610-8G-2S+IN") # Shelf depth seems smaller than the device + broken_shelves.append("RB260GS") # Shelf depth seems smaller than the device + broken_shelves.append("RB260GSP") # Shelf depth seems smaller than the device + broken_shelves.append("Enterprise_8_PoE") # Shelf depth seems smaller than the device + broken_shelves.append("Lite_16_PoE") # Shelf depth seems smaller than the device + broken_shelves.append("Pro_8_PoE") # Shelf depth seems smaller than the device + # The configuration of hardware/shelves that we want to test against - test_config = ["Raspberry_Pi_4B"] + test_config = ["Raspberry_Pi_4B", + "Raspberry_Pi_5", + "hAP", + "hAP_ac_lite", + "UniFi_Express", + "Beryl_AX", + "Beryl", + "Slate_Plus", + "Hex", + "hEX_PoE_lite", + "hEX_S", + "LtAP_mini", + "LtAP_mini_LTE_kit", + "Gateway_Lite", + "MINI_S12_Pro", + "SEi12_i5-12450H", + "SER5_MAX_5800H", + "NUC10FNK_", + "NUC10i5FNH", + "NUC8I5BEH", + "NUC8i5BEK", + "UniFi_8_PoE_(Gen1)", + "Unifi_Flex_Mini", + "Unifi_Switch_Flex" + ] # Load the needed information to generate a Shelf object config = NimbleConfiguration(test_config) @@ -17,9 +98,33 @@ def test_generating_raspberry_pi_shelf(): # Make sure that each shelf can generate the proper files for i, shelf in enumerate(config.shelves): # Find the matching device for the shelf - device = config.devices[i] + device = shelf.device + + # Instantiate the shelf object and check to make sure it has a valid name + shelf = RaspberryPiShelf(device, + assembly_key=f"shelf_{i}", + position=(1.0, 0.0, 12.0), + color='deepskyblue1', + rack_params=config._rack_params) + assert shelf.name.lower().startswith(test_config[i].lower().split("_")[0]) + + # Check that the device model was generated with the proper dimensions per the configuration + device_model = shelf.generate_device_model() + x_size = device_model.val().BoundingBox().xmax - device_model.val().BoundingBox().xmin + y_size = device_model.val().BoundingBox().ymax - device_model.val().BoundingBox().ymin + z_size = device_model.val().BoundingBox().zmax - device_model.val().BoundingBox().zmin + assert x_size == pytest.approx(config.devices[i].width, 0.001) + assert y_size == pytest.approx(config.devices[i].depth, 0.001) + assert z_size == pytest.approx(config.devices[i].height, 0.001) - # Generate the right type of shelf based on the name - if "raspberry" in shelf.name.lower(): - shelf = RaspberryPiShelf(shelf, device) - assert shelf.name == "Raspberry Pi 4B shelf" + # Make sure the shelf model is valid and has generally the correct dimensions + shelf_model = shelf.generate_shelf_model().cq() + x_size = shelf_model.val().BoundingBox().xmax - shelf_model.val().BoundingBox().xmin + y_size = shelf_model.val().BoundingBox().ymax - shelf_model.val().BoundingBox().ymin + z_size = shelf_model.val().BoundingBox().zmax - shelf_model.val().BoundingBox().zmin + print(x_size, y_size, z_size) + print(config.devices[i].width, config.devices[i].depth, config.devices[i].height) + assert shelf_model.val().isValid() + assert x_size > config.devices[i].width + assert y_size > config.devices[i].depth + assert z_size > config.devices[i].height