From f9392e4ee5078599e533bc5f5d5eaaf2e1306ea0 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Mon, 17 Jun 2024 16:17:51 +0100 Subject: [PATCH 01/16] Most generic parts of orchestration split out into cadorchestrator --- generate.py | 2 +- generate_static.py | 4 +- .../assembly_def_generator.py | 54 ---- nimble_orchestration/components.py | 272 ------------------ nimble_orchestration/configuration.py | 64 +---- .../exsource_def_generator.py | 89 ------ nimble_orchestration/orchestration.py | 142 --------- nimble_orchestration/shelf.py | 75 +++++ nimble_orchestration/yaml_cleaner.py | 42 --- 9 files changed, 87 insertions(+), 657 deletions(-) delete mode 100644 nimble_orchestration/assembly_def_generator.py delete mode 100644 nimble_orchestration/components.py delete mode 100644 nimble_orchestration/exsource_def_generator.py delete mode 100644 nimble_orchestration/orchestration.py create mode 100644 nimble_orchestration/shelf.py delete mode 100644 nimble_orchestration/yaml_cleaner.py diff --git a/generate.py b/generate.py index 97fc2d6..6c81bc4 100755 --- a/generate.py +++ b/generate.py @@ -6,7 +6,7 @@ for a specific nimble configuration. """ -from nimble_orchestration.orchestration import OrchestrationRunner +from cadorchestrator.orchestration import OrchestrationRunner from nimble_orchestration.configuration import NimbleConfiguration def generate(selected_devices_ids): diff --git a/generate_static.py b/generate_static.py index 6bf13df..cef9b5f 100755 --- a/generate_static.py +++ b/generate_static.py @@ -8,8 +8,8 @@ """ import os -from nimble_orchestration.orchestration import OrchestrationRunner -from nimble_orchestration.components import GeneratedMechanicalComponent +from cadorchestrator.orchestration import OrchestrationRunner +from cadorchestrator.components import GeneratedMechanicalComponent from nimble_orchestration.paths import BUILD_DIR, REL_MECH_DIR def generate(): diff --git a/nimble_orchestration/assembly_def_generator.py b/nimble_orchestration/assembly_def_generator.py deleted file mode 100644 index d9d6ce4..0000000 --- a/nimble_orchestration/assembly_def_generator.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -This module contains a simple class for generating a file that stores the basic assembly -information. -""" - -import pathlib -import yaml -from nimble_orchestration import yaml_cleaner - -class AssemblyDefGenerator: - """ - Generate the assembly definition file. - """ - - def __init__(self): - self._parts = [] - - - def add_part( - self, - name: str, - step_file: str, - position, assembly_step: str | None = None, - color: str | tuple | None = None - ): - """ - Add a part to the assembly definition file. - """ - part = { - "name": name, - "step-file": step_file, - "position": position, - "assembly-step": assembly_step, - "color": color - } - self._parts.append(part) - - def get_step_files(self) -> list: - """ - Get a list of all step files. - """ - return [part["step-file"] for part in self._parts] - - def save(self, output_file: str | pathlib.Path): - """ - Save the assembly definition file. - """ - data = { - "assembly": { - "parts": self._parts - } - } - with open(output_file, "w", encoding="utf-8") as f: - yaml.dump(yaml_cleaner.clean(data), f, sort_keys=False) diff --git a/nimble_orchestration/components.py b/nimble_orchestration/components.py deleted file mode 100644 index f86bc7f..0000000 --- a/nimble_orchestration/components.py +++ /dev/null @@ -1,272 +0,0 @@ -""" -This components module contains classes for holding the information for components -in a nimble rack. - -`MechanicalComponent` is a base class and can also be used for generic components - that have no source code -`GeneratedMechanicalComponent` is a child class of `MechanicalComponent`, it contains - all the information for exsource to generate the CAD models for this component -`AssembledComponent` is a class that holds very basic assembly information for a given - `MechanicalComponent` -""" -import os -from copy import copy, deepcopy -import yaml -from nimble_orchestration.device import Device - -class MechanicalComponent: - """ - This is a generic class for any mechanical component. If it is a generic - component rather than a generated one then use this class, for generated - components use the child-class GeneratedMechanicalComponent - """ - - def __init__(self, key: str, name: str, description:str, output_files: list) -> None: - self._key = key - self._name = name - self._description = description - self._output_files = output_files - - @property - def key(self): - """Return the unique key identifying the component""" - return self._key - - @property - def name(self): - """Return the human readable name of the component""" - return self._name - - @property - def description(self): - """Return the description of the component""" - return self._description - - @property - def output_files(self): - """Return a copy of the list of output CAD files that represent the component""" - return copy(self._output_files) - - @property - def step_representation(self): - """ - Return the path to the STEP file that represents this part. Return None - if not defined - """ - for output_file in self.output_files: - if output_file.lower().endswith(('stp','step')): - return output_file - return None - - @property - def stl_representation(self): - """ - Return the path to the STL file that represents this part. Return None - if not defined. - """ - for output_file in self.output_files: - if output_file.lower().endswith('stl'): - return output_file - return None - -class GeneratedMechanicalComponent(MechanicalComponent): - - """ - This is a class for a mechanical component that needs to be generated from - source files. - """ - - def __init__( - self, - key: str, - name: str, - description: str, - output_files: list, - source_files: list, - parameters: dict, - application: str - ) -> None: - - super().__init__(key, name, description, output_files) - self._source_files = source_files - self._parameters = parameters - self._application = application - - def __eq__(self, other): - if isinstance(other, str): - return self.key==str - if isinstance(other, GeneratedMechanicalComponent): - return self.as_exsource_dict == other.as_exsource_dict - return NotImplemented - - @property - def source_files(self): - """Return a copy of the list of the input CAD files that represent the component""" - return copy(self._source_files) - - @property - def parameters(self): - """Return the parameters associated with generating this mechancial component""" - return deepcopy(self._parameters) - - @property - def application(self): - """Return the name of the application used to process the input CAD files""" - return self._application - - @property - def as_exsource_dict(self): - """Return this object as a dictionary of the part information for exsource""" - return { - "key": self.key, - "name": self.name, - "description": self.description, - "output_files": self.output_files, - "source_files": self.source_files, - "parameters": self.parameters, - "application": self.application - } - -class AssembledComponent: - """ - A class for an assembled component. This includes its position in the model. - An assembled components cannot yet be nested to create true assemblies - """ - - def __init__(self, - key: str, - component: MechanicalComponent, - position: tuple, - step: int, - color: tuple | str = None): - self._key = key - self._component = component - self._position = position - self._step = step - self._color = color - - @property - def name(self): - """ - Return the name of the assembled component. This is the same name as the - component that is to be assembled. - """ - return self._component.name - - @property - def stl_representation(self): - """ - Return the path to the STL file that represents this part. Return None - if not defined. - Note this is the STL in the original poisition not in the assembled position. - """ - return self._component.stl_representation - - @property - def key(self): - """ - A unique key to identify the assembled component. - """ - return self._key - - @property - def component(self): - """ - Return the Object describing the component that is being assembled - This is either a MechanicalComponent object or a child object such as - GeneratedMechanicalComponent. - """ - return self._component - - @property - def position(self): - """ - The position at which the component is assembled - """ - return self._position - - @property - def step(self): - """ - The assembly step in which this component is assembled. - """ - return self._step - - @property - def color(self): - """ - The color of this component. - """ - return self._color - - -class Shelf: - """ - A class for all the orchestration information relating to a shelf - """ - def __init__(self, - assembled_shelf: AssembledComponent, - device: Device): - self._assembled_shelf = assembled_shelf - self._device = device - - @property - def name(self): - """ - Return the name of the shelf. This is the same name as the - component. - """ - return self.assembled_shelf.name - - @property - def stl_representation(self): - """ - Return the path to the STL file that represents this shelf. Return None - if not defined. - Note this is the STL in the original poisition not in the assembled position. - """ - return self.assembled_shelf.stl_representation - - @property - def assembled_shelf(self): - """ - Return the Object describing the assembled shelf (currently this in an empty - shelf in the correct location on the rack). - This is an AssembledComponent - """ - return self._assembled_shelf - - @property - def device(self): - """ - Return the Device object for the networking component that sits on this shelf - """ - return self._device - - @property - def md(self): - """ - Return the markdown (BuildUp) for the GitBuilding page for assembling this shelf - """ - meta_data = { - "Tag": "shelf", - "Make": { - self.name: { - "template": "printing.md", - "stl-file": "../build/"+self.stl_representation, - "stlname": os.path.split(self.stl_representation)[1], - "material": "PLA", - "weight-qty": "50g", - } - } - } - md = f"---\n{yaml.dump(meta_data)}\n---\n\n" - md += f"# Assembling the {self.name}\n\n" - md += "{{BOM}}\n\n" - md += "## Position the "+self._device.name+" {pagestep}\n\n" - md += "* Take the ["+self.name+"]{make, qty:1, cat:printed} you printed earlier\n" - md += "* Position the ["+self._device.name+"]{qty:1, cat:net} on the shelf\n\n" - md += "## Secure the "+self._device.name+" {pagestep}\n\n" - md += ">!! **TODO** \n>!! Need information on how the item is secured to the shelf." - - return md diff --git a/nimble_orchestration/configuration.py b/nimble_orchestration/configuration.py index 92b9d1a..b329fd4 100644 --- a/nimble_orchestration/configuration.py +++ b/nimble_orchestration/configuration.py @@ -9,27 +9,28 @@ import posixpath import json -from nimble_builder import RackParameters +from cadorchestrator.configuration import Configuration +from cadorchestrator.components import (GeneratedMechanicalComponent, + AssembledComponent) -from nimble_orchestration.assembly_def_generator import AssemblyDefGenerator -from nimble_orchestration.components import (GeneratedMechanicalComponent, - AssembledComponent, - Shelf) +from nimble_builder import RackParameters +from nimble_orchestration.shelf import Shelf from nimble_orchestration.device import Device from nimble_orchestration.paths import MODULE_PATH, REL_MECH_DIR -class NimbleConfiguration: +class NimbleConfiguration(Configuration): """ This class represents a specific nimble configuration """ _rack_params: RackParameters _devices: list _shelves: list - _components: list - _assembled_components: list + def __init__(self, selected_devices_ids): + super().__init__() + self._rack_params = RackParameters() devices_filename = os.path.join(MODULE_PATH, "devices.json") @@ -45,11 +46,6 @@ def find_device(device_id): self._devices = deepcopy(selected_devices) self._shelves = self._generate_shelf_list self._assembled_components = self._generate_assembled_components_list() - self._components = [] - for assembled_component in self._assembled_components: - component = assembled_component.component - if component not in self._components: - self._components.append(component) @property @@ -60,27 +56,6 @@ def devices(self): #Deepcopy to avoid them being edited in place return deepcopy(self._devices) - @property - def components(self): - """ - Return a list of the mechanical components used in this nimble configuration. - There is only one per each type of object, to see all objects for assembly see - `assembled_components()` - - Each object in the list is and instance of GeneratedMechanicalComponent - """ - return deepcopy(self._components) - - @property - def assembled_components(self): - """ - Return a list of the components assembled in this nimble rack. - - Each object in the list is and instance of AssembledComponent, giving information - such as the position and the assembly step. To just see the list of componet types - see `components()` - """ - return deepcopy(self._assembled_components) @property def shelves(self): @@ -252,24 +227,3 @@ def _generate_shelf_list(self): height_in_u += device.height_in_u return shelves - @property - def assembly_definition(self): - """ - Create an assembly defition. This is a simple object reprenting a file - that is output. The file only contains the name, step-file, position, and - assembly step. It is sufficent for creating the final rack, but too - simplistic for generating assembly renders - """ - - assembly = AssemblyDefGenerator() - - for assembled_component in self.assembled_components: - assembly.add_part( - name=assembled_component.key, - step_file=assembled_component.component.step_representation, - position=assembled_component.position, - assembly_step=str(assembled_component.step), - color=assembled_component.color - ) - - return assembly diff --git a/nimble_orchestration/exsource_def_generator.py b/nimble_orchestration/exsource_def_generator.py deleted file mode 100644 index a527679..0000000 --- a/nimble_orchestration/exsource_def_generator.py +++ /dev/null @@ -1,89 +0,0 @@ -""" -Helper class for generating the exsource definition file. -Structure is like this: - -exports: - hull: - name: hull - description: > - Main hull of the AUV, made from aluminum tubing. - output-files: - - ./docs/manufacturing_files/generated/hull_right_side_view.dxf - source-files: - - ./mech/components/auv_hull.py - parameters: - hull_diameter: 88.9 - hull_wall_thickness: 3.0 - hull_length: 520.0 - application: cadquery - assembly: - name: assembly - description: > - Entire AUV assembly - output-files: - - ./docs/images/generated/baby_auv_assembly.gltf - source-files: - - ./mech/baby_auv.py - parameters: - use_conductivity_sensor: True # Needs to be global - hull_length: 520.0 # mm - Needs to be global - exploded: False - application: cadquery - app-options: - - --hardwarnings - dependencies: - - ./mech/components/auv_hull.step - - ./mech/components/auv_hull.step - - -""" -import pathlib -import yaml - -from nimble_orchestration import yaml_cleaner - - -class ExsourceDefGenerator: - """ - Generate the exsource definition file. - """ - - def __init__(self) -> None: - self._parts = {} - - def add_part(self, - key: str, - name: str, - description: str, - output_files: list, - source_files: list, - parameters: dict, - application: str, - dependencies: list | None = None): - """ - Add a part to the exsource definition file. - """ - - part = { - "name": name, - "description": description, - "output-files": output_files, - "source-files": source_files, - "parameters": parameters, - "application": application, - "dependencies": dependencies - } - if key in self._parts: - print(f"Part with id: {key} already specified in exsource. Skipping!") - self._parts[key] = part - - - def save(self, output_file: str | pathlib.Path): - """ - Save the exsource definition file. - """ - data = { - "exports": self._parts - } - with open(output_file, "w", encoding="utf-8") as f: - yaml.dump(yaml_cleaner.clean(data), f, sort_keys=False) diff --git a/nimble_orchestration/orchestration.py b/nimble_orchestration/orchestration.py deleted file mode 100644 index fd4deee..0000000 --- a/nimble_orchestration/orchestration.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -This orchestration moule contains the main runner class `OrchestrationRunner`. -This should be used to execute the orchestration script to generate models -and assemblies. -To generate for a sepcific nimble configuration see the `cofiguration` module -To generate from a list of components see the `components` module -""" - -import os -import shutil -import subprocess - -import exsource_tools -import exsource_tools.cli -import exsource_tools.tools - -from nimble_orchestration.exsource_def_generator import ExsourceDefGenerator -from nimble_orchestration.paths import BUILD_DIR, REL_MECH_DIR, DOCS_DIR, DOCS_TMP_DIR - -class OrchestrationRunner: - """ - This class is used to generate all the files needed to build and document - any nimble configuration - This includes: - Preparing the build environment, - generating the components - generating assembly renders - generate gitbuilding files - """ - - - def __init__(self) -> None: - """ - Set up build environment. - """ - if not os.path.exists(BUILD_DIR): - os.makedirs(BUILD_DIR) - - def generate_components(self, components): - """ - generate the rack components - """ - exsource = ExsourceDefGenerator() - exsource_path = os.path.join(BUILD_DIR, "component-exsource-def.yaml") - for component in components: - exsource.add_part(**component.as_exsource_dict) - exsource.save(exsource_path) - self._run_exsource(exsource_path) - - - def generate_assembly(self, assembly_definition): - """ - A bit of a mish mash of a method that saves the assembly definition - file, then creates an exsource for a model of the final assembly, finally - running exsource. - This does not create renders of the assembly steps - """ - # save assembly definition - assembly_definition.save(os.path.join(BUILD_DIR, "assembly-def.yaml")) - - # add assembly to exsource - exsource = ExsourceDefGenerator() - exsource_path = os.path.join(BUILD_DIR, "assembly-exsource-def.yaml") - exsource.add_part( - key="assembly", - name="assembly", - description="assembly", - output_files=[ - "./assembly/assembly.stl", - "./assembly/assembly.step", - "./assembly/assembly.glb", - ], - source_files=[os.path.join(REL_MECH_DIR, "assembly_renderer.py")], - parameters={ - "assembly_definition_file": "assembly-def.yaml", - }, - application="cadquery", - dependencies=assembly_definition.get_step_files() - ) - - # save exsource definition - exsource.save(exsource_path) - self._run_exsource(exsource_path) - - def generate_docs(self, configuration): - """ - Run GitBuilding to generate documentation - """ - if os.path.exists(DOCS_TMP_DIR): - shutil.rmtree(DOCS_TMP_DIR) - shutil.copytree(DOCS_DIR, DOCS_TMP_DIR) - - sehlves_md = "* Insert the shelves into the rack in the following order " - sehlves_md += "(from top to bottom)\n" - - for shelf in configuration.shelves: - md_file = f"{shelf.device.id}_shelf.md" - filename = os.path.join(DOCS_TMP_DIR, md_file) - with open(filename, 'w', encoding="utf-8") as gb_file: - gb_file.write(shelf.md) - sehlves_md += "[Assembled "+shelf.name+"]("+md_file+"){make, qty:1, cat: prev}\n" - - sehlves_md += "* Secure each in place with four [M4x10mm cap screws]{qty:" - sehlves_md += str(2*len(configuration.shelves))+", cat:mech}\n\n" - - filename = os.path.join(DOCS_TMP_DIR, "insert_shelves.md") - with open(filename, 'w', encoding="utf-8") as gb_file: - gb_file.write(sehlves_md) - - - # Here we really need to be listing the assembly steps differently if the - # shelves are broad. - - self._run_gitbuilding() - - def _run_gitbuilding(self): - cur_dir = os.getcwd() - os.chdir(DOCS_TMP_DIR) - subprocess.run( - ['gitbuilding', 'build-html'], - check=True, - capture_output=True - ) - os.chdir(cur_dir) - tmp_built_docs = os.path.join(DOCS_TMP_DIR, "_site") - built_docs = os.path.join(BUILD_DIR, "assembly-docs") - if os.path.exists(built_docs): - shutil.rmtree(built_docs) - shutil.copytree(tmp_built_docs, built_docs) - - def _run_exsource(self, exsource_path): - - cur_dir = os.getcwd() - # change into self._build_dir - exsource_dir, exsource_filename = os.path.split(exsource_path) - os.chdir(exsource_dir) - - # run exsource-make - exsource_def = exsource_tools.cli.load_exsource_file(exsource_filename) - processor = exsource_tools.tools.ExSourceProcessor(exsource_def, None, None) - processor.make() - os.chdir(cur_dir) diff --git a/nimble_orchestration/shelf.py b/nimble_orchestration/shelf.py new file mode 100644 index 0000000..8b678d1 --- /dev/null +++ b/nimble_orchestration/shelf.py @@ -0,0 +1,75 @@ +import os +import yaml +from cadorchestrator.components import AssembledComponent +from nimble_orchestration.device import Device + +class Shelf: + """ + A class for all the orchestration information relating to a shelf + """ + def __init__(self, + assembled_shelf: AssembledComponent, + device: Device): + self._assembled_shelf = assembled_shelf + self._device = device + + @property + def name(self): + """ + Return the name of the shelf. This is the same name as the + component. + """ + return self.assembled_shelf.name + + @property + def stl_representation(self): + """ + Return the path to the STL file that represents this shelf. Return None + if not defined. + Note this is the STL in the original poisition not in the assembled position. + """ + return self.assembled_shelf.stl_representation + + @property + def assembled_shelf(self): + """ + Return the Object describing the assembled shelf (currently this in an empty + shelf in the correct location on the rack). + This is an AssembledComponent + """ + return self._assembled_shelf + + @property + def device(self): + """ + Return the Device object for the networking component that sits on this shelf + """ + return self._device + + @property + def md(self): + """ + Return the markdown (BuildUp) for the GitBuilding page for assembling this shelf + """ + meta_data = { + "Tag": "shelf", + "Make": { + self.name: { + "template": "printing.md", + "stl-file": "../build/"+self.stl_representation, + "stlname": os.path.split(self.stl_representation)[1], + "material": "PLA", + "weight-qty": "50g", + } + } + } + md = f"---\n{yaml.dump(meta_data)}\n---\n\n" + md += f"# Assembling the {self.name}\n\n" + md += "{{BOM}}\n\n" + md += "## Position the "+self._device.name+" {pagestep}\n\n" + md += "* Take the ["+self.name+"]{make, qty:1, cat:printed} you printed earlier\n" + md += "* Position the ["+self._device.name+"]{qty:1, cat:net} on the shelf\n\n" + md += "## Secure the "+self._device.name+" {pagestep}\n\n" + md += ">!! **TODO** \n>!! Need information on how the item is secured to the shelf." + + return md diff --git a/nimble_orchestration/yaml_cleaner.py b/nimble_orchestration/yaml_cleaner.py deleted file mode 100644 index b2b2366..0000000 --- a/nimble_orchestration/yaml_cleaner.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -A module containting functions to clean up dictionaries before writing to yaml file. -""" - -import pathlib - - -def clean(data: dict) -> dict: - """ - 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. - """ - # 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 - - -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 From e360b120129500c74c0501f653cfcbf182a30a64 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 19 Jun 2024 15:20:59 +0100 Subject: [PATCH 02/16] server split out into cadorchestrator --- server/README.md | 39 --- server/configurator.html | 65 ---- server/dockerfile | 0 server/nimble_server.py | 341 --------------------- server/requirements.txt | 9 - server/static/css/custom.css | 13 - server/static/css/normalize.css | 427 --------------------------- server/static/css/skeleton.css | 418 -------------------------- server/static/images/favicon.png | Bin 1156 -> 0 bytes server/static/images/wakoma-logo.png | Bin 9706 -> 0 bytes server/static/index.html | 46 --- server/static/js/index.js | 268 ----------------- 12 files changed, 1626 deletions(-) delete mode 100644 server/README.md delete mode 100644 server/configurator.html delete mode 100644 server/dockerfile delete mode 100644 server/nimble_server.py delete mode 100644 server/requirements.txt delete mode 100644 server/static/css/custom.css delete mode 100644 server/static/css/normalize.css delete mode 100644 server/static/css/skeleton.css delete mode 100644 server/static/images/favicon.png delete mode 100644 server/static/images/wakoma-logo.png delete mode 100644 server/static/index.html delete mode 100644 server/static/js/index.js diff --git a/server/README.md b/server/README.md deleted file mode 100644 index 19bbcea..0000000 --- a/server/README.md +++ /dev/null @@ -1,39 +0,0 @@ -# Nimble Back End Server - -Back end server for Nimble documentation generation. - -## Dependencies - -Run the following in a Python virtual environment to install the dependencies. - -``` -pip install -r requirements.txt -``` - -## Local Usage - -Run the following to run the server locally. Notice that the `PYTHONPATH` variable needs to be set so that imports will work correctly. - -``` -PYTHONPATH=/path/to/nimble/root/directory uvicorn nimble_server:app --reload -``` - -The server will spin up a local web page used for testing of the data flow. - -`http://127.0.0.1:8000/wakoma/nimble/configurator` - -The server can also be tested with curl, and an example is given below. - -``` -curl -X GET \ - -H "Content-type: application/json" \ - -H "Accept: application/json" \ - -d '{"config":{"server_1": "raspberry_pi_4_model_b", "router_1": "librerouter_v_1_india_version"}}' \ - "http://127.0.0.1:8000/wakoma/nimble" -``` - -It is also possible to request individual parts without requesting the whole assembly and its associated documents. For example, to download just the rack leg the following URL is entered into the address bar of a web browser. The parameters `length` and `model_format` can be altered in the URL as-needed. - -`http://127.0.0.1:8000/wakoma/nimble/rack_leg?length=50.0&model_format=stl` - -The customized rack leg should be downloaded in STL format after a short delay during generation. diff --git a/server/configurator.html b/server/configurator.html deleted file mode 100644 index 1d6adcb..0000000 --- a/server/configurator.html +++ /dev/null @@ -1,65 +0,0 @@ - - - - - Nimble Configuration App - - - - - - - - - - - -
-
-
-
- - Wakoma Logo -

Wakoma Nimble Configurator

-

Select Components for Your Nimble:

- -
-
-
-
-
-
- -
-
- -
-
-
-
- -
-
- -
-
-
-
-
-
-
- -
-
- -
-
-
-
- -
-
- - - - diff --git a/server/dockerfile b/server/dockerfile deleted file mode 100644 index e69de29..0000000 diff --git a/server/nimble_server.py b/server/nimble_server.py deleted file mode 100644 index cbc46d7..0000000 --- a/server/nimble_server.py +++ /dev/null @@ -1,341 +0,0 @@ -import os, sys -import re -import json -import tempfile -from pathlib import Path -import shutil -from distutils.dir_util import copy_tree - -from fastapi import FastAPI, Request -from fastapi.responses import FileResponse, HTMLResponse, ORJSONResponse -from fastapi.staticfiles import StaticFiles - -# Change this to the base URL of the server that is serving this app -server_base_url = "http://127.0.0.1:8000" - -# Used to keep track of the configurations that are being, or have been, generated -generated_docs_urls = {} - -# Allow imports from the parent directory (potential security issue) -sys.path.append(os.path.dirname(__file__) + "/..") - -from generate import generate - -app = FastAPI() - -# Static files for the test page -app.mount("/static", StaticFiles(directory="static"), name="static") - - -# http://127.0.0.1:8000/wakoma/nimble/configurator -@app.get("/wakoma/nimble/configurator") -async def read_index(): - """ - Loads a sample page so that the system can be tested end-to-end. - """ - - return FileResponse('configurator.html') - - -def get_unique_name(config): - """ - Allows the caller to get a unique name for a configuration. - """ - - # Concatenate all the component names together - unique_name = "!".join(config) - - # Replace spaces with underscores - unique_name = unique_name.replace(" ", "_") - - # Delete all non-alphanumeric characters (other than _) - unique_name = re.sub(r'[^a-zA-Z0-9_!]', '', unique_name) - - return unique_name - - -# http://127.0.0.1:8000/wakoma/nimble -@app.post("/wakoma/nimble") -async def get_body(request: Request): - """ - Allows the caller to request documentation to be generated for the - specific configuration that they pass. - """ - - # TODO: Need to define at least a temporary data structure for this based on what the orchestration script needs to see - req = await request.json() - config = req['config'] - - # Make sure that a config was passed - if (len(config) == 0): - print("Nothing to build") - return ORJSONResponse([{"error": "Configuration must have components in it."}], status_code=500) - - print("Starting build for config:") - print(config) - - # Create a clean, unique name for this build - unique_name = get_unique_name(config) - - # Trigger the generation of all materials, but only if they do not already exist - module_path = Path(__file__).resolve().parent.parent - if not os.path.exists(module_path / "_cache_" / f"{unique_name}.zip"): - # Call all the underlying Nimble generator code - generate(config) - - # Create the zip file - shutil.make_archive(str(module_path / "_cache_" / unique_name), 'zip', os.path.join(module_path, "build")) - - # Make a copy of the glTF preview file to cache it - shutil.copyfile(str(module_path / "build" / "assembly" / "assembly.glb"), str(module_path / "_cache_" / f"{unique_name}.glb")) - - # Make a cached copy of the assembly docs so that they can be served to the user - copy_tree(str(module_path / "build" / "assembly-docs"), str(module_path / "server" / "static" / "builds" / f"{unique_name}_assembly_docs")) - - # This commented code is left in as a placeholder for a different way of serving the cached assembly-docs - # directories dynamically, instead of copying it wholesale into the server's static directory - # app.mount(str(module_path / "_cache_" / f"{unique_name}-assembly-docs"), StaticFiles(directory="static"), name="static") - - # Check to make sure we have the _cache_ directory that holds the distribution files - if not os.path.isdir(str(module_path / "_cache_")): - os.makedirs(str(module_path / "_cache_")) - - # Save the fact that this has been generated before - config_url = server_base_url + "/wakoma/nimble/auto-doc?config=" + unique_name - - # Let the client know where they can download the file - return ORJSONResponse([{"redirect": config_url, "description": "Poll this URL until your documentation package is ready."}]) - - -def check_model_format(model_format): - """ - Checks to make sure the model_format provided by the caller is valid. - """ - if (model_format != 'stl' and model_format != 'amf' and model_format != '3mf' and model_format != 'step' and model_format != 'stp'): - return False - else: - return True - - -def cqgi_model_script(file_name, params): - """ - Handle executing the model script via CQGI. - """ - - from cadquery import cqgi - - # Read and execute the rack leg script - cq_path = os.path.join("..", "mechanical", "components", "cadquery", file_name) - user_script = "" - with open(cq_path) as f: - user_script = f.read() - - # Build the object with the customized parameters and get it ready to export - build_result = cqgi.parse(user_script).build(build_parameters=params) - if build_result.success: - res = build_result.results[0].shape - else: - return HTMLResponse(f'ERROR: There was an error executing the script: {build_result.exception}') - - return res - - -# http://127.0.0.1:8000/wakoma/nimble/components -@app.get("/wakoma/nimble/components") -async def get_body(request: Request): - # Load the devices.json file - with open('../devices.json', 'r', encoding='utf-8') as component_file: - component_data = json.load(component_file) - - return ORJSONResponse([component_data]) - - -# http://127.0.0.1:8000/wakoma/nimble/rack_leg?length=50.0&model_format=stl -@app.get("/wakoma/nimble/rack_leg") -def read_item(length: float = 294.0, hole_spacing: float = 14.0, long_axis_hole_dia: float = 4.6, mounting_holes_dia:float = 3.6, model_format: str = "stl"): - """ - Provides access to the individual parameterized model of a rack leg. - """ - # Check to make sure the user requested a valid model format - if not check_model_format(model_format): - return HTMLResponse(f'ERROR: Model format {model_format} is not supported. Please go back and try again.') - - # Check to make sure the user has not set a length that is too long - if length > 1000: - return HTMLResponse('ERROR: Length parameter is too large. Please check your parameters and try again.') - - # Make sure that the hole spacing is valid - if hole_spacing <= 0.0 or hole_spacing >= length or hole_spacing <= mounting_holes_dia: - return HTMLResponse('ERROR: Invalid hole_spacing parameter. Please check your parameters and try again.') - - # Make sure that the long axis hole diameter is valid - if long_axis_hole_dia <= 0.0 or long_axis_hole_dia >= 20.0: - return HTMLResponse('ERROR: Invalid long_axis_hole_dia parameter. Please check your parameters and try again.') - - # Make sure that the mounting hole diameter is valid - if mounting_holes_dia <= 0.0 or mounting_holes_dia >= 20.0: - return HTMLResponse('ERROR: Invalid mounting_holes_dia parameter. Please check your parameters and try again.') - - # Add the CadQuery model path so that all models only need to do absolute imports - cq_path = os.path.join("..", "mechanical", "components", "cadquery") - sys.path.append(cq_path) - - import cadquery as cq - - # Run the script with customized parameters - leg = cqgi_model_script("rack_leg.py", {'length': length, 'hole_spacing': hole_spacing, 'long_axis_hole_dia': long_axis_hole_dia, 'mounting_holes_dia': mounting_holes_dia}) - - # In case there was an error - if (type(leg).__name__ == "HTMLResponse"): - return leg - - # Figure out what the file name and temporary path should be - export_file_name = "rack_leg-leg_length-" + str(length) + "_hole_spacing-" + str(hole_spacing) + "_long_axis_hole_dia-" + str(long_axis_hole_dia) + "_mounting_holes_dia-" + str(mounting_holes_dia) + "." + model_format - export_path = os.path.join(tempfile.gettempdir(), export_file_name) - - # If the leg does not already exist, export it to a temporary file - if not os.path.exists(export_path): - cq.exporters.export(leg, export_path) - - return FileResponse(export_path, headers={'Content-Disposition': 'attachment; filename=' + export_file_name}) - - -# http://127.0.0.1:8000/wakoma/nimble/top_plate?width=100&depth=100&height=3&model_format=stl -@app.get("/wakoma/nimble/top_plate") -def read_item(width: float = 100, depth: float = 100, height: float = 3, model_format: str = "stl"): - # Check to make sure the user requested a valid model format - if not check_model_format(model_format): - return HTMLResponse(f'ERROR: Model format {model_format} is not supported. Please check your parameters and try again.') - - # Check to make sure all the model dimensions are positive - if width < 0 or depth < 0 or height < 0: - return HTMLResponse(f'ERROR: None of the model dimensions can be negative. Please check your parameters and try again.') - - # Add the CadQuery model path so that all models only need to do absolute imports - cq_path = os.path.join("..", "mechanical", "components", "cadquery") - sys.path.append(cq_path) - - import cadquery as cq - - # Run the script with customized parameters - plate = cqgi_model_script("top_plate.py", {'width': width, 'depth': depth, 'height': height}) - - # In case there was an error - if (type(plate).__name__ == "HTMLResponse"): - return plate - - # Figure out what the file name and temporary path should be - export_file_name = "top_plate_width-" + str(width) + "_depth-" + str(depth) + "_height-" + str(height) + "." + model_format - export_path = os.path.join(tempfile.gettempdir(), export_file_name) - - # If the leg does not already exist, export it to a temporary file - if not os.path.exists(export_path): - cq.exporters.export(plate, export_path) - - return FileResponse(export_path, headers={'Content-Disposition': 'attachment; filename=' + export_file_name}) - - -# http://127.0.0.1:8000/wakoma/nimble/tray?number_of_units=2&tray_width=115&tray_depth=115&model_format=stl -@app.get("/wakoma/nimble/tray") -def read_item(number_of_units: float = 2, tray_width: float = 115, tray_depth: float = 115, model_format: str = "stl"): - # Check to make sure the user requested a valid model format - if not check_model_format(model_format): - return HTMLResponse(f'ERROR: Model format {model_format} is not supported. Please check your parameters and try again.') - - # Check to make sure all the model dimensions are positive - if number_of_units < 0 or tray_width < 0 or tray_depth < 0: - return HTMLResponse(f'ERROR: None of the model dimensions can be negative. Please check your parameters and try again.') - - # Add the CadQuery model path so that all models only need to do absolute imports - cq_path = os.path.join("..", "mechanical", "components", "cadquery") - sys.path.append(cq_path) - - 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("tray_6in.py", {"height_in_u": number_of_units, "shelf_type": "generic"}) - - # In case there was an error - if (type(tray).__name__ == "HTMLResponse"): - return tray - - # Figure out what the file name and temporary path should be - export_file_name = "tray_number_of_units-" + str(number_of_units) + "_tray_width-" + str(tray_width) + "_tray_depth-" + str(tray_depth) + "." + model_format - export_path = os.path.join(tempfile.gettempdir(), export_file_name) - - # If the leg does not already exist, export it to a temporary file - if not os.path.exists(export_path): - cq.exporters.export(tray, export_path) - - return FileResponse(export_path, headers={'Content-Disposition': 'attachment; filename=' + export_file_name}) - - -# http://127.0.0.1:8000/wakoma/nimble/base_plate?width=100&depth=100&height=3&model_format=stl -@app.get("/wakoma/nimble/base_plate") -def read_item(width: float = 100, depth: float = 100, height: float = 3, model_format: str = "stl"): - # Check to make sure the user requested a valid model format - if not check_model_format(model_format): - return HTMLResponse(f'ERROR: Model format {model_format} is not supported. Please check your parameters and try again.') - - # Check to make sure all the model dimensions are positive - if width < 0 or depth < 0 or height < 0: - return HTMLResponse(f'ERROR: None of the model dimensions can be negative. Please check your parameters and try again.') - - # Add the CadQuery model path so that all models only need to do absolute imports - cq_path = os.path.join("..", "mechanical", "components", "cadquery") - sys.path.append(cq_path) - - import cadquery as cq - - # Run the script with customized parameters - base_plate = cqgi_model_script("base_plate.py", {"width": width, "depth": depth, "height": height}) - - # In case there was an error - if (type(base_plate).__name__ == "HTMLResponse"): - return base_plate - - # Figure out what the file name and temporary path should be - export_file_name = "base_plate_width-" + str(width) + "_depth-" + str(depth) + "_height-" + str(height) + "." + model_format - export_path = os.path.join(tempfile.gettempdir(), export_file_name) - - # If the leg does not already exist, export it to a temporary file - if not os.path.exists(export_path): - cq.exporters.export(base_plate, export_path) - - return FileResponse(export_path, headers={'Content-Disposition': 'attachment; filename=' + export_file_name}) - - -@app.get("/wakoma/nimble/auto-doc") -def get_files(config): - """ - Loads any auto-generated documentation files. - """ - - # Figure out what the build path is - module_path = Path(__file__).resolve().parent.parent - build_path = module_path / "_cache_" / config - - # Once the build exists we can send it to the user, but before that we give them a temporary redirect - if os.path.exists(str(build_path) + ".zip"): - return FileResponse(str(build_path) + ".zip", headers={'Content-Disposition': 'attachment; filename=' + config + ".zip"}) - else: - return HTMLResponse(content="

The File is Still Processing

", status_code=307) - - -@app.get("/wakoma/nimble/preview") -def get_preview(config): - """ - Sends a 3D preview (glTF) file to the client. - """ - - # Figure out what the build and glb path is - module_path = Path(__file__).resolve().parent.parent - glb_file_name = f"{config}.glb" - glb_path = module_path / "_cache_" / glb_file_name - - # If the glb file exists, send it to the client - if os.path.exists(glb_path): - return FileResponse(str(glb_path), headers={'Content-Disposition': 'attachment; filename=' + glb_file_name}) - else: - return HTMLResponse(content="

Preview is not available.

", status_code=500) diff --git a/server/requirements.txt b/server/requirements.txt deleted file mode 100644 index 7bed86f..0000000 --- a/server/requirements.txt +++ /dev/null @@ -1,9 +0,0 @@ -fastapi>=0.109.1 -uvicorn[standard]>=0.23.2,<0.24.0 -gunicorn -git+https://github.com/cadscriptHQ/cadscript.git@dev -orjson -exsource-tools==0.0.6 -gitbuilding==0.15.0a2 -git+https://github.com/jmwright/cq-annotate.git -git+https://github.com/CadQuery/cq-cli.git diff --git a/server/static/css/custom.css b/server/static/css/custom.css deleted file mode 100644 index d45deba..0000000 --- a/server/static/css/custom.css +++ /dev/null @@ -1,13 +0,0 @@ -.loader { - border: 16px solid #f3f3f3; - border-top: 16px solid #33c3f0; - border-radius: 50%; - width: 120px; - height: 120px; - animation: spin 2s linear infinite; - } - - @keyframes spin { - 0% { transform: rotate(0deg); } - 100% { transform: rotate(360deg); } - } \ No newline at end of file diff --git a/server/static/css/normalize.css b/server/static/css/normalize.css deleted file mode 100644 index 81c6f31..0000000 --- a/server/static/css/normalize.css +++ /dev/null @@ -1,427 +0,0 @@ -/*! normalize.css v3.0.2 | MIT License | git.io/normalize */ - -/** - * 1. Set default font family to sans-serif. - * 2. Prevent iOS text size adjust after orientation change, without disabling - * user zoom. - */ - -html { - font-family: sans-serif; /* 1 */ - -ms-text-size-adjust: 100%; /* 2 */ - -webkit-text-size-adjust: 100%; /* 2 */ -} - -/** - * Remove default margin. - */ - -body { - margin: 0; -} - -/* HTML5 display definitions - ========================================================================== */ - -/** - * Correct `block` display not defined for any HTML5 element in IE 8/9. - * Correct `block` display not defined for `details` or `summary` in IE 10/11 - * and Firefox. - * Correct `block` display not defined for `main` in IE 11. - */ - -article, -aside, -details, -figcaption, -figure, -footer, -header, -hgroup, -main, -menu, -nav, -section, -summary { - display: block; -} - -/** - * 1. Correct `inline-block` display not defined in IE 8/9. - * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera. - */ - -audio, -canvas, -progress, -video { - display: inline-block; /* 1 */ - vertical-align: baseline; /* 2 */ -} - -/** - * Prevent modern browsers from displaying `audio` without controls. - * Remove excess height in iOS 5 devices. - */ - -audio:not([controls]) { - display: none; - height: 0; -} - -/** - * Address `[hidden]` styling not present in IE 8/9/10. - * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22. - */ - -[hidden], -template { - display: none; -} - -/* Links - ========================================================================== */ - -/** - * Remove the gray background color from active links in IE 10. - */ - -a { - background-color: transparent; -} - -/** - * Improve readability when focused and also mouse hovered in all browsers. - */ - -a:active, -a:hover { - outline: 0; -} - -/* Text-level semantics - ========================================================================== */ - -/** - * Address styling not present in IE 8/9/10/11, Safari, and Chrome. - */ - -abbr[title] { - border-bottom: 1px dotted; -} - -/** - * Address style set to `bolder` in Firefox 4+, Safari, and Chrome. - */ - -b, -strong { - font-weight: bold; -} - -/** - * Address styling not present in Safari and Chrome. - */ - -dfn { - font-style: italic; -} - -/** - * Address variable `h1` font-size and margin within `section` and `article` - * contexts in Firefox 4+, Safari, and Chrome. - */ - -h1 { - font-size: 2em; - margin: 0.67em 0; -} - -/** - * Address styling not present in IE 8/9. - */ - -mark { - background: #ff0; - color: #000; -} - -/** - * Address inconsistent and variable font size in all browsers. - */ - -small { - font-size: 80%; -} - -/** - * Prevent `sub` and `sup` affecting `line-height` in all browsers. - */ - -sub, -sup { - font-size: 75%; - line-height: 0; - position: relative; - vertical-align: baseline; -} - -sup { - top: -0.5em; -} - -sub { - bottom: -0.25em; -} - -/* Embedded content - ========================================================================== */ - -/** - * Remove border when inside `a` element in IE 8/9/10. - */ - -img { - border: 0; -} - -/** - * Correct overflow not hidden in IE 9/10/11. - */ - -svg:not(:root) { - overflow: hidden; -} - -/* Grouping content - ========================================================================== */ - -/** - * Address margin not present in IE 8/9 and Safari. - */ - -figure { - margin: 1em 40px; -} - -/** - * Address differences between Firefox and other browsers. - */ - -hr { - -moz-box-sizing: content-box; - box-sizing: content-box; - height: 0; -} - -/** - * Contain overflow in all browsers. - */ - -pre { - overflow: auto; -} - -/** - * Address odd `em`-unit font size rendering in all browsers. - */ - -code, -kbd, -pre, -samp { - font-family: monospace, monospace; - font-size: 1em; -} - -/* Forms - ========================================================================== */ - -/** - * Known limitation: by default, Chrome and Safari on OS X allow very limited - * styling of `select`, unless a `border` property is set. - */ - -/** - * 1. Correct color not being inherited. - * Known issue: affects color of disabled elements. - * 2. Correct font properties not being inherited. - * 3. Address margins set differently in Firefox 4+, Safari, and Chrome. - */ - -button, -input, -optgroup, -select, -textarea { - color: inherit; /* 1 */ - font: inherit; /* 2 */ - margin: 0; /* 3 */ -} - -/** - * Address `overflow` set to `hidden` in IE 8/9/10/11. - */ - -button { - overflow: visible; -} - -/** - * Address inconsistent `text-transform` inheritance for `button` and `select`. - * All other form control elements do not inherit `text-transform` values. - * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera. - * Correct `select` style inheritance in Firefox. - */ - -button, -select { - text-transform: none; -} - -/** - * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio` - * and `video` controls. - * 2. Correct inability to style clickable `input` types in iOS. - * 3. Improve usability and consistency of cursor style between image-type - * `input` and others. - */ - -button, -html input[type="button"], /* 1 */ -input[type="reset"], -input[type="submit"] { - -webkit-appearance: button; /* 2 */ - cursor: pointer; /* 3 */ -} - -/** - * Re-set default cursor for disabled elements. - */ - -button[disabled], -html input[disabled] { - cursor: default; -} - -/** - * Remove inner padding and border in Firefox 4+. - */ - -button::-moz-focus-inner, -input::-moz-focus-inner { - border: 0; - padding: 0; -} - -/** - * Address Firefox 4+ setting `line-height` on `input` using `!important` in - * the UA stylesheet. - */ - -input { - line-height: normal; -} - -/** - * It's recommended that you don't attempt to style these elements. - * Firefox's implementation doesn't respect box-sizing, padding, or width. - * - * 1. Address box sizing set to `content-box` in IE 8/9/10. - * 2. Remove excess padding in IE 8/9/10. - */ - -input[type="checkbox"], -input[type="radio"] { - box-sizing: border-box; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Fix the cursor style for Chrome's increment/decrement buttons. For certain - * `font-size` values of the `input`, it causes the cursor style of the - * decrement button to change from `default` to `text`. - */ - -input[type="number"]::-webkit-inner-spin-button, -input[type="number"]::-webkit-outer-spin-button { - height: auto; -} - -/** - * 1. Address `appearance` set to `searchfield` in Safari and Chrome. - * 2. Address `box-sizing` set to `border-box` in Safari and Chrome - * (include `-moz` to future-proof). - */ - -input[type="search"] { - -webkit-appearance: textfield; /* 1 */ - -moz-box-sizing: content-box; - -webkit-box-sizing: content-box; /* 2 */ - box-sizing: content-box; -} - -/** - * Remove inner padding and search cancel button in Safari and Chrome on OS X. - * Safari (but not Chrome) clips the cancel button when the search input has - * padding (and `textfield` appearance). - */ - -input[type="search"]::-webkit-search-cancel-button, -input[type="search"]::-webkit-search-decoration { - -webkit-appearance: none; -} - -/** - * Define consistent border, margin, and padding. - */ - -fieldset { - border: 1px solid #c0c0c0; - margin: 0 2px; - padding: 0.35em 0.625em 0.75em; -} - -/** - * 1. Correct `color` not being inherited in IE 8/9/10/11. - * 2. Remove padding so people aren't caught out if they zero out fieldsets. - */ - -legend { - border: 0; /* 1 */ - padding: 0; /* 2 */ -} - -/** - * Remove default vertical scrollbar in IE 8/9/10/11. - */ - -textarea { - overflow: auto; -} - -/** - * Don't inherit the `font-weight` (applied by a rule above). - * NOTE: the default cannot safely be changed in Chrome and Safari on OS X. - */ - -optgroup { - font-weight: bold; -} - -/* Tables - ========================================================================== */ - -/** - * Remove most spacing between table cells. - */ - -table { - border-collapse: collapse; - border-spacing: 0; -} - -td, -th { - padding: 0; -} \ No newline at end of file diff --git a/server/static/css/skeleton.css b/server/static/css/skeleton.css deleted file mode 100644 index f28bf6c..0000000 --- a/server/static/css/skeleton.css +++ /dev/null @@ -1,418 +0,0 @@ -/* -* Skeleton V2.0.4 -* Copyright 2014, Dave Gamache -* www.getskeleton.com -* Free to use under the MIT license. -* http://www.opensource.org/licenses/mit-license.php -* 12/29/2014 -*/ - - -/* Table of contents -–––––––––––––––––––––––––––––––––––––––––––––––––– -- Grid -- Base Styles -- Typography -- Links -- Buttons -- Forms -- Lists -- Code -- Tables -- Spacing -- Utilities -- Clearing -- Media Queries -*/ - - -/* Grid -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.container { - position: relative; - width: 100%; - max-width: 960px; - margin: 0 auto; - padding: 0 20px; - box-sizing: border-box; } -.column, -.columns { - width: 100%; - float: left; - box-sizing: border-box; } - -/* For devices larger than 400px */ -@media (min-width: 400px) { - .container { - width: 85%; - padding: 0; } -} - -/* For devices larger than 550px */ -@media (min-width: 550px) { - .container { - width: 80%; } - .column, - .columns { - margin-left: 4%; } - .column:first-child, - .columns:first-child { - margin-left: 0; } - - .one.column, - .one.columns { width: 4.66666666667%; } - .two.columns { width: 13.3333333333%; } - .three.columns { width: 22%; } - .four.columns { width: 30.6666666667%; } - .five.columns { width: 39.3333333333%; } - .six.columns { width: 48%; } - .seven.columns { width: 56.6666666667%; } - .eight.columns { width: 65.3333333333%; } - .nine.columns { width: 74.0%; } - .ten.columns { width: 82.6666666667%; } - .eleven.columns { width: 91.3333333333%; } - .twelve.columns { width: 100%; margin-left: 0; } - - .one-third.column { width: 30.6666666667%; } - .two-thirds.column { width: 65.3333333333%; } - - .one-half.column { width: 48%; } - - /* Offsets */ - .offset-by-one.column, - .offset-by-one.columns { margin-left: 8.66666666667%; } - .offset-by-two.column, - .offset-by-two.columns { margin-left: 17.3333333333%; } - .offset-by-three.column, - .offset-by-three.columns { margin-left: 26%; } - .offset-by-four.column, - .offset-by-four.columns { margin-left: 34.6666666667%; } - .offset-by-five.column, - .offset-by-five.columns { margin-left: 43.3333333333%; } - .offset-by-six.column, - .offset-by-six.columns { margin-left: 52%; } - .offset-by-seven.column, - .offset-by-seven.columns { margin-left: 60.6666666667%; } - .offset-by-eight.column, - .offset-by-eight.columns { margin-left: 69.3333333333%; } - .offset-by-nine.column, - .offset-by-nine.columns { margin-left: 78.0%; } - .offset-by-ten.column, - .offset-by-ten.columns { margin-left: 86.6666666667%; } - .offset-by-eleven.column, - .offset-by-eleven.columns { margin-left: 95.3333333333%; } - - .offset-by-one-third.column, - .offset-by-one-third.columns { margin-left: 34.6666666667%; } - .offset-by-two-thirds.column, - .offset-by-two-thirds.columns { margin-left: 69.3333333333%; } - - .offset-by-one-half.column, - .offset-by-one-half.columns { margin-left: 52%; } - -} - - -/* Base Styles -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -/* NOTE -html is set to 62.5% so that all the REM measurements throughout Skeleton -are based on 10px sizing. So basically 1.5rem = 15px :) */ -html { - font-size: 62.5%; } -body { - font-size: 1.5em; /* currently ems cause chrome bug misinterpreting rems on body element */ - line-height: 1.6; - font-weight: 400; - font-family: "Raleway", "HelveticaNeue", "Helvetica Neue", Helvetica, Arial, sans-serif; - color: #222; } - - -/* Typography -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -h1, h2, h3, h4, h5, h6 { - margin-top: 0; - margin-bottom: 2rem; - font-weight: 300; } -h1 { font-size: 4.0rem; line-height: 1.2; letter-spacing: -.1rem;} -h2 { font-size: 3.6rem; line-height: 1.25; letter-spacing: -.1rem; } -h3 { font-size: 3.0rem; line-height: 1.3; letter-spacing: -.1rem; } -h4 { font-size: 2.4rem; line-height: 1.35; letter-spacing: -.08rem; } -h5 { font-size: 1.8rem; line-height: 1.5; letter-spacing: -.05rem; } -h6 { font-size: 1.5rem; line-height: 1.6; letter-spacing: 0; } - -/* Larger than phablet */ -@media (min-width: 550px) { - h1 { font-size: 5.0rem; } - h2 { font-size: 4.2rem; } - h3 { font-size: 3.6rem; } - h4 { font-size: 3.0rem; } - h5 { font-size: 2.4rem; } - h6 { font-size: 1.5rem; } -} - -p { - margin-top: 0; } - - -/* Links -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -a { - color: #1EAEDB; } -a:hover { - color: #0FA0CE; } - - -/* Buttons -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.button, -button, -input[type="submit"], -input[type="reset"], -input[type="button"] { - display: inline-block; - height: 38px; - padding: 0 30px; - color: #555; - text-align: center; - font-size: 11px; - font-weight: 600; - line-height: 38px; - letter-spacing: .1rem; - text-transform: uppercase; - text-decoration: none; - white-space: nowrap; - background-color: transparent; - border-radius: 4px; - border: 1px solid #bbb; - cursor: pointer; - box-sizing: border-box; } -.button:hover, -button:hover, -input[type="submit"]:hover, -input[type="reset"]:hover, -input[type="button"]:hover, -.button:focus, -button:focus, -input[type="submit"]:focus, -input[type="reset"]:focus, -input[type="button"]:focus { - color: #333; - border-color: #888; - outline: 0; } -.button.button-primary, -button.button-primary, -input[type="submit"].button-primary, -input[type="reset"].button-primary, -input[type="button"].button-primary { - color: #FFF; - background-color: #33C3F0; - border-color: #33C3F0; } -.button.button-primary:hover, -button.button-primary:hover, -input[type="submit"].button-primary:hover, -input[type="reset"].button-primary:hover, -input[type="button"].button-primary:hover, -.button.button-primary:focus, -button.button-primary:focus, -input[type="submit"].button-primary:focus, -input[type="reset"].button-primary:focus, -input[type="button"].button-primary:focus { - color: #FFF; - background-color: #1EAEDB; - border-color: #1EAEDB; } - - -/* Forms -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea, -select { - height: 38px; - padding: 6px 10px; /* The 6px vertically centers text on FF, ignored by Webkit */ - background-color: #fff; - border: 1px solid #D1D1D1; - border-radius: 4px; - box-shadow: none; - box-sizing: border-box; } -/* Removes awkward default styles on some inputs for iOS */ -input[type="email"], -input[type="number"], -input[type="search"], -input[type="text"], -input[type="tel"], -input[type="url"], -input[type="password"], -textarea { - -webkit-appearance: none; - -moz-appearance: none; - appearance: none; } -textarea { - min-height: 65px; - padding-top: 6px; - padding-bottom: 6px; } -input[type="email"]:focus, -input[type="number"]:focus, -input[type="search"]:focus, -input[type="text"]:focus, -input[type="tel"]:focus, -input[type="url"]:focus, -input[type="password"]:focus, -textarea:focus, -select:focus { - border: 1px solid #33C3F0; - outline: 0; } -label, -legend { - display: block; - margin-bottom: .5rem; - font-weight: 600; } -fieldset { - padding: 0; - border-width: 0; } -input[type="checkbox"], -input[type="radio"] { - display: inline; } -label > .label-body { - display: inline-block; - margin-left: .5rem; - font-weight: normal; } - - -/* Lists -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -ul { - list-style: circle inside; } -ol { - list-style: decimal inside; } -ol, ul { - padding-left: 0; - margin-top: 0; } -ul ul, -ul ol, -ol ol, -ol ul { - margin: 1.5rem 0 1.5rem 3rem; - font-size: 90%; } -li { - margin-bottom: 1rem; } - - -/* Code -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -code { - padding: .2rem .5rem; - margin: 0 .2rem; - font-size: 90%; - white-space: nowrap; - background: #F1F1F1; - border: 1px solid #E1E1E1; - border-radius: 4px; } -pre > code { - display: block; - padding: 1rem 1.5rem; - white-space: pre; } - - -/* Tables -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -th, -td { - padding: 12px 15px; - text-align: left; - border-bottom: 1px solid #E1E1E1; } -th:first-child, -td:first-child { - padding-left: 0; } -th:last-child, -td:last-child { - padding-right: 0; } - - -/* Spacing -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -button, -.button { - margin-bottom: 1rem; } -input, -textarea, -select, -fieldset { - margin-bottom: 1.5rem; } -pre, -blockquote, -dl, -figure, -table, -p, -ul, -ol, -form { - margin-bottom: 2.5rem; } - - -/* Utilities -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -.u-full-width { - width: 100%; - box-sizing: border-box; } -.u-max-full-width { - max-width: 100%; - box-sizing: border-box; } -.u-pull-right { - float: right; } -.u-pull-left { - float: left; } - - -/* Misc -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -hr { - margin-top: 3rem; - margin-bottom: 3.5rem; - border-width: 0; - border-top: 1px solid #E1E1E1; } - - -/* Clearing -–––––––––––––––––––––––––––––––––––––––––––––––––– */ - -/* Self Clearing Goodness */ -.container:after, -.row:after, -.u-cf { - content: ""; - display: table; - clear: both; } - - -/* Media Queries -–––––––––––––––––––––––––––––––––––––––––––––––––– */ -/* -Note: The best way to structure the use of media queries is to create the queries -near the relevant code. For example, if you wanted to change the styles for buttons -on small devices, paste the mobile query code up in the buttons section and style it -there. -*/ - - -/* Larger than mobile */ -@media (min-width: 400px) {} - -/* Larger than phablet (also point when grid becomes active) */ -@media (min-width: 550px) {} - -/* Larger than tablet */ -@media (min-width: 750px) {} - -/* Larger than desktop */ -@media (min-width: 1000px) {} - -/* Larger than Desktop HD */ -@media (min-width: 1200px) {} diff --git a/server/static/images/favicon.png b/server/static/images/favicon.png deleted file mode 100644 index 7a3c81c1e32b4e4224452cf8261a585480caa0ea..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1156 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 diff --git a/server/static/index.html b/server/static/index.html deleted file mode 100644 index c81c190..0000000 --- a/server/static/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - Your page title here :) - - - - - - - - - - - - - - - - - - - - -
-
-
-

Basic Page

-

This index.html page is a placeholder with the CSS, font and favicon. It's just waiting for you to add some content! If you need some help hit up the Skeleton documentation.

-
-
-
- - - - diff --git a/server/static/js/index.js b/server/static/js/index.js deleted file mode 100644 index 5a829e6..0000000 --- a/server/static/js/index.js +++ /dev/null @@ -1,268 +0,0 @@ -var devices_json = null; - - -/* - * Called when the user wants to send the collected components to the orchestration script. - */ -function trigger_orchestration() { - main_config = {}; - var sub_config = []; - - // Walk through the drop downs that have been added to the components div - select_elements = document.getElementById("components").querySelectorAll('select') - select_elements.forEach(element => { - // If the select is not set to None, save it into the config - if (element.value !== "none") { - sub_config.push(element.value); - } - }); - main_config["config"] = sub_config; - - // Hide the 3D preview and the download button as those things may be changing - document.getElementById("model-viewer").style.visibility = "hidden"; - document.getElementById("view-docs").style.visibility = "hidden"; - - // let the user know something is happening - document.getElementById("spinner").classList.add("loader"); - - // Send the request for the auto-generated documentation, and include the configuration data in it - const xhr = new XMLHttpRequest(); - xhr.open("POST", "/wakoma/nimble"); - xhr.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); - xhr.send(JSON.stringify(main_config)); - xhr.responseType = "json"; - xhr.onload = () => { - if (xhr.readyState == 4 && xhr.status == 200) { - poll(xhr.response); - } else { - // The loading indicator is no longer needed - document.getElementById("spinner").classList.remove("loader"); - - console.log(`Error: ${xhr.status}`); - } - }; -} - - -/* - * Allows the page to poll to see when the zip file is - * ready for download - */ -function poll(response_object) { - var poll_url_end = response_object[0]["redirect"]; - var config_name = poll_url_end.split("=")[1] - - // Try to load the file location - const xhr = new XMLHttpRequest(); - xhr.open("GET", poll_url_end, true); - xhr.responseType = 'blob'; - xhr.send(); - xhr.onload = () => { - // If the file is ready, download it now - if (xhr.readyState == 4 && xhr.status == 200) { - // The loading indicator is no longer needed - document.getElementById("spinner").classList.remove("loader"); - - // Update and show the 3D viewer - document.getElementById("model-viewer").style.visibility = "visible"; - document.getElementById("model-viewer").src = "/wakoma/nimble/preview?config=" + config_name; - - // Update and show the Download button - document.getElementById("view-docs").style.visibility = "visible"; - document.getElementById("view-docs").onclick = function() { var link = document.createElement("a") - link.href = "/static/builds/" + config_name + "_assembly_docs/index.html" - link.target = "_blank" - link.click()}; - - // Boiler plate to force a download - const url = window.URL.createObjectURL(xhr.response); - const a = document.createElement('a'); - a.style.display = 'none'; - a.href = url; - a.download = config_name + '.zip'; - document.body.appendChild(a); - a.click(); - window.URL.revokeObjectURL(url); - } - // Wait and try again later if the file is not ready - else if (xhr.status == 307) { - setTimeout(poll, 5000, response_object); - } - else { - // The loading indicator is no longer needed - document.getElementById("spinner").classList.remove("loader"); - - console.log(`Error: ${xhr.statusText}`); - } - }; -} - - -/* - * Takes a component key with spaces and other non-allowed characters - * and fixes it - */ -function clean_up_key(component_key) { - var new_key = component_key.replaceAll(" ", "_"); - return new_key; -} - - -/* - * Allows the page to pull the component JSON data and save it for later use. - */ -function load_json() { - // URL to retrieve the omponent JSON data - const comp_url = "/wakoma/nimble/components" - - // Set up the request for the component JSON data - const xhr = new XMLHttpRequest(); - xhr.open("GET", comp_url, true); - xhr.responseType = 'json'; - xhr.send(); - xhr.onload = () => { - // If the file is ready, download it now - if (xhr.readyState == 4 && xhr.status == 200) { - xhr.response[0].forEach(element => { - // Save the JSON to use it for loading dynamic controls - devices_json = xhr.response[0]; - }); - } - else { - console.log(`Error: ${xhr.statusText}`); - } - }; -} - - -/* - * Removes any drop downs that the user has set to None. - */ -function remove_unused_drops() { - // Walk through the drop downs and remove any that have been set to None - select_elements = document.getElementById("components").querySelectorAll('select') - select_elements.forEach(element => { - // If the select is not set to None, save it into the config - if (element.value === "none") { - element.remove(); - document.getElementById(element.id + "_lbl").remove(); - } - }); -} - - -/* - * Loads a given select element up based on the devices.json data that was stored when - * the page loaded. - */ -function load_select(drop_id, component_type, secondary_type) { - devices_json.forEach(element => { - // Check to make sure all the necessary attributes are available - if (element["HeightUnits"] === "") { - return; - } - - if (element["Type"] == component_type || element["Type"] === secondary_type) { - // Create a new option to be added to the select in question - var opt = document.createElement('option'); - opt.value = clean_up_key(element["ID"]); - opt.innerHTML = element["Brand"].concat(" ", element["Hardware"]); - - document.getElementById(drop_id).appendChild(opt); - } - }); -} - - -/* - * Allows a new component drop down to be created dynamically. - */ -function create_new_drop(drop_class, drop_label) { - // Figure out how many AP drop downs there are so we can create an incremented name - num_drops = document.getElementsByClassName(drop_class).length + 1; - - // Construct the drop down name - drop_name = drop_class + num_drops; - - // Create the dynamic select element - var lbl = document.createElement('label'); - lbl.for = drop_name; - lbl.id = drop_name + "_lbl"; - lbl.innerHTML = drop_label; - var drop = document.createElement('select'); - drop.id = drop_name; - drop.classList.add(drop_class); - drop.onchange = remove_unused_drops; - var opt = document.createElement('option'); - opt.value = "none"; - opt.innerHTML = "None"; - - // Add the select element to the list of controls - document.getElementById("components").appendChild(lbl); - document.getElementById("components").appendChild(drop); - document.getElementById(drop_name).appendChild(opt); -} - - -/* - * Allows the user to add access point drop-downs dynamically to the user interface. - */ -function add_access_point() { - console.log("Adding access point..."); - - // Create the new select element that will hold the access point components - create_new_drop("ap_", "Access Point"); - - // Load the select from the saved devices JSON - load_select("ap_" + num_drops, "Access Point", "Router + AP"); -} - - -/* - * Allows the user to add router drop-downs dynamically to the user interface. - */ -function add_router() { - console.log("Adding router..."); - - // Create the new select element that will hold the router components - create_new_drop("router_", "Router"); - - // Load the select from the saved devices JSON - load_select("router_" + num_drops, "Router", "Router + AP"); -} - - -/* - * Allows the user to add server drop-downs dynamically to the user interface. - */ -function add_server() { - console.log("Adding server..."); - - // Create the new select element that will hold the router components - create_new_drop("server_", "Server"); - - // Load the select from the saved devices JSON - load_select("server_" + num_drops, "Server", ""); -} - - -/* - * Allows the user to add switch drop-downs dynamically to the user interface. - */ -function add_switch() { - console.log("Adding switch..."); - - // Create the new select element that will hold the router components - create_new_drop("switch_", "Switch"); - - // Load the select from the saved devices JSON - load_select("switch_" + num_drops, "Switch", ""); -} - - -/* - * Triggers a load of the component JSON data into the UI. - */ -window.onload = function() { - load_json(); -} From 97d87d9e7c9e71488b04a7f7b044a188f81e7bc8 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 19 Jun 2024 15:55:44 +0100 Subject: [PATCH 03/16] update setup to require cadorchestrator --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 7219787..555799c 100644 --- a/setup.py +++ b/setup.py @@ -16,6 +16,7 @@ 'gitbuilding==0.15.0a2', 'cq-annotate @ git+https://github.com/jmwright/cq-annotate.git', 'cq_warehouse @ git+https://github.com/gumyr/cq_warehouse.git', + 'cadorchestrator @ git+https://gitlab.com/gitbuilding/cadorchestrator.git' ], extras_require={ 'dev': [ From e726829f540882a57c8a68d58888c9f2787e6b92 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 19 Jun 2024 16:08:34 +0100 Subject: [PATCH 04/16] Pin numpy version --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index 555799c..e0498a0 100644 --- a/setup.py +++ b/setup.py @@ -9,6 +9,7 @@ packages=['nimble_orchestration', 'nimble_builder'], version = '0.0.1', install_requires=[ + 'numpy~=1.26', 'cadquery>=2', 'cadscript>=0.5.2', 'exsource-tools', From 11039c0d2862fd6677b6dfdef86a840df7f18038 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 19 Jun 2024 17:23:10 +0100 Subject: [PATCH 05/16] Use cadorchestration to generate docs in ci --- .github/workflows/publish.yml | 2 +- cadorchestration.yml | 3 +++ generate.py | 40 ----------------------------------- 3 files changed, 4 insertions(+), 41 deletions(-) create mode 100644 cadorchestration.yml delete mode 100755 generate.py diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 8c3bc19..0e95439 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -47,7 +47,7 @@ jobs: - name: Generate example docs run: | - ./generate.py + cadorchestrator generate '["NUC10i5FNH", "Raspberry_Pi_4B", "Raspberry_Pi_4B"]' - name: Upload artifact uses: actions/upload-pages-artifact@v1 diff --git a/cadorchestration.yml b/cadorchestration.yml new file mode 100644 index 0000000..79a1d7c --- /dev/null +++ b/cadorchestration.yml @@ -0,0 +1,3 @@ +configuration-class: + module: nimble_orchestration.configuration + class: NimbleConfiguration diff --git a/generate.py b/generate.py deleted file mode 100755 index 6c81bc4..0000000 --- a/generate.py +++ /dev/null @@ -1,40 +0,0 @@ -#! /usr/bin/env python - -""" -This script is in development. It is being developed to -create and bundle all STLs (and eventually all documentation) -for a specific nimble configuration. -""" - -from cadorchestrator.orchestration import OrchestrationRunner -from nimble_orchestration.configuration import NimbleConfiguration - -def generate(selected_devices_ids): - """ - This will eveneually generate everything needed for a specific nimble - configuration including, STL files, renders, of assembly steps, and assembly - documentation. Currently it only creates the rack components. - """ - - config = NimbleConfiguration(selected_devices_ids) - - print("Starting build") - runner = OrchestrationRunner() - - print("Generating components") - runner.generate_components(config.components) - - runner.generate_assembly(config.assembly_definition) - - runner.generate_docs(config) - - -def generate_example_configuration(): - """ - Generate the trays and assembly model for a simple set of devices - """ - selected_devices_ids = ['NUC10i5FNH', 'Raspberry_Pi_4B', 'Raspberry_Pi_4B'] - generate(selected_devices_ids) - -if __name__ == "__main__": - generate_example_configuration() From 5fe62249215a70dbaf30ab9d0292ed448072654e Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Wed, 19 Jun 2024 17:23:38 +0100 Subject: [PATCH 06/16] typo fix --- generate_static.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/generate_static.py b/generate_static.py index cef9b5f..c179b77 100755 --- a/generate_static.py +++ b/generate_static.py @@ -4,7 +4,7 @@ This script generates a number of STL files and also creates as simple website to view them. This is designed to be run via a github action to provide a list of available STLs for -direct download without them needing to be commited to the repository +direct download without them needing to be committed to the repository """ import os From e68c2f7d1d30b1267e4c7bb3c1c34cb04e81fef8 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 10:36:46 +0100 Subject: [PATCH 07/16] Simplify paths --- nimble_orchestration/paths.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nimble_orchestration/paths.py b/nimble_orchestration/paths.py index 1e1d3d9..f59b225 100644 --- a/nimble_orchestration/paths.py +++ b/nimble_orchestration/paths.py @@ -4,8 +4,6 @@ import os -MODULE_PATH = os.path.normpath(os.path.join(os.path.split(__file__)[0], '..')) +MODULE_PATH = '.' BUILD_DIR = os.path.join(MODULE_PATH, "build") REL_MECH_DIR = os.path.relpath(os.path.join(MODULE_PATH, "mechanical"), BUILD_DIR) -DOCS_DIR = os.path.join(MODULE_PATH, "documentation") -DOCS_TMP_DIR = os.path.join(MODULE_PATH, "_gb_temp_") From b4e17c65a9165d3ced476053b596220eaab80273 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 10:44:58 +0100 Subject: [PATCH 08/16] Linter fixes --- lint_test.py | 1 - nimble_orchestration/configuration.py | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lint_test.py b/lint_test.py index 469171f..b243274 100755 --- a/lint_test.py +++ b/lint_test.py @@ -32,7 +32,6 @@ def main(): args = parser.parse_args() if args.file_type == "py": lint([ - 'generate.py', 'generate_static.py', 'nimble_orchestration', 'nimble_builder', diff --git a/nimble_orchestration/configuration.py b/nimble_orchestration/configuration.py index b329fd4..af217d9 100644 --- a/nimble_orchestration/configuration.py +++ b/nimble_orchestration/configuration.py @@ -62,8 +62,9 @@ def shelves(self): """ Return a list of the shelves assembled in this nimble rack. - Each object in the list is an instance of the Shelf object, this holds both the information on - the assembled shelf, and on the Device the shelf is for. + Each object in the list is an instance of the Shelf object, + this holds both the information on the assembled shelf, and + on the Device the shelf is for. """ return deepcopy(self._shelves) @@ -226,4 +227,3 @@ def _generate_shelf_list(self): height_in_u += device.height_in_u return shelves - From 07945f3e716990273aa01370c712524993427369 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 14:45:00 +0100 Subject: [PATCH 09/16] Move config site logo and favicon into repo, add site dettings to cadorchestration.yml --- assets/favicon.png | Bin 0 -> 1888 bytes assets/wakoma-logo.png | Bin 0 -> 9706 bytes cadorchestration.yml | 4 ++++ 3 files changed, 4 insertions(+) create mode 100644 assets/favicon.png create mode 100644 assets/wakoma-logo.png diff --git a/assets/favicon.png b/assets/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..4aa446c55a59b1627e581f03c52dbd64d3ebb606 GIT binary patch literal 1888 zcmZ{lX;jh)7smf6Q)s!BjhHqfE{O{vs5q!2C?;U8gJmuyA{i>;g1D9AQi*7dd*w1( zN=lhqE@`RF$#j}AO-&uw(Gr&;mn?IT@}-aOhdK8=_c`ae=l6WQnI3M=N{WXS0RT`U zlZak2hkX-7PPV1c_mwgOhY(x|0MPjBJ}K?HZ$~=G%M}0;4FMqK5&%eKQOYs^#G(M; zZ6E;PZUBHrWNC+|t?aNT$iht*oMwH5NRceDGBg~{gSpbJL;~_6`!w56X+Kg!X&5IKn zc>THO!HWZ(?12wuYsoPl6ouVt%~L}fAcIpt3J-WvR~!cigB@N*tr0n~a;j=-y0?e< zcrE0$8@wnHNP8TpX|YnuH|O3psO7xoE%OuS;*Y}b7cx8IMz72UNalt4FR-(y==rn+ zB60hyaYfI&+p{gq4*RdcqUrWy?q%qIM-HuJY`wROT+g9FiPwYGK(`KpLBN~xf#buE z+Rty~nRSN;w7jkqi#dX-f0inV2YN4e$^n7s=T$Ba4jHC+h*3*)$*Mrd^xpJi*63Xk zs5=91X?djWc4KMkmrM(Ilbd1B3SNN(QB5VuqXs~p`CQ|u*A#=9J4rJVo$=b!Z5Jkz zK;78GSK;gTr{Wg(i`QIT4mzNTIbMporEycOmsK4|h$?k22tq*hbsA`WRtr_j+-_`U zmGG2*u(iDvObNIsul6jcNYxykxSo47d6G&X7BR>OrL_3^9P@N@Adhk&H%85}fq&Ly zY7)*fwZUxO(W+m9_%eI(#&+zlX-CTwIYiG?#xv-fE3}glGqgVO6s6BZ0HqUqv@cjM z-OnAH?67k#J`75Us%u`H9$oL#q40OAXDkzfGFg6MU4B{*z@W(0#H5Y9kNwFi*InDh zMoY-OZ$;&`57%ayrBMQPT>BHr^vENRXWU9%wU6iVstSne zlj98^vg$Kyy5>I*ljF*4V~a@&Q%$B>JS%C##&0Vs#)2w!GD-|9x5|2s#eu_|S^ag@qB*S4aLj>+j>y*7gH1jS$}JmfS(UYT!QYJ$Tgp z;Lx%kd%{~wva>eUr-S&JqB(6)xX|zOam>bX^MheQcFTJx-8A8tras3Ky5FkJ0u$KE z`c69gj?W)sOBqXS%l7ew6Ycb%C_?hckm1sEkBd()ZM?BwqL77Dz74}gbnQ>p)Y-%+ z<&>c8)Jk_Z04#?`FcQ1F8gGn_4Gk!19AH+kyhkgYuNEin^tc`>8RT%bllB4?Z`)?( zN6c0{@Z%?tT+Ae8yNhDH@#g8j9f3*r?+KiIzY+87#J}0?c`Ww%Ihp!}?^<4?# zTbjQ7kI95M-_TfZSHi3Z;PWWr%Yuyy1}(WV*xccd)E%vz5XHHd_@X1SM^rNAugUc= z6(JC#s^#)RhTm9w%#o?Pp=iv543Vx9!~!@>5sR#v*!s{r*QLVR|XX5QH4r<-98pLTX_Ergs}k03mEw+O@XYdbY) zQjRF}*;)w=l79ZOgBeLr?%vG|IVX5H!+yNQFUT|B!-M>-@C%lws^&>q*@&+y@C{@8 z9%jm=yq!rp?(eriCTqrQ)_7vG!>@AGe}&F({#!kR(W^Y$oA-=OVICFO=$5U@*A2&>mZD8ND^#eyyo#C*(x8A7tO} zk&@z^4-5ZHCxDs_M>rST7pJBl(@t7+&_)Ig=+5ri*mH3;$Y0K%xqCA$+XkssZp9P%Ip08sn6uc@1#?Y+05vj9;c0E9hn=nWcUDAU; zoy#`AU%<`d?;>xW!7d*=|91>WA1>B@q3sqJRAyv*6T{W(Pl!}gJ2M|Xd1$rn-NN`i zE8LlWBzyhs(91gTdkKCVht$KXfRXJh;$8W5pAPGFf4;r!K@d1jJ{&rm#bQKo07ow; z0@d9M2MY-{3yoxiz_{i}6wD|#h8Yn8BU7AV6b~n77}pwwFoi|M(!!aX1ehJn8m*X_ z^9n61Gx$~(Oye+c^k|0605l41VTQ6YL!+qX7@P$bhsKzo%yB4GMfk?t{{bv^F!Nl( Ve*;72WPcd|ke%F!bq;}P{{t2GRBHeL literal 0 HcmV?d00001 diff --git a/assets/wakoma-logo.png b/assets/wakoma-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c7182e5b946a0bb21b945dd1259aa7c3c44c5d7f GIT binary patch literal 9706 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 literal 0 HcmV?d00001 diff --git a/cadorchestration.yml b/cadorchestration.yml index 79a1d7c..994373a 100644 --- a/cadorchestration.yml +++ b/cadorchestration.yml @@ -1,3 +1,7 @@ configuration-class: module: nimble_orchestration.configuration class: NimbleConfiguration +site-title: Wakoma Nimble Configurator +site-tagline: Select Components for Your Nimble +site-logo: assets/wakoma-logo.png +site-favicon: assets/favicon.png From 6cf410818dc2f71ce0d06c9d0b8e527c3d8eb210 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 16:03:49 +0100 Subject: [PATCH 10/16] Changing the devices.json license to Wakoma as this is now pulled from their NocoDb --- devices.json.license | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/devices.json.license b/devices.json.license index 14800eb..18157f4 100644 --- a/devices.json.license +++ b/devices.json.license @@ -1,3 +1,3 @@ -SPDX-FileCopyrightText: 2023 Andreas Kahler +SPDX-FileCopyrightText: 2024 Wakoma SPDX-License-Identifier: CC-BY-SA-4.0 From 1f230d4a12f6edf0229fe7d69cad9dde113db9b9 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 19:57:08 +0100 Subject: [PATCH 11/16] Generate and pass configuration options for server --- .gitignore | 2 +- cadorchestration.yml | 1 + .../gen_nimble_conf_options.py | 81 +++++++++++++++++++ setup.py | 3 +- 4 files changed, 85 insertions(+), 2 deletions(-) create mode 100755 nimble_orchestration/gen_nimble_conf_options.py diff --git a/.gitignore b/.gitignore index 2640349..8ca77c7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ _site/ _gb_temp_/ build/ _cache_/ -server/static/builds/* +OrchestratorConfigOptions.json .vscode/* .venv/ diff --git a/cadorchestration.yml b/cadorchestration.yml index 994373a..f1f2fff 100644 --- a/cadorchestration.yml +++ b/cadorchestration.yml @@ -1,6 +1,7 @@ configuration-class: module: nimble_orchestration.configuration class: NimbleConfiguration +configuration-options: OrchestratorConfigOptions.json site-title: Wakoma Nimble Configurator site-tagline: Select Components for Your Nimble site-logo: assets/wakoma-logo.png diff --git a/nimble_orchestration/gen_nimble_conf_options.py b/nimble_orchestration/gen_nimble_conf_options.py new file mode 100755 index 0000000..8c227ab --- /dev/null +++ b/nimble_orchestration/gen_nimble_conf_options.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python + +""" +This module is used to generate config options for CadOrchestrator using devices.json +""" + +import os +import sys +import json + +def usage(): + """ + Prints a usage message for this utility. + """ + print("This utility exists to generate config options for CadOrchestrator") + print("using devices.json") + print("Usage:") + print(" gen_nimble_conf_options") + +def main(): + """ + Main script to turn a CSV file into a JSON file. It does not do pretty formatting, + it is a JSON file with no newlines. + """ + + if not os.path.exists('devices.json'): + print("Error. devices.json not found") + sys.exit(1) + # Write the JSON data to file + with open('devices.json', 'r', encoding="utf-8") as dev_file: + devices = json.load(dev_file) + + access_points = [] + routers = [] + servers = [] + switches = [] + for device in devices: + item = {'value': device['ID'], + 'name': device['Brand']+" "+device['Hardware']} + if device['Type'] == "Access Point": + access_points.append(item) + elif device['Type'].startswith("Router"): + routers.append(item) + elif device['Type'] == "Server": + servers.append(item) + elif device['Type'] == "Switch": + switches.append(item) + + conf_dict = { + "options": [{ + "option-type": "list", + "add-options": [ + { + "display-name": "Access Point", + "id": "accesspoint", + "items": access_points + }, + { + "display-name": "Router", + "id": "router", + "items": routers + }, + { + "display-name": "Server", + "id": "server", + "items": servers + }, + { + "display-name": "Switch", + "id": "switch", + "items": switches + } + ] + } + ]} + + with open('OrchestratorConfigOptions.json', 'w', encoding="utf-8") as conf_file: + json.dump(conf_dict, conf_file) + +if __name__ == "__main__": + main() diff --git a/setup.py b/setup.py index e0498a0..edc1a53 100644 --- a/setup.py +++ b/setup.py @@ -24,5 +24,6 @@ 'pylint', 'colorama' ] - } + }, + entry_points={'console_scripts': ['gen_nimble_conf_options = nimble_orchestration.gen_nimble_conf_options:main']} ) From 2c8ee9bc7260afaf9ebe28c8a644c51f84fbbd63 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 21:35:22 +0100 Subject: [PATCH 12/16] Show router + ap for ap and router options --- nimble_orchestration/gen_nimble_conf_options.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nimble_orchestration/gen_nimble_conf_options.py b/nimble_orchestration/gen_nimble_conf_options.py index 8c227ab..25939ea 100755 --- a/nimble_orchestration/gen_nimble_conf_options.py +++ b/nimble_orchestration/gen_nimble_conf_options.py @@ -37,9 +37,9 @@ def main(): for device in devices: item = {'value': device['ID'], 'name': device['Brand']+" "+device['Hardware']} - if device['Type'] == "Access Point": + if device['Type'] in ["Access Point", "Router + AP"]: access_points.append(item) - elif device['Type'].startswith("Router"): + elif device['Type'] in ["Router", "Router + AP"]: routers.append(item) elif device['Type'] == "Server": servers.append(item) From b1f15a8d4a3e979657939dc87d16db7dfe15d7ae Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 21:43:27 +0100 Subject: [PATCH 13/16] Update the instructions in generate.md --- generate.md | 50 +++++++++++++++++++++++++++++--------------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/generate.md b/generate.md index 6e9f00b..3da6e73 100644 --- a/generate.md +++ b/generate.md @@ -10,12 +10,12 @@ SPDX-License-Identifier: CERN-OHL-S-2.0 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. +Our preferred way to generate the models needed for a nimble configuration is via our orchestration module `nimble-orchestration`. This uses an orchestration tool called [CadOrchestrator](https://gitlab.com/gitbuilding/cadorchestrator) 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. ## Installation -You must have [Python 3.8](https://www.python.org/about/gettingstarted/) or higher installed on your system and also have pip installed. We recommend that you use a [Python virtual environment](https://realpython.com/python-virtual-environments-a-primer/) for interacting with nimble. +You must have [Python 3.10](https://www.python.org/about/gettingstarted/) or higher installed on your system and also have pip installed. We recommend that you use a [Python virtual environment](https://realpython.com/python-virtual-environments-a-primer/) for interacting with nimble. Clone or download this repository to your computer @@ -27,46 +27,54 @@ Run: *Note, if pip doesn't work depending on your system is configured you man need to replace `pip` with `pipx`, `pip3`, or `python -m pip`.* -*Also note: The `-e` is optional. It allows you to adjust the code in `nimble-builder` or `nimble-orchestration` without having to run the installation again.* - ## Running the code -As the code is very much a work in progress we do not have detailed documentation of how to run it for custom configurations. +### Lunching the configuration server -For now the best way to run the code is with the two scripts in this repository. +Starting in the root directory of this project. First you will need to generate the configuration options. To do this run: -### Generate static + gen_nimble_conf_options -The script `generate-static.py` is used to create a number of example components for a Nimble rack. The script is not trying to support any specific configuration, but is instead being developed to provide a static library interface for which a large number of Nimble components can be downloaded. +Next run: -To run this script, run the following command: + cadorchestrator serve - python generate_static.py +The server should now be available at http://127.0.0.1:8000/ -This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `stl` files that can be 3D printed. +### Generating a specific configuration from command line -The script also creates a simple web-page with an index of all of the files. The final link to the "Partially complete automatically generated documentation" will be broken unless you also run `generate.py` (see below). +The script `cadorchestrator generate` can be used generating a complete set of information for a nimble configuration from the command line. -It also creates a number of files that are used by the orchestration script. +Currently the configuration is just a list of the networking components which are hard coded. At a future date it should be possible to pass a configuration and other parameters to this script. -If you don't want to run this locally you can see a [hosted version of the output](https://wakoma.github.io/nimble/). +To generate run the following: + cadorchestrator generate '["NUC10i5FNH", "Raspberry_Pi_4B", "Raspberry_Pi_4B"]' -### Generate configuration +This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `step` files that can be 3D printed (you may need to convert them to `stl` files first). -The script `generate.py` is our test-bed script for generating a complete set of information for a nimble configuration. +The script also creates an `stl` and a `gltf` of the final assembled rack. **Don't print this, it is not going to print properly as one piece!** -Currently the configuration is just a list of the networking components which are hard coded. At a future date it should be possible to pass a configuration and other parameters to this script. +It also creates a number of files that are used by the orchestration script. + +At a later date this script will be improved to create a directory (and associated zip-file of each configuration). + + +### Generate static + +The script `generate-static.py` is used to create a number of example components for a Nimble rack. The script is not trying to support any specific configuration, but is instead being developed to provide a static library interface for which a large number of Nimble components can be downloaded. To run this script, run the following command: - python generate.py + python generate_static.py -This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `step` files that can be 3D printed (you may need to convert them to `stl` files first). +This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `stl` files that can be 3D printed. -The script also creates an `stl` and a `gltf` of the final assembled rack. **Don't print this, it is not going to print properly as one piece!** +The script also creates a simple web-page with an index of all of the files. The final link to the "Partially complete automatically generated documentation" will be broken unless you also run `generate.py` (see below). It also creates a number of files that are used by the orchestration script. -At a later date this script will be improved to create a directory (and associated zip-file of each configuration). +If you don't want to run this locally you can see a [hosted version of the output](https://wakoma.github.io/nimble/). + + From 03167cb07af55fbf8b02c7b3f42fc696e9b15bb2 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Thu, 20 Jun 2024 22:18:40 +0100 Subject: [PATCH 14/16] Define assembly source file in NimbleConfiguration --- nimble_orchestration/configuration.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/nimble_orchestration/configuration.py b/nimble_orchestration/configuration.py index af217d9..2ef7dbe 100644 --- a/nimble_orchestration/configuration.py +++ b/nimble_orchestration/configuration.py @@ -227,3 +227,14 @@ def _generate_shelf_list(self): height_in_u += device.height_in_u return shelves + + @property + def assembly_source_file(self): + """ + This is a bit add hoc until we we work out how best to specify assemblies. + Currently we just pass a cad quey file that should pass the assembly def. + We should move away from this with classes for assemblies and sub assemblies. + """ + source = os.path.join(REL_MECH_DIR, "assembly_renderer.py") + source = posixpath.normpath(source) + return source From af8accf3cee3b2b108bb7f56e65049d202b8225d Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Mon, 1 Jul 2024 15:59:16 +0100 Subject: [PATCH 15/16] Apply suggestions from code review Co-authored-by: Jeremy Wright --- nimble_orchestration/configuration.py | 4 ++-- nimble_orchestration/shelf.py | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/nimble_orchestration/configuration.py b/nimble_orchestration/configuration.py index 2ef7dbe..1e4fefd 100644 --- a/nimble_orchestration/configuration.py +++ b/nimble_orchestration/configuration.py @@ -231,8 +231,8 @@ def _generate_shelf_list(self): @property def assembly_source_file(self): """ - This is a bit add hoc until we we work out how best to specify assemblies. - Currently we just pass a cad quey file that should pass the assembly def. + This is a bit ad hoc until we we work out how best to specify assemblies. + Currently we just pass a Cadquery file that should pass the assembly def. We should move away from this with classes for assemblies and sub assemblies. """ source = os.path.join(REL_MECH_DIR, "assembly_renderer.py") diff --git a/nimble_orchestration/shelf.py b/nimble_orchestration/shelf.py index 8b678d1..d39501f 100644 --- a/nimble_orchestration/shelf.py +++ b/nimble_orchestration/shelf.py @@ -5,7 +5,7 @@ class Shelf: """ - A class for all the orchestration information relating to a shelf + A class for all the orchestration information relating to a shelf. """ def __init__(self, assembled_shelf: AssembledComponent, @@ -35,21 +35,21 @@ def assembled_shelf(self): """ Return the Object describing the assembled shelf (currently this in an empty shelf in the correct location on the rack). - This is an AssembledComponent + This is an AssembledComponent. """ return self._assembled_shelf @property def device(self): """ - Return the Device object for the networking component that sits on this shelf + Return the Device object for the networking component that sits on this shelf. """ return self._device @property def md(self): """ - Return the markdown (BuildUp) for the GitBuilding page for assembling this shelf + Return the markdown (BuildUp) for the GitBuilding page for assembling this shelf. """ meta_data = { "Tag": "shelf", From 6ef3ba28732ce7f98847018b021e47c6ae7775e2 Mon Sep 17 00:00:00 2001 From: Julian Stirling Date: Mon, 1 Jul 2024 16:32:25 +0100 Subject: [PATCH 16/16] Minor fixes to setup.py and generate.md as requested by review --- generate.md | 4 ++-- setup.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/generate.md b/generate.md index 3da6e73..c864bbc 100644 --- a/generate.md +++ b/generate.md @@ -52,9 +52,9 @@ To generate run the following: cadorchestrator generate '["NUC10i5FNH", "Raspberry_Pi_4B", "Raspberry_Pi_4B"]' -This should create the `build` directory. Inside this the `printed_components` directory should contain a number of `step` files that can be 3D printed (you may need to convert them to `stl` files first). +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. -The script also creates an `stl` and a `gltf` of the final assembled rack. **Don't print this, it is not going to print properly as one piece!** +The script also creates an `stl` and a `gltf` of the final assembled rack. **Don't print this, it is not going to print properly as a single piece!** It also creates a number of files that are used by the orchestration script. diff --git a/setup.py b/setup.py index edc1a53..a8c7cdf 100644 --- a/setup.py +++ b/setup.py @@ -8,6 +8,7 @@ description = 'Python module for generating any nimble configuration', packages=['nimble_orchestration', 'nimble_builder'], version = '0.0.1', + python_requires='>=3.10', install_requires=[ 'numpy~=1.26', 'cadquery>=2',