solve optimizer

This commit is contained in:
MarkVoltov 2024-12-01 22:05:03 +03:00
parent a59b3a4775
commit ca0df838a2
17 changed files with 491 additions and 236 deletions

View file

@ -1,49 +0,0 @@
import FreeCAD
import FreeCADGui
from PySide import QtGui, QtCore
class DatumTool:
"""
A tool for creating datums in existing models
"""
def __init__(self):
self.active = False
def activate(self):
self.active = True
FreeCAD.Console.PrintMessage("Datum tool activatedn")
def deactivate(self):
self.active = False
FreeCAD.Console.PrintMessage("Datum tool deactivatedn")
def mousePressEvent(self, event):
if self.active:
# Create a datum at the position of the mouse click
pos = FreeCADGui.ActiveDocument.ActiveView.getCursorPos()
point = FreeCADGui.ActiveDocument.ActiveView.getPoint(pos)
datum = FreeCAD.ActiveDocument.addObject("Part::Datum", "Datum")
datum.Placement.Base = point
datum.ViewObject.ShapeColor = (0.0, 1.0, 0.0) # Set the color of the datum to green
FreeCAD.ActiveDocument.recompute()
class DatumCommand:
"""
A command for activating and deactivating the datum tool
"""
def __init__(self):
self.tool = DatumTool()
def Activated(self):
self.tool.activate()
FreeCADGui.ActiveDocument.ActiveView.addEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent)
def Deactivated(self):
self.tool.deactivate()
FreeCADGui.ActiveDocument.ActiveView.removeEventCallback("SoMouseButtonEvent", self.tool.mousePressEvent)
def GetResources(self):
return {'Pixmap': 'path/to/icon.png', 'MenuText': 'Datum Tool', 'ToolTip': 'Creates datum elements in existing models'}
# Add the command to the Draft Workbench
FreeCADGui.addCommand('DatumCommand', DatumCommand())

View file

@ -0,0 +1,28 @@
import FreeCAD as App
from .geometric_feasibility_predicate.main import main as asm_analysis
from get_sequences import process_adjacency_data
from valid_sequences import filter_valid_sequences
from solve_optimizer import restore_full_sequence # Убедитесь, что эта функция импортирована
from constraints_operator import collect_assembly_settings
def main(flag):
# Выполняем анализ сборки и получаем необходимые данные
#flag используется для того, чтобы выбирать между обычным и оптимизированным вариантом работы
intersection_geometry, sequences, topologyMatrix = asm_analysis()
adjacency_matrix = topologyMatrix.matrix
assembly_settings = collect_assembly_settings()
# Упрощаем матрицу смежности
if flag:
simplified_matrix = simplify_adjacency_matrix(assembly_settings, adjacency_matrix)
all_parts, graph, first_detail, leaf_nodes, all_sequences = process_adjacency_data(simplified_matrix)
else:
all_parts, graph, first_detail, leaf_nodes, all_sequences = process_adjacency_data(adjacency_matrix)
# Фильтруем допустимые последовательности
valid_sequences = filter_valid_sequences(adjacency_matrix, sequences, assembly_settings)
full_sequence = restore_full_sequence(assembly_settings, all_sequences)
return full_sequence
main()

View file

@ -35,8 +35,9 @@ def draw_graph(G):
plt.show()
def main():
sequence = load_assembly_sequence('assembly_sequence.json')
sequence = load_assembly_sequence('/home/markvoltov/GitProjects/framework/test_models/adjacency_matrix.json')
assembly_graph = create_assembly_graph(sequence)
draw_graph(assembly_graph)
# main()

View file

@ -135,6 +135,50 @@ def export_assembly_settings():
with open(save_path, "w") as f:
json.dump(data, f, indent=4)
#для работы с данными как с переменной
def collect_assembly_settings():
doc = App.activeDocument()
if not doc:
return None
assembly_settings_folder = None
for obj in doc.Objects:
if obj.Name == "Assembly_Settings":
assembly_settings_folder = obj
break
if not assembly_settings_folder:
return None
assembly_settings = []
for obj in assembly_settings_folder.Group:
if hasattr(obj, "Type"):
obj_dict = {"Name": obj.Name}
if obj.Type == "fastener_set":
fasteners = [part.Label for part in obj.Fasteners]
obj_dict.update({
"Type": "fastener_set",
"Parent": obj.Parent.Label,
"Child": obj.Child.Label,
"Fasteners": fasteners
})
elif obj.Type == "asm_sequence":
obj_dict.update({
"Type": "asm_sequence",
"Parent": obj.Parent.Label,
"Child": obj.Child.Label
})
elif obj.Type == "clearance":
partnames = [part.Label for part in obj.PartName]
obj_dict.update({
"Type": "clearance",
"PartName": partnames,
"MaxClearance": obj.MaxClearance
})
assembly_settings.append(obj_dict)
return assembly_settings
# create_fastener_set()
# create_assembly_sequence()
# create_clearance_constraint()

View file

@ -1,5 +1,5 @@
{
"cadFilePath": "path/to/file",
"outPath": "out/path",
"cadFilePath": "/home/markvoltov/GitProjects/framework/test_models/test_reductor.FCStd",
"outPath": "/home/markvoltov/GitProjects/framework/test_models/",
"objectIndentation": 0
}

View file

@ -120,7 +120,7 @@ class AllSequences:
for el in v:
adj[inc][el - 1] = 1
inc += 1
return self.find_all_sequences(adj)
self.find_all_sequences(adj)
class VectorModel:
@ -580,8 +580,8 @@ class CadAdjacencyMatrix:
collisionResult: int = int(
part.Shape.distToShape(nextPart.Shape)[0]
)
print(collisionResult)
print("collisionResult")
# print(collisionResult)
# print("collisionResult")
if collisionResult == 0:
matrix[part.Label].append(nextPart.Label)
@ -708,68 +708,82 @@ class ExitFreeCadUseCase:
# FreeCadRepository().obj
# pass
#функция, проверяющая, открывается ли программа через консоль или через верстак freecad.
def get_paths():
if 'FreeCAD' in globals():
active_doc = FreeCAD.activeDocument()
if active_doc:
cadFilePath = active_doc.FileName
outPath = os.path.dirname(cadFilePath)
else:
raise Exception("Нет активного документа в FreeCAD.")
else:
with open('env.json', 'r', encoding='utf-8') as file:
env_data = json.load(file)
cadFilePath = env_data.get('cadFilePath')
outPath = env_data.get('outPath')
if not cadFilePath or not outPath:
raise Exception("Не найдены cadFilePath или outPath в env.json.")
return cadFilePath, outPath
def main():
env = FS.readJSON("env.json")
cadFilePath = str(env["cadFilePath"])
outPath = str(env["outPath"])
objectIndentation = float(env["objectIndentation"])
if 'FreeCAD' in globals():
active_doc = FreeCAD.activeDocument()
if active_doc:
cadFilePath = active_doc.FileName
outPath = os.path.dirname(cadFilePath)
else:
# raise Exception("Нет активного документа в FreeCAD.")
# else:
env = FS.readJSON("env.json")
print(env)
cadFilePath = str(env["cadFilePath"])
outPath = str(env["outPath"])
objectIndentation = float(env["objectIndentation"])
if cadFilePath == None:
return TypeError("CadFile not found env.json")
App.open("" + cadFilePath)
if cadFilePath == None:
return TypeError("CadFile not found env.json")
App.open("" + cadFilePath)
# isAllObjectSolidsCheckUseCase = IsAllObjectSolidsCheckUseCase.call()
isAllObjectSolidsCheckUseCase = IsAllObjectSolidsCheckUseCase.call()
# if isAllObjectSolidsCheckUseCase != None:
# FS.writeFile(isAllObjectSolidsCheckUseCase.toString(), outPath, 'error.json')
# ExitFreeCadUseCase.call()
# return
if isAllObjectSolidsCheckUseCase != None:
FS.writeFile(isAllObjectSolidsCheckUseCase.toString(), outPath, 'error.json')
ExitFreeCadUseCase.call()
return
FreeCAD.open(cadFilePath)
# checkObjectHasTouchesUseCase = CheckObjectHasTouchesUseCase.call(objectIndentation)
# if checkObjectHasTouchesUseCase != None:
# FS.writeFile(checkObjectHasTouchesUseCase.toString(), outPath, 'error.json')
# ExitFreeCadUseCase.call()
# return
topologyMatrix = CadAdjacencyMatrix().matrixBySurfaces()
import json
sequences = json.dumps(
{"sequences": AllSequences(topologyMatrix.matrix).adj_matrix_names},
ensure_ascii=False,
indent=4,
)
sequences = {
"sequences": AllSequences(topologyMatrix.matrix).adj_matrix_names
}
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:
if intersection_geometry.get("recalculations") is None:
intersection_geometry["status"] = False
intersection_geometry["recalculations"] = []
intersection_geometry["recalculations"].append(
{"area": area, "connect": el.get("child") + " " + el.get("parent")}
)
# print(intersection_geometry, sequences, topologyMatrix.to_dict())
return intersection_geometry, sequences, topologyMatrix.to_dict()
FS.writeFile(
json.dumps(intersection_geometry, ensure_ascii=False, indent=4),
outPath,
"intersection_geometry.json",
)
FS.writeFile(sequences, outPath, "sequences.json")
FS.writeFile(
json.dumps(topologyMatrix.to_dict(), ensure_ascii=False, indent=4),
outPath,
"adjacency_matrix.json",
)
ExitFreeCadUseCase.call()
# ExitFreeCadUseCase.call() Сейчас пока не нужна
# return intersection_geometry, sequences, topologyMatrix.to_dict()
#main()
# main()

View file

@ -42,20 +42,36 @@ 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)
# data = load_data('/home/markvoltov/GitProjects/framework/test_models/adjacency_matrix.json')
# # constraints = load_constraints('constraints.json')
# all_parts = data['allParts']
# print(all_parts)
# 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)
# 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')
# save_sequences(all_sequences, 'valid_sequences.json')
print(f"Найдено {len(all_sequences)} допустимых последовательностей сборки.")
# print(f"Найдено {len(all_sequences)} допустимых последовательностей сборки.")
def process_adjacency_data(topology_matrix):
all_parts = topology_matrix['allParts']
graph = create_graph(topology_matrix)
first_detail = topology_matrix['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)
return all_parts, graph, first_detail, leaf_nodes, all_sequences

View file

@ -3,7 +3,7 @@ import networkx as nx
import matplotlib.pyplot as plt
# Загружаем данные из файла
with open('adjacency_matrix.json', 'r') as file:
with open('/home/markvoltov/GitProjects/framework/freecad_workbench/freecad/robossembler/simplified_adjacency_matrix.json', 'r') as file:
data = json.load(file)
# Создаем пустой граф

View file

@ -0,0 +1,76 @@
{
"allParts": [
"body_down",
"body_up",
"sol_gear",
"output_shaft",
"planet_gear",
"planet_gear003",
"planet_gear004",
"planet_gear005",
"planet_gear002",
"bolt",
"bolt2",
"bolt3",
"bolt4"
],
"matrix": {
"body_down": [
"sol_gear",
"planet_gear",
"planet_gear003",
"planet_gear004",
"planet_gear005",
"planet_gear002",
"body_up"
],
"body_up": [
"body_down",
"output_shaft"
],
"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"
]
}
}

View file

@ -1,112 +1,136 @@
import FreeCAD
import numpy as np
'''
Файл, содержащий скрипт для обработки и упрощения матрицы смежности. Запускается через команду в основном меню верстака freecad
'''
import json
import FreeCAD as App
from geometric_feasibility_predicate.main import main as asm_analysis
from constraints_operator import collect_assembly_settings
#Запускается после генерации матрицы смежности и разметки изделия
# === Для работы с json-файлами. Работает. ===
def simplify_adjacency_matrix_json(assembly_file, adjacency_file, output_file):
def load_json(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
return json.load(file)
def get_adjacency_matrix_from_file(file_path):
with open(file_path, 'r') as f:
adjacency_matrix = np.array(json.load(f))
#print(adjacency_matrix)
return adjacency_matrix
def save_json(file_path, data):
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
assembly_settings = load_json('/assembly_settings.json')
adjacency_matrix = load_json('/adjacency_matrix.json')
fasteners = set()
for item in assembly_settings:
if item.get("Type") == "fastener_set":
fasteners.add(item["Parent"])
fasteners.add(item["Child"])
simplified_matrix = {
"allParts": [],
"matrix": {}
}
for part in adjacency_matrix["allParts"]:
if part not in fasteners:
simplified_matrix["allParts"].append(part)
neighbors = [
neighbor for neighbor in adjacency_matrix["matrix"].get(part, [])
if neighbor not in fasteners
]
if neighbors or part not in fasteners:
simplified_matrix["matrix"][part] = neighbors
save_json(output_file, simplified_matrix)
# simplify_adjacency_matrix('assembly_settings.json', 'adjacency_matrix.json', 'simplified_adjacency_matrix.json')
def restore_full_sequence_json(assembly_file, sequence_file, output_file):
def load_json(file_path):
with open(file_path, 'r', encoding='utf-8') as file:
return json.load(file)
def save_json(file_path, data):
with open(file_path, 'w', encoding='utf-8') as file:
json.dump(data, file, ensure_ascii=False, indent=4)
assembly_settings = load_json(assembly_file)
sequence = load_json(sequence_file)
full_sequence = []
sequence_set = set(sequence)
for item in sequence:
full_sequence.append(item)
for setting in assembly_settings:
if setting.get("Type") == "fastener_set":
parent = setting["Parent"]
child = setting["Child"]
if parent in sequence_set and child in sequence_set:
full_sequence.append(setting["Fasteners"])
save_json(output_file, full_sequence)
def get_asm_settings(asm_settings_path):
with open(asm_settings_path), 'r') as f:
asm_settings = np.array(json.load(f))
#print(asm_settings)
return asm_settings
#def get_parts_with_fasteners(asm_settings):
'''
"Type": "fastener_set",
"Parent": obj.Parent.Label,
"Child": obj.Child.Label,
"Fasteners": fasteners '''
#вероятно, тут нужно создавать список объектов
# fastener_set = []
#чтобы это работало, нужно работать с позициями элементов в дереве
#возможно, отработает и через лейблы
# restore_full_sequence('assembly_settings.json', 'sequence.json', 'full_sequence.json')
#return part_names, fasteners
def remove_fasteners_from_matrix(adjacency_matrix, part_names, fasteners):
fastener_connections = {}
indices_to_remove = []
# ==== Для работы с внутренними переменными
for fastener in fasteners:
idx = part_names.index(fastener)
indices_to_remove.append(idx)
def simplify_adjacency_matrix(assembly_settings, adjacency_matrix):
fasteners = set()
for item in assembly_settings:
if item.get("Type") == "fastener_set":
fasteners.add(item["Parent"])
fasteners.add(item["Child"])
connected_parts = []
for i in range(len(adjacency_matrix)):
if adjacency_matrix[i, idx] == 1:
connected_parts.append(part_names[i])
simplified_matrix = {
"allParts": [],
"matrix": {}
}
for part in adjacency_matrix["allParts"]:
if part not in fasteners:
simplified_matrix["allParts"].append(part)
neighbors = [
neighbor for neighbor in adjacency_matrix["matrix"].get(part, [])
if neighbor not in fasteners
]
if neighbors or part not in fasteners:
simplified_matrix["matrix"][part] = neighbors
return simplified_matrix
def restore_full_sequence(assembly_settings, sequence):
full_sequence = []
sequence_set = set(sequence)
for item in sequence:
full_sequence.append(item)
for setting in assembly_settings:
if setting.get("Type") == "fastener_set":
parent = setting["Parent"]
child = setting["Child"]
if parent in sequence_set and child in sequence_set:
full_sequence.append(setting["Fasteners"])
return full_sequence
def main():
App.open("/home/markvoltov/GitProjects/framework/test_models/desk_table.FCStd")
if App.ActiveDocument:
intersection_geometry, sequences, topologyMatrix = asm_analysis()
print(sequences)
assembly_settings = collect_assembly_settings()
simplified_matrix = simplify_adjacency_matrix(assembly_settings, topologyMatrix)
fastener_connections[fastener] = connected_parts
else:
print('Ошибка. Нет активного документа!')
reduced_matrix = np.delete(adjacency_matrix, indices_to_remove, axis=0)
reduced_matrix = np.delete(reduced_matrix, indices_to_remove, axis=1)
reduced_part_names = [part for part in part_names if part not in fasteners]
return reduced_matrix, reduced_part_names, fastener_connections
def save_to_json(data, file_path):
with open(file_path, 'w') as f:
json.dump(data, f, indent=4)
doc = FreeCAD.ActiveDocument
adjacency_matrix = get_adjacency_matrix_from_file('adjacency_matrix.json')
part_names, fasteners = get_parts_with_fasteners(doc)
# Построение сокращенной матрицы смежности
reduced_matrix, reduced_part_names, fastener_connections = remove_fasteners_from_matrix(adjacency_matrix, part_names, fasteners)
# Сохранение результатов в файлы JSON
save_to_json(reduced_matrix.tolist(), 'reduced_adjacency_matrix.json')
save_to_json(reduced_part_names, 'reduced_part_names.json')
save_to_json(fastener_connections, 'fastener_connections.json')
import json
with open('adjacency_matrix.json') as f:
adjacency_matrix = json.load(f)
with open('assembly_settings.json') as f:
assembly_settings = json.load(f)
fasteners_to_exclude = set()
for setting in assembly_settings:
if setting.get('Type') == 'fastener_set':
fasteners_to_exclude.update(setting.get('Fasteners'))
#for fastener in fasteners_to_exclude:
# del adjacency_matrix[fastener]
#здесь должен запуститься файл генерации последовательности сборки
#на вход поступает упрощенная матрица смежности
with open('assembly_sequence.json') as f:
assembly_sequence = json.load(f)
#TODO: сделать в виде функции, вызываемой отдельно
#Добавляем крепежи в посл. сборки
for setting in assembly_settings:
if setting.get('Type') == 'fastener_set':
parent = setting.get('Parent')
child = setting.get('Child')
fasteners = set(setting.get('Fasteners'))
for step in assembly_sequence:
if step.get('Parent') == parent and step.get('Child') == child:
step['Fasteners'] = list(fasteners.intersection(step.get('Fasteners', [])))
break
main()

View file

@ -0,0 +1 @@
[]

View file

@ -15,22 +15,29 @@ 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')
# 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']
# 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)
# 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')
# save_sequences(valid_sequences, 'valid_sequences.json')
print(f"Найдено {len(valid_sequences)} допустимых последовательностей сборки.")
# print(f"Найдено {len(valid_sequences)} допустимых последовательностей сборки.")
def filter_valid_sequences(adjacency_matrix, sequences, constraints):
all_parts = adjacency_matrix['allParts']
first_detail = adjacency_matrix['firstDetail']
valid_sequences = []
for sequence in sequences:
if len(set(sequence)) == len(set(all_parts)):
valid_sequences.append(sequence)
return valid_sequences

View file

@ -1,5 +0,0 @@
{
"body_down":["body_up","bolt1", "bolt2", "bolt3", "bolt4","sol_gear","planet_gear"],
"body_up":["body_down","bolt1", "bolt2", "bolt3", "bolt4","sol_gear"],
"body_down":["body_up","bolt1", "bolt2", "bolt3", "bolt4","sol_gear","planet_gear"]
}

View file

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

View file

@ -0,0 +1,4 @@
{
"status": true,
"recalculations": null
}

View file

@ -1,10 +1,3 @@
{
"sequences": [
"body_down",
"sol_gear",
"output_shaft",
"planet_gear",
"planet_gear002"
]
"sequences": []
}

Binary file not shown.