diff --git a/examples/procedure_example/components/create_floor.py b/examples/procedure_example/components/create_floor.py index 99366974..940121ac 100644 --- a/examples/procedure_example/components/create_floor.py +++ b/examples/procedure_example/components/create_floor.py @@ -9,16 +9,27 @@ app = typer.Typer() +@procedure_decorator(app, export_file_type=FileTypeDC.IFC, export_file_var="output_file") +def main( + name: str = 'floor1', + origin: tuple[float, float, float] = (0, 0, 0), + width: float = 40, + length: float = 20, + thickness: float = 0.01, + output_file: pathlib.Path = None +) -> pathlib.Path: -@procedure_decorator(app, export_file_type=FileTypeDC.IFC) -def main(name: str = 'floor1', p0: tuple[float, float, float] = (0, 0, 0), width: float = 40, length: float = 20) -> pathlib.Path: temp_dir = Config().websockets_server_temp_dir - comp_dir = temp_dir / "components" - p = ada.Part(name) / ada.Plate("pl1", [(0, 0), (width, 0), (width, length), (0, length)], 0.01, origin=p0) + this_file_name = pathlib.Path(__file__).stem + output_dir = temp_dir / "components" / this_file_name + + p = ada.Part(name) / ada.Plate("pl1", [(0, 0), (width, 0), (width, length), (0, length)], thickness, origin=origin) # The assembly level is to be discarded. Only the part is relevant for merging into another IFC file a = ada.Assembly("TempAssembly") / p - comp_dir.mkdir(parents=True, exist_ok=True) - new_file = comp_dir / f"{name}.ifc" + output_dir.mkdir(parents=True, exist_ok=True) + new_file = output_dir / f"{this_file_name}.ifc" + if new_file.exists(): + new_file.unlink() a.to_ifc(new_file) return new_file diff --git a/examples/procedure_example/procedures/add_stiffeners.py b/examples/procedure_example/procedures/add_stiffeners.py index 20e91c88..34f55957 100644 --- a/examples/procedure_example/procedures/add_stiffeners.py +++ b/examples/procedure_example/procedures/add_stiffeners.py @@ -47,7 +47,7 @@ def add_stiffeners(pl: ada.Plate) -> list[ada.Beam]: @procedure_decorator(app, input_file_var="ifc_file", input_file_type=FileTypeDC.IFC, export_file_type=FileTypeDC.IFC) -def main(ifc_file: pathlib.Path = None) -> pathlib.Path: +def main(ifc_file: pathlib.Path = None, output_file: pathlib.Path = None) -> pathlib.Path: """A procedure to add stiffeners to all plates in the IFC file""" a = ada.from_ifc(ifc_file) diff --git a/src/ada/comms/msg_handling/run_procedure.py b/src/ada/comms/msg_handling/run_procedure.py index 335aea99..8d9aec70 100644 --- a/src/ada/comms/msg_handling/run_procedure.py +++ b/src/ada/comms/msg_handling/run_procedure.py @@ -3,7 +3,7 @@ import pathlib from typing import TYPE_CHECKING -from ada.comms.fb_model_gen import FileObjectDC, MessageDC +from ada.comms.fb_model_gen import FileObjectDC, MessageDC, ParameterDC, ParameterTypeDC, ProcedureStartDC from ada.comms.msg_handling.update_server import update_server from ada.comms.procedures import Procedure from ada.config import logger @@ -17,47 +17,43 @@ def run_procedure(server: WebSocketAsyncServer, client: ConnectedClient, message start_procedure = message.procedure_store.start_procedure procedure: Procedure = server.procedure_store.get(start_procedure.procedure_name) - params = procedure.params - for param in start_procedure.parameters: - if param.type == "string": - params[param.name].value = param.value.string_value - elif param.type == "float": - params[param.name].value = param.value.float_value - elif param.type == "integer": - params[param.name].value = param.value.integer_value - elif param.type == "boolean": - params[param.name].value = param.value.boolean_value - elif param.type == "array": - params[param.name].value = param.value.array_value - else: - if param.value.string_value: - params[param.name].value = param.value.string_value - else: - raise ValueError(f"Unknown parameter type {param.type}") - + params = {p.name: p for p in start_procedure.parameters} + procedure(**params) - procedure(**procedure.params) logger.info(f"Procedure {procedure.name} ran successfully") - update_server_on_successful_procedure_run(server, procedure, client, message) + + update_server_on_successful_procedure_run(server, procedure, client, message, start_procedure) def update_server_on_successful_procedure_run( - server: WebSocketAsyncServer, procedure: Procedure, client: ConnectedClient, message: MessageDC + server: WebSocketAsyncServer, procedure: Procedure, client: ConnectedClient, message: MessageDC, start_procedure: ProcedureStartDC ) -> None: - param = procedure.params.get(procedure.input_file_var) - if isinstance(param.value, str): - input_file_path = pathlib.Path(param.value) + params = [p for p in start_procedure.parameters if p.name == procedure.input_file_var] + if len(params) == 0: + # it's a component procedure + input_file_path = None + output_dir = procedure.get_component_output_dir() + if procedure.export_file_type + output_file = output_dir / else: - input_file_path = pathlib.Path(param.value.string_value) + # it's a modification procedure on an existing file + param = params[0] + if param.type == ParameterTypeDC.STRING: + input_file_path = pathlib.Path(param.value.string_value) + elif param.type == ParameterTypeDC.UNKNOWN and param.value.string_value: + input_file_path = pathlib.Path(param.value.string_value) + else: + raise NotImplementedError("Only string input file paths are supported for now") - server_file_object = server.scene.get_file_object(input_file_path.stem) - output_file = procedure.get_procedure_output(input_file_path.stem) + server_file_object = server.scene.get_file_object(input_file_path.stem) + output_file = procedure.get_procedure_output(input_file_path.stem) + purpose = server_file_object.purpose new_file_object = FileObjectDC( name=output_file.name, filepath=output_file, file_type=procedure.export_file_type, - purpose=server_file_object.purpose, + purpose=purpose, is_procedure_output=True, procedure_parent=message.procedure_store.start_procedure, ) diff --git a/src/ada/comms/procedures.py b/src/ada/comms/procedures.py index 5d8bac60..faf80bc8 100644 --- a/src/ada/comms/procedures.py +++ b/src/ada/comms/procedures.py @@ -42,12 +42,16 @@ def _call_script_subprocess(self, *args, **kwargs): for arg in args: call_args.append(str(arg)) # Build the command-line arguments - for arg_name, value in kwargs.items(): + for arg_name, param_dc in kwargs.items(): + param_dc: ParameterDC + value = make_param_value(param_dc) + # Convert underscores to hyphens - if isinstance(value, ParameterDC): - value = value.value arg_name_cli = arg_name.replace("_", "-") - if isinstance(value, bool): + if param_dc.type == ParameterTypeDC.ARRAY: + call_args.append(f"--{arg_name_cli}") + call_args.extend(value) + elif isinstance(value, bool): if value: call_args.append(f"--{arg_name_cli}") else: @@ -79,6 +83,13 @@ def to_procedure_dc(self) -> ProcedureDC: export_file_type=self.export_file_type, ) + def get_component_output_dir(self): + from ada.comms.scene_model import Scene + temp_dir = Scene.get_temp_dir() + component_dir = temp_dir / "components" + + return component_dir / self.name + def get_procedure_output(self, input_file_name: str): from ada.comms.scene_model import Scene @@ -104,6 +115,38 @@ def get_procedure_output(self, input_file_name: str): else: raise NotImplementedError(f"Export file type {self.export_file_type} not implemented") +def make_param_value(param: ParameterDC) -> str | list[str]: + if param.type == ParameterTypeDC.STRING: + return param.value.string_value + elif param.type == ParameterTypeDC.INTEGER: + return str(param.value.integer_value) + elif param.type == ParameterTypeDC.FLOAT: + return str(param.value.float_value) + elif param.type == ParameterTypeDC.BOOLEAN: + return str(param.value.boolean_value) + elif param.type == ParameterTypeDC.ARRAY: + if param.value.array_value is None: + raise ValueError("Array value is None") + values = [] + + for val in param.value.array_value: + if param.value.array_value_type == ParameterTypeDC.STRING: + values.append(val.string_value) + elif param.value.array_value_type == ParameterTypeDC.INTEGER: + values.append(str(val.integer_value)) + elif param.value.array_value_type == ParameterTypeDC.FLOAT: + values.append(str(val.float_value)) + elif param.value.array_value_type == ParameterTypeDC.BOOLEAN: + values.append(str(val.boolean_value)) + else: + raise NotImplementedError(f"Parameter type {param.value.array_value_type} not implemented") + + return values + else: + if param.type == ParameterTypeDC.UNKNOWN and param.value.string_value is not None: + return param.value.string_value + raise NotImplementedError(f"Parameter type {param.type} not implemented") + @dataclass class ProcedureStore: @@ -149,11 +192,13 @@ def procedure_decorator( input_file_var: str | None = None, input_file_type: FileTypeDC | None = None, export_file_type: FileTypeDC | None = None, + export_file_var: str | None = None, ) -> Callable: def wrapper(func: Callable) -> Callable: func.input_file_var = input_file_var func.input_file_type = input_file_type func.export_file_type = export_file_type + func.export_file_var = export_file_var if Typer is not None: app.command()(func) # Apply the app.command decorator return func diff --git a/src/frontend/src/components/node_editor/DynamicHandle.tsx b/src/frontend/src/components/node_editor/DynamicHandle.tsx index 42017c0d..2bc6bcda 100644 --- a/src/frontend/src/components/node_editor/DynamicHandle.tsx +++ b/src/frontend/src/components/node_editor/DynamicHandle.tsx @@ -1,7 +1,7 @@ // DynamicHandle.tsx import React from 'react'; import {Connection, Handle, HandleType, Position} from '@xyflow/react'; -import {FileObject} from "../../flatbuffers/wsock/file-object"; + interface DynamicHandleProps { id: string; diff --git a/src/frontend/src/components/node_editor/customProcedureNode.tsx b/src/frontend/src/components/node_editor/customProcedureNode.tsx index f6b385ef..cb5673d0 100644 --- a/src/frontend/src/components/node_editor/customProcedureNode.tsx +++ b/src/frontend/src/components/node_editor/customProcedureNode.tsx @@ -55,6 +55,9 @@ function ProcedureNode(props: { id: string, data: Record ); } else if (param.type() === ParameterType.ARRAY) { - let default_value = null; let values = []; if (paramDefaultValue) { let array_value_type = paramDefaultValue.arrayValueType() @@ -151,4 +153,4 @@ function ProcedureNode(props: { id: string, data: Record-' // we need to first get the parent div, then get the input value from the input element @@ -67,29 +67,54 @@ function extract_input_params(builder: Builder, params: string[], procedure: Pro Parameter.startParameter(builder); Parameter.addName(builder, param_name_str); Parameter.addValue(builder, input_value_buf); + Parameter.addType(builder, param_type); let input_param = Parameter.endParameter(builder); input_params.push(input_param) continue } else if (param_input.length > 1) { - let tuple_values = [] + let tuple_values = []; + let array_value_type = parameter.defaultValue?.arrayValueType; + for (let input of param_input) { - let input_value = input.value + let input_value = input.value; + // Start building a Value based on the arrayValueType (STRING, FLOAT, etc.) Value.startValue(builder); - if (parameter.value?.arrayValueType === ParameterType.STRING) { - let input_value_str = builder.createString(input_value) - Value.addStringValue(builder, input_value_str); + if (array_value_type === ParameterType.STRING) { + let input_value_str = builder.createString(input_value); + Value.addStringValue(builder, input_value_str); + } else if (array_value_type === ParameterType.FLOAT) { + Value.addFloatValue(builder, parseFloat(input_value)); + } else if (array_value_type === ParameterType.INTEGER) { + Value.addIntegerValue(builder, parseInt(input_value)); + } else if (array_value_type === ParameterType.BOOLEAN) { + Value.addBooleanValue(builder, input_value === "true"); } + + // End building Value and add to the tuple_values array let input_value_buf = Value.endValue(builder); - tuple_values.push(input_value_buf) + tuple_values.push(input_value_buf); + } + + let tuple_values_vector = Value.createArrayValueVector(builder, tuple_values); + // Create a vector for tuple_values + Value.startValue(builder); + Value.addArrayValue(builder, tuple_values_vector); + if (array_value_type){ + Value.addArrayValueType(builder, array_value_type); } + let array_value = Value.endValue(builder); + + // Start building the Parameter with the array of values Parameter.startParameter(builder); Parameter.addName(builder, param_name_str); - Parameter.addValue(builder, tuple_values_vector); + Parameter.addValue(builder, array_value); + Parameter.addType(builder, ParameterType.ARRAY); // Indicate that this is an array type parameter + let input_param = Parameter.endParameter(builder); - input_params.push(input_param) - continue + input_params.push(input_param); + continue; } // if param_div is an input element, get the value directly @@ -149,8 +174,9 @@ export function run_procedure(props: { id: string, data: Record