framework/geometric_feasibility_predicate/main.py
2023-09-12 19:09:33 +00:00

538 lines
No EOL
18 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import FreeCAD as App
import uuid
import os
import json
from typing import List, Dict, Any, TypeVar, Callable, Type, cast
from itertools import repeat
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 FreeCadRepository:
_solids = []
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, 'Mass'):
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])
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
def intersectionComputed(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
def main():
env = FS.readJSON('env.json')
cadFile = env['cadFilePath']
outPath = env['outPath']
if (cadFile == None):
return TypeError('CadFile not found env.json')
App.open(u'' + cadFile)
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 = intersectionComputed([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')
main()