diff --git a/geometric_feasibility_predicate/__init__.py b/geometric_feasibility_predicate/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/env.json b/geometric_feasibility_predicate/env.json index 29d89bc..ecc3e53 100644 --- a/geometric_feasibility_predicate/env.json +++ b/geometric_feasibility_predicate/env.json @@ -1,7 +1,7 @@ { - "cadFilePath": "/Users/idontsudo/Desktop/reductor/test_reductor.FCStd", - "outPath": "/Users/idontsudo/Desktop/reductor/", - "solidBodyPadding": 1, + "cadFilePath": "/home/markvoltov/TestFolder/bottle_jack/bottle_jack.FCStd", + "outPath": "/home/markvoltov/TestFolder/bottle_jack/", + "solidBodyPadding": 3, "firstDetail": "Куб", "sequencesFixed": [], "restrictionsOnFasteners": [] diff --git a/geometric_feasibility_predicate/extensions/__init__.py b/geometric_feasibility_predicate/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/extensions/dict.py b/geometric_feasibility_predicate/extensions/dict.py new file mode 100644 index 0000000..5cf7f9f --- /dev/null +++ b/geometric_feasibility_predicate/extensions/dict.py @@ -0,0 +1,53 @@ +class CoreDict(dict): + """ + Class for handling cases related to dictionaries: + + + Args: + dict (dict): dictionary to analysis + """ + + def isEquivalentByKeys(self, checkDict) -> bool: + """ + The function checks whether the values ​​of the keys and the data associated with them match between the processed and the reference dictionary + + + Args: + checkDict (dict): reference dictionary + + Returns: + bool: + """ + + for key in self: + value = checkDict.get(key) + if value is None: + return False + + return True + + # + def isMatchByKeys(self, checkList) -> bool: + """ + The function checks whether the key values ​​match the elements of the reference list: + + Args: + checkList (list): reference list + + Returns: + bool + """ + 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 diff --git a/geometric_feasibility_predicate/extensions/list.py b/geometric_feasibility_predicate/extensions/list.py new file mode 100644 index 0000000..6c7d44d --- /dev/null +++ b/geometric_feasibility_predicate/extensions/list.py @@ -0,0 +1,70 @@ +from typing import List + + +class CoreList(List): + """ + Class for handling cases related to lists: + + Args: + List (list): list to analysis + """ + + 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: + """Сhecking that the analyzed list is equal to the reference list + + Args: + array (list): reference list + + Returns: + 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 diff --git a/geometric_feasibility_predicate/helpers/background_console_colors.py b/geometric_feasibility_predicate/helpers/background_console_colors.py new file mode 100644 index 0000000..e419c51 --- /dev/null +++ b/geometric_feasibility_predicate/helpers/background_console_colors.py @@ -0,0 +1,10 @@ +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" diff --git a/geometric_feasibility_predicate/helpers/either.py b/geometric_feasibility_predicate/helpers/either.py new file mode 100644 index 0000000..dfe77a5 --- /dev/null +++ b/geometric_feasibility_predicate/helpers/either.py @@ -0,0 +1,83 @@ +from abc import ABC, abstractmethod + + +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) diff --git a/geometric_feasibility_predicate/helpers/freecadtest.py b/geometric_feasibility_predicate/helpers/freecadtest.py new file mode 100644 index 0000000..db824d0 --- /dev/null +++ b/geometric_feasibility_predicate/helpers/freecadtest.py @@ -0,0 +1,73 @@ +import os +from helpers.background_console_colors import ( + BackgroundConsoleColors, +) +from usecases.open_freecad_document_use_case import ( + OpenFreeCadDocumentUseCase, +) +from usecases.read_file_system_and_get_instance_model_use_case import ( + ReadFileSystemAndGetInstanceModelUseCase, +) + + +class FreeCadTest: + testName: str + """Class for performing FreeCAD model checks + """ + + 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 + """ + FreeCAD Document Validation Controller + """ + + 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.replace("/helpers", "") + 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 diff --git a/geometric_feasibility_predicate/main.py b/geometric_feasibility_predicate/main.py index d46096a..c669ba4 100644 --- a/geometric_feasibility_predicate/main.py +++ b/geometric_feasibility_predicate/main.py @@ -1,1089 +1,34 @@ -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", - ) +from extensions.list import CoreList +from extensions.dict import CoreDict +from helpers.freecadtest import FreeCadASPGenerationTestController +from models.adjacency_matrix_model import ( + AdjacencyMatrixModel, +) +from usecases.check_object_has_touches_use_case import ( + CheckObjectHasTouchesUseCase, +) +from usecases.clusterisation_sequences_use_case import ( + ClusterisationSequenceUseCase, +) +from usecases.check_sequence_use_case import ( + CheckSequenceUsecase, +) +from usecases.env_reader_use_case import ( + EnvReaderUseCase, +) +from usecases.exit_freecad_use_case import ( + ExitFreeCadUseCase, +) +from usecases.intersection_geometry_use_case import ( + IntersectionGeometryUseCase, +) +from usecases.open_freecad_document_use_case import ( + OpenFreeCadDocumentUseCase, +) + + +from mocks.mock_structure import bottle_jack_mock_structure, simple_cube_mock_structure def main(): @@ -1092,34 +37,30 @@ def main(): 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(), + ( + CheckObjectHasTouchesUseCase() + .call(environment.solidBodyPadding) + .either( + leftF=lambda adjaxedMatrix: ( + adjaxedMatrix.sequencesToFileSystem( + environment.outPath, + environment.sequencesFixed, ), - rightF=lambda error: error.toFileSystem( - environment.outPath + IntersectionGeometryUseCase.call( + adjaxedMatrix.matrixGetUniqueContact(), + environment.outPath, ), + adjaxedMatrix.matrixToFileSystem( + environment.outPath, + ), + ClusterisationSequenceUseCase(environment.outPath), + ExitFreeCadUseCase.call(), + ), + rightF=lambda error: error.toFileSystem( + environment.outPath ), ), - rightF=lambda error: error.toFileSystem( - environment.outPath - ), - ), + ) ), rightF=lambda error: print(error), ), @@ -1135,159 +76,15 @@ def main(): # 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"] + FreeCadASPGenerationTestController("test adjaxed matrix simple cube").test( + assertFn=lambda model: CoreList(model.all_parts).equal( + simple_cube_mock_structure ), execComposition=lambda _: ( CheckObjectHasTouchesUseCase() @@ -1298,19 +95,74 @@ def test(): ) ), documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd", - modelName=AdjacencyMatrix.fileName, - model=AdjacencyMatrix, + modelName=AdjacencyMatrixModel.fileName, + model=AdjacencyMatrixModel, + ) + + FreeCadASPGenerationTestController( + "test adjaxed matrix vs structure of document" + ).test( + assertFn=lambda model: CoreDict(model.matrix).isEquivalentByKeys( + bottle_jack_mock_structure + ), + execComposition=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(0) + .either( + leftF=lambda matrix: matrix.matrixToFileSystem(outFolder), + rightF=lambda error: print(error), + ) + ), + documentPath=mocksFolder + "bottle_jack.FCStd", + modelName=AdjacencyMatrixModel.fileName, + model=AdjacencyMatrixModel, + ) + + FreeCadASPGenerationTestController( + "test adjacency matrix keys vs allparts" + ).test( + assertFn=lambda model: CoreDict(model.matrix).isMatchByKeys( + model.all_parts + ), + execComposition=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(0) + .either( + leftF=lambda matrix: (matrix.matrixToFileSystem(outFolder)), + rightF=lambda error: print(error), + ) + ), + documentPath=mocksFolder + "bottle_jack.FCStd", + modelName=AdjacencyMatrixModel.fileName, + model=AdjacencyMatrixModel, + ) + + FreeCadASPGenerationTestController("test all parts vs assembly sequence").test( + assertFn=lambda model: CheckSequenceUsecase( + ClusterisationSequenceUseCase(outFolder) + ).isCorrectByParts(model.all_parts), + execComposition=lambda _: ( + CheckObjectHasTouchesUseCase() + .call(0) + .either( + leftF=lambda matrix: ( + matrix.matrixToFileSystem(outFolder), + ClusterisationSequenceUseCase(outFolder), + ), + rightF=lambda error: print(error), + ) + ), + documentPath=mocksFolder + "bottle_jack.FCStd", + modelName=AdjacencyMatrixModel.fileName, + model=AdjacencyMatrixModel, ) ExitFreeCadUseCase.call() - except: + except Exception as e: + print(e) print("test error") ExitFreeCadUseCase.call() pass test() - - -# TODO: -# 1. diff --git a/geometric_feasibility_predicate/mocks/__init__.py b/geometric_feasibility_predicate/mocks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/mocks/desk_table.FCStd b/geometric_feasibility_predicate/mocks/desk_table.FCStd new file mode 100644 index 0000000..8b3c384 Binary files /dev/null and b/geometric_feasibility_predicate/mocks/desk_table.FCStd differ diff --git a/geometric_feasibility_predicate/mocks/mock_structure.py b/geometric_feasibility_predicate/mocks/mock_structure.py new file mode 100644 index 0000000..af2bc7e --- /dev/null +++ b/geometric_feasibility_predicate/mocks/mock_structure.py @@ -0,0 +1,36 @@ +bottle_jack_mock_structure = { + "Бутылочный домкрат 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т_Гильза"], +} +simple_cube_mock_structure = ["Куб", "Куб001"] + diff --git a/geometric_feasibility_predicate/model/__init__.py b/geometric_feasibility_predicate/model/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/models/__init__.py b/geometric_feasibility_predicate/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/models/adjacency_matrix_model.py b/geometric_feasibility_predicate/models/adjacency_matrix_model.py new file mode 100644 index 0000000..4b33729 --- /dev/null +++ b/geometric_feasibility_predicate/models/adjacency_matrix_model.py @@ -0,0 +1,118 @@ +from models.all_sequences_model import AllSequencesModel +from repository.file_system_repository import FileSystemRepository +from typing import List, Dict, Any +import json + +from models.var import from_str, from_list, from_dict + + +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 AdjacencyMatrixModel: + 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, + AdjacencyMatrixModel.fileName, + ) + pass + + def sequencesToFileSystem(self, path: str, restrictions: list[str]): + FileSystemRepository.writeFile( + json.dumps( + { + "sequences": AllSequencesModel( + 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) -> "AdjacencyMatrixModel": + 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 AdjacencyMatrixModel(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 diff --git a/geometric_feasibility_predicate/models/all_sequences_model.py b/geometric_feasibility_predicate/models/all_sequences_model.py new file mode 100644 index 0000000..e8dfd25 --- /dev/null +++ b/geometric_feasibility_predicate/models/all_sequences_model.py @@ -0,0 +1,130 @@ +from extensions.list import CoreList +from itertools import repeat + + +class AllSequencesModel: + """ + A class that processes information from the assembly and creates the objects necessary to create the assembly sequence + + """ + + 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]): + """_summary_ + + Args: + restrictions (CoreList[str]): _description_ + """ + 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): + """Find all assembly sequences with dfs + + Args: + adj_matrix (dict): Assembly ajacency 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) diff --git a/geometric_feasibility_predicate/models/cad_adjacency_matrix_model.py b/geometric_feasibility_predicate/models/cad_adjacency_matrix_model.py new file mode 100644 index 0000000..496e8d4 --- /dev/null +++ b/geometric_feasibility_predicate/models/cad_adjacency_matrix_model.py @@ -0,0 +1,71 @@ +from usecases.clear_work_space_document_use_case import ( + ClearWorkSpaceDocumentUseCase, +) +from geometric_feasibility_predicate.usecases.get_collision_at_primitive_use_case import ( + GetCollisionAtPrimitiveUseCase, +) +from usecases.render_primitive_use_case import RenderPrimitiveUseCase +from usecases.get_part_primitive_coordinates_use_case import ( + GetPartPrimitiveCoordinatesUseCase, +) +from usecases.init_parts_parse_use_case import ( + InitPartsParseUseCase, +) +from usecases.render_primitives_scenario import RenderPrimitivesScenario +from usecases.get_first_detail_use_case import GetFirstDetailUseCase +from geometric_feasibility_predicate.usecases.get_all_parts_labels_use_case import ( + GetAllPartsLabelsUseCase, +) +from models.adjacency_matrix_model import AdjacencyMatrixModel +from repository.freecad_repository import FreeCadRepository + + +class CadAdjacencyMatrixModel: + """Class for handling adjacency matrix creation scenario + + Returns: + dict: adjacency matrix + """ + + def primitiveMatrix(self): + """get matrix of primitives""" + # Получение матрицы + matrix = RenderPrimitivesScenario( + InitPartsParseUseCase(), + GetPartPrimitiveCoordinatesUseCase(), + RenderPrimitiveUseCase(), + GetCollisionAtPrimitiveUseCase(), + ClearWorkSpaceDocumentUseCase(), + ).call() + return AdjacencyMatrixModel( + all_parts=GetAllPartsLabelsUseCase().call(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) + + # Матрица основанная на соприкосновении обьектов + + def matrixBySurfaces(self): + """Adjacency matrix by touches between parts + + Returns: + dict: adjacency matrix + """ + 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 AdjacencyMatrixModel( + all_parts=GetAllPartsLabelsUseCase().call(), + first_detail=GetFirstDetailUseCase().call(), + matrix=matrix, + ) diff --git a/geometric_feasibility_predicate/models/env_model.py b/geometric_feasibility_predicate/models/env_model.py new file mode 100644 index 0000000..4948519 --- /dev/null +++ b/geometric_feasibility_predicate/models/env_model.py @@ -0,0 +1,48 @@ +from dataclasses import dataclass +from typing import List, Dict, Any, TypeVar, Callable, Type, cast +from extensions.list import CoreList + +from models.var import from_str, from_float + + +@dataclass +class EnvModel: + cadFilePath: str + outPath: str + solidBodyPadding: float + firstDetail: str + sequencesFixed: list[list[str]] + + @staticmethod + def from_dict(obj: Any) -> "EnvModel": + 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 EnvModel( + 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 diff --git a/geometric_feasibility_predicate/models/error_string_model.py b/geometric_feasibility_predicate/models/error_string_model.py new file mode 100644 index 0000000..d99c410 --- /dev/null +++ b/geometric_feasibility_predicate/models/error_string_model.py @@ -0,0 +1,28 @@ +import json +from repository.file_system_repository import FileSystemRepository +from usecases.exit_freecad_use_case import ( + ExitFreeCadUseCase, +) + + +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(), + ) diff --git a/geometric_feasibility_predicate/models/freecad_meta_model.py b/geometric_feasibility_predicate/models/freecad_meta_model.py new file mode 100644 index 0000000..6bbd54d --- /dev/null +++ b/geometric_feasibility_predicate/models/freecad_meta_model.py @@ -0,0 +1,4 @@ +class FreeCadMetaModel(object): + def __init__(self, label, vertex) -> None: + self.label = label + self.vertex = vertex diff --git a/geometric_feasibility_predicate/models/mesh_geometry_coordinate_model.py b/geometric_feasibility_predicate/models/mesh_geometry_coordinate_model.py new file mode 100644 index 0000000..93a201a --- /dev/null +++ b/geometric_feasibility_predicate/models/mesh_geometry_coordinate_model.py @@ -0,0 +1,41 @@ +import uuid + +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): + import FreeCAD as App + + 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() diff --git a/geometric_feasibility_predicate/models/var.py b/geometric_feasibility_predicate/models/var.py new file mode 100644 index 0000000..a4a3c28 --- /dev/null +++ b/geometric_feasibility_predicate/models/var.py @@ -0,0 +1,51 @@ +# Data Conversion Helpers: + + +from typing import List, Dict, Any, TypeVar, Callable, Type, cast + +T = TypeVar("T") + + +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() + + +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() diff --git a/geometric_feasibility_predicate/models/vector_model.py b/geometric_feasibility_predicate/models/vector_model.py new file mode 100644 index 0000000..347ffe4 --- /dev/null +++ b/geometric_feasibility_predicate/models/vector_model.py @@ -0,0 +1,18 @@ +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): + import FreeCAD as App + + 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)) diff --git a/geometric_feasibility_predicate/repository/__init__.py b/geometric_feasibility_predicate/repository/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/repository/file_system_repository.py b/geometric_feasibility_predicate/repository/file_system_repository.py new file mode 100644 index 0000000..c6d2ec8 --- /dev/null +++ b/geometric_feasibility_predicate/repository/file_system_repository.py @@ -0,0 +1,25 @@ +import os +import json + + +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='utf-8') + + 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 diff --git a/geometric_feasibility_predicate/repository/freecad_repository.py b/geometric_feasibility_predicate/repository/freecad_repository.py new file mode 100644 index 0000000..a1be24a --- /dev/null +++ b/geometric_feasibility_predicate/repository/freecad_repository.py @@ -0,0 +1,171 @@ +import FreeCAD as App + + +from typing import List +from extensions.list import CoreList + +from models.vector_model import VectorModel + + +class FreeCadRepository: + """Class for handling FreeCAD related functionality""" + + _solids = [] + + def openDocument(self, path: str): + App.open("" + path) + + def closeIfOpenDocument(self): + """ + If there is an open document being processed in FreeCAD, it may be closed + """ + try: + if App.ActiveDocument is not None: + App.ActiveDocument.clearDocument() + except Exception as e: + print(e) + + def getAllLabelsSolids(self) -> List[str]: + """Returns a list of solid part labels + + Returns: + List[str]: part labels + """ + return list(map(lambda el: el.Label, self.getAllSolids())) + + def isAllObjectsSolids(self) -> List[str]: + """Get all non-solid objects + + Returns: + 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): + _solids = [] + + for part in App.ActiveDocument.Objects: + if self.is_object_solid(part): + _solids.append(part) + + return _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 diff --git a/geometric_feasibility_predicate/usecases/__init__.py b/geometric_feasibility_predicate/usecases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/geometric_feasibility_predicate/usecases/check_object_has_touches_use_case.py b/geometric_feasibility_predicate/usecases/check_object_has_touches_use_case.py new file mode 100644 index 0000000..52ef66f --- /dev/null +++ b/geometric_feasibility_predicate/usecases/check_object_has_touches_use_case.py @@ -0,0 +1,50 @@ +from helpers.either import Either, Left, Right +from models.adjacency_matrix_model import ( + AdjacencyMatrixModel, +) +from models.error_string_model import ErrorStringModel +from repository.freecad_repository import ( + FreeCadRepository, +) +from usecases.get_first_detail_use_case import ( + GetFirstDetailUseCase, +) + + +class CheckObjectHasTouchesUseCase: + """Check touches between objects and returns matrix + + Returns: + dict: adjacency matrix + """ + + 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( + AdjacencyMatrixModel( + 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")) diff --git a/geometric_feasibility_predicate/usecases/check_sequence_use_case.py b/geometric_feasibility_predicate/usecases/check_sequence_use_case.py new file mode 100644 index 0000000..0644864 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/check_sequence_use_case.py @@ -0,0 +1,11 @@ +from extensions.list import CoreList + +class CheckSequenceUsecase(list): + def isCorrectByParts(self, checkList) -> bool: + parts = self[len(self) - 1] + for part in parts: + part = str(part) + part = part[1:-1] + + return CoreList(self).equal(checkList) + diff --git a/geometric_feasibility_predicate/usecases/clear_work_space_document_use_case.py b/geometric_feasibility_predicate/usecases/clear_work_space_document_use_case.py new file mode 100644 index 0000000..22f1ec9 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/clear_work_space_document_use_case.py @@ -0,0 +1,16 @@ +class ClearWorkSpaceDocumentUseCase(object): + """Clear of the workspace + + Args: + object : active CAD-model + """ + + def call(self, detailSquares): + import FreeCAD as App + + for key in detailSquares: + for renderPrimitive in detailSquares[key]: + primitivePart = App.ActiveDocument.getObjectsByLabel( + renderPrimitive.cadLabel + )[0] + App.ActiveDocument.removeObject(primitivePart.Name) diff --git a/geometric_feasibility_predicate/usecases/clusterisation_sequences_use_case.py b/geometric_feasibility_predicate/usecases/clusterisation_sequences_use_case.py new file mode 100644 index 0000000..4963f72 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/clusterisation_sequences_use_case.py @@ -0,0 +1,66 @@ +import os +import json +import networkx as nx +from repository.file_system_repository import FileSystemRepository + + +class GraphProcessor: + file_path: str + graph = None + + def __init__(self, file_path: str): + self.file_path = file_path + self.graph = self.load_graph_from_json() + + def load_graph_from_json(self): + with open(self.file_path, "r") as file: + data = json.load(file) + + G = nx.Graph() + + if "matrix" in data: + matrix = data["matrix"] + for part1, neighbors in matrix.items(): + for neighbor in neighbors: + G.add_edge(part1, neighbor) + + return G + + +class EdgeBetweensClustering: + def __init__(self, graph): + self.graph = graph.copy() + self.clusters = [] + + def cluster(self): + while self.graph.number_of_edges() > 0: + edge_betweens = nx.edge_betweenness_centrality(self.graph) + max_betweens_edge = max(edge_betweens, key=edge_betweens.get) + self.graph.remove_edge(*max_betweens_edge) + components = list(nx.connected_components(self.graph)) + if components not in self.clusters: + self.clusters.append(components) + return [] + + def get_clusters(self): + return self.clusters + + +class ClusterisationSequenceUseCase: + def call(self, file_path: str): + graph_processor = GraphProcessor(file_path + "adjacency_matrix.json") + G = graph_processor.load_graph_from_json() + ebc = EdgeBetweensClustering(G) + ebc.cluster() + clusters = ebc.get_clusters() + + for i in range(len(clusters)): + for j in range(len(clusters[i])): + clusters[i][j] = list(clusters[i][j]) + + FileSystemRepository.writeFile( + json.dumps(clusters, ensure_ascii=False, indent=2), + file_path, + "assembly_sequence.json", + ) + return clusters diff --git a/geometric_feasibility_predicate/usecases/env_reader_use_case.py b/geometric_feasibility_predicate/usecases/env_reader_use_case.py new file mode 100644 index 0000000..753b884 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/env_reader_use_case.py @@ -0,0 +1,12 @@ +from repository.file_system_repository import FileSystemRepository +from helpers.either import Either, Left, Right +from models.env_model import EnvModel + + +class EnvReaderUseCase: + def call() -> Either: + try: + return Left(EnvModel.from_dict(FileSystemRepository.readJSON("env.json"))) + except: + print("env reader error") + return Right(None) diff --git a/geometric_feasibility_predicate/usecases/exit_freecad_use_case.py b/geometric_feasibility_predicate/usecases/exit_freecad_use_case.py new file mode 100644 index 0000000..d9fdd59 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/exit_freecad_use_case.py @@ -0,0 +1,6 @@ +from repository.freecad_repository import FreeCadRepository + + +class ExitFreeCadUseCase: + def call(): + FreeCadRepository().closeIfOpenDocument() diff --git a/geometric_feasibility_predicate/usecases/get_all_parts_labels_use_case.py b/geometric_feasibility_predicate/usecases/get_all_parts_labels_use_case.py new file mode 100644 index 0000000..3449f35 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/get_all_parts_labels_use_case.py @@ -0,0 +1,13 @@ +from repository.freecad_repository import ( + FreeCadRepository, +) + + +class GetAllPartsLabelsUseCase: + """Get all parts label in assembly""" + + def call(self): + parts = [] + for part in FreeCadRepository().getAllSolids(): + parts.append(part.Label) + return parts diff --git a/geometric_feasibility_predicate/usecases/get_collision_at_primitive_use_case.py b/geometric_feasibility_predicate/usecases/get_collision_at_primitive_use_case.py new file mode 100644 index 0000000..99c5368 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/get_collision_at_primitive_use_case.py @@ -0,0 +1,44 @@ +from typing import List, Dict + + +def isUnique(array, element): + for i in array: + if i == element: + return False + + return True + + +class GetCollisionAtPrimitiveUseCase(object): + """Get collisions between primitives + + Args: + object: cad-model + + Returns: + dict: collision matrix + """ + + # Получение колизий примитивов + def call(self, freeCadMetaModels, detailSquares) -> Dict[str, List[str]]: + import FreeCAD as App + + 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 diff --git a/geometric_feasibility_predicate/usecases/get_first_detail_use_case.py b/geometric_feasibility_predicate/usecases/get_first_detail_use_case.py new file mode 100644 index 0000000..ea7242a --- /dev/null +++ b/geometric_feasibility_predicate/usecases/get_first_detail_use_case.py @@ -0,0 +1,8 @@ +from repository.freecad_repository import FreeCadRepository + + +class GetFirstDetailUseCase: + """Get label of first part in tree assembly""" + + def call(self): + return FreeCadRepository().getAllSolids()[0].Label diff --git a/geometric_feasibility_predicate/usecases/get_part_primitive_coordinates_use_case.py b/geometric_feasibility_predicate/usecases/get_part_primitive_coordinates_use_case.py new file mode 100644 index 0000000..6995047 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/get_part_primitive_coordinates_use_case.py @@ -0,0 +1,27 @@ +from models.mesh_geometry_coordinate_model import ( + MeshGeometryCoordinateModel, +) + + +class GetPartPrimitiveCoordinatesUseCase(object): + """Get positions of parts in assembly + + Args: + object : cad-model + """ + + 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 diff --git a/geometric_feasibility_predicate/usecases/init_parts_parse_use_case.py b/geometric_feasibility_predicate/usecases/init_parts_parse_use_case.py new file mode 100644 index 0000000..11faa4f --- /dev/null +++ b/geometric_feasibility_predicate/usecases/init_parts_parse_use_case.py @@ -0,0 +1,17 @@ +from models.freecad_meta_model import FreeCadMetaModel +from repository.freecad_repository import ( + FreeCadRepository, +) + + +class InitPartsParseUseCase: + """Initialisation of parsing geometry models info""" + + 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 diff --git a/geometric_feasibility_predicate/usecases/intersection_computed_use_case.py b/geometric_feasibility_predicate/usecases/intersection_computed_use_case.py new file mode 100644 index 0000000..e2cf148 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/intersection_computed_use_case.py @@ -0,0 +1,20 @@ +class IntersectionComputedUseCase: + def call(parts): + import FreeCAD as App + + 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 diff --git a/geometric_feasibility_predicate/usecases/intersection_geometry_use_case.py b/geometric_feasibility_predicate/usecases/intersection_geometry_use_case.py new file mode 100644 index 0000000..56e1da8 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/intersection_geometry_use_case.py @@ -0,0 +1,31 @@ +from repository.file_system_repository import FileSystemRepository +from usecases.intersection_computed_use_case import ( + IntersectionComputedUseCase, +) + +import json + + +class IntersectionGeometryUseCase: + """A class that checks bodies in an assembly for interference and returns the result of the check to a file""" + + def call(contacts, path): + import FreeCAD as App + + 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", + ) diff --git a/geometric_feasibility_predicate/usecases/is_all_object_solids_check_use_case.py b/geometric_feasibility_predicate/usecases/is_all_object_solids_check_use_case.py new file mode 100644 index 0000000..107f41b --- /dev/null +++ b/geometric_feasibility_predicate/usecases/is_all_object_solids_check_use_case.py @@ -0,0 +1,14 @@ +from helpers.either import Either, Left, Right +from models.error_string_model import ErrorStringModel +from repository import freecad_repository + + +class IsAllObjectSolidsCheckUseCase: + def call() -> Either: + result = freecad_repository().isAllObjectsSolids() + if result.__len__() == 0: + return Left(None) + + return Right( + ErrorStringModel(error="Is not solid objects: " + ",".join(result)) + ) diff --git a/geometric_feasibility_predicate/usecases/open_freecad_document_use_case.py b/geometric_feasibility_predicate/usecases/open_freecad_document_use_case.py new file mode 100644 index 0000000..d426d6f --- /dev/null +++ b/geometric_feasibility_predicate/usecases/open_freecad_document_use_case.py @@ -0,0 +1,18 @@ +from helpers.either import Either, Left, Right +from repository.freecad_repository import ( + FreeCadRepository, +) + + +class OpenFreeCadDocumentUseCase: + """A class that checks open documents, closes them and opens the one with which the program will work""" + + def call(path: str) -> Either: + try: + FreeCadRepository().closeIfOpenDocument() + FreeCadRepository().openDocument(path) + return Left(None) + except Exception as e: + print(e) + print("OpenFreeCadDocumentUseCase error") + return Right(None) diff --git a/geometric_feasibility_predicate/usecases/read_file_system_and_get_instance_model_use_case.py b/geometric_feasibility_predicate/usecases/read_file_system_and_get_instance_model_use_case.py new file mode 100644 index 0000000..52e411c --- /dev/null +++ b/geometric_feasibility_predicate/usecases/read_file_system_and_get_instance_model_use_case.py @@ -0,0 +1,21 @@ +from repository.file_system_repository import FileSystemRepository +from helpers.either import Left, Right, Either + + +class ReadFileSystemAndGetInstanceModelUseCase: + def call(self, model, path) -> Either: + 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 Exception as e: + print(e) + error = str(model) + " " + "from dict error " + "path: " + path + print("ReadFileSystemAndGetInstanceModelUseCase error" + error) + return Right(error) + pass diff --git a/geometric_feasibility_predicate/usecases/render_primitive_use_case.py b/geometric_feasibility_predicate/usecases/render_primitive_use_case.py new file mode 100644 index 0000000..57b5927 --- /dev/null +++ b/geometric_feasibility_predicate/usecases/render_primitive_use_case.py @@ -0,0 +1,15 @@ +from models.mesh_geometry_coordinate_model import MeshGeometryCoordinateModel + + +class RenderPrimitiveUseCase(object): + """Rendering primitives + + Args: + object: CAD-model + """ + + def call( + self, meshModels: list[MeshGeometryCoordinateModel], detailSquares + ) -> None: + for mesh in meshModels: + mesh.initializePrimitivesByCoordinate(detailSquares) diff --git a/geometric_feasibility_predicate/usecases/render_primitives_scenario.py b/geometric_feasibility_predicate/usecases/render_primitives_scenario.py new file mode 100644 index 0000000..9d8283f --- /dev/null +++ b/geometric_feasibility_predicate/usecases/render_primitives_scenario.py @@ -0,0 +1,41 @@ +from usecases.clear_work_space_document_use_case import ( + ClearWorkSpaceDocumentUseCase, +) +from geometric_feasibility_predicate.usecases.get_collision_at_primitive_use_case import ( + GetCollisionAtPrimitiveUseCase, +) +from usecases.get_part_primitive_coordinates_use_case import ( + GetPartPrimitiveCoordinatesUseCase, +) +from usecases.init_parts_parse_use_case import ( + InitPartsParseUseCase, +) +from usecases.render_primitive_use_case import ( + RenderPrimitiveUseCase, +) + + +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