Добавлена валидация проекта сборки с выводом в консоль обратной связи

This commit is contained in:
Mark Voltov 2024-05-10 08:41:24 +00:00 committed by Igor Brylyov
parent 8fa9076f91
commit 05b1963a34
15 changed files with 1603 additions and 156 deletions

View file

@ -4,8 +4,9 @@ from . import ICONPATH, TRANSLATIONSPATH
from .Tools import spawnClassCommand, placement2axisvec, describeSubObject, getPrimitiveInfo #, RobossemblerFreeCadExportScenario
from .pddl import freecad2pddl
from .BoMList import run_BoM_list
from .ImportExportEntities import export_coordinate_systems
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
@ -296,11 +297,6 @@ spawnClassCommand("FrameCommand",
"MenuText": "Make a free frame",
"ToolTip": "Make a freestanding reference frame."})
# spawnClassCommand("ASM4StructureParsing",
# RobossemblerFreeCadExportScenario().call,
# {"Pixmap": str(os.path.join(ICONPATH, "assembly4.svg")),
# "MenuText": "Make a ASM4 parsing",
# "ToolTip": "Make a ASM4 1"})
spawnClassCommand("SelectedPartFrameCommand",
makeSelectedPartFrames,
@ -319,44 +315,17 @@ spawnClassCommand("FeatureFrameCommand",
"MenuText": "frame on selected primitive",
"ToolTip": "Create a frame on selected primitive."})
spawnClassCommand("PDDL_CreateTypes",
freecad2pddl.add_types,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "Types",
"ToolTip": "Add Types"})
spawnClassCommand("PDDL_CreateParameters",
freecad2pddl.add_parameters,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "Parameters",
"ToolTip": "Add Parameters"})
spawnClassCommand("PDDL_CreateAction",
freecad2pddl.add_action,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "Action",
"ToolTip": "Add Action"})
spawnClassCommand("PDDL_CreatePredicate",
freecad2pddl.add_predicate,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "Predicate",
"ToolTip": "Add Predicate"})
spawnClassCommand("PDDL_CreateDurativeAction",
freecad2pddl.add_durative_action,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "DurativeAction",
"ToolTip": "Add Durative Action"})
spawnClassCommand("PDDL_ExportPDDL",
freecad2pddl.export_to_file,
{"Pixmap": str(os.path.join(ICONPATH, "featureframecreator.svg")),
"MenuText": "ExportDomain",
"ToolTip": "Create and Export Domain.pddl to File"})
spawnClassCommand("Export_Entities",
export_coordinate_systems,
{"Pixmap": str(os.path.join(ICONPATH, "BoMList.svg")),
"MenuText": "ExportLCS",
"ToolTip": "Export all the markups"})
spawnClassCommand("Validate_Project",
validate_project,
{"Pixmap": str(os.path.join(ICONPATH, "")),
"MenuText": "Validate Project",
"ToolTip": "Check errors in project file"})
spawnClassCommand("Publish_Project",
process_file,
{"Pixmap": str(os.path.join(ICONPATH, "publish.svg")),

View file

@ -0,0 +1,63 @@
import FreeCAD as App
import FreeCAD.Console as Console
class GeometryUseCase:
def __init__(self, document):
self.document = document
def is_valid_geometry(self, obj):
return hasattr(obj, "Shape") and obj.Shape.Volume is not None
def create_intersection_matrix(self):
bodies = [obj for obj in self.document.Objects if self.is_valid_geometry(obj)]
num_bodies = len(bodies)
intersection_matrix = [[False for _ in range(num_bodies)] for _ in range(num_bodies)]
for i in range(num_bodies):
for j in range(i + 1, num_bodies):
body1 = bodies[i]
body2 = bodies[j]
if body1.Shape.common(body2.Shape).Volume > 0:
intersection_matrix[i][j] = True
intersection_matrix[j][i] = True
if not hasattr(body1, "AllowIntersections") or not hasattr(body2, "AllowIntersections"):
Console.PrintWarning(
f"Пересечение между '{body1.Name}' и '{body2.Name}' без разрешения.\n"
)
return intersection_matrix
def create_contact_matrix(self):
bodies = [obj for obj in self.document.Objects if self.is_valid_geometry(obj)]
num_bodies = len(bodies)
contact_matrix = [[False for _ in range(num_bodies)] for _ in range(num_bodies)]
for i in range(num_bodies):
for j in range(i + 1, num_bodies):
body1 = bodies[i]
body2 = bodies[j]
if body1.Shape.common(body2.Shape).SurfaceArea > 0:
contact_matrix[i][j] = True
contact_matrix[j][i] = True
return contact_matrix
doc = App.ActiveDocument
if doc is None:
Console.PrintError("Нет активного документа в FreeCAD.\n")
else:
use_case = GeometryUseCase(doc)
intersection_matrix = use_case.create_intersection_matrix()
Console.PrintMessage("Матрица пересечений:\n")
for row in intersection_matrix:
Console.PrintMessage(f"{row}\n")
contact_matrix = use_case.create_contact_matrix()
Console.PrintMessage("Матрица контактов:\n")
for row in contact_matrix:
Console.PrintMessage(f"{row}\n")

View file

@ -31,6 +31,7 @@ from usecases.open_freecad_document_use_case import (
from mocks.mock_structure import bottle_jack_mock_structure, simple_cube_mock_structure
def main():
try:
EnvReaderUseCase.call().either(

File diff suppressed because it is too large Load diff

View file

@ -1,81 +0,0 @@
import FreeCAD
import Part
import ImportGui
class GeometryValidateUseCase:
def __init__(self):
self.doc = None
def import_step_and_check(self, file_path):
FreeCAD.closeDocument("Unnamed")
self.doc = FreeCAD.newDocument("Unnamed")
ImportGui.open(file_path, "Unnamed")
FreeCAD.ActiveDocument.recompute()
Gui.SendMsgToActiveView("ViewFit")
bodies = getBodies(self.doc)
intersections = self.check_intersections(bodies)
self.print_intersections(intersections)
zero_volume_bodies = self.check_zero_volume(bodies)
self.print_zero_volume(zero_volume_bodies)
self.save_checked_document(file_path)
def getBodies(doc):
bodies = []
for obj in doc.Objects:
if hasattr(obj, 'TypeId') and obj.TypeId == "Part::Feature":
bodies.append(obj)
return bodies
def check_intersections(self, bodies):
intersections = []
for i in range(len(bodies)):
for j in range(i + 1, len(bodies)):
if bodies[i].intersects(bodies[j]):
intersections.append((i + 1, j + 1))
return intersections
def check_zero_volume(self, bodies):
zero_volume_bodies = [i + 1 for i, body in enumerate(bodies) if body.Volume == 0]
return zero_volume_bodies
def print_intersections(self, intersections):
for i, j in intersections:
print("Тела пересекаются: Body {} и Body {}".format(i, j))
def print_zero_volume(self, zero_volume_bodies):
for i in zero_volume_bodies:
print("Тело {} имеет нулевой объем".format(i))
def save_checked_document(self, original_file_path):
checked_file_path = original_file_path.replace(".step", "_checked.FCStd")
FreeCAD.saveDocument(self.doc, checked_file_path)
print("Проверенная сборка сохранена в файл:", checked_file_path)
FreeCAD.closeDocument("Unnamed")
use_case = GeometryValidateUseCase()
step_file_path = '/path/to/file'
use_case.import_step_and_check(step_file_path)

View file

@ -49,13 +49,7 @@ class Robossembler(Gui.Workbench):
"Export_Entities",
"ExportGazeboModels",
"InsertGraspPose",
# "ASM4StructureParsing",
"PDDL_CreateTypes",
"PDDL_CreateParameters",
"PDDL_CreateAction",
"PDDL_CreatePredicate",
"PDDL_CreateDurativeAction",
"Validate_Project",
"Publish_Project"
]
self.appendToolbar(f"{__class__.__name__} Frames", self.framecommands)

View file

@ -1,29 +0,0 @@
# coding: utf-8
# Copyright (C) 2023 Ilia Kurochkin <brothermechanic@yandex.com>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
'''
DESCRIPTION.
Simple FreeCAD's object test for manifold mawater-tight surface.
'''
import FreeCAD
def is_object_solid(obj):
'''If obj is solid return True'''
if not isinstance(obj, FreeCAD.DocumentObject):
return False
if not hasattr(obj, 'Shape'):
return False
return obj.Shape.isClosed()

View file

@ -0,0 +1,184 @@
import FreeCAD as App
import unicodedata
import numpy as np
from .helper.is_solid import is_object_solid
## Программа, содержащая все проверки активного проекта FreeCAD в соответствии с issue#149
doc = App.ActiveDocument
Console = App.Console
#========== Проверка имен ===============
class FormalValidator:
def __init__(self, document):
self.document = document
def is_unicode_safe(self, name):
normalized_name = unicodedata.normalize('NFKD', name).encode('ASCII', 'ignore').decode()
return name == normalized_name
def fix_invalid_names(self):
for obj in self.document.Objects:
if not self.is_unicode_safe(obj.Label):
safe_name = unicodedata.normalize('NFKD', obj.Label).encode('ASCII', 'ignore').decode()
obj.Label = safe_name
self.document.recompute()
#запуск проверки и исправления имен файлов
def validate(self):
invalid_names = [obj.Label for obj in self.document.Objects if not self.is_unicode_safe(obj.Label)]
if invalid_names:
print("Некорректные имена:")
for name in invalid_names:
print(name)
self.fix_invalid_names()
else:
print("Все имена корректны.")
class ModelValidator:
def __init__(self, document):
self.document = document
self.partlist = [obj for obj in self.document.Objects if (is_object_solid(obj) and not obj.TypeId == 'App::Part') ]
self.material_objects = [obj for obj in document.Objects if obj.TypeId == "App::MaterialObjectPython"]
def get_parts(self):
pass
def validate_geometry(self):
for obj in self.document.Objects:
if hasattr(obj, "Shape") and not is_object_solid(obj):
Console.PrintError(f"Объект '{obj.Name}' не является твердым телом.\n")
def is_intersecting(self, obj1, obj2):
if obj1.Shape.common(obj2.Shape).Volume > 0.0:
return True
def has_allow_intersections(self, obj):
if hasattr(obj, 'Base_Allowed_Intersection') and getattr(obj, 'Base_Allowed_Intersection', True):
return True
def check_bodies_for_intersections(self):
bodies = self.partlist
num_bodies = len(bodies)
for i in range(num_bodies):
for j in range(i + 1, num_bodies):
body1 = bodies[i]
body2 = bodies[j]
if self.is_intersecting(body1, body2):
if self.has_allow_intersections(body1) or self.has_allow_intersections(body2):
Console.PrintWarning(
f"Тела '{body1.Label}' и '{body2.Label}' ""предусмотренно пересекаются.\n")
else:
Console.PrintError(
f"Тела '{body1.Label}' и '{body2.Label}' ""непредусмотренно пересекаются.\n")
print('V = ' + str(body1.Shape.common(body2.Shape).Volume))
def get_adjacency_matrix(self, padding = 1):
# print(int(padding))
bodies = self.partlist
num_bodies = len(bodies)
adjacency_matrix = np.zeros((num_bodies, num_bodies), dtype=bool)
for i in range(num_bodies):
shape1 = bodies[i].Shape
for j in range(i + 1, num_bodies):
shape2 = bodies[j].Shape
if shape1.common(shape2).Volume > 0:
continue
distance = shape1.distToShape(shape2)
if distance[0] < padding:
adjacency_matrix[i][j] = 1
adjacency_matrix[j][i] = 1
return adjacency_matrix
def check_bodies_without_contacts(self):
adjacency_matrix = self.get_adjacency_matrix()
bodies = self.partlist
isolated_bodies = []
num_bodies = len(bodies)
for i in range(num_bodies):
# Проверяем, есть ли у тела касания с другими телами
if not any(adjacency_matrix[i]):
isolated_bodies.append(bodies[i].Label)
if isolated_bodies:
App.Console.PrintWarning("Следующие тела не касаются других:\n")
for label in isolated_bodies:
App.Console.PrintWarning(f"- {label}\n")
else:
App.Console.PrintMessage("Все тела имеют контакты.\n")
def find_material_objects(document):
pass
def check_bodies_with_multiple_materials(self):
material_objects = self.material_objects
material_references = {}
for material in material_objects:
if hasattr(material, "References"):
for reference in material.References:
if reference in material_references:
material_references[reference].append(material.Label)
else:
material_references[reference] = [material.Label]
conflicts = {k: v for k, v in material_references.items() if len(v) > 1}
if conflicts:
App.Console.PrintWarning("Обнаружены конфликты между объектами материалов:\n")
for ref, materials in conflicts.items():
App.Console.PrintWarning(f"Деталь '{ref[0].Label}' используется в материалах: {', '.join(materials)}\n")
else:
App.Console.PrintMessage("Конфликтов в ссылках материалов не обнаружено.\n")
def check_bodies_without_material(self):
material_objects = self.material_objects
partlist = self.partlist
referenced_bodies = []
for material in material_objects:
if hasattr(material, "References"):
for reference in material.References:
referenced_bodies.append(reference[0].Label)
bodies_without_material = []
for part in partlist:
if part.Label not in referenced_bodies:
bodies_without_material.append(part.Label)
if bodies_without_material:
App.Console.PrintWarning("Следующие тела не имеют назначенного материала:\n")
for name in bodies_without_material:
App.Console.PrintWarning(f"- {name}\n")
else:
App.Console.PrintMessage("Все тела имеют назначенные материалы.\n")
def validate(self):
self.validate_geometry()
self.check_bodies_for_intersections()
self.check_bodies_without_contacts()
self.check_bodies_without_material()
self.check_bodies_with_multiple_materials()
def validate_project():
doc = App.ActiveDocument
FormalValidator(doc).validate()
ModelValidator(doc).validate()

View file

@ -0,0 +1,29 @@
#!/bin/bash
# Укажите путь к папке, которую нужно скопировать
SOURCE_DIR="/home/markvoltov/GitProjects/framework/freecad_workbench/freecad/robossembler"
# Укажите путь к папке, которую нужно заменить
DEST_DIR="/home/markvoltov/.local/share/FreeCAD/Mod/freecad_workbench"
# Проверка, что исходная папка существует
if [ ! -d "$SOURCE_DIR" ]; then
echo "Исходная папка не существует: $SOURCE_DIR"
exit 1
fi
# Удаление содержимого папки назначения
if [ -d "$DEST_DIR" ]; then
echo "Удаление содержимого папки назначения: $DEST_DIR"
rm -rf "$DEST_DIR/*"
else
# Создание папки назначения, если её нет
echo "Создание папки назначения: $DEST_DIR"
mkdir -p "$DEST_DIR"
fi
# Копирование содержимого исходной папки в папку назначения
echo "Копирование содержимого из $SOURCE_DIR в $DEST_DIR"
cp -r "$SOURCE_DIR/." "$DEST_DIR/"
echo "Копирование завершено"