538 lines
No EOL
18 KiB
Python
538 lines
No EOL
18 KiB
Python
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()
|
||
|