ASP refactoring, sequence generation via clusterization
This commit is contained in:
parent
d2ab856d64
commit
fd59ab9e26
45 changed files with 1579 additions and 1267 deletions
0
geometric_feasibility_predicate/__init__.py
Normal file
0
geometric_feasibility_predicate/__init__.py
Normal 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": []
|
||||
|
|
0
geometric_feasibility_predicate/extensions/__init__.py
Normal file
0
geometric_feasibility_predicate/extensions/__init__.py
Normal file
53
geometric_feasibility_predicate/extensions/dict.py
Normal file
53
geometric_feasibility_predicate/extensions/dict.py
Normal 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
|
70
geometric_feasibility_predicate/extensions/list.py
Normal file
70
geometric_feasibility_predicate/extensions/list.py
Normal 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
|
|
@ -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"
|
83
geometric_feasibility_predicate/helpers/either.py
Normal file
83
geometric_feasibility_predicate/helpers/either.py
Normal 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)
|
73
geometric_feasibility_predicate/helpers/freecadtest.py
Normal file
73
geometric_feasibility_predicate/helpers/freecadtest.py
Normal 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
0
geometric_feasibility_predicate/mocks/__init__.py
Normal file
0
geometric_feasibility_predicate/mocks/__init__.py
Normal file
BIN
geometric_feasibility_predicate/mocks/desk_table.FCStd
Normal file
BIN
geometric_feasibility_predicate/mocks/desk_table.FCStd
Normal file
Binary file not shown.
36
geometric_feasibility_predicate/mocks/mock_structure.py
Normal file
36
geometric_feasibility_predicate/mocks/mock_structure.py
Normal 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"]
|
||||
|
0
geometric_feasibility_predicate/model/__init__.py
Normal file
0
geometric_feasibility_predicate/model/__init__.py
Normal file
0
geometric_feasibility_predicate/models/__init__.py
Normal file
0
geometric_feasibility_predicate/models/__init__.py
Normal file
118
geometric_feasibility_predicate/models/adjacency_matrix_model.py
Normal file
118
geometric_feasibility_predicate/models/adjacency_matrix_model.py
Normal 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
|
130
geometric_feasibility_predicate/models/all_sequences_model.py
Normal file
130
geometric_feasibility_predicate/models/all_sequences_model.py
Normal 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)
|
|
@ -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,
|
||||
)
|
48
geometric_feasibility_predicate/models/env_model.py
Normal file
48
geometric_feasibility_predicate/models/env_model.py
Normal 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
|
28
geometric_feasibility_predicate/models/error_string_model.py
Normal file
28
geometric_feasibility_predicate/models/error_string_model.py
Normal 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(),
|
||||
)
|
|
@ -0,0 +1,4 @@
|
|||
class FreeCadMetaModel(object):
|
||||
def __init__(self, label, vertex) -> None:
|
||||
self.label = label
|
||||
self.vertex = vertex
|
|
@ -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()
|
51
geometric_feasibility_predicate/models/var.py
Normal file
51
geometric_feasibility_predicate/models/var.py
Normal 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()
|
18
geometric_feasibility_predicate/models/vector_model.py
Normal file
18
geometric_feasibility_predicate/models/vector_model.py
Normal 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))
|
0
geometric_feasibility_predicate/repository/__init__.py
Normal file
0
geometric_feasibility_predicate/repository/__init__.py
Normal 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
|
171
geometric_feasibility_predicate/repository/freecad_repository.py
Normal file
171
geometric_feasibility_predicate/repository/freecad_repository.py
Normal 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
|
0
geometric_feasibility_predicate/usecases/__init__.py
Normal file
0
geometric_feasibility_predicate/usecases/__init__.py
Normal 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"))
|
|
@ -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)
|
||||
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -0,0 +1,6 @@
|
|||
from repository.freecad_repository import FreeCadRepository
|
||||
|
||||
|
||||
class ExitFreeCadUseCase:
|
||||
def call():
|
||||
FreeCadRepository().closeIfOpenDocument()
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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",
|
||||
)
|
|
@ -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))
|
||||
)
|
|
@ -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)
|
|
@ -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
|
|
@ -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)
|
|
@ -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
|
Loading…
Add table
Add a link
Reference in a new issue