Слияние веток 91 и path2pddl

This commit is contained in:
Mark Voltov 2024-03-24 15:26:20 +03:00
parent 44ee21414b
commit e86914dba1
24 changed files with 732 additions and 0 deletions

View file

@ -0,0 +1,79 @@
import FreeCAD
import FreeCADGui
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
FreeCADGui.addCommand(, my_function1)
class MyWorkbenchGui:
def Initialize(self):
# Словарь с именами функций и путями к иконкам
commands_dict = {
"MyCommand1": (my_function1, "Path/to/icon1.svg"),
"MyCommand2": (my_function2, "Path/to/icon2.svg")
}
# Регистрация команд и иконок
register_commands(commands_dict)
# Создание панели инструментов и добавление команд с иконками
self.appendToolbar("My Tools", [("MyCommand1", "MyCommand1_icon"), ("MyCommand2", "MyCommand2_icon")])
def appendToolbar(self, toolbar_name, commands):
toolbar = FreeCADGui.ToolBar(toolbar_name)
for cmd, icon in commands:
toolbar.appendCommand(cmd, icon)
class MyWorkbench:
def __init__(self):
self.__class__.Icon = FreeCAD.getHomePath() + "Path\\to\\icon.svg"
self.__class__.MenuText = "Assembly Structure Analysis"
self.__class__.ToolTip = "Workbench made for structure analysis and assembly planning"
def GetClassName(self):
return "Gui::AsmSAWorkbench"
def Initialize(self):
# Create toolbar
self.appendToolbar("Preprocessing", ["Check Model", "Constraints Autogeneration", "Constraints Editing"])
self.appendToolbar("Analysis", ["Generate AdjMatrix", "Generate Relationship Matrix", "Clusterisation", "StepByStepClusterisation"])
self.appendToolbar("AssemblySequence", ["Generate Assembly Sequences", "Decomposition Step" ])
# Create menu
# self.appendMenu("My Menu", ["MySubMenu1", "MySubMenu2"])
def Activated(self):
# Code to run when the workbench is activated
pass
def Deactivated(self):
# Code to run when the workbench is deactivated
pass
def GetResources(self):
return {'Pixmap': self.__class__.Icon, 'MenuText': self.__class__.MenuText, 'ToolTip': self.__class__.ToolTip}
def appendToolbar(self, toolbar_name, commands):
toolbar = FreeCADGui.ToolBar(toolbar_name)
for cmd in commands:
toolbar.appendCommand(cmd)
def appendMenu(self, menu_name, commands):
menu = FreeCADGui.Menu(menu_name)
for cmd in commands:
menu.appendCommand(cmd)
# Create instance of the workbench
my_workbench = MyWorkbench()
# Register the workbench with FreeCAD
FreeCADGui.addWorkbench(my_workbench)

View file

@ -0,0 +1,45 @@
import FreeCAD
import FreeCADGui
def register_commands(commands_dict):
for cmd_name, (func, icon_path) in commands_dict.items():
FreeCADGui.addCommand(cmd_name, func)
FreeCADGui.addIcon(cmd_name + "_icon", icon_path)
class MyWorkbenchGui:
def Initialize(self):
pass
def InitGui(self):
# Словарь с именами функций и путями к иконкам
commands_dict = {
'Check Model': (my_function1, "Path/to/icon1.svg"),
'Constraints Autogeneration': (my_function2, "Path/to/icon2.svg"),
'Constraints Editing': (my_function1, "Path/to/icon1.svg"),
'Generate AdjMatrix': (my_function1, "Path/to/icon1.svg"),
'Generate Relationship Matrix': (my_function1, "Path/to/icon1.svg"),
'Clusterisation': (my_function1, "Path/to/icon1.svg"),
'StepByStepClusterisation': (my_function1, "Path/to/icon1.svg"),
'Generate Assembly Sequences': (my_function1, "Path/to/icon1.svg"),
'Decomposition Step': (my_function1, "Path/to/icon1.svg")
}
# Регистрация команд и иконок
register_commands(commands_dict)
# Создание панели инструментов и добавление команд с иконками
self.appendToolbar("Preprocessing", ["Check Model", "Constraints Autogeneration", "Constraints Editing"])
self.appendToolbar("Analysis", ["Generate AdjMatrix", "Generate Relationship Matrix", "Clusterisation", "StepByStepClusterisation"])
self.appendToolbar("AssemblySequence", ["Generate Assembly Sequences", "Decomposition Step" ])
def appendToolbar(self, toolbar_name, commands):
toolbar = FreeCADGui.ToolBar(toolbar_name)
for cmd, icon in commands:
toolbar.appendCommand(cmd, icon)
# Создание экземпляра верстака GUI
my_workbench_gui = MyWorkbenchGui()
# Регистрация верстака
FreeCADGui.addWorkbench(my_workbench_gui)

109
sequence_generation/Main.py Normal file
View file

@ -0,0 +1,109 @@
import os
from extensions.list import CoreList
from extensions.dict import CoreDict
from helpers.freecadtest import FreeCadASPGenerationTestController
from models.adjacency_matrix_model import (
AdjacencyMatrixModel,
)
from usecases.check_object_has_touches_use_case import (
CheckObjectHasTouchesUseCase,
)
from usecases.clusterisation_sequences_use_case import (
ClusterisationSequenceUseCase,
)
from usecases.check_sequence_use_case import (
CheckSequenceUsecase,
)
from usecases.env_reader_use_case import (
EnvReaderUseCase,
)
from usecases.exit_freecad_use_case import (
ExitFreeCadUseCase,
)
from usecases.intersection_geometry_use_case import (
IntersectionGeometryUseCase,
)
from usecases.open_freecad_document_use_case import (
OpenFreeCadDocumentUseCase,
)
import FreeCad as App
import FreeCadGUI as Gui
from mocks.mock_structure import bottle_jack_mock_structure, simple_cube_mock_structure
#Функционал для реализации в main
#Отсюда тянем необходимые для работы верстака функции
""" 'Check Model': (my_function1, "Path/to/icon1.svg"),
'Constraints Autogeneration': (my_function2, "Path/to/icon2.svg"),
'Constraints Editing': (my_function1, "Path/to/icon1.svg"),
'Generate AdjMatrix': (my_function1, "Path/to/icon1.svg"),
'Generate Relationship Matrix': (my_function1, "Path/to/icon1.svg"),
'Clusterisation': (my_function1, "Path/to/icon1.svg"),
'StepByStepClusterisation': (my_function1, "Path/to/icon1.svg"),
'Generate Assembly Sequences': (my_function1, "Path/to/icon1.svg"),
'Decomposition Step': (my_function1, "Path/to/icon1.svg")"""
class PreprocessorUsecase:
def initActiveDocument():
activeDoc = App.ActiveDocument
return activeDoc
def checkModel(activeDoc):
IntersectionGeometryUseCase.call(activeDoc, out_path)
def constrAutoGen():
ConstraintsAutoGeneration.call(activeDoc)
def
def main():
try:
EnvReaderUseCase.call().either(
leftF=lambda environment: (
OpenFreeCadDocumentUseCase.call(environment.cadFilePath).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,
),
ClusterisationSequenceUseCase(environment.outPath),
ExitFreeCadUseCase.call(),
),
rightF=lambda error: error.toFileSystem(
environment.outPath
),
),
)
),
rightF=lambda error: print(error),
),
),
rightF=lambda error: print(error),
)
except Exception as error:
print(error)
ExitFreeCadUseCase.call()

View file

View file

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,69 @@
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 EdgeBetweennessClustering:
def __init__(self, graph):
self.graph = graph.copy()
self.clusters = []
def cluster(self):
while self.graph.number_of_edges() > 0:
edge_betweenness = nx.edge_betweenness_centrality(self.graph)
max_betweenness_edge = max(edge_betweenness, key=edge_betweenness.get)
# Removing the edge with the highest centrality
self.graph.remove_edge(*max_betweenness_edge)
# Formation of clusters after edge removal
components = list(nx.connected_components(self.graph))
if components not in self.clusters:
self.clusters.append(components)
def get_clusters(self):
return self.clusters
def clusterisationSequence(file_path):
#outFolder = os.path.dirname(__file__) + "/out/"
#file_path = outFolder + 'adjacency_matrix.json'
graph_processor = GraphProcessor(file_path + "adjacency_matrix.json")
G = graph_processor.load_graph_from_json()
ebc = EdgeBetweennessClustering(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])
#print(clusters)
FileSystemRepository.writeFile(json.dumps(clusters, ensure_ascii=False, indent=2), file_path, "assembly_sequence.json")
return clusters

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