import FreeCAD as App import uuid import os import json from typing import List, Dict, Any, TypeVar, Callable, Type, cast from itertools import repeat class CoreList(List): # the list contains only True def onlyTrue(self) -> bool: print(self) for el in self: if el is not True: return False return True 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) -> None: self.adj_matrix = adj_matrix self.all_possible_sequences(self.adj_matrix) self.matrix_by_name() 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] = list( 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) class FreeCadRepository: _solids = [] 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) -> bool: for solid in self.getAllSolids(): if solid.Label != part.Label: collisionResult: int = int(part.Shape.distToShape(solid.Shape)[0]) if collisionResult == 0: return True return False def objectHasTouches(self, part, objectIndentation: float) -> List[str]: positionVector = self.objectGetPosition(part) result = CoreList() result.append(self.isObjectIntersections(part=part)) if objectIndentation != 0 and objectIndentation != None: result.append( self.axis_movement_and_intersections_observer( positionVector=positionVector, alongAxis="x", objectIndentation=objectIndentation, part=part, ) ) result.append( self.axis_movement_and_intersections_observer( positionVector=positionVector, alongAxis="y", objectIndentation=objectIndentation, part=part, ) ) result.append( self.axis_movement_and_intersections_observer( positionVector=positionVector, alongAxis="z", objectIndentation=objectIndentation, part=part, ) ) return result.onlyTrue() def axis_movement_and_intersections_observer( self, positionVector: VectorModel, alongAxis: str, objectIndentation: float, part, ) -> bool: # UP positionVector.__setattr__( alongAxis, positionVector.__getattribute__(alongAxis) + objectIndentation ) self.objectSetPosition(part, positionVector.toFreeCadVector()) result = self.isObjectIntersections(part=part) if result: return True # DOWN positionVector.__setattr__( alongAxis, positionVector.__getattribute__(alongAxis) - objectIndentation ) positionVector.__setattr__( alongAxis, positionVector.__getattribute__(alongAxis) - objectIndentation ) result = self.isObjectIntersections(part=part) if result: return True self.isObjectIntersections(part=part) # ROLLBACK positionVector.__setattr__( alongAxis, positionVector.__getattribute__(alongAxis) + objectIndentation ) self.objectSetPosition(part, positionVector.toFreeCadVector()) return False 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]] 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 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 FS: 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] ) print(collisionResult) print("collisionResult") 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) def matrixGetUniqueContact(matrix): detailsToCheck = [] detailsHashCheck = {} for k, v in 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 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, ) class IsAllObjectSolidsCheckUseCase: def call() -> ErrorStringModel: result = FreeCadRepository().isAllObjectsSolids() if result.__len__() == 0: return None return ErrorStringModel(error="Is not solid objects: " + ",".join(result)) class CheckObjectHasTouchesUseCase: def call(objectIndentation: float) -> ErrorStringModel: result = [] for part in FreeCadRepository().getAllSolids(): if ( FreeCadRepository().objectHasTouches( part=part, objectIndentation=objectIndentation ) is False ): result.append(part.Label) if result.__len__() == 0: return None return ErrorStringModel( error="Solids bodies have no recounts: " + ",".join(result) ) class CheckCadIntersectionObjects: report = [] def call() -> bool: FreeCadRepository().getAllSolids() return False class ExitFreeCadUseCase: def call(): import FreeCADGui as Gui freecadQTWindow = Gui.getMainWindow() freecadQTWindow.close() # class CheckValidIntersectionUseCase: # def call() -> ErrorStringModel: # for part in FreeCadRepository().getAllSolids(): # print(part) # FreeCadRepository().obj # pass def main(): env = FS.readJSON("env.json") cadFilePath = str(env["cadFilePath"]) outPath = str(env["outPath"]) objectIndentation = float(env["objectIndentation"]) if cadFilePath == None: return TypeError("CadFile not found env.json") App.open("" + cadFilePath) # isAllObjectSolidsCheckUseCase = IsAllObjectSolidsCheckUseCase.call() # if isAllObjectSolidsCheckUseCase != None: # FS.writeFile(isAllObjectSolidsCheckUseCase.toString(), outPath, 'error.json') # ExitFreeCadUseCase.call() # return # checkObjectHasTouchesUseCase = CheckObjectHasTouchesUseCase.call(objectIndentation) # if checkObjectHasTouchesUseCase != None: # FS.writeFile(checkObjectHasTouchesUseCase.toString(), outPath, 'error.json') # ExitFreeCadUseCase.call() # return topologyMatrix = CadAdjacencyMatrix().matrixBySurfaces() import json sequences = json.dumps( {"sequences": AllSequences(topologyMatrix.matrix).adj_matrix_names}, ensure_ascii=False, indent=4, ) matrix = topologyMatrix.matrix contacts = matrixGetUniqueContact(matrix) 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")} ) FS.writeFile( json.dumps(intersection_geometry, ensure_ascii=False, indent=4), outPath, "intersection_geometry.json", ) FS.writeFile(sequences, outPath, "sequences.json") FS.writeFile( json.dumps(topologyMatrix.to_dict(), ensure_ascii=False, indent=4), outPath, "adjacency_matrix.json", ) ExitFreeCadUseCase.call() main()