diff --git a/freecad_workbench/freecad/robossembler/Frames.py b/freecad_workbench/freecad/robossembler/Frames.py index 7b3599f..0c0ca8e 100644 --- a/freecad_workbench/freecad/robossembler/Frames.py +++ b/freecad_workbench/freecad/robossembler/Frames.py @@ -8,7 +8,11 @@ from .export_entities import export_coordinate_systems from .utils.freecad_processor import process_file from .project_validator import validate_project from .usecases.asm4parser_usecase import Asm4StructureParseUseCase - +from .constraints_operator import create_assembly_parameters, create_fastener_set, create_assembly_sequence, create_clearance_constraint, export_assembly_settings +from .geometric_feasibility_predicate.main import main as structure_analysis +from .asm_graph import main as asm_graph +from .autodock_generator import main as asm_layers +from .assembly_graph_generation import main as structure_graph if FreeCAD.GuiUp: import FreeCADGui @@ -331,6 +335,46 @@ spawnClassCommand("Publish_Project", {"Pixmap": str(os.path.join(ICONPATH, "publish.svg")), "MenuText": "Publish Project", "ToolTip": "Save and export project files"}) +spawnClassCommand("Create Assembly Parameters", + create_assembly_parameters, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Assembly Parameters", + "ToolTip": "Create Assembly Parameters"}) +spawnClassCommand("Create Fastener Set", + create_fastener_set, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Fastener Set", + "ToolTip": "Create Fastener Set"}) +spawnClassCommand("Compute Assembly Sequence", + structure_analysis, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Fastener Set", + "ToolTip": "Create Fastener Set"}) +spawnClassCommand("Create Assembly Sequence", + create_assembly_sequence, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Assembly Sequence", + "ToolTip": "Create Assembly Sequence"}) +spawnClassCommand("Export Assembly Settings", + export_assembly_settings, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Export Assembly Settings", + "ToolTip": "Export Assembly Settings"}) +spawnClassCommand("Create Assembly Layers", + asm_layers, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Assembly Layers", + "ToolTip": "Create Assembly Layers"}) +spawnClassCommand("Create Structure Graph", + structure_graph, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Structure Graph", + "ToolTip": "Create Structure Graph"}) +spawnClassCommand("Create Assembly Graph", + asm_graph, + {"Pixmap": str(os.path.join(ICONPATH, ".svg")), + "MenuText": "Create Assembly Graph", + "ToolTip": "Create Assembly Graph"}) diff --git a/freecad_workbench/freecad/robossembler/adjacency_matrix.json b/freecad_workbench/freecad/robossembler/adjacency_matrix.json new file mode 100644 index 0000000..999a349 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/adjacency_matrix.json @@ -0,0 +1,101 @@ +{ + "allParts": [ + "body_down", + "sol_gear", + "output_shaft", + "planet_gear", + "planet_gear003", + "planet_gear004", + "planet_gear005", + "planet_gear002", + "body_up", + "bolt", + "bolt2", + "bolt3", + "bolt4" + ], + "firstDetail": "body_down", + "matrix": { + "body_down": [ + "sol_gear", + "planet_gear", + "planet_gear003", + "planet_gear004", + "planet_gear005", + "planet_gear002", + "body_up", + "bolt", + "bolt2", + "bolt3", + "bolt4" + ], + "sol_gear": [ + "body_down", + "output_shaft", + "planet_gear", + "planet_gear003", + "planet_gear004", + "planet_gear005", + "planet_gear002" + ], + "output_shaft": [ + "sol_gear", + "planet_gear", + "planet_gear003", + "planet_gear004", + "planet_gear005", + "planet_gear002", + "body_up" + ], + "planet_gear": [ + "body_down", + "sol_gear", + "output_shaft" + ], + "planet_gear003": [ + "body_down", + "sol_gear", + "output_shaft" + ], + "planet_gear004": [ + "body_down", + "sol_gear", + "output_shaft" + ], + "planet_gear005": [ + "body_down", + "sol_gear", + "output_shaft" + ], + "planet_gear002": [ + "body_down", + "sol_gear", + "output_shaft" + ], + "body_up": [ + "body_down", + "output_shaft", + "bolt", + "bolt2", + "bolt3", + "bolt4" + ], + "bolt": [ + "body_down", + "body_up" + ], + "bolt2": [ + "body_down", + "body_up" + ], + "bolt3": [ + "body_down", + "body_up" + ], + "bolt4": [ + "body_down", + "body_up" + ] + }, + "matrixError": null +} \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/asm_graph.py b/freecad_workbench/freecad/robossembler/asm_graph.py new file mode 100644 index 0000000..dddb6fc --- /dev/null +++ b/freecad_workbench/freecad/robossembler/asm_graph.py @@ -0,0 +1,40 @@ +import json +import networkx as nx +import matplotlib.pyplot as plt +import os + +def load_sequences(filename): + with open(filename, 'r') as f: + data = json.load(f) + return data['sequences'] + +def create_graph(sequences): + G = nx.DiGraph() + #for seq in sequences: + for i in range(len(sequences) - 1): + G.add_edge(sequences[i], sequences[i + 1]) + return G + +def draw_graph_with_thumbnails(G, image_folder): + pos = {} + x = 1 + for node in G.nodes(): + pos[node] = (1, -x) # Устанавливаем позиции узлов по вертикали + x += 1 + + plt.figure(figsize=(8, 6)) + nx.draw(G, pos, with_labels=True, node_size=100, node_color='skyblue', font_size=10, font_weight='bold') + + # for node, (x, y) in pos.items(): + # image_path = f"{image_folder}/{node}.svg" + # if os.path.exists(image_path): + # img = plt.imread(image_path) + # plt.imshow(img, aspect='auto', extent=(x - 0.5, x + 0.5, y - 0.5, y + 0.5), zorder=0) + + plt.gca().invert_yaxis() # Инвертируем ось y для вертикального отображения + plt.show() + +def main(): + sequences = load_sequences('valid_sequences.json') + G = create_graph(sequences) + draw_graph_with_thumbnails(G, '/home/markvoltov/GitProjects/framework/test_models/img') diff --git a/freecad_workbench/freecad/robossembler/assembly_graph_generation.py b/freecad_workbench/freecad/robossembler/assembly_graph_generation.py index c1e8fc8..8265ac4 100644 --- a/freecad_workbench/freecad/robossembler/assembly_graph_generation.py +++ b/freecad_workbench/freecad/robossembler/assembly_graph_generation.py @@ -40,5 +40,3 @@ def main(): draw_graph(assembly_graph) -if __name__ == '__main__': - main() diff --git a/freecad_workbench/freecad/robossembler/cad_generation/env.json b/freecad_workbench/freecad/robossembler/cad_generation/env.json index 21f4910..7cbd24f 100644 --- a/freecad_workbench/freecad/robossembler/cad_generation/env.json +++ b/freecad_workbench/freecad/robossembler/cad_generation/env.json @@ -1,4 +1,4 @@ { - "cadFilePath": "/home/markvoltov/GitProjects/framework/test_models/cubes.FCStd", - "outPath": "/home/markvoltov/GitProjects/framework/test_models/" + "cadFilePath": "path/to/file", + "outPath": "out/path" } diff --git a/freecad_workbench/freecad/robossembler/constraints.json b/freecad_workbench/freecad/robossembler/constraints.json new file mode 100644 index 0000000..544d5da --- /dev/null +++ b/freecad_workbench/freecad/robossembler/constraints.json @@ -0,0 +1,6 @@ +[ + [ + "Extruder", + "Extruder001" + ] +] \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/constraints_operator.py b/freecad_workbench/freecad/robossembler/constraints_operator.py index 2833d56..0006731 100644 --- a/freecad_workbench/freecad/robossembler/constraints_operator.py +++ b/freecad_workbench/freecad/robossembler/constraints_operator.py @@ -86,6 +86,8 @@ def create_assembly_parameters(): assembly_settings_folder.addObject(assembly_parameters) + + #экспорт всех заданных настроек в общий файл json def export_assembly_settings(): doc = App.activeDocument() @@ -133,7 +135,7 @@ def export_assembly_settings(): with open(save_path, "w") as f: json.dump(data, f, indent=4) -#create_fastener_set() -#create_assembly_sequence() -#create_clearance_constraint() -export_assembly_settings() \ No newline at end of file +# create_fastener_set() +# create_assembly_sequence() +# create_clearance_constraint() +# export_assembly_settings() \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json index bcd23a5..0780dea 100644 --- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json +++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json @@ -1,5 +1,5 @@ { - "cadFilePath": "/home/markvoltov/GitProjects/framework/test_models/cubes.FCStd", - "outPath": "/home/markvoltov/GitProjects/framework/test_models/", + "cadFilePath": "path/to/file", + "outPath": "out/path", "objectIndentation": 0 } diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py index 690bc2a..b27b75f 100644 --- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py +++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py @@ -771,4 +771,4 @@ def main(): ExitFreeCadUseCase.call() -main() +#main() diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py deleted file mode 100644 index 19bce43..0000000 --- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py +++ /dev/null @@ -1,1313 +0,0 @@ -import FreeCAD as App -import uuid -import os -import json -from typing import List, Dict, Any, TypeVar, Callable, Type, cast -from itertools import repeat -from abc import ABC, abstractmethod -from dataclasses import dataclass -from typing import Any, TypeVar, Type, cast - - -T = TypeVar("T") - - -class BackgroundConsoleColors: - HEADER = "\033[95m" - OKBLUE = "\033[94m" - OKCYAN = "\033[96m" - OKGREEN = "\033[92m" - WARNING = "\033[93m" - FAIL = "\033[91m" - ENDC = "\033[0m" - BOLD = "\033[1m" - UNDERLINE = "\033[4m" - - -def from_float(x: Any) -> float: - assert isinstance(x, (float, int)) and not isinstance(x, bool) - return float(x) - - -def to_float(x: Any) -> float: - assert isinstance(x, float) - return x - - -def from_str(x: Any) -> str: - assert isinstance(x, str) - return x - - -def from_int(x: Any) -> int: - assert isinstance(x, int) and not isinstance(x, bool) - return x - - -def to_class(c: Type[T], x: Any) -> dict: - assert isinstance(x, c) - return cast(Any, x).to_dict() - - -@dataclass -class Env: - cadFilePath: str - outPath: str - solidBodyPadding: float - firstDetail: str - sequencesFixed: list[list[str]] - - @staticmethod - def from_dict(obj: Any) -> "Env": - assert isinstance(obj, dict) - cadFilePath = from_str(obj.get("cadFilePath")) - outPath = from_str(obj.get("outPath")) - solidBodyPadding = from_float(obj.get("solidBodyPadding")) - firstDetail = from_str(obj.get("firstDetail")) - sequencesFixed = [] - sequencesFixedParse = CoreList(obj.get("sequencesFixed")) - for el in sequencesFixedParse: - sequencesFixed.append(el) - - restrictionsOnFasteners = CoreList(obj.get("restrictionsOnFasteners")) - - for el in restrictionsOnFasteners: - for part in el["parts"]: - sequencesFixed.append([part, el["fastener"]]) - return Env( - cadFilePath, - outPath, - solidBodyPadding, - firstDetail, - sequencesFixed, - ) - - def to_dict(self) -> dict: - result: dict = {} - result["cadFilePath"] = from_str(self.cadFilePath) - result["outPath"] = from_str(self.outPath) - result["solidBodyPadding"] = from_float(self.solidBodyPadding) - result["firstDetail"] = from_str(self.firstDetail) - result["sequencesFixed"] = self.sequencesFixed - return result - - -class Either(ABC): - @abstractmethod - def isLeft(self): - pass - - @abstractmethod - def isRight(self): - pass - - @abstractmethod - def getLeft(self): - pass - - @abstractmethod - def getRight(self): - pass - - @abstractmethod - def mapLeft(self, lf): - pass - - @abstractmethod - def mapRight(self, rf): - pass - - @abstractmethod - def either(self, leftF, rightF): - pass - - -class Left(Either): - def __init__(self, lvalue): - self.lvalue = lvalue - - def isLeft(self): - return True - - def isRight(self): - return False - - def getLeft(self): - return self.lvalue - - def getRight(self): - raise Error("Cannot get a right value out of Left.") - - def mapLeft(self, lf): - return Left(lf(self.lvalue)) - - def mapRight(self, rf): - return Left(self.lvalue) - - def either(self, leftF, rightF): - return leftF(self.lvalue) - - -class Right(Either): - def __init__(self, rvalue): - self.rvalue = rvalue - - def isLeft(self): - return False - - def isRight(self): - return True - - def getLeft(self): - raise Error("Cannot get a left value out of Right.") - - def getRight(self): - return self.rvalue - - def mapLeft(self, lf): - return Right(self.rvalue) - - def mapRight(self, rf): - return Right(rf(self.rvalue)) - - def either(self, leftF, rightF): - return rightF(self.rvalue) - - -class CoreDict(dict): - def isEquivalentByKeys(self, checkDict) -> bool: - print(checkDict) - for key in self: - value = checkDict.get(key) - if value is None: - return False - - return True - - def isMatchByKeys(self, checkList): - if len(self) != len(checkList): - return False - - sorted_dict_keys = sorted(self.keys()) - sorted_list_elements = sorted(checkList) - - if sorted_dict_keys != sorted_list_elements: - missing_keys = [ - key for key in sorted_list_elements if key not in sorted_dict_keys - ] - print(f"Отсутствующие ключи в словаре: {missing_keys}") - return False - - return True - - -class CoreList(List): - def onlyTrue(self) -> bool: - for el in self: - if el is True: - return True - return False - - def onlyUniqueElementAppend(self, el): - if el is None: - return - if self.is_element_in_array(el) is False: - self.append(el) - pass - - def is_element_in_array(self, element): - return element in self - - def equal(self, array: list) -> bool: - if len(self) != len(array): - return False - return self.sort() == array.sort() - - def isConstrainsString(self) -> bool: - for el in self: - if isinstance(el, str): - return True - return False - - def indexedPriorities(self, lowerPriority, highPriority) -> bool: - try: - lowerIndex = self.index(lowerPriority) - highPriorityIndex = self.index(highPriority) - return lowerIndex < highPriorityIndex - except: - return False - - def getAllString(self) -> list[str]: - return list(filter(lambda x: isinstance(x, str), self)) - - def onlyUnique(self) -> list: - result = [] - [result.append(x) for x in self if x not in self] - return result - - def spreadArray(self) -> "CoreList": - unpacked_array = CoreList([]) - for el in self: - if isinstance(el, list): - unpacked_array.extend(el) - else: - unpacked_array.append(el) - return unpacked_array - - -def isInListRange(listIn, index): - try: - listIn[index] - return False - except: - return True - - -class AllSequences: - all_sequences = None - adj_matrix = None - topologyIds = None - adj_matrix_names = None - - def __init__(self, adj_matrix, restrictions: list[str]) -> None: - self.adj_matrix = adj_matrix - self.all_possible_sequences(self.adj_matrix) - self.matrix_by_name() - if restrictions.__len__() != 0: - self.restrictionsValidate(restrictions) - pass - - def restrictionsValidate(self, restrictions: CoreList[str]): - filterMatrix = CoreList() - - for el in self.adj_matrix_names: - result = False - for restraint in restrictions: - result = CoreList(el).indexedPriorities(restraint[0], restraint[1]) - if result: - filterMatrix.onlyUniqueElementAppend(el) - self.adj_matrix_names = filterMatrix - pass - - def matrix_by_name(self): - result = self.all_sequences - inc = 0 - for matrix in self.all_sequences: - for index in range(len(matrix)): - result[inc][index] = CoreList( - filter( - lambda el: el.get("number") == matrix[index] + 1, - self.topologyIds, - ) - )[0].get("name") - inc += 1 - self.adj_matrix_names = result - pass - - def find_all_sequences(self, adj_matrix): - sequences = [] - num_vertices = len(adj_matrix) - - def dfs(vertex, sequence): - sequence.append(vertex) - - if len(sequence) == num_vertices: - sequences.append(sequence) - return - - for i in range(num_vertices): - if adj_matrix[vertex][i] == 1 and i not in sequence: - dfs(i, sequence.copy()) - - for i in range(num_vertices): - dfs(i, []) - - self.all_sequences = sequences - - def findId(self, listMatrix, id): - def filter_odd_num(in_num): - if in_num["name"] == id: - return True - else: - return False - - return list(filter(filter_odd_num, listMatrix))[0]["number"] - - def iter_paths(self, adj, min_length=6, path=None): - if not path: - for start_node in range(len(adj)): - yield from self.iter_paths(adj, min_length, [start_node]) - else: - if len(path) >= min_length: - yield path - if path[-1] in path[:-1]: - return - current_node = path[-1] - for next_node in range(len(adj[current_node])): - if adj[current_node][next_node] == 1: - yield from self.iter_paths(adj, min_length, path + [next_node]) - - def allUnique(self, x): - seen = set() - return not any(i in seen or seen.add(i) for i in x) - - def all_possible_sequences(self, matrix): - topologyIds = [] - topologyMatrixNumber = {} - inc = 0 - for k, v in matrix.items(): - inc += 1 - topologyIds.append({"name": k, "number": inc}) - inc = 0 - for k, v in matrix.items(): - inc += 1 - topologyMatrixNumber[inc] = list( - map(lambda el: self.findId(topologyIds, el), v) - ) - self.topologyIds = topologyIds - adj = [] - matrixSize = matrix.keys().__len__() - inc = 0 - for k, v in topologyMatrixNumber.items(): - adj.append(list(repeat(0, matrixSize))) - for el in v: - adj[inc][el - 1] = 1 - inc += 1 - return self.find_all_sequences(adj) - - -class VectorModel: - x: float - y: float - z: float - - def __init__(self, cadVector) -> None: - self.x = cadVector[0] - self.y = cadVector[1] - self.z = cadVector[2] - pass - - def toFreeCadVector(self): - return App.Vector(self.x, self.y, self.z) - - def toString(self): - return str("x:" + str(self.x) + "y:" + str(self.y) + "z:" + str(self.z)) - - -class FreeCadRepository: - _solids = [] - - def openDocument(self, path: str): - App.open("" + path) - - def closeIfOpenDocument(self): - # print('Проверка на документ') - try: - if App.ActiveDocument is not None: - # print(App.ActiveDocument.name + "закрыт") - # App.closeDocument(App.ActiveDocument.name) - App.ActiveDocument.clearDocument() - except Exception as e: - print(e) - - def getAllLabelsSolids(self) -> List[str]: - return list(map(lambda el: el.Label, self.getAllSolids())) - - def isAllObjectsSolids(self) -> List[str]: - result = [] - for part in App.ActiveDocument.Objects: - if self.is_object_solid(part) is False: - result.append(part.Label) - return result - - def objectSetPosition(self, solid, cadVector): - solid.Placement.Base = cadVector - pass - - def objectGetPosition(self, solid) -> VectorModel: - return VectorModel(cadVector=solid.Placement.Base) - - def isObjectIntersections(self, part) -> str: - result = [] - for solid in self.getAllSolids(): - if solid.ID != part.ID: - collisionResult: int = int(part.Shape.distToShape(solid.Shape)[0]) - if collisionResult == 0: - result.append(solid.Label) - if result.__len__() == 0: - return None - return result - - def objectHasTouches(self, part, solidBodyPadding: float) -> List[str]: - try: - positionVector = self.objectGetPosition(part) - result = CoreList() - result.append(self.isObjectIntersections(part=part)) - if solidBodyPadding != 0 and solidBodyPadding != None: - result.append( - self.axis_movement_and_intersections_observer( - positionVector=positionVector, - alongAxis="x", - solidBodyPadding=solidBodyPadding, - part=part, - ) - ) - result.append( - self.axis_movement_and_intersections_observer( - positionVector=positionVector, - alongAxis="y", - solidBodyPadding=solidBodyPadding, - part=part, - ) - ) - result.append( - self.axis_movement_and_intersections_observer( - positionVector=positionVector, - alongAxis="z", - solidBodyPadding=solidBodyPadding, - part=part, - ) - ) - spreadArr = result.spreadArray() - if spreadArr.isConstrainsString(): - return spreadArr.getAllString() - return None - except Exception as error: - print(error) - return None - - def axis_movement_and_intersections_observer( - self, - positionVector: VectorModel, - alongAxis: str, - solidBodyPadding: float, - part, - ) -> bool: - result = CoreList() - # UP - positionVector.__setattr__( - alongAxis, - positionVector.__getattribute__(alongAxis) + solidBodyPadding, - ) - self.objectSetPosition(part, positionVector.toFreeCadVector()) - # result.onlyUniqueElementAppend(self.isObjectIntersections(part=part)) - result.extend(self.isObjectIntersections(part=part)) - # RESET UP CHANGES - positionVector.__setattr__( - alongAxis, - positionVector.__getattribute__(alongAxis) - solidBodyPadding, - ) - self.objectSetPosition(part, positionVector.toFreeCadVector()) - - # DOWN - positionVector.__setattr__( - alongAxis, - positionVector.__getattribute__(alongAxis) - solidBodyPadding, - ) - self.objectSetPosition(part, positionVector.toFreeCadVector()) - # CHECK DOWN INTERSECTIONS - # result.onlyUniqueElementAppend(self.isObjectIntersections(part=part)) - result.extend(self.isObjectIntersections(part=part)) - - # RESET DOWN CHANGES - positionVector.__setattr__( - alongAxis, - positionVector.__getattribute__(alongAxis) + solidBodyPadding, - ) - self.objectSetPosition(part, positionVector.toFreeCadVector()) - if result.__len__() == 0: - return None - - return result.onlyUnique() - - def getAllSolids(self): - if self._solids.__len__() == 0: - for part in App.ActiveDocument.Objects: - if self.is_object_solid(part): - self._solids.append(part) - - return self._solids - - def is_object_solid(self, obj): - if not isinstance(obj, App.DocumentObject): - return False - if hasattr(obj, "Group"): - return False - - if not hasattr(obj, "Shape"): - return False - if not hasattr(obj.Shape, "Solids"): - return False - - if len(obj.Shape.Solids) == 0: - return False - - return True - - -T = TypeVar("T") - - -def from_list(f: Callable[[Any], T], x: Any) -> List[T]: - assert isinstance(x, list) - return [f(y) for y in x] - - -def from_str(x: Any) -> str: - assert isinstance(x, str) - return x - - -def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]: - assert isinstance(x, dict) - return {k: f(v) for (k, v) in x.items()} - - -def to_class(c: Type[T], x: Any) -> dict: - assert isinstance(x, c) - return cast(Any, x).to_dict() - - -# Вспомогательный класс который делает генрацию JSON на основе пайтон обьектов - - -class AdjacencyMatrix: - matrixError: Dict[str, str] = {} - all_parts: List[str] - first_detail: str - matrix: Dict[str, List[str]] - - fileName = "adjacency_matrix.json" - - def __init__( - self, all_parts: List[str], first_detail: str, matrix: Dict[str, List[str]] - ) -> None: - self.all_parts = all_parts - self.first_detail = first_detail - self.matrix = matrix - self.validateMatrix() - - def matrixToFileSystem(self, path: str): - FileSystemRepository.writeFile( - json.dumps(self.to_dict(), ensure_ascii=False, indent=4), - path, - AdjacencyMatrix.fileName, - ) - pass - - def sequencesToFileSystem(self, path: str, restrictions: list[str]): - FileSystemRepository.writeFile( - json.dumps( - {"sequences": AllSequences(self.matrix, restrictions).adj_matrix_names}, - ensure_ascii=False, - indent=4, - ), - path, - "sequences.json", - ), - pass - - def matrixGetUniqueContact(self): - detailsToCheck = [] - detailsHashCheck = {} - for k, v in self.matrix.items(): - for el in v: - if el != k: - hash = to_ascii_hash(k + el) - if detailsHashCheck.get(hash) == None: - detailsHashCheck[hash] = hash - detailsToCheck.append({"child": el, "parent": k}) - return detailsToCheck - - def whatPlaceLeadingPartIndex(self): - i = 0 - for el in self.matrix: - if el == self.first_detail: - return i - i = +1 - - def validateMatrix(self): - for el in self.all_parts: - if self.matrix.get(el) == None: - self.matrixError[el] = "Not found adjacency " + el - - @staticmethod - def from_dict(obj: Any) -> "AdjacencyMatrix": - assert isinstance(obj, dict) - - all_pars = from_list(from_str, obj.get("allParts")) - first_detail = from_str(obj.get("firstDetail")) - matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix")) - - return AdjacencyMatrix(all_pars, first_detail, matrix) - - def to_dict(self) -> dict: - result: dict = {} - result["allParts"] = from_list(from_str, self.all_parts) - result["firstDetail"] = from_str(self.first_detail) - result["matrix"] = from_dict(lambda x: from_list(from_str, x), self.matrix) - if self.matrixError.values().__len__() == 0: - result["matrixError"] = None - else: - result["matrixError"] = self.matrixError - return result - - def getDictMatrix(self) -> dict: - result = {} - - for k, v in self.matrix.items(): - result[k] = {} - for el in v: - result[k][el] = el - - return result - - -def adjacency_matrix_from_dict(s: Any) -> AdjacencyMatrix: - return AdjacencyMatrix.from_dict(s) - - -def adjacency_matrix_to_dict(x: AdjacencyMatrix) -> Any: - return to_class(AdjacencyMatrix, x) - - -# Вспомогательный класс для работы с Freecad - - -class FreeCadMetaModel(object): - def __init__(self, label, vertex) -> None: - self.label = label - self.vertex = vertex - - -collision_squares_labels = [] - - -class MeshGeometryCoordinateModel(object): - # Получение геометрии мешей - def __init__( - self, - x, - y, - z, - label, - ): - self.x = x - self.y = y - self.z = z - self.label = label - self.cadLabel = "" - - def initializePrimitivesByCoordinate(self, detailSquares): - uuidDoc = str(uuid.uuid1()) - App.ActiveDocument.addObject("Part::Box", "Box") - App.ActiveDocument.ActiveObject.Label = uuidDoc - App.ActiveDocument.recompute() - part = App.ActiveDocument.getObjectsByLabel(uuidDoc)[0] - collision_squares_labels.append(uuidDoc) - part.Width = 2 - part.Height = 2 - part.Length = 2 - part.Placement = App.Placement( - App.Vector(self.x - 1, self.y - 1, self.z - 1), - App.Rotation(App.Vector(0.00, 0.00, 1.00), 0.00), - ) - if detailSquares.get(self.label) is None: - detailSquares[self.label] = [] - detailSquares[self.label].append(self) - self.cadLabel = uuidDoc - App.ActiveDocument.recompute() - - -class FileSystemRepository: - def readJSON(path: str): - return json.loads((open(path)).read()) - - def writeFile(data, filePath, fileName): - file_to_open = filePath + fileName - - f = open(file_to_open, "w", encoding="utf8") - - f.write(data) - - def readFile(path: str): - return open(path).read() - - def readFilesTypeFolder(pathFolder: str, fileType=".json"): - filesJson = list( - filter( - lambda x: x[-fileType.__len__() :] == fileType, os.listdir(pathFolder) - ) - ) - return filesJson - - -class GetAllPartsLabelsUseCase: - # Получение всех названий деталей - def call(self): - parts = [] - for part in FreeCadRepository().getAllSolids(): - parts.append(part.Label) - return parts - - -def isUnique(array, element): - for i in array: - if i == element: - return False - - return True - - -class GetCollisionAtPrimitiveUseCase(object): - # Получение колизий примитивов - def call(self, freeCadMetaModels, detailSquares) -> Dict[str, List[str]]: - matrix: Dict[str, List[str]] = {} - for model in freeCadMetaModels: - activePart = App.ActiveDocument.getObjectsByLabel(model.label)[0] - for key in detailSquares: - if model.label != key: - for renderPrimitive in detailSquares[key]: - primitivePart = App.ActiveDocument.getObjectsByLabel( - renderPrimitive.cadLabel - )[0] - collisionResult: int = int( - activePart.Shape.distToShape(primitivePart.Shape)[0] - ) - if collisionResult == 0: - if matrix.get(model.label) == None: - matrix[model.label] = [renderPrimitive.label] - else: - if isUnique(matrix[model.label], renderPrimitive.label): - matrix[model.label].append(renderPrimitive.label) - return matrix - - -class GetFirstDetailUseCase: - # Получение первой детали - def call(self): - return FreeCadRepository().getAllSolids()[0].Label - - -class GetPartPrimitiveCoordinatesUseCase(object): - # Получение координат примитивов - def call(self, freeCadMetaModels): - meshCoordinates: list[MeshGeometryCoordinateModel] = [] - for model in freeCadMetaModels: - vertexesDetail = model.vertex - labelDetail = model.label - for coords in vertexesDetail: - detailVertex = MeshGeometryCoordinateModel( - coords.X, - coords.Y, - coords.Z, - labelDetail, - ) - meshCoordinates.append(detailVertex) - - return meshCoordinates - - -class InitPartsParseUseCase: - # Инициализация парсинга - def call(self): - product_details = [] - for part in FreeCadRepository().getAllSolids(): - if part is not None: - model = FreeCadMetaModel(part.Label, part.Shape.Vertexes) - if model is not None: - product_details.append(model) - return product_details - - -class RenderPrimitiveUseCase(object): - # Рендеринг премитивов - def call( - self, meshModels: list[MeshGeometryCoordinateModel], detailSquares - ) -> None: - for mesh in meshModels: - mesh.initializePrimitivesByCoordinate(detailSquares) - - -class ClearWorkSpaceDocumentUseCase(object): - # Очистка рабочего простарнства - def call(self, detailSquares): - for key in detailSquares: - for renderPrimitive in detailSquares[key]: - primitivePart = App.ActiveDocument.getObjectsByLabel( - renderPrimitive.cadLabel - )[0] - App.ActiveDocument.removeObject(primitivePart.Name) - - -class RenderPrimitivesScenario(object): - def __init__( - self, - initPartsParseUseCase: InitPartsParseUseCase, - getPartPrimitiveCoordinatesUseCase: GetPartPrimitiveCoordinatesUseCase, - renderPrimitiveUseCase: RenderPrimitiveUseCase, - getCollisionAtPrimitives: GetCollisionAtPrimitiveUseCase, - clearWorkSpaceDocument: ClearWorkSpaceDocumentUseCase, - ) -> None: - self.initPartsParseUseCase = initPartsParseUseCase - self.getPartPrimitiveCoordinatesUseCase = getPartPrimitiveCoordinatesUseCase - self.renderPrimitiveUseCase = renderPrimitiveUseCase - self.getCollisionAtPrimitives = getCollisionAtPrimitives - self.clearWorkSpaceDocument = clearWorkSpaceDocument - - def call(self) -> None: - meshCoordinates = [] - detailSquares = {} - parts = self.initPartsParseUseCase.call() - meshCoordinates = self.getPartPrimitiveCoordinatesUseCase.call(parts) - self.renderPrimitiveUseCase.call(meshCoordinates, detailSquares) - matrix = self.getCollisionAtPrimitives.call(parts, detailSquares) - self.clearWorkSpaceDocument.call(detailSquares) - return matrix - - -class ClearWorkSpaceDocumentUseCase(object): - # Очистака рабочего пространства - def call(self, detailSquares): - for key in detailSquares: - for renderPrimitive in detailSquares[key]: - primitivePart = App.ActiveDocument.getObjectsByLabel( - renderPrimitive.cadLabel - )[0] - App.ActiveDocument.removeObject(primitivePart.Name) - - -class CadAdjacencyMatrix: - # Матрица основанная на соприкосновении примитива с обьектами - def primitiveMatrix(self): - # Получение матрицы - matrix = RenderPrimitivesScenario( - InitPartsParseUseCase(), - GetPartPrimitiveCoordinatesUseCase(), - RenderPrimitiveUseCase(), - GetCollisionAtPrimitiveUseCase(), - ClearWorkSpaceDocumentUseCase(), - ).call() - return AdjacencyMatrix( - all_parts=GetAllPartsLabelsUseCase().call(), - first_detail=GetFirstDetailUseCase().call(), - matrix=matrix, - ) - - # Матрица основанная на соприкосновении обьектов - - def matrixBySurfaces(self): - matrix = {} - for part in FreeCadRepository().getAllSolids(): - matrix[part.Label] = [] - for nextPart in FreeCadRepository().getAllSolids(): - if part.Label != nextPart.Label: - # Вычисление соприконсоновения площади деталей - collisionResult: int = int( - part.Shape.distToShape(nextPart.Shape)[0] - ) - - if collisionResult == 0: - matrix[part.Label].append(nextPart.Label) - - return AdjacencyMatrix( - all_parts=GetAllPartsLabelsUseCase().call(), - first_detail=GetFirstDetailUseCase().call(), - matrix=matrix, - ) - - -def reduce(function, iterable, initializer=None): - it = iter(iterable) - if initializer is None: - value = next(it) - else: - value = initializer - for element in it: - value = function(value, element) - return value - - -def to_ascii_hash(text): - ascii_values = [ord(character) for character in text] - return reduce(lambda x, y: x + y, ascii_values) - - -class IntersectionComputedUseCase: - def call(parts): - App.activeDocument().addObject("Part::MultiCommon", "Common") - App.activeDocument().Common.Shapes = [parts[0], parts[1]] - App.activeDocument().getObject("Common").ViewObject.ShapeColor = getattr( - parts[0].getLinkedObject(True).ViewObject, - "ShapeColor", - App.activeDocument().getObject("Common").ViewObject.ShapeColor, - ) - App.activeDocument().getObject("Common").ViewObject.DisplayMode = getattr( - parts[0].getLinkedObject(True).ViewObject, - "DisplayMode", - App.activeDocument().getObject("Common").ViewObject.DisplayMode, - ) - App.ActiveDocument.recompute() - area = App.activeDocument().getObject("Common").Shape.Area - App.ActiveDocument.removeObject("Common") - return area - - -class ErrorStringModel: - def __init__(self, error: str) -> None: - self.error = error - pass - - error: str - - def toString(self) -> str: - return json.dumps( - { - "error": self.error, - }, - ensure_ascii=False, - indent=4, - ) - - def toFileSystem(self, path: str): - return ( - FileSystemRepository.writeFile(self.toString(), path, "error.json"), - ExitFreeCadUseCase.call(), - ) - - -class IsAllObjectSolidsCheckUseCase: - def call() -> Either: - result = FreeCadRepository().isAllObjectsSolids() - if result.__len__() == 0: - return Left(None) - - return Right( - ErrorStringModel(error="Is not solid objects: " + ",".join(result)) - ) - - -class CheckObjectHasTouchesUseCase: - freeCadRepository = FreeCadRepository() - - def call(self, solidBodyPadding: float) -> Either: - try: - errorResult = [] - matrix = {} - for part in self.freeCadRepository.getAllSolids(): - matrix[part.Label] = [] - touches = FreeCadRepository().objectHasTouches( - part=part, solidBodyPadding=solidBodyPadding - ) - matrix[part.Label].extend(touches) - if errorResult.__len__() == 0: - return Left( - AdjacencyMatrix( - all_parts=self.freeCadRepository.getAllLabelsSolids(), - first_detail=GetFirstDetailUseCase().call(), - matrix=matrix, - ) - ) - else: - return Right( - ErrorStringModel( - error="Solids bodies have no recounts: " + ",".join(errorResult) - ) - ) - except Exception as error: - print(error) - print("CheckObjectHasTouchesUseCase error") - return Right(ErrorStringModel(error="CheckObjectHasTouchesUseCase error")) - - -class CheckCadIntersectionObjects: - report = [] - - def call() -> bool: - FreeCadRepository().getAllSolids() - return False - - -class ExitFreeCadUseCase: - def call(): - import FreeCADGui as Gui - - App.ActiveDocument.clearDocument() - freecadQTWindow = Gui.getMainWindow() - freecadQTWindow.close() - - -# class CheckValidIntersectionUseCase: -# def call() -> ErrorStringModel: -# for part in FreeCadRepository().getAllSolids(): -# print(part) -# FreeCadRepository().obj -# pass - - -class EnvReaderUseCase: - def call() -> Either: - try: - return Left(Env.from_dict(FileSystemRepository.readJSON("env.json"))) - except: - print("env reader error") - return Right(None) - - -class OpenFreeCadDocumentUseCase: - def call(path: str) -> Either: - try: - FreeCadRepository().closeIfOpenDocument() - FreeCadRepository().openDocument(path) - return Left(None) - except: - print("OpenFreeCadDocumentUseCase error") - return Right(None) - - -class IntersectionGeometryUseCase: - def call(contacts, path): - intersection_geometry = {"status": True, "recalculations": None} - for el in contacts: - child = App.ActiveDocument.getObjectsByLabel(el.get("child"))[0] - parent = App.ActiveDocument.getObjectsByLabel(el.get("parent"))[0] - area = IntersectionComputedUseCase.call([child, parent]) - if area != 0.0: - if intersection_geometry.get("recalculations") == None: - intersection_geometry["status"] = False - intersection_geometry["recalculations"] = [] - intersection_geometry["recalculations"].append( - {"area": area, "connect": el.get("child") + " " + el.get("parent")} - ) - FileSystemRepository.writeFile( - json.dumps(intersection_geometry, ensure_ascii=False, indent=4), - path, - "intersection_geometry.json", - ) - - -def main(): - try: - EnvReaderUseCase.call().either( - leftF=lambda environment: ( - OpenFreeCadDocumentUseCase.call(environment.cadFilePath).either( - leftF=lambda _: ( - IsAllObjectSolidsCheckUseCase.call().either( - leftF=lambda _: ( - CheckObjectHasTouchesUseCase() - .call(environment.solidBodyPadding) - .either( - leftF=lambda adjaxedMatrix: ( - adjaxedMatrix.sequencesToFileSystem( - environment.outPath, - environment.sequencesFixed, - ), - IntersectionGeometryUseCase.call( - adjaxedMatrix.matrixGetUniqueContact(), - environment.outPath, - ), - adjaxedMatrix.matrixToFileSystem( - environment.outPath, - ), - ExitFreeCadUseCase.call(), - ), - rightF=lambda error: error.toFileSystem( - environment.outPath - ), - ), - ), - rightF=lambda error: error.toFileSystem( - environment.outPath - ), - ), - ), - rightF=lambda error: print(error), - ), - ), - rightF=lambda error: print(error), - ) - - except Exception as error: - print(error) - ExitFreeCadUseCase.call() - - -# main() - - -class ReadFileSystemAndGetInstanceModelUseCase: - def call(self, model, path): - if hasattr(model, "from_dict") is False: - return Right( - "ReadFileSystemAndGetInstanceModelUseCase error:" - + model - + "is not have method" - + "from_dict()" - ) - try: - return Left(model.from_dict(FileSystemRepository.readJSON(path))) - except: - error = str(model) + " " + "from dict error " + "path: " + path - print("ReadFileSystemAndGetInstanceModelUseCase error" + error) - return Right(error) - pass - - -class FreeCadTest: - testName: str - - def testHelper(self, testResult): - if isinstance(testResult, bool) is False: - print( - BackgroundConsoleColors.WARNING, - self.testName - + " expected a value of type Boolean, returned a value of type below ", - ) - print(testResult) - return - if testResult: - print(BackgroundConsoleColors.OKGREEN, self.testName + "is complete!") - else: - print(BackgroundConsoleColors.FAIL, self.testName + " is Error!") - pass - - -class FreeCadASPGenerationTestController(FreeCadTest): - testName: str - - def __init__(self, testName: str) -> None: - self.testName = testName - pass - - def test( - self, - assertFn, - documentPath: str, - modelName: str, - model, - execComposition, - outPath=os.path.dirname(__file__) + "/out/", - ): - try: - OpenFreeCadDocumentUseCase.call(documentPath).either( - leftF=lambda _: ( - execComposition(""), - ReadFileSystemAndGetInstanceModelUseCase() - .call(model=model, path=outPath + modelName) - .either( - leftF=lambda model: ( - self.testHelper( - assertFn(model), - ) - ), - rightF=lambda error: print(error), - ), - ), - rightF=lambda error: print(error), - ) - except Exception as inst: - print("FreeCadASPGenerationTestController error") - print(inst) - pass - - -def test(): - try: - mocksFolder = os.path.dirname(__file__) + "/mocks/" - outFolder = os.path.dirname(__file__) + "/out/" - - # FreeCadASPGenerationTestController("test adjaxed matrix simple cube").test( - # assertFn=lambda model: CoreList(model.all_parts).equal(["Куб", "Куб001"]), - # execComposition=lambda _: ( - # CheckObjectHasTouchesUseCase() - # .call(0) - # .either( - # leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), - # rightF=lambda error: print(error), - # ) - # ), - # documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd", - # modelName=AdjacencyMatrix.fileName, - # model=AdjacencyMatrix, - # ) - - FreeCadASPGenerationTestController( - "test adjaxed matrix vs structure of document" - ).test( - assertFn=lambda model: CoreDict(model.matrix).isEquivalentByKeys( - { - "Бутылочный домкрат 4т_Гильза": [ - "Бутылочный домкрат 4т_Тяга", - "Бутылочный домкрат 4т_Тяга001", - "Бутылочный домкрат 4т_Шток насоса", - "Бутылочный домкрат 4т_Шток", - "Бутылочный домкрат 4т_Вентиль", - ], - "Бутылочный домкрат 4т_Тяга": [ - "Бутылочный домкрат 4т_Гильза", - "Бутылочный домкрат 4т_Тяга001", - "Бутылочный домкрат 4т_Коромысло", - ], - "Бутылочный домкрат 4т_Тяга001": [ - "Бутылочный домкрат 4т_Гильза", - "Бутылочный домкрат 4т_Тяга", - "Бутылочный домкрат 4т_Коромысло", - ], - "Бутылочный домкрат 4т_Шток насоса": [ - "Бутылочный домкрат 4т_Гильза", - "Бутылочный домкрат 4т_Коромысло", - ], - "Бутылочный домкрат 4т_Коромысло": [ - "Бутылочный домкрат 4т_Тяга", - "Бутылочный домкрат 4т_Тяга001", - "Бутылочный домкрат 4т_Шток насоса", - ], - "Бутылочный домкрат 4т_Шток": [ - "Бутылочный домкрат 4т_Гильза", - "Бутылочный домкрат 4т_Винт штока", - ], - "Бутылочный домкрат 4т_Винт штока": ["Бутылочный домкрат 4т_Шток"], - "Бутылочный домкрат 4т_Вентиль": ["Бутылочный домкрат 4т_Гильза"], - } - ), - execComposition=lambda _: ( - CheckObjectHasTouchesUseCase() - .call(0) - .either( - leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), - rightF=lambda error: print(error), - ) - ), - documentPath=mocksFolder + "bottle_jack.FCStd", - modelName=AdjacencyMatrix.fileName, - model=AdjacencyMatrix, - ) - - FreeCadASPGenerationTestController( - "test adjacency matrix keys vs allparts" - ).test( - assertFn=lambda model: CoreDict(model.matrix).isMatchByKeys( - ["Куб", "Куб001"] - ), - execComposition=lambda _: ( - CheckObjectHasTouchesUseCase() - .call(0) - .either( - leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), - rightF=lambda error: print(error), - ) - ), - documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd", - modelName=AdjacencyMatrix.fileName, - model=AdjacencyMatrix, - ) - - ExitFreeCadUseCase.call() - except: - print("test error") - ExitFreeCadUseCase.call() - pass - - -# test() - diff --git a/freecad_workbench/freecad/robossembler/get_sequences.py b/freecad_workbench/freecad/robossembler/get_sequences.py new file mode 100644 index 0000000..7d268bf --- /dev/null +++ b/freecad_workbench/freecad/robossembler/get_sequences.py @@ -0,0 +1,61 @@ +import json +import networkx as nx + +def load_data(file_path): + with open(file_path, 'r') as file: + return json.load(file) + +def create_graph(data): + G = nx.Graph() + for part in data['allParts']: + G.add_node(part) + for part, connections in data['matrix'].items(): + for connected_part in connections: + G.add_edge(part, connected_part) + return G + +def find_leaf_nodes(graph, central_node): + leaf_nodes = [] + for node in graph.nodes: + if node != central_node and graph.degree(node) == 1: + leaf_nodes.append(node) + return leaf_nodes + +def find_all_paths(graph, start_node, end_node): + try: + return list(nx.all_simple_paths(graph, start_node, end_node)) + except nx.NetworkXNoPath: + return [] + +def load_constraints(file_path): + with open(file_path, 'r') as file: + return json.load(file) + +def is_valid_sequence(sequence, constraints): + for constraint in constraints: + if constraint[0] in sequence and constraint[1] in sequence: + if sequence.index(constraint[0]) > sequence.index(constraint[1]): + return False + return True + +def save_sequences(sequences, file_path): + with open(file_path, 'w') as file: + json.dump(sequences, file, indent=4) + +data = load_data('adjacency_matrix.json') +constraints = load_constraints('constraints.json') +all_parts = data['allParts'] +graph = create_graph(data) +first_detail = data['firstDetail'] +leaf_nodes = find_leaf_nodes(graph, first_detail) + +all_sequences = [] +for leaf in leaf_nodes: + paths = find_all_paths(graph, leaf, first_detail) + for path in paths: + if set(path) == set(all_parts) and is_valid_sequence(path, constraints): + all_sequences.append(path) + +save_sequences(all_sequences, 'valid_sequences.json') + +print(f"Найдено {len(all_sequences)} допустимых последовательностей сборки.") diff --git a/freecad_workbench/freecad/robossembler/graph_visualisation.py b/freecad_workbench/freecad/robossembler/graph_visualisation.py new file mode 100644 index 0000000..9baf51c --- /dev/null +++ b/freecad_workbench/freecad/robossembler/graph_visualisation.py @@ -0,0 +1,26 @@ +import json +import networkx as nx +import matplotlib.pyplot as plt + +# Загружаем данные из файла +with open('adjacency_matrix.json', 'r') as file: + data = json.load(file) + +# Создаем пустой граф +G = nx.Graph() + +# Добавляем узлы +for part in data['allParts']: + G.add_node(part) + +# Добавляем ребра +for part, connections in data['matrix'].items(): + for connected_part in connections: + G.add_edge(part, connected_part) + +# Визуализируем граф +plt.figure(figsize=(10, 8)) +pos = nx.spring_layout(G) +nx.draw(G, pos, with_labels=True, node_size=100, node_color='lightblue', font_size=10, font_weight='bold', edge_color='gray') +plt.title('Graph of Part Connections') +plt.show() diff --git a/freecad_workbench/freecad/robossembler/init_gui.py b/freecad_workbench/freecad/robossembler/init_gui.py index ef93952..5289d6a 100644 --- a/freecad_workbench/freecad/robossembler/init_gui.py +++ b/freecad_workbench/freecad/robossembler/init_gui.py @@ -38,8 +38,7 @@ class Robossembler(Gui.Workbench): self.framecommands = [ "BoMGeneration", - "FrameCommand", - + "FrameCommand", "SelectedPartFrameCommand", "AllPartFramesCommand", "FeatureFrameCommand" @@ -52,8 +51,21 @@ class Robossembler(Gui.Workbench): "Validate_Project", "Publish_Project" ] + self.asmcommands = [ + "Create Assembly Parameters", + "Create Fastener Set", + "Create Assembly Sequence", + "Export Assembly Settings", + "Compute Assembly Sequence", + "Create Assembly Layers", + "Create Structure Graph", + "Create Assembly Graph" + + ] + self.appendToolbar(f"{__class__.__name__} Frames", self.framecommands) self.appendToolbar(f"{__class__.__name__} Tools", self.toolcommands) + self.appendToolbar(f"{__class__.__name__} Assembly Setup", self.asmcommands) App.Console.PrintMessage(translate("Console", "Switching to robossembler") + "\n") diff --git a/freecad_workbench/freecad/robossembler/sequences.json b/freecad_workbench/freecad/robossembler/sequences.json index 07d5216..e69de29 100644 --- a/freecad_workbench/freecad/robossembler/sequences.json +++ b/freecad_workbench/freecad/robossembler/sequences.json @@ -1,10 +0,0 @@ -{ - "sequences": [ - - "body_down", - "sol_gear", - "output_shaft", - "planet_gear", - "planet_gear002" - ] -} \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/valid_sequences.json b/freecad_workbench/freecad/robossembler/valid_sequences.json new file mode 100644 index 0000000..b0c3c75 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/valid_sequences.json @@ -0,0 +1,14 @@ +{"sequences":[ + "PTFE Tube Nut M6 v1", + "PTFE Tube Nut M6 v002", + "Extruder002", + "Extruder", + "Extruder001", + "91239A140_Button Head Hex Drive Screw", + "Hotend002", + "Hotend001", + "Hotend003", + "Extruder003", + "Extruder004", + "Hotend" +]} \ No newline at end of file diff --git a/freecad_workbench/freecad/robossembler/valid_sequences.py b/freecad_workbench/freecad/robossembler/valid_sequences.py new file mode 100644 index 0000000..7bf1387 --- /dev/null +++ b/freecad_workbench/freecad/robossembler/valid_sequences.py @@ -0,0 +1,36 @@ +import json + +def load_data(file_path): + with open(file_path, 'r') as file: + return json.load(file) + +def is_valid_sequence(sequence, constraints): + for constraint in constraints: + if constraint[0] in sequence and constraint[1] in sequence: + if sequence.index(constraint[0]) > sequence.index(constraint[1]): + return False + return True + +def save_sequences(sequences, file_path): + with open(file_path, 'w') as file: + json.dump(sequences, file, indent=4) + +# Load data from files +adjacency_matrix = load_data('adjacency_matrix.json') +constraints = load_data('constraints.json') +sequences = load_data('sequences.json') + +# Get all parts and first detail +all_parts = adjacency_matrix['allParts'] +first_detail = adjacency_matrix['firstDetail'] + +# Filter valid sequences +valid_sequences = [] +for sequence in sequences: + if len(set(sequence)) == len(set(all_parts)): #and is_valid_sequence(sequence, constraints): + valid_sequences.append(sequence) + +# Save valid sequences to file +save_sequences(valid_sequences, 'valid_sequences.json') + +print(f"Найдено {len(valid_sequences)} допустимых последовательностей сборки.") diff --git a/test_models/assembly_settings_test_reductor.json b/test_models/assembly_settings_test_reductor.json new file mode 100644 index 0000000..1ffac35 --- /dev/null +++ b/test_models/assembly_settings_test_reductor.json @@ -0,0 +1,34 @@ +[ + { + "Name": "Fastener_Set", + "Type": "fastener_set", + "Parent": "body_down", + "Child": "body_up", + "Fasteners": [ + "bolt4", + "bolt", + "bolt2", + "bolt3" + ] + }, + { + "Name": "Assembly_Sequence", + "Type": "asm_sequence", + "Parent": "body_down", + "Child": "sol_gear" + }, + { + "Name": "Clearance_Constraint", + "Type": "clearance", + "PartName": [ + "planet_gear002", + "planet_gear005", + "planet_gear004", + "planet_gear003", + "planet_gear", + "output_shaft", + "sol_gear" + ], + "MaxClearance": 1.0 + } +] \ No newline at end of file diff --git a/test_models/img/body_down.svg b/test_models/img/body_down.svg deleted file mode 100644 index fab4fec..0000000 --- a/test_models/img/body_down.svg +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -b'body_down' - - \ No newline at end of file diff --git a/test_models/img/body_up.svg b/test_models/img/body_up.svg deleted file mode 100644 index 2a2e3ae..0000000 --- a/test_models/img/body_up.svg +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - -b'body_up' - - \ No newline at end of file diff --git a/test_models/img/bolt.svg b/test_models/img/bolt.svg deleted file mode 100644 index 08881a4..0000000 --- a/test_models/img/bolt.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - -b'bolt' - - \ No newline at end of file diff --git a/test_models/img/bolt2.svg b/test_models/img/bolt2.svg deleted file mode 100644 index 4d86fc4..0000000 --- a/test_models/img/bolt2.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - -b'bolt2' - - \ No newline at end of file diff --git a/test_models/img/bolt3.svg b/test_models/img/bolt3.svg deleted file mode 100644 index d114b57..0000000 --- a/test_models/img/bolt3.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - -b'bolt3' - - \ No newline at end of file diff --git a/test_models/img/bolt4.svg b/test_models/img/bolt4.svg deleted file mode 100644 index ca088bf..0000000 --- a/test_models/img/bolt4.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - -b'bolt4' - - \ No newline at end of file diff --git a/test_models/img/output_shaft.svg b/test_models/img/output_shaft.svg deleted file mode 100644 index d30fd59..0000000 --- a/test_models/img/output_shaft.svg +++ /dev/null @@ -1,53 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -b'output_shaft' - - \ No newline at end of file diff --git a/test_models/img/planet_gear.svg b/test_models/img/planet_gear.svg deleted file mode 100644 index c7d8898..0000000 --- a/test_models/img/planet_gear.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - -b'planet_gear' - - \ No newline at end of file diff --git a/test_models/img/planet_gear002.svg b/test_models/img/planet_gear002.svg deleted file mode 100644 index 32101ac..0000000 --- a/test_models/img/planet_gear002.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - -b'planet_gear002' - - \ No newline at end of file diff --git a/test_models/img/planet_gear003.svg b/test_models/img/planet_gear003.svg deleted file mode 100644 index a692e01..0000000 --- a/test_models/img/planet_gear003.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - -b'planet_gear003' - - \ No newline at end of file diff --git a/test_models/img/planet_gear004.svg b/test_models/img/planet_gear004.svg deleted file mode 100644 index e5a7285..0000000 --- a/test_models/img/planet_gear004.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - -b'planet_gear004' - - \ No newline at end of file diff --git a/test_models/img/planet_gear005.svg b/test_models/img/planet_gear005.svg deleted file mode 100644 index 7b3470e..0000000 --- a/test_models/img/planet_gear005.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - -b'planet_gear005' - - \ No newline at end of file diff --git a/test_models/img/sol_gear.svg b/test_models/img/sol_gear.svg deleted file mode 100644 index eebe881..0000000 --- a/test_models/img/sol_gear.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - - - - -b'sol_gear' - - \ No newline at end of file