API with assembly options and constraints; DFS Liaison/Adjacency matrix generation fix; add unit-tests
This commit is contained in:
parent
c64bbf4a70
commit
d2ab856d64
8 changed files with 693 additions and 148 deletions
|
@ -1,5 +1,8 @@
|
||||||
{
|
{
|
||||||
"cadFilePath": "/Users/idontsudo/Desktop/asp-example/disk_and_axis_n.FCStd",
|
"cadFilePath": "/Users/idontsudo/Desktop/reductor/test_reductor.FCStd",
|
||||||
"outPath": "/Users/idontsudo/Desktop/asp-example/",
|
"outPath": "/Users/idontsudo/Desktop/reductor/",
|
||||||
"objectIndentation": 0
|
"solidBodyPadding": 1,
|
||||||
|
"firstDetail": "Куб",
|
||||||
|
"sequencesFixed": [],
|
||||||
|
"restrictionsOnFasteners": []
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,16 +4,255 @@ import os
|
||||||
import json
|
import json
|
||||||
from typing import List, Dict, Any, TypeVar, Callable, Type, cast
|
from typing import List, Dict, Any, TypeVar, Callable, Type, cast
|
||||||
from itertools import repeat
|
from itertools import repeat
|
||||||
|
from abc import ABC, abstractmethod
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, TypeVar, Type, cast
|
||||||
|
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Env:
|
||||||
|
cadFilePath: str
|
||||||
|
outPath: str
|
||||||
|
solidBodyPadding: float
|
||||||
|
firstDetail: str
|
||||||
|
sequencesFixed: list[list[str]]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def from_dict(obj: Any) -> "Env":
|
||||||
|
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 Env(
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
class CoreDict(dict):
|
||||||
|
def isEquivalentByKeys(self, checkDict) -> bool:
|
||||||
|
print(checkDict)
|
||||||
|
for key in self:
|
||||||
|
value = checkDict.get(key)
|
||||||
|
if value is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def isMatchByKeys(self, checkList):
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
class CoreList(List):
|
class CoreList(List):
|
||||||
# the list contains only True
|
|
||||||
def onlyTrue(self) -> bool:
|
def onlyTrue(self) -> bool:
|
||||||
print(self)
|
|
||||||
for el in self:
|
for el in self:
|
||||||
if el is not True:
|
if el is True:
|
||||||
return False
|
return 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:
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def isInListRange(listIn, index):
|
def isInListRange(listIn, index):
|
||||||
|
@ -30,10 +269,24 @@ class AllSequences:
|
||||||
topologyIds = None
|
topologyIds = None
|
||||||
adj_matrix_names = None
|
adj_matrix_names = None
|
||||||
|
|
||||||
def __init__(self, adj_matrix) -> None:
|
def __init__(self, adj_matrix, restrictions: list[str]) -> None:
|
||||||
self.adj_matrix = adj_matrix
|
self.adj_matrix = adj_matrix
|
||||||
self.all_possible_sequences(self.adj_matrix)
|
self.all_possible_sequences(self.adj_matrix)
|
||||||
self.matrix_by_name()
|
self.matrix_by_name()
|
||||||
|
if restrictions.__len__() != 0:
|
||||||
|
self.restrictionsValidate(restrictions)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def restrictionsValidate(self, restrictions: CoreList[str]):
|
||||||
|
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
|
pass
|
||||||
|
|
||||||
def matrix_by_name(self):
|
def matrix_by_name(self):
|
||||||
|
@ -41,7 +294,7 @@ class AllSequences:
|
||||||
inc = 0
|
inc = 0
|
||||||
for matrix in self.all_sequences:
|
for matrix in self.all_sequences:
|
||||||
for index in range(len(matrix)):
|
for index in range(len(matrix)):
|
||||||
result[inc][index] = list(
|
result[inc][index] = CoreList(
|
||||||
filter(
|
filter(
|
||||||
lambda el: el.get("number") == matrix[index] + 1,
|
lambda el: el.get("number") == matrix[index] + 1,
|
||||||
self.topologyIds,
|
self.topologyIds,
|
||||||
|
@ -137,10 +390,29 @@ class VectorModel:
|
||||||
def toFreeCadVector(self):
|
def toFreeCadVector(self):
|
||||||
return App.Vector(self.x, self.y, self.z)
|
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))
|
||||||
|
|
||||||
|
|
||||||
class FreeCadRepository:
|
class FreeCadRepository:
|
||||||
_solids = []
|
_solids = []
|
||||||
|
|
||||||
|
def openDocument(self, path: str):
|
||||||
|
App.open("" + path)
|
||||||
|
|
||||||
|
def closeIfOpenDocument(self):
|
||||||
|
# print('Проверка на документ')
|
||||||
|
try:
|
||||||
|
if App.ActiveDocument is not None:
|
||||||
|
# print(App.ActiveDocument.name + "закрыт")
|
||||||
|
# App.closeDocument(App.ActiveDocument.name)
|
||||||
|
App.ActiveDocument.clearDocument()
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def getAllLabelsSolids(self) -> List[str]:
|
||||||
|
return list(map(lambda el: el.Label, self.getAllSolids()))
|
||||||
|
|
||||||
def isAllObjectsSolids(self) -> List[str]:
|
def isAllObjectsSolids(self) -> List[str]:
|
||||||
result = []
|
result = []
|
||||||
for part in App.ActiveDocument.Objects:
|
for part in App.ActiveDocument.Objects:
|
||||||
|
@ -155,79 +427,98 @@ class FreeCadRepository:
|
||||||
def objectGetPosition(self, solid) -> VectorModel:
|
def objectGetPosition(self, solid) -> VectorModel:
|
||||||
return VectorModel(cadVector=solid.Placement.Base)
|
return VectorModel(cadVector=solid.Placement.Base)
|
||||||
|
|
||||||
def isObjectIntersections(self, part) -> bool:
|
def isObjectIntersections(self, part) -> str:
|
||||||
|
result = []
|
||||||
for solid in self.getAllSolids():
|
for solid in self.getAllSolids():
|
||||||
if solid.Label != part.Label:
|
if solid.ID != part.ID:
|
||||||
collisionResult: int = int(part.Shape.distToShape(solid.Shape)[0])
|
collisionResult: int = int(part.Shape.distToShape(solid.Shape)[0])
|
||||||
if collisionResult == 0:
|
if collisionResult == 0:
|
||||||
return True
|
result.append(solid.Label)
|
||||||
return False
|
if result.__len__() == 0:
|
||||||
|
return None
|
||||||
|
return result
|
||||||
|
|
||||||
def objectHasTouches(self, part, objectIndentation: float) -> List[str]:
|
def objectHasTouches(self, part, solidBodyPadding: float) -> List[str]:
|
||||||
positionVector = self.objectGetPosition(part)
|
try:
|
||||||
result = CoreList()
|
positionVector = self.objectGetPosition(part)
|
||||||
result.append(self.isObjectIntersections(part=part))
|
result = CoreList()
|
||||||
|
result.append(self.isObjectIntersections(part=part))
|
||||||
if objectIndentation != 0 and objectIndentation != None:
|
if solidBodyPadding != 0 and solidBodyPadding != None:
|
||||||
result.append(
|
result.append(
|
||||||
self.axis_movement_and_intersections_observer(
|
self.axis_movement_and_intersections_observer(
|
||||||
positionVector=positionVector,
|
positionVector=positionVector,
|
||||||
alongAxis="x",
|
alongAxis="x",
|
||||||
objectIndentation=objectIndentation,
|
solidBodyPadding=solidBodyPadding,
|
||||||
part=part,
|
part=part,
|
||||||
|
)
|
||||||
)
|
)
|
||||||
)
|
result.append(
|
||||||
result.append(
|
self.axis_movement_and_intersections_observer(
|
||||||
self.axis_movement_and_intersections_observer(
|
positionVector=positionVector,
|
||||||
positionVector=positionVector,
|
alongAxis="y",
|
||||||
alongAxis="y",
|
solidBodyPadding=solidBodyPadding,
|
||||||
objectIndentation=objectIndentation,
|
part=part,
|
||||||
part=part,
|
)
|
||||||
)
|
)
|
||||||
)
|
result.append(
|
||||||
result.append(
|
self.axis_movement_and_intersections_observer(
|
||||||
self.axis_movement_and_intersections_observer(
|
positionVector=positionVector,
|
||||||
positionVector=positionVector,
|
alongAxis="z",
|
||||||
alongAxis="z",
|
solidBodyPadding=solidBodyPadding,
|
||||||
objectIndentation=objectIndentation,
|
part=part,
|
||||||
part=part,
|
)
|
||||||
)
|
)
|
||||||
)
|
spreadArr = result.spreadArray()
|
||||||
|
if spreadArr.isConstrainsString():
|
||||||
return result.onlyTrue()
|
return spreadArr.getAllString()
|
||||||
|
return None
|
||||||
|
except Exception as error:
|
||||||
|
print(error)
|
||||||
|
return None
|
||||||
|
|
||||||
def axis_movement_and_intersections_observer(
|
def axis_movement_and_intersections_observer(
|
||||||
self,
|
self,
|
||||||
positionVector: VectorModel,
|
positionVector: VectorModel,
|
||||||
alongAxis: str,
|
alongAxis: str,
|
||||||
objectIndentation: float,
|
solidBodyPadding: float,
|
||||||
part,
|
part,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
|
result = CoreList()
|
||||||
# UP
|
# UP
|
||||||
positionVector.__setattr__(
|
positionVector.__setattr__(
|
||||||
alongAxis, positionVector.__getattribute__(alongAxis) + objectIndentation
|
alongAxis,
|
||||||
|
positionVector.__getattribute__(alongAxis) + solidBodyPadding,
|
||||||
)
|
)
|
||||||
self.objectSetPosition(part, positionVector.toFreeCadVector())
|
self.objectSetPosition(part, positionVector.toFreeCadVector())
|
||||||
result = self.isObjectIntersections(part=part)
|
# result.onlyUniqueElementAppend(self.isObjectIntersections(part=part))
|
||||||
if result:
|
result.extend(self.isObjectIntersections(part=part))
|
||||||
return True
|
# RESET UP CHANGES
|
||||||
|
positionVector.__setattr__(
|
||||||
|
alongAxis,
|
||||||
|
positionVector.__getattribute__(alongAxis) - solidBodyPadding,
|
||||||
|
)
|
||||||
|
self.objectSetPosition(part, positionVector.toFreeCadVector())
|
||||||
|
|
||||||
# DOWN
|
# DOWN
|
||||||
positionVector.__setattr__(
|
positionVector.__setattr__(
|
||||||
alongAxis, positionVector.__getattribute__(alongAxis) - objectIndentation
|
alongAxis,
|
||||||
)
|
positionVector.__getattribute__(alongAxis) - solidBodyPadding,
|
||||||
positionVector.__setattr__(
|
|
||||||
alongAxis, positionVector.__getattribute__(alongAxis) - objectIndentation
|
|
||||||
)
|
|
||||||
result = self.isObjectIntersections(part=part)
|
|
||||||
if result:
|
|
||||||
return True
|
|
||||||
self.isObjectIntersections(part=part)
|
|
||||||
# ROLLBACK
|
|
||||||
positionVector.__setattr__(
|
|
||||||
alongAxis, positionVector.__getattribute__(alongAxis) + objectIndentation
|
|
||||||
)
|
)
|
||||||
self.objectSetPosition(part, positionVector.toFreeCadVector())
|
self.objectSetPosition(part, positionVector.toFreeCadVector())
|
||||||
return False
|
# 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):
|
def getAllSolids(self):
|
||||||
if self._solids.__len__() == 0:
|
if self._solids.__len__() == 0:
|
||||||
|
@ -286,6 +577,8 @@ class AdjacencyMatrix:
|
||||||
first_detail: str
|
first_detail: str
|
||||||
matrix: Dict[str, List[str]]
|
matrix: Dict[str, List[str]]
|
||||||
|
|
||||||
|
fileName = "adjacency_matrix.json"
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, all_parts: List[str], first_detail: str, matrix: Dict[str, List[str]]
|
self, all_parts: List[str], first_detail: str, matrix: Dict[str, List[str]]
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -294,6 +587,38 @@ class AdjacencyMatrix:
|
||||||
self.matrix = matrix
|
self.matrix = matrix
|
||||||
self.validateMatrix()
|
self.validateMatrix()
|
||||||
|
|
||||||
|
def matrixToFileSystem(self, path: str):
|
||||||
|
FileSystemRepository.writeFile(
|
||||||
|
json.dumps(self.to_dict(), ensure_ascii=False, indent=4),
|
||||||
|
path,
|
||||||
|
AdjacencyMatrix.fileName,
|
||||||
|
)
|
||||||
|
pass
|
||||||
|
|
||||||
|
def sequencesToFileSystem(self, path: str, restrictions: list[str]):
|
||||||
|
FileSystemRepository.writeFile(
|
||||||
|
json.dumps(
|
||||||
|
{"sequences": AllSequences(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):
|
def whatPlaceLeadingPartIndex(self):
|
||||||
i = 0
|
i = 0
|
||||||
for el in self.matrix:
|
for el in self.matrix:
|
||||||
|
@ -309,6 +634,7 @@ class AdjacencyMatrix:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_dict(obj: Any) -> "AdjacencyMatrix":
|
def from_dict(obj: Any) -> "AdjacencyMatrix":
|
||||||
assert isinstance(obj, dict)
|
assert isinstance(obj, dict)
|
||||||
|
|
||||||
all_pars = from_list(from_str, obj.get("allParts"))
|
all_pars = from_list(from_str, obj.get("allParts"))
|
||||||
first_detail = from_str(obj.get("firstDetail"))
|
first_detail = from_str(obj.get("firstDetail"))
|
||||||
matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix"))
|
matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix"))
|
||||||
|
@ -393,7 +719,7 @@ class MeshGeometryCoordinateModel(object):
|
||||||
App.ActiveDocument.recompute()
|
App.ActiveDocument.recompute()
|
||||||
|
|
||||||
|
|
||||||
class FS:
|
class FileSystemRepository:
|
||||||
def readJSON(path: str):
|
def readJSON(path: str):
|
||||||
return json.loads((open(path)).read())
|
return json.loads((open(path)).read())
|
||||||
|
|
||||||
|
@ -580,8 +906,7 @@ class CadAdjacencyMatrix:
|
||||||
collisionResult: int = int(
|
collisionResult: int = int(
|
||||||
part.Shape.distToShape(nextPart.Shape)[0]
|
part.Shape.distToShape(nextPart.Shape)[0]
|
||||||
)
|
)
|
||||||
print(collisionResult)
|
|
||||||
print("collisionResult")
|
|
||||||
if collisionResult == 0:
|
if collisionResult == 0:
|
||||||
matrix[part.Label].append(nextPart.Label)
|
matrix[part.Label].append(nextPart.Label)
|
||||||
|
|
||||||
|
@ -608,19 +933,6 @@ def to_ascii_hash(text):
|
||||||
return reduce(lambda x, y: x + y, ascii_values)
|
return reduce(lambda x, y: x + y, ascii_values)
|
||||||
|
|
||||||
|
|
||||||
def matrixGetUniqueContact(matrix):
|
|
||||||
detailsToCheck = []
|
|
||||||
detailsHashCheck = {}
|
|
||||||
for k, v in matrix.items():
|
|
||||||
for el in v:
|
|
||||||
if el != k:
|
|
||||||
hash = to_ascii_hash(k + el)
|
|
||||||
if detailsHashCheck.get(hash) == None:
|
|
||||||
detailsHashCheck[hash] = hash
|
|
||||||
detailsToCheck.append({"child": el, "parent": k})
|
|
||||||
return detailsToCheck
|
|
||||||
|
|
||||||
|
|
||||||
class IntersectionComputedUseCase:
|
class IntersectionComputedUseCase:
|
||||||
def call(parts):
|
def call(parts):
|
||||||
App.activeDocument().addObject("Part::MultiCommon", "Common")
|
App.activeDocument().addObject("Part::MultiCommon", "Common")
|
||||||
|
@ -657,32 +969,55 @@ class ErrorStringModel:
|
||||||
indent=4,
|
indent=4,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def toFileSystem(self, path: str):
|
||||||
|
return (
|
||||||
|
FileSystemRepository.writeFile(self.toString(), path, "error.json"),
|
||||||
|
ExitFreeCadUseCase.call(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class IsAllObjectSolidsCheckUseCase:
|
class IsAllObjectSolidsCheckUseCase:
|
||||||
def call() -> ErrorStringModel:
|
def call() -> Either:
|
||||||
result = FreeCadRepository().isAllObjectsSolids()
|
result = FreeCadRepository().isAllObjectsSolids()
|
||||||
if result.__len__() == 0:
|
if result.__len__() == 0:
|
||||||
return None
|
return Left(None)
|
||||||
|
|
||||||
return ErrorStringModel(error="Is not solid objects: " + ",".join(result))
|
return Right(
|
||||||
|
ErrorStringModel(error="Is not solid objects: " + ",".join(result))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CheckObjectHasTouchesUseCase:
|
class CheckObjectHasTouchesUseCase:
|
||||||
def call(objectIndentation: float) -> ErrorStringModel:
|
freeCadRepository = FreeCadRepository()
|
||||||
result = []
|
|
||||||
for part in FreeCadRepository().getAllSolids():
|
def call(self, solidBodyPadding: float) -> Either:
|
||||||
if (
|
try:
|
||||||
FreeCadRepository().objectHasTouches(
|
errorResult = []
|
||||||
part=part, objectIndentation=objectIndentation
|
matrix = {}
|
||||||
|
for part in self.freeCadRepository.getAllSolids():
|
||||||
|
matrix[part.Label] = []
|
||||||
|
touches = FreeCadRepository().objectHasTouches(
|
||||||
|
part=part, solidBodyPadding=solidBodyPadding
|
||||||
)
|
)
|
||||||
is False
|
matrix[part.Label].extend(touches)
|
||||||
):
|
if errorResult.__len__() == 0:
|
||||||
result.append(part.Label)
|
return Left(
|
||||||
if result.__len__() == 0:
|
AdjacencyMatrix(
|
||||||
return None
|
all_parts=self.freeCadRepository.getAllLabelsSolids(),
|
||||||
return ErrorStringModel(
|
first_detail=GetFirstDetailUseCase().call(),
|
||||||
error="Solids bodies have no recounts: " + ",".join(result)
|
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"))
|
||||||
|
|
||||||
|
|
||||||
class CheckCadIntersectionObjects:
|
class CheckCadIntersectionObjects:
|
||||||
|
@ -697,6 +1032,7 @@ class ExitFreeCadUseCase:
|
||||||
def call():
|
def call():
|
||||||
import FreeCADGui as Gui
|
import FreeCADGui as Gui
|
||||||
|
|
||||||
|
App.ActiveDocument.clearDocument()
|
||||||
freecadQTWindow = Gui.getMainWindow()
|
freecadQTWindow = Gui.getMainWindow()
|
||||||
freecadQTWindow.close()
|
freecadQTWindow.close()
|
||||||
|
|
||||||
|
@ -709,66 +1045,272 @@ class ExitFreeCadUseCase:
|
||||||
# pass
|
# pass
|
||||||
|
|
||||||
|
|
||||||
|
class EnvReaderUseCase:
|
||||||
|
def call() -> Either:
|
||||||
|
try:
|
||||||
|
return Left(Env.from_dict(FileSystemRepository.readJSON("env.json")))
|
||||||
|
except:
|
||||||
|
print("env reader error")
|
||||||
|
return Right(None)
|
||||||
|
|
||||||
|
|
||||||
|
class OpenFreeCadDocumentUseCase:
|
||||||
|
def call(path: str) -> Either:
|
||||||
|
try:
|
||||||
|
FreeCadRepository().closeIfOpenDocument()
|
||||||
|
FreeCadRepository().openDocument(path)
|
||||||
|
return Left(None)
|
||||||
|
except:
|
||||||
|
print("OpenFreeCadDocumentUseCase error")
|
||||||
|
return Right(None)
|
||||||
|
|
||||||
|
|
||||||
|
class IntersectionGeometryUseCase:
|
||||||
|
def call(contacts, path):
|
||||||
|
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",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
env = FS.readJSON("env.json")
|
try:
|
||||||
cadFilePath = str(env["cadFilePath"])
|
EnvReaderUseCase.call().either(
|
||||||
outPath = str(env["outPath"])
|
leftF=lambda environment: (
|
||||||
objectIndentation = float(env["objectIndentation"])
|
OpenFreeCadDocumentUseCase.call(environment.cadFilePath).either(
|
||||||
|
leftF=lambda _: (
|
||||||
|
IsAllObjectSolidsCheckUseCase.call().either(
|
||||||
|
leftF=lambda _: (
|
||||||
|
CheckObjectHasTouchesUseCase()
|
||||||
|
.call(environment.solidBodyPadding)
|
||||||
|
.either(
|
||||||
|
leftF=lambda adjaxedMatrix: (
|
||||||
|
adjaxedMatrix.sequencesToFileSystem(
|
||||||
|
environment.outPath,
|
||||||
|
environment.sequencesFixed,
|
||||||
|
),
|
||||||
|
IntersectionGeometryUseCase.call(
|
||||||
|
adjaxedMatrix.matrixGetUniqueContact(),
|
||||||
|
environment.outPath,
|
||||||
|
),
|
||||||
|
adjaxedMatrix.matrixToFileSystem(
|
||||||
|
environment.outPath,
|
||||||
|
),
|
||||||
|
ExitFreeCadUseCase.call(),
|
||||||
|
),
|
||||||
|
rightF=lambda error: error.toFileSystem(
|
||||||
|
environment.outPath
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightF=lambda error: error.toFileSystem(
|
||||||
|
environment.outPath
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightF=lambda error: print(error),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
rightF=lambda error: print(error),
|
||||||
|
)
|
||||||
|
|
||||||
if cadFilePath == None:
|
except Exception as error:
|
||||||
return TypeError("CadFile not found env.json")
|
print(error)
|
||||||
App.open("" + cadFilePath)
|
ExitFreeCadUseCase.call()
|
||||||
|
|
||||||
# isAllObjectSolidsCheckUseCase = IsAllObjectSolidsCheckUseCase.call()
|
|
||||||
|
|
||||||
# if isAllObjectSolidsCheckUseCase != None:
|
# main()
|
||||||
# FS.writeFile(isAllObjectSolidsCheckUseCase.toString(), outPath, 'error.json')
|
|
||||||
# ExitFreeCadUseCase.call()
|
|
||||||
# return
|
|
||||||
|
|
||||||
# checkObjectHasTouchesUseCase = CheckObjectHasTouchesUseCase.call(objectIndentation)
|
|
||||||
|
|
||||||
# if checkObjectHasTouchesUseCase != None:
|
class ReadFileSystemAndGetInstanceModelUseCase:
|
||||||
# FS.writeFile(checkObjectHasTouchesUseCase.toString(), outPath, 'error.json')
|
def call(self, model, path):
|
||||||
# ExitFreeCadUseCase.call()
|
if hasattr(model, "from_dict") is False:
|
||||||
# return
|
return Right(
|
||||||
|
"ReadFileSystemAndGetInstanceModelUseCase error:"
|
||||||
topologyMatrix = CadAdjacencyMatrix().matrixBySurfaces()
|
+ model
|
||||||
import json
|
+ "is not have method"
|
||||||
|
+ "from_dict()"
|
||||||
sequences = json.dumps(
|
|
||||||
{"sequences": AllSequences(topologyMatrix.matrix).adj_matrix_names},
|
|
||||||
ensure_ascii=False,
|
|
||||||
indent=4,
|
|
||||||
)
|
|
||||||
matrix = topologyMatrix.matrix
|
|
||||||
contacts = matrixGetUniqueContact(matrix)
|
|
||||||
intersection_geometry = {"status": True, "recalculations": None}
|
|
||||||
for el in contacts:
|
|
||||||
child = App.ActiveDocument.getObjectsByLabel(el.get("child"))[0]
|
|
||||||
parent = App.ActiveDocument.getObjectsByLabel(el.get("parent"))[0]
|
|
||||||
area = IntersectionComputedUseCase.call([child, parent])
|
|
||||||
if area != 0.0:
|
|
||||||
if intersection_geometry.get("recalculations") == None:
|
|
||||||
intersection_geometry["status"] = False
|
|
||||||
intersection_geometry["recalculations"] = []
|
|
||||||
intersection_geometry["recalculations"].append(
|
|
||||||
{"area": area, "connect": el.get("child") + " " + el.get("parent")}
|
|
||||||
)
|
)
|
||||||
|
try:
|
||||||
FS.writeFile(
|
return Left(model.from_dict(FileSystemRepository.readJSON(path)))
|
||||||
json.dumps(intersection_geometry, ensure_ascii=False, indent=4),
|
except:
|
||||||
outPath,
|
error = str(model) + " " + "from dict error " + "path: " + path
|
||||||
"intersection_geometry.json",
|
print("ReadFileSystemAndGetInstanceModelUseCase error" + error)
|
||||||
)
|
return Right(error)
|
||||||
FS.writeFile(sequences, outPath, "sequences.json")
|
pass
|
||||||
|
|
||||||
FS.writeFile(
|
|
||||||
json.dumps(topologyMatrix.to_dict(), ensure_ascii=False, indent=4),
|
|
||||||
outPath,
|
|
||||||
"adjacency_matrix.json",
|
|
||||||
)
|
|
||||||
ExitFreeCadUseCase.call()
|
|
||||||
|
|
||||||
|
|
||||||
main()
|
class FreeCadTest:
|
||||||
|
testName: str
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
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 + 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
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
try:
|
||||||
|
mocksFolder = os.path.dirname(__file__) + "/mocks/"
|
||||||
|
outFolder = os.path.dirname(__file__) + "/out/"
|
||||||
|
|
||||||
|
# FreeCadASPGenerationTestController("test adjaxed matrix simple cube").test(
|
||||||
|
# assertFn=lambda model: CoreList(model.all_parts).equal(["Куб", "Куб001"]),
|
||||||
|
# execComposition=lambda _: (
|
||||||
|
# CheckObjectHasTouchesUseCase()
|
||||||
|
# .call(0)
|
||||||
|
# .either(
|
||||||
|
# leftF=lambda matrix: matrix.matrixToFileSystem(outFolder),
|
||||||
|
# rightF=lambda error: print(error),
|
||||||
|
# )
|
||||||
|
# ),
|
||||||
|
# documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd",
|
||||||
|
# modelName=AdjacencyMatrix.fileName,
|
||||||
|
# model=AdjacencyMatrix,
|
||||||
|
# )
|
||||||
|
|
||||||
|
FreeCadASPGenerationTestController(
|
||||||
|
"test adjaxed matrix vs structure of document"
|
||||||
|
).test(
|
||||||
|
assertFn=lambda model: CoreDict(model.matrix).isEquivalentByKeys(
|
||||||
|
{
|
||||||
|
"Бутылочный домкрат 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т_Гильза"],
|
||||||
|
}
|
||||||
|
),
|
||||||
|
execComposition=lambda _: (
|
||||||
|
CheckObjectHasTouchesUseCase()
|
||||||
|
.call(0)
|
||||||
|
.either(
|
||||||
|
leftF=lambda matrix: matrix.matrixToFileSystem(outFolder),
|
||||||
|
rightF=lambda error: print(error),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
documentPath=mocksFolder + "bottle_jack.FCStd",
|
||||||
|
modelName=AdjacencyMatrix.fileName,
|
||||||
|
model=AdjacencyMatrix,
|
||||||
|
)
|
||||||
|
|
||||||
|
FreeCadASPGenerationTestController(
|
||||||
|
"test adjacency matrix keys vs allparts"
|
||||||
|
).test(
|
||||||
|
assertFn=lambda model: CoreDict(model.matrix).isMatchByKeys(
|
||||||
|
["Куб", "Куб001"]
|
||||||
|
),
|
||||||
|
execComposition=lambda _: (
|
||||||
|
CheckObjectHasTouchesUseCase()
|
||||||
|
.call(0)
|
||||||
|
.either(
|
||||||
|
leftF=lambda matrix: matrix.matrixToFileSystem(outFolder),
|
||||||
|
rightF=lambda error: print(error),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
documentPath=mocksFolder + "simple_assembly_with_two_cubes.FCStd",
|
||||||
|
modelName=AdjacencyMatrix.fileName,
|
||||||
|
model=AdjacencyMatrix,
|
||||||
|
)
|
||||||
|
|
||||||
|
ExitFreeCadUseCase.call()
|
||||||
|
except:
|
||||||
|
print("test error")
|
||||||
|
ExitFreeCadUseCase.call()
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
test()
|
||||||
|
|
||||||
|
|
||||||
|
# TODO:
|
||||||
|
# 1.
|
||||||
|
|
BIN
geometric_feasibility_predicate/mocks/bottle_jack.FCStd
Normal file
BIN
geometric_feasibility_predicate/mocks/bottle_jack.FCStd
Normal file
Binary file not shown.
Binary file not shown.
BIN
test_models/asm_reductor.FCStd
Normal file
BIN
test_models/asm_reductor.FCStd
Normal file
Binary file not shown.
BIN
test_models/desk_table.FCStd
Normal file
BIN
test_models/desk_table.FCStd
Normal file
Binary file not shown.
BIN
test_models/table_pc.FCStd
Normal file
BIN
test_models/table_pc.FCStd
Normal file
Binary file not shown.
BIN
test_models/test_reductor.FCStd
Normal file
BIN
test_models/test_reductor.FCStd
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue