diff --git a/freecad_workbench/freecad/robossembler/Frames.py b/freecad_workbench/freecad/robossembler/Frames.py
index 7b3599f..0c0ca8e 100644
--- a/freecad_workbench/freecad/robossembler/Frames.py
+++ b/freecad_workbench/freecad/robossembler/Frames.py
@@ -8,7 +8,11 @@ from .export_entities import export_coordinate_systems
from .utils.freecad_processor import process_file
from .project_validator import validate_project
from .usecases.asm4parser_usecase import Asm4StructureParseUseCase
-
+from .constraints_operator import create_assembly_parameters, create_fastener_set, create_assembly_sequence, create_clearance_constraint, export_assembly_settings
+from .geometric_feasibility_predicate.main import main as structure_analysis
+from .asm_graph import main as asm_graph
+from .autodock_generator import main as asm_layers
+from .assembly_graph_generation import main as structure_graph
if FreeCAD.GuiUp:
import FreeCADGui
@@ -331,6 +335,46 @@ spawnClassCommand("Publish_Project",
{"Pixmap": str(os.path.join(ICONPATH, "publish.svg")),
"MenuText": "Publish Project",
"ToolTip": "Save and export project files"})
+spawnClassCommand("Create Assembly Parameters",
+ create_assembly_parameters,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Assembly Parameters",
+ "ToolTip": "Create Assembly Parameters"})
+spawnClassCommand("Create Fastener Set",
+ create_fastener_set,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Fastener Set",
+ "ToolTip": "Create Fastener Set"})
+spawnClassCommand("Compute Assembly Sequence",
+ structure_analysis,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Fastener Set",
+ "ToolTip": "Create Fastener Set"})
+spawnClassCommand("Create Assembly Sequence",
+ create_assembly_sequence,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Assembly Sequence",
+ "ToolTip": "Create Assembly Sequence"})
+spawnClassCommand("Export Assembly Settings",
+ export_assembly_settings,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Export Assembly Settings",
+ "ToolTip": "Export Assembly Settings"})
+spawnClassCommand("Create Assembly Layers",
+ asm_layers,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Assembly Layers",
+ "ToolTip": "Create Assembly Layers"})
+spawnClassCommand("Create Structure Graph",
+ structure_graph,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Structure Graph",
+ "ToolTip": "Create Structure Graph"})
+spawnClassCommand("Create Assembly Graph",
+ asm_graph,
+ {"Pixmap": str(os.path.join(ICONPATH, ".svg")),
+ "MenuText": "Create Assembly Graph",
+ "ToolTip": "Create Assembly Graph"})
diff --git a/freecad_workbench/freecad/robossembler/adjacency_matrix.json b/freecad_workbench/freecad/robossembler/adjacency_matrix.json
new file mode 100644
index 0000000..999a349
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/adjacency_matrix.json
@@ -0,0 +1,101 @@
+{
+ "allParts": [
+ "body_down",
+ "sol_gear",
+ "output_shaft",
+ "planet_gear",
+ "planet_gear003",
+ "planet_gear004",
+ "planet_gear005",
+ "planet_gear002",
+ "body_up",
+ "bolt",
+ "bolt2",
+ "bolt3",
+ "bolt4"
+ ],
+ "firstDetail": "body_down",
+ "matrix": {
+ "body_down": [
+ "sol_gear",
+ "planet_gear",
+ "planet_gear003",
+ "planet_gear004",
+ "planet_gear005",
+ "planet_gear002",
+ "body_up",
+ "bolt",
+ "bolt2",
+ "bolt3",
+ "bolt4"
+ ],
+ "sol_gear": [
+ "body_down",
+ "output_shaft",
+ "planet_gear",
+ "planet_gear003",
+ "planet_gear004",
+ "planet_gear005",
+ "planet_gear002"
+ ],
+ "output_shaft": [
+ "sol_gear",
+ "planet_gear",
+ "planet_gear003",
+ "planet_gear004",
+ "planet_gear005",
+ "planet_gear002",
+ "body_up"
+ ],
+ "planet_gear": [
+ "body_down",
+ "sol_gear",
+ "output_shaft"
+ ],
+ "planet_gear003": [
+ "body_down",
+ "sol_gear",
+ "output_shaft"
+ ],
+ "planet_gear004": [
+ "body_down",
+ "sol_gear",
+ "output_shaft"
+ ],
+ "planet_gear005": [
+ "body_down",
+ "sol_gear",
+ "output_shaft"
+ ],
+ "planet_gear002": [
+ "body_down",
+ "sol_gear",
+ "output_shaft"
+ ],
+ "body_up": [
+ "body_down",
+ "output_shaft",
+ "bolt",
+ "bolt2",
+ "bolt3",
+ "bolt4"
+ ],
+ "bolt": [
+ "body_down",
+ "body_up"
+ ],
+ "bolt2": [
+ "body_down",
+ "body_up"
+ ],
+ "bolt3": [
+ "body_down",
+ "body_up"
+ ],
+ "bolt4": [
+ "body_down",
+ "body_up"
+ ]
+ },
+ "matrixError": null
+}
\ No newline at end of file
diff --git a/freecad_workbench/freecad/robossembler/asm_graph.py b/freecad_workbench/freecad/robossembler/asm_graph.py
new file mode 100644
index 0000000..dddb6fc
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/asm_graph.py
@@ -0,0 +1,40 @@
+import json
+import networkx as nx
+import matplotlib.pyplot as plt
+import os
+
+def load_sequences(filename):
+ with open(filename, 'r') as f:
+ data = json.load(f)
+ return data['sequences']
+
+def create_graph(sequences):
+ G = nx.DiGraph()
+ #for seq in sequences:
+ for i in range(len(sequences) - 1):
+ G.add_edge(sequences[i], sequences[i + 1])
+ return G
+
+def draw_graph_with_thumbnails(G, image_folder):
+ pos = {}
+ x = 1
+ for node in G.nodes():
+ pos[node] = (1, -x) # Устанавливаем позиции узлов по вертикали
+ x += 1
+
+ plt.figure(figsize=(8, 6))
+ nx.draw(G, pos, with_labels=True, node_size=100, node_color='skyblue', font_size=10, font_weight='bold')
+
+ # for node, (x, y) in pos.items():
+ # image_path = f"{image_folder}/{node}.svg"
+ # if os.path.exists(image_path):
+ # img = plt.imread(image_path)
+ # plt.imshow(img, aspect='auto', extent=(x - 0.5, x + 0.5, y - 0.5, y + 0.5), zorder=0)
+
+ plt.gca().invert_yaxis() # Инвертируем ось y для вертикального отображения
+ plt.show()
+
+def main():
+ sequences = load_sequences('valid_sequences.json')
+ G = create_graph(sequences)
+ draw_graph_with_thumbnails(G, '/home/markvoltov/GitProjects/framework/test_models/img')
diff --git a/freecad_workbench/freecad/robossembler/assembly_graph_generation.py b/freecad_workbench/freecad/robossembler/assembly_graph_generation.py
index c1e8fc8..8265ac4 100644
--- a/freecad_workbench/freecad/robossembler/assembly_graph_generation.py
+++ b/freecad_workbench/freecad/robossembler/assembly_graph_generation.py
@@ -40,5 +40,3 @@ def main():
draw_graph(assembly_graph)
-if __name__ == '__main__':
- main()
diff --git a/freecad_workbench/freecad/robossembler/cad_generation/env.json b/freecad_workbench/freecad/robossembler/cad_generation/env.json
index 21f4910..7cbd24f 100644
--- a/freecad_workbench/freecad/robossembler/cad_generation/env.json
+++ b/freecad_workbench/freecad/robossembler/cad_generation/env.json
@@ -1,4 +1,4 @@
{
- "cadFilePath": "/home/markvoltov/GitProjects/framework/test_models/cubes.FCStd",
- "outPath": "/home/markvoltov/GitProjects/framework/test_models/"
+ "cadFilePath": "path/to/file",
+ "outPath": "out/path"
}
diff --git a/freecad_workbench/freecad/robossembler/constraints.json b/freecad_workbench/freecad/robossembler/constraints.json
new file mode 100644
index 0000000..544d5da
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/constraints.json
@@ -0,0 +1,6 @@
+[
+ [
+ "Extruder",
+ "Extruder001"
+ ]
+]
\ No newline at end of file
diff --git a/freecad_workbench/freecad/robossembler/constraints_operator.py b/freecad_workbench/freecad/robossembler/constraints_operator.py
index 2833d56..0006731 100644
--- a/freecad_workbench/freecad/robossembler/constraints_operator.py
+++ b/freecad_workbench/freecad/robossembler/constraints_operator.py
@@ -86,6 +86,8 @@ def create_assembly_parameters():
assembly_settings_folder.addObject(assembly_parameters)
+
+
#экспорт всех заданных настроек в общий файл json
def export_assembly_settings():
doc = App.activeDocument()
@@ -133,7 +135,7 @@ def export_assembly_settings():
with open(save_path, "w") as f:
json.dump(data, f, indent=4)
-#create_fastener_set()
-#create_assembly_sequence()
-#create_clearance_constraint()
-export_assembly_settings()
\ No newline at end of file
+# create_fastener_set()
+# create_assembly_sequence()
+# create_clearance_constraint()
+# export_assembly_settings()
\ No newline at end of file
diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json
index bcd23a5..0780dea 100644
--- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json
+++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/env.json
@@ -1,5 +1,5 @@
{
- "cadFilePath": "/home/markvoltov/GitProjects/framework/test_models/cubes.FCStd",
- "outPath": "/home/markvoltov/GitProjects/framework/test_models/",
+ "cadFilePath": "path/to/file",
+ "outPath": "out/path",
"objectIndentation": 0
}
diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py
index 690bc2a..b27b75f 100644
--- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py
+++ b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/main.py
@@ -771,4 +771,4 @@ def main():
ExitFreeCadUseCase.call()
-main()
+#main()
diff --git a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py b/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py
deleted file mode 100644
index 19bce43..0000000
--- a/freecad_workbench/freecad/robossembler/geometric_feasibility_predicate/re_main.py
+++ /dev/null
@@ -1,1313 +0,0 @@
-import FreeCAD as App
-import uuid
-import os
-import json
-from typing import List, Dict, Any, TypeVar, Callable, Type, cast
-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):
- 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:
- 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):
- try:
- listIn[index]
- return False
- except:
- return True
-
-
-class AllSequences:
- all_sequences = None
- adj_matrix = None
- topologyIds = None
- adj_matrix_names = None
-
- def __init__(self, adj_matrix, 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]):
- 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):
- sequences = []
- num_vertices = len(adj_matrix)
-
- def dfs(vertex, sequence):
- sequence.append(vertex)
-
- if len(sequence) == num_vertices:
- sequences.append(sequence)
- return
-
- for i in range(num_vertices):
- if adj_matrix[vertex][i] == 1 and i not in sequence:
- dfs(i, sequence.copy())
-
- for i in range(num_vertices):
- dfs(i, [])
-
- self.all_sequences = sequences
-
- def findId(self, listMatrix, id):
- def filter_odd_num(in_num):
- if in_num["name"] == id:
- return True
- else:
- return False
-
- return list(filter(filter_odd_num, listMatrix))[0]["number"]
-
- def iter_paths(self, adj, min_length=6, path=None):
- if not path:
- for start_node in range(len(adj)):
- yield from self.iter_paths(adj, min_length, [start_node])
- else:
- if len(path) >= min_length:
- yield path
- if path[-1] in path[:-1]:
- return
- current_node = path[-1]
- for next_node in range(len(adj[current_node])):
- if adj[current_node][next_node] == 1:
- yield from self.iter_paths(adj, min_length, path + [next_node])
-
- def allUnique(self, x):
- seen = set()
- return not any(i in seen or seen.add(i) for i in x)
-
- def all_possible_sequences(self, matrix):
- topologyIds = []
- topologyMatrixNumber = {}
- inc = 0
- for k, v in matrix.items():
- inc += 1
- topologyIds.append({"name": k, "number": inc})
- inc = 0
- for k, v in matrix.items():
- inc += 1
- topologyMatrixNumber[inc] = list(
- map(lambda el: self.findId(topologyIds, el), v)
- )
- self.topologyIds = topologyIds
- adj = []
- matrixSize = matrix.keys().__len__()
- inc = 0
- for k, v in topologyMatrixNumber.items():
- adj.append(list(repeat(0, matrixSize)))
- for el in v:
- adj[inc][el - 1] = 1
- inc += 1
- return self.find_all_sequences(adj)
-
-
-class 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):
- 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:
- _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]:
- 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):
- if self._solids.__len__() == 0:
- for part in App.ActiveDocument.Objects:
- if self.is_object_solid(part):
- self._solids.append(part)
-
- return self._solids
-
- def is_object_solid(self, obj):
- if not isinstance(obj, App.DocumentObject):
- return False
- if hasattr(obj, "Group"):
- return False
-
- if not hasattr(obj, "Shape"):
- return False
- if not hasattr(obj.Shape, "Solids"):
- return False
-
- if len(obj.Shape.Solids) == 0:
- return False
-
- return True
-
-
-T = TypeVar("T")
-
-
-def from_list(f: Callable[[Any], T], x: Any) -> List[T]:
- assert isinstance(x, list)
- return [f(y) for y in x]
-
-
-def from_str(x: Any) -> str:
- assert isinstance(x, str)
- return x
-
-
-def from_dict(f: Callable[[Any], T], x: Any) -> Dict[str, T]:
- assert isinstance(x, dict)
- return {k: f(v) for (k, v) in x.items()}
-
-
-def to_class(c: Type[T], x: Any) -> dict:
- assert isinstance(x, c)
- return cast(Any, x).to_dict()
-
-
-# Вспомогательный класс который делает генрацию JSON на основе пайтон обьектов
-
-
-class AdjacencyMatrix:
- matrixError: Dict[str, str] = {}
- all_parts: List[str]
- first_detail: str
- matrix: Dict[str, List[str]]
-
- 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,
- 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):
- i = 0
- for el in self.matrix:
- if el == self.first_detail:
- return i
- i = +1
-
- def validateMatrix(self):
- for el in self.all_parts:
- if self.matrix.get(el) == None:
- self.matrixError[el] = "Not found adjacency " + el
-
- @staticmethod
- def from_dict(obj: Any) -> "AdjacencyMatrix":
- assert isinstance(obj, dict)
-
- all_pars = from_list(from_str, obj.get("allParts"))
- first_detail = from_str(obj.get("firstDetail"))
- matrix = from_dict(lambda x: from_list(from_str, x), obj.get("matrix"))
-
- return AdjacencyMatrix(all_pars, first_detail, matrix)
-
- def to_dict(self) -> dict:
- result: dict = {}
- result["allParts"] = from_list(from_str, self.all_parts)
- result["firstDetail"] = from_str(self.first_detail)
- result["matrix"] = from_dict(lambda x: from_list(from_str, x), self.matrix)
- if self.matrixError.values().__len__() == 0:
- result["matrixError"] = None
- else:
- result["matrixError"] = self.matrixError
- return result
-
- def getDictMatrix(self) -> dict:
- result = {}
-
- for k, v in self.matrix.items():
- result[k] = {}
- for el in v:
- result[k][el] = el
-
- return result
-
-
-def adjacency_matrix_from_dict(s: Any) -> AdjacencyMatrix:
- return AdjacencyMatrix.from_dict(s)
-
-
-def adjacency_matrix_to_dict(x: AdjacencyMatrix) -> Any:
- return to_class(AdjacencyMatrix, x)
-
-
-# Вспомогательный класс для работы с Freecad
-
-
-class FreeCadMetaModel(object):
- def __init__(self, label, vertex) -> None:
- self.label = label
- self.vertex = vertex
-
-
-collision_squares_labels = []
-
-
-class MeshGeometryCoordinateModel(object):
- # Получение геометрии мешей
- def __init__(
- self,
- x,
- y,
- z,
- label,
- ):
- self.x = x
- self.y = y
- self.z = z
- self.label = label
- self.cadLabel = ""
-
- def initializePrimitivesByCoordinate(self, detailSquares):
- uuidDoc = str(uuid.uuid1())
- App.ActiveDocument.addObject("Part::Box", "Box")
- App.ActiveDocument.ActiveObject.Label = uuidDoc
- App.ActiveDocument.recompute()
- part = App.ActiveDocument.getObjectsByLabel(uuidDoc)[0]
- collision_squares_labels.append(uuidDoc)
- part.Width = 2
- part.Height = 2
- part.Length = 2
- part.Placement = App.Placement(
- App.Vector(self.x - 1, self.y - 1, self.z - 1),
- App.Rotation(App.Vector(0.00, 0.00, 1.00), 0.00),
- )
- if detailSquares.get(self.label) is None:
- detailSquares[self.label] = []
- detailSquares[self.label].append(self)
- self.cadLabel = uuidDoc
- App.ActiveDocument.recompute()
-
-
-class 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="utf8")
-
- f.write(data)
-
- def readFile(path: str):
- return open(path).read()
-
- def readFilesTypeFolder(pathFolder: str, fileType=".json"):
- filesJson = list(
- filter(
- lambda x: x[-fileType.__len__() :] == fileType, os.listdir(pathFolder)
- )
- )
- return filesJson
-
-
-class GetAllPartsLabelsUseCase:
- # Получение всех названий деталей
- def call(self):
- parts = []
- for part in FreeCadRepository().getAllSolids():
- parts.append(part.Label)
- return parts
-
-
-def isUnique(array, element):
- for i in array:
- if i == element:
- return False
-
- return True
-
-
-class GetCollisionAtPrimitiveUseCase(object):
- # Получение колизий примитивов
- def call(self, freeCadMetaModels, detailSquares) -> Dict[str, List[str]]:
- matrix: Dict[str, List[str]] = {}
- for model in freeCadMetaModels:
- activePart = App.ActiveDocument.getObjectsByLabel(model.label)[0]
- for key in detailSquares:
- if model.label != key:
- for renderPrimitive in detailSquares[key]:
- primitivePart = App.ActiveDocument.getObjectsByLabel(
- renderPrimitive.cadLabel
- )[0]
- collisionResult: int = int(
- activePart.Shape.distToShape(primitivePart.Shape)[0]
- )
- if collisionResult == 0:
- if matrix.get(model.label) == None:
- matrix[model.label] = [renderPrimitive.label]
- else:
- if isUnique(matrix[model.label], renderPrimitive.label):
- matrix[model.label].append(renderPrimitive.label)
- return matrix
-
-
-class GetFirstDetailUseCase:
- # Получение первой детали
- def call(self):
- return FreeCadRepository().getAllSolids()[0].Label
-
-
-class GetPartPrimitiveCoordinatesUseCase(object):
- # Получение координат примитивов
- def call(self, freeCadMetaModels):
- meshCoordinates: list[MeshGeometryCoordinateModel] = []
- for model in freeCadMetaModels:
- vertexesDetail = model.vertex
- labelDetail = model.label
- for coords in vertexesDetail:
- detailVertex = MeshGeometryCoordinateModel(
- coords.X,
- coords.Y,
- coords.Z,
- labelDetail,
- )
- meshCoordinates.append(detailVertex)
-
- return meshCoordinates
-
-
-class InitPartsParseUseCase:
- # Инициализация парсинга
- def call(self):
- product_details = []
- for part in FreeCadRepository().getAllSolids():
- if part is not None:
- model = FreeCadMetaModel(part.Label, part.Shape.Vertexes)
- if model is not None:
- product_details.append(model)
- return product_details
-
-
-class RenderPrimitiveUseCase(object):
- # Рендеринг премитивов
- def call(
- self, meshModels: list[MeshGeometryCoordinateModel], detailSquares
- ) -> None:
- for mesh in meshModels:
- mesh.initializePrimitivesByCoordinate(detailSquares)
-
-
-class ClearWorkSpaceDocumentUseCase(object):
- # Очистка рабочего простарнства
- def call(self, detailSquares):
- for key in detailSquares:
- for renderPrimitive in detailSquares[key]:
- primitivePart = App.ActiveDocument.getObjectsByLabel(
- renderPrimitive.cadLabel
- )[0]
- App.ActiveDocument.removeObject(primitivePart.Name)
-
-
-class RenderPrimitivesScenario(object):
- def __init__(
- self,
- initPartsParseUseCase: InitPartsParseUseCase,
- getPartPrimitiveCoordinatesUseCase: GetPartPrimitiveCoordinatesUseCase,
- renderPrimitiveUseCase: RenderPrimitiveUseCase,
- getCollisionAtPrimitives: GetCollisionAtPrimitiveUseCase,
- clearWorkSpaceDocument: ClearWorkSpaceDocumentUseCase,
- ) -> None:
- self.initPartsParseUseCase = initPartsParseUseCase
- self.getPartPrimitiveCoordinatesUseCase = getPartPrimitiveCoordinatesUseCase
- self.renderPrimitiveUseCase = renderPrimitiveUseCase
- self.getCollisionAtPrimitives = getCollisionAtPrimitives
- self.clearWorkSpaceDocument = clearWorkSpaceDocument
-
- def call(self) -> None:
- meshCoordinates = []
- detailSquares = {}
- parts = self.initPartsParseUseCase.call()
- meshCoordinates = self.getPartPrimitiveCoordinatesUseCase.call(parts)
- self.renderPrimitiveUseCase.call(meshCoordinates, detailSquares)
- matrix = self.getCollisionAtPrimitives.call(parts, detailSquares)
- self.clearWorkSpaceDocument.call(detailSquares)
- return matrix
-
-
-class ClearWorkSpaceDocumentUseCase(object):
- # Очистака рабочего пространства
- def call(self, detailSquares):
- for key in detailSquares:
- for renderPrimitive in detailSquares[key]:
- primitivePart = App.ActiveDocument.getObjectsByLabel(
- renderPrimitive.cadLabel
- )[0]
- App.ActiveDocument.removeObject(primitivePart.Name)
-
-
-class CadAdjacencyMatrix:
- # Матрица основанная на соприкосновении примитива с обьектами
- def primitiveMatrix(self):
- # Получение матрицы
- matrix = RenderPrimitivesScenario(
- InitPartsParseUseCase(),
- GetPartPrimitiveCoordinatesUseCase(),
- RenderPrimitiveUseCase(),
- GetCollisionAtPrimitiveUseCase(),
- ClearWorkSpaceDocumentUseCase(),
- ).call()
- return AdjacencyMatrix(
- all_parts=GetAllPartsLabelsUseCase().call(),
- first_detail=GetFirstDetailUseCase().call(),
- matrix=matrix,
- )
-
- # Матрица основанная на соприкосновении обьектов
-
- def matrixBySurfaces(self):
- matrix = {}
- for part in FreeCadRepository().getAllSolids():
- matrix[part.Label] = []
- for nextPart in FreeCadRepository().getAllSolids():
- if part.Label != nextPart.Label:
- # Вычисление соприконсоновения площади деталей
- collisionResult: int = int(
- part.Shape.distToShape(nextPart.Shape)[0]
- )
-
- if collisionResult == 0:
- matrix[part.Label].append(nextPart.Label)
-
- return AdjacencyMatrix(
- all_parts=GetAllPartsLabelsUseCase().call(),
- first_detail=GetFirstDetailUseCase().call(),
- matrix=matrix,
- )
-
-
-def reduce(function, iterable, initializer=None):
- it = iter(iterable)
- if initializer is None:
- value = next(it)
- else:
- value = initializer
- for element in it:
- value = function(value, element)
- return value
-
-
-def to_ascii_hash(text):
- ascii_values = [ord(character) for character in text]
- return reduce(lambda x, y: x + y, ascii_values)
-
-
-class IntersectionComputedUseCase:
- def call(parts):
- App.activeDocument().addObject("Part::MultiCommon", "Common")
- App.activeDocument().Common.Shapes = [parts[0], parts[1]]
- App.activeDocument().getObject("Common").ViewObject.ShapeColor = getattr(
- parts[0].getLinkedObject(True).ViewObject,
- "ShapeColor",
- App.activeDocument().getObject("Common").ViewObject.ShapeColor,
- )
- App.activeDocument().getObject("Common").ViewObject.DisplayMode = getattr(
- parts[0].getLinkedObject(True).ViewObject,
- "DisplayMode",
- App.activeDocument().getObject("Common").ViewObject.DisplayMode,
- )
- App.ActiveDocument.recompute()
- area = App.activeDocument().getObject("Common").Shape.Area
- App.ActiveDocument.removeObject("Common")
- return area
-
-
-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(),
- )
-
-
-class IsAllObjectSolidsCheckUseCase:
- def call() -> Either:
- result = FreeCadRepository().isAllObjectsSolids()
- if result.__len__() == 0:
- return Left(None)
-
- return Right(
- ErrorStringModel(error="Is not solid objects: " + ",".join(result))
- )
-
-
-class CheckObjectHasTouchesUseCase:
- 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(
- AdjacencyMatrix(
- 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"))
-
-
-class CheckCadIntersectionObjects:
- report = []
-
- def call() -> bool:
- FreeCadRepository().getAllSolids()
- return False
-
-
-class ExitFreeCadUseCase:
- def call():
- import FreeCADGui as Gui
-
- App.ActiveDocument.clearDocument()
- freecadQTWindow = Gui.getMainWindow()
- freecadQTWindow.close()
-
-
-# class CheckValidIntersectionUseCase:
-# def call() -> ErrorStringModel:
-# for part in FreeCadRepository().getAllSolids():
-# print(part)
-# FreeCadRepository().obj
-# 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():
- try:
- EnvReaderUseCase.call().either(
- leftF=lambda environment: (
- 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),
- )
-
- except Exception as error:
- print(error)
- ExitFreeCadUseCase.call()
-
-
-# main()
-
-
-class ReadFileSystemAndGetInstanceModelUseCase:
- def call(self, model, path):
- 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:
- error = str(model) + " " + "from dict error " + "path: " + path
- print("ReadFileSystemAndGetInstanceModelUseCase error" + error)
- return Right(error)
- pass
-
-
-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()
-
diff --git a/freecad_workbench/freecad/robossembler/get_sequences.py b/freecad_workbench/freecad/robossembler/get_sequences.py
new file mode 100644
index 0000000..7d268bf
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/get_sequences.py
@@ -0,0 +1,61 @@
+import json
+import networkx as nx
+
+def load_data(file_path):
+ with open(file_path, 'r') as file:
+ return json.load(file)
+
+def create_graph(data):
+ G = nx.Graph()
+ for part in data['allParts']:
+ G.add_node(part)
+ for part, connections in data['matrix'].items():
+ for connected_part in connections:
+ G.add_edge(part, connected_part)
+ return G
+
+def find_leaf_nodes(graph, central_node):
+ leaf_nodes = []
+ for node in graph.nodes:
+ if node != central_node and graph.degree(node) == 1:
+ leaf_nodes.append(node)
+ return leaf_nodes
+
+def find_all_paths(graph, start_node, end_node):
+ try:
+ return list(nx.all_simple_paths(graph, start_node, end_node))
+ except nx.NetworkXNoPath:
+ return []
+
+def load_constraints(file_path):
+ with open(file_path, 'r') as file:
+ return json.load(file)
+
+def is_valid_sequence(sequence, constraints):
+ for constraint in constraints:
+ if constraint[0] in sequence and constraint[1] in sequence:
+ if sequence.index(constraint[0]) > sequence.index(constraint[1]):
+ return False
+ return True
+
+def save_sequences(sequences, file_path):
+ with open(file_path, 'w') as file:
+ json.dump(sequences, file, indent=4)
+
+data = load_data('adjacency_matrix.json')
+constraints = load_constraints('constraints.json')
+all_parts = data['allParts']
+graph = create_graph(data)
+first_detail = data['firstDetail']
+leaf_nodes = find_leaf_nodes(graph, first_detail)
+
+all_sequences = []
+for leaf in leaf_nodes:
+ paths = find_all_paths(graph, leaf, first_detail)
+ for path in paths:
+ if set(path) == set(all_parts) and is_valid_sequence(path, constraints):
+ all_sequences.append(path)
+
+save_sequences(all_sequences, 'valid_sequences.json')
+
+print(f"Найдено {len(all_sequences)} допустимых последовательностей сборки.")
diff --git a/freecad_workbench/freecad/robossembler/graph_visualisation.py b/freecad_workbench/freecad/robossembler/graph_visualisation.py
new file mode 100644
index 0000000..9baf51c
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/graph_visualisation.py
@@ -0,0 +1,26 @@
+import json
+import networkx as nx
+import matplotlib.pyplot as plt
+
+# Загружаем данные из файла
+with open('adjacency_matrix.json', 'r') as file:
+ data = json.load(file)
+
+# Создаем пустой граф
+G = nx.Graph()
+
+# Добавляем узлы
+for part in data['allParts']:
+ G.add_node(part)
+
+# Добавляем ребра
+for part, connections in data['matrix'].items():
+ for connected_part in connections:
+ G.add_edge(part, connected_part)
+
+# Визуализируем граф
+plt.figure(figsize=(10, 8))
+pos = nx.spring_layout(G)
+nx.draw(G, pos, with_labels=True, node_size=100, node_color='lightblue', font_size=10, font_weight='bold', edge_color='gray')
+plt.title('Graph of Part Connections')
+plt.show()
diff --git a/freecad_workbench/freecad/robossembler/init_gui.py b/freecad_workbench/freecad/robossembler/init_gui.py
index ef93952..5289d6a 100644
--- a/freecad_workbench/freecad/robossembler/init_gui.py
+++ b/freecad_workbench/freecad/robossembler/init_gui.py
@@ -38,8 +38,7 @@ class Robossembler(Gui.Workbench):
self.framecommands = [
"BoMGeneration",
- "FrameCommand",
-
+ "FrameCommand",
"SelectedPartFrameCommand",
"AllPartFramesCommand",
"FeatureFrameCommand"
@@ -52,8 +51,21 @@ class Robossembler(Gui.Workbench):
"Validate_Project",
"Publish_Project"
]
+ self.asmcommands = [
+ "Create Assembly Parameters",
+ "Create Fastener Set",
+ "Create Assembly Sequence",
+ "Export Assembly Settings",
+ "Compute Assembly Sequence",
+ "Create Assembly Layers",
+ "Create Structure Graph",
+ "Create Assembly Graph"
+
+ ]
+
self.appendToolbar(f"{__class__.__name__} Frames", self.framecommands)
self.appendToolbar(f"{__class__.__name__} Tools", self.toolcommands)
+ self.appendToolbar(f"{__class__.__name__} Assembly Setup", self.asmcommands)
App.Console.PrintMessage(translate("Console",
"Switching to robossembler") + "\n")
diff --git a/freecad_workbench/freecad/robossembler/sequences.json b/freecad_workbench/freecad/robossembler/sequences.json
index 07d5216..e69de29 100644
--- a/freecad_workbench/freecad/robossembler/sequences.json
+++ b/freecad_workbench/freecad/robossembler/sequences.json
@@ -1,10 +0,0 @@
-{
- "sequences": [
-
- "body_down",
- "sol_gear",
- "output_shaft",
- "planet_gear",
- "planet_gear002"
- ]
-}
\ No newline at end of file
diff --git a/freecad_workbench/freecad/robossembler/valid_sequences.json b/freecad_workbench/freecad/robossembler/valid_sequences.json
new file mode 100644
index 0000000..b0c3c75
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/valid_sequences.json
@@ -0,0 +1,14 @@
+{"sequences":[
+ "PTFE Tube Nut M6 v1",
+ "PTFE Tube Nut M6 v002",
+ "Extruder002",
+ "Extruder",
+ "Extruder001",
+ "91239A140_Button Head Hex Drive Screw",
+ "Hotend002",
+ "Hotend001",
+ "Hotend003",
+ "Extruder003",
+ "Extruder004",
+ "Hotend"
+]}
\ No newline at end of file
diff --git a/freecad_workbench/freecad/robossembler/valid_sequences.py b/freecad_workbench/freecad/robossembler/valid_sequences.py
new file mode 100644
index 0000000..7bf1387
--- /dev/null
+++ b/freecad_workbench/freecad/robossembler/valid_sequences.py
@@ -0,0 +1,36 @@
+import json
+
+def load_data(file_path):
+ with open(file_path, 'r') as file:
+ return json.load(file)
+
+def is_valid_sequence(sequence, constraints):
+ for constraint in constraints:
+ if constraint[0] in sequence and constraint[1] in sequence:
+ if sequence.index(constraint[0]) > sequence.index(constraint[1]):
+ return False
+ return True
+
+def save_sequences(sequences, file_path):
+ with open(file_path, 'w') as file:
+ json.dump(sequences, file, indent=4)
+
+# Load data from files
+adjacency_matrix = load_data('adjacency_matrix.json')
+constraints = load_data('constraints.json')
+sequences = load_data('sequences.json')
+
+# Get all parts and first detail
+all_parts = adjacency_matrix['allParts']
+first_detail = adjacency_matrix['firstDetail']
+
+# Filter valid sequences
+valid_sequences = []
+for sequence in sequences:
+ if len(set(sequence)) == len(set(all_parts)): #and is_valid_sequence(sequence, constraints):
+ valid_sequences.append(sequence)
+
+# Save valid sequences to file
+save_sequences(valid_sequences, 'valid_sequences.json')
+
+print(f"Найдено {len(valid_sequences)} допустимых последовательностей сборки.")
diff --git a/test_models/assembly_settings_test_reductor.json b/test_models/assembly_settings_test_reductor.json
new file mode 100644
index 0000000..1ffac35
--- /dev/null
+++ b/test_models/assembly_settings_test_reductor.json
@@ -0,0 +1,34 @@
+[
+ {
+ "Name": "Fastener_Set",
+ "Type": "fastener_set",
+ "Parent": "body_down",
+ "Child": "body_up",
+ "Fasteners": [
+ "bolt4",
+ "bolt",
+ "bolt2",
+ "bolt3"
+ ]
+ },
+ {
+ "Name": "Assembly_Sequence",
+ "Type": "asm_sequence",
+ "Parent": "body_down",
+ "Child": "sol_gear"
+ },
+ {
+ "Name": "Clearance_Constraint",
+ "Type": "clearance",
+ "PartName": [
+ "planet_gear002",
+ "planet_gear005",
+ "planet_gear004",
+ "planet_gear003",
+ "planet_gear",
+ "output_shaft",
+ "sol_gear"
+ ],
+ "MaxClearance": 1.0
+ }
+]
\ No newline at end of file
diff --git a/test_models/img/body_down.svg b/test_models/img/body_down.svg
deleted file mode 100644
index fab4fec..0000000
--- a/test_models/img/body_down.svg
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/body_up.svg b/test_models/img/body_up.svg
deleted file mode 100644
index 2a2e3ae..0000000
--- a/test_models/img/body_up.svg
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/bolt.svg b/test_models/img/bolt.svg
deleted file mode 100644
index 08881a4..0000000
--- a/test_models/img/bolt.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/bolt2.svg b/test_models/img/bolt2.svg
deleted file mode 100644
index 4d86fc4..0000000
--- a/test_models/img/bolt2.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/bolt3.svg b/test_models/img/bolt3.svg
deleted file mode 100644
index d114b57..0000000
--- a/test_models/img/bolt3.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/bolt4.svg b/test_models/img/bolt4.svg
deleted file mode 100644
index ca088bf..0000000
--- a/test_models/img/bolt4.svg
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/output_shaft.svg b/test_models/img/output_shaft.svg
deleted file mode 100644
index d30fd59..0000000
--- a/test_models/img/output_shaft.svg
+++ /dev/null
@@ -1,53 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/planet_gear.svg b/test_models/img/planet_gear.svg
deleted file mode 100644
index c7d8898..0000000
--- a/test_models/img/planet_gear.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/planet_gear002.svg b/test_models/img/planet_gear002.svg
deleted file mode 100644
index 32101ac..0000000
--- a/test_models/img/planet_gear002.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/planet_gear003.svg b/test_models/img/planet_gear003.svg
deleted file mode 100644
index a692e01..0000000
--- a/test_models/img/planet_gear003.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/planet_gear004.svg b/test_models/img/planet_gear004.svg
deleted file mode 100644
index e5a7285..0000000
--- a/test_models/img/planet_gear004.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/planet_gear005.svg b/test_models/img/planet_gear005.svg
deleted file mode 100644
index 7b3470e..0000000
--- a/test_models/img/planet_gear005.svg
+++ /dev/null
@@ -1,13 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/test_models/img/sol_gear.svg b/test_models/img/sol_gear.svg
deleted file mode 100644
index eebe881..0000000
--- a/test_models/img/sol_gear.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
\ No newline at end of file