ASP refactoring, sequence generation via clusterization

This commit is contained in:
Mark Voltov 2024-02-02 14:22:21 +00:00 committed by Igor Brylyov
parent d2ab856d64
commit fd59ab9e26
45 changed files with 1579 additions and 1267 deletions

View file

@ -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": []

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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)

View file

@ -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

File diff suppressed because it is too large Load diff

Binary file not shown.

View file

@ -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"]

View file

@ -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

View file

@ -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)

View file

@ -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,
)

View file

@ -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

View file

@ -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(),
)

View file

@ -0,0 +1,4 @@
class FreeCadMetaModel(object):
def __init__(self, label, vertex) -> None:
self.label = label
self.vertex = vertex

View file

@ -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()

View file

@ -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()

View file

@ -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))

View file

@ -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

View file

@ -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

View file

@ -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"))

View file

@ -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)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -0,0 +1,6 @@
from repository.freecad_repository import FreeCadRepository
class ExitFreeCadUseCase:
def call():
FreeCadRepository().closeIfOpenDocument()

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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",
)

View file

@ -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))
)

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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