|
|
|
|
@ -4,10 +4,12 @@ from pathlib import Path
|
|
|
|
|
import FreeCAD as App
|
|
|
|
|
import FreeCADGui as Gui
|
|
|
|
|
import ImportGui
|
|
|
|
|
import DevicePreview
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ROOT_GROUP_NAME = "QETExchangeDevices"
|
|
|
|
|
ROOT_GROUP_LABEL = "QET Exchange Devices"
|
|
|
|
|
CABINET_MODEL_GROUP_NAME = "QETCabinetModel"
|
|
|
|
|
DEVICE_GROUP_PREFIX = "QETDevice_"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@ -61,29 +63,130 @@ def _new_objects_since(doc, before_names):
|
|
|
|
|
return [obj for obj in doc.Objects if obj.Name not in before_names]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _top_level_imported_objects(imported_objects):
|
|
|
|
|
imported_by_name = {obj.Name: obj for obj in imported_objects}
|
|
|
|
|
child_names = set()
|
|
|
|
|
for obj in imported_objects:
|
|
|
|
|
for parent in list(getattr(obj, "InList", []) or []):
|
|
|
|
|
if getattr(parent, "Name", None) in imported_by_name:
|
|
|
|
|
child_names.add(obj.Name)
|
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
|
|
|
if getattr(child, "Name", None) in imported_by_name:
|
|
|
|
|
child_names.add(child.Name)
|
|
|
|
|
return [obj for obj in imported_objects if obj.Name not in child_names]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_string_property(obj, prop_name, group_name, description, value):
|
|
|
|
|
if prop_name not in getattr(obj, "PropertiesList", []):
|
|
|
|
|
obj.addProperty("App::PropertyString", prop_name, group_name, description)
|
|
|
|
|
setattr(obj, prop_name, value or "")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_bool_property(obj, prop_name, group_name, description, value):
|
|
|
|
|
if prop_name not in getattr(obj, "PropertiesList", []):
|
|
|
|
|
obj.addProperty("App::PropertyBool", prop_name, group_name, description)
|
|
|
|
|
setattr(obj, prop_name, bool(value))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_document(scene_path):
|
|
|
|
|
if App.ActiveDocument:
|
|
|
|
|
return App.ActiveDocument
|
|
|
|
|
preferred_name = _safe_token(Path(scene_path).stem if scene_path else "QETScene")[:48] or "QETScene"
|
|
|
|
|
existing_doc = DevicePreview.find_main_exchange_document(preferred_name)
|
|
|
|
|
if existing_doc is not None:
|
|
|
|
|
return existing_doc
|
|
|
|
|
|
|
|
|
|
return App.newDocument(preferred_name)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
doc_name = Path(scene_path).stem if scene_path else "QETScene"
|
|
|
|
|
doc_name = _safe_token(doc_name)[:48] or "QETScene"
|
|
|
|
|
return App.newDocument(doc_name)
|
|
|
|
|
def _cabinet_label_text(cabinet):
|
|
|
|
|
if not isinstance(cabinet, dict):
|
|
|
|
|
return "QET Cabinet"
|
|
|
|
|
|
|
|
|
|
label = (cabinet.get("display_text") or "").strip()
|
|
|
|
|
if label:
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
def _ensure_root_group(doc):
|
|
|
|
|
label = (cabinet.get("label") or "").strip()
|
|
|
|
|
if label:
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
label = (cabinet.get("name") or "").strip()
|
|
|
|
|
if label:
|
|
|
|
|
return label
|
|
|
|
|
|
|
|
|
|
return "QET Cabinet"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_root_group(doc, cabinet=None):
|
|
|
|
|
root = doc.getObject(ROOT_GROUP_NAME)
|
|
|
|
|
if root is None:
|
|
|
|
|
root = doc.addObject("App::DocumentObjectGroup", ROOT_GROUP_NAME)
|
|
|
|
|
root.Label = ROOT_GROUP_LABEL
|
|
|
|
|
if isinstance(cabinet, dict):
|
|
|
|
|
root.Label = _cabinet_label_text(cabinet)
|
|
|
|
|
else:
|
|
|
|
|
root.Label = ROOT_GROUP_LABEL
|
|
|
|
|
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetLabel",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Cabinet label from QET exchange",
|
|
|
|
|
cabinet.get("label", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetName",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Cabinet name from QET exchange",
|
|
|
|
|
cabinet.get("name", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetDisplayText",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Cabinet display text from QET exchange",
|
|
|
|
|
cabinet.get("display_text", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetFileSet",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Associated fileset from QET exchange",
|
|
|
|
|
cabinet.get("associated_fileset", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetRelativePath",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Relative 3D cabinet path from QET exchange",
|
|
|
|
|
cabinet.get("three_d_relative_path", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetResolvedScenePath",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Resolved local cabinet scene path from QET exchange",
|
|
|
|
|
cabinet.get("resolved_scene_path", "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
root,
|
|
|
|
|
"QetCabinetLocationId",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Cabinet location id from QET exchange",
|
|
|
|
|
str(cabinet.get("location_id") or "") if isinstance(cabinet, dict) else "",
|
|
|
|
|
)
|
|
|
|
|
return root
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_cabinet_model_group(doc, root_group):
|
|
|
|
|
group = doc.getObject(CABINET_MODEL_GROUP_NAME)
|
|
|
|
|
if group is None:
|
|
|
|
|
group = doc.addObject("App::DocumentObjectGroup", CABINET_MODEL_GROUP_NAME)
|
|
|
|
|
group.Label = "3D机柜"
|
|
|
|
|
if group not in getattr(root_group, "Group", []):
|
|
|
|
|
root_group.addObject(group)
|
|
|
|
|
return group
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _find_device_group(doc, element_uuid):
|
|
|
|
|
target_uuid = (element_uuid or "").strip()
|
|
|
|
|
if not target_uuid:
|
|
|
|
|
@ -111,13 +214,19 @@ def _device_label_text(display_tag, instance_id, element_uuid):
|
|
|
|
|
return "QET Device"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_device_group(doc, root_group, element_uuid, instance_id, model_path, display_tag):
|
|
|
|
|
def _ensure_device_group(doc, root_group, element_uuid, instance_id, model_path, display_tag, layout_index):
|
|
|
|
|
created_now = False
|
|
|
|
|
device_group = _find_device_group(doc, element_uuid)
|
|
|
|
|
if device_group is not None and getattr(device_group, "TypeId", "") != "App::Part":
|
|
|
|
|
_remove_object_tree(doc, device_group)
|
|
|
|
|
device_group = None
|
|
|
|
|
|
|
|
|
|
if device_group is None:
|
|
|
|
|
device_group = doc.addObject(
|
|
|
|
|
"App::DocumentObjectGroup",
|
|
|
|
|
"App::Part",
|
|
|
|
|
DEVICE_GROUP_PREFIX + _safe_token(element_uuid),
|
|
|
|
|
)
|
|
|
|
|
created_now = True
|
|
|
|
|
|
|
|
|
|
if device_group not in getattr(root_group, "Group", []):
|
|
|
|
|
root_group.addObject(device_group)
|
|
|
|
|
@ -151,7 +260,16 @@ def _ensure_device_group(doc, root_group, element_uuid, instance_id, model_path,
|
|
|
|
|
"2D display tag from QET exchange",
|
|
|
|
|
display_tag,
|
|
|
|
|
)
|
|
|
|
|
return device_group
|
|
|
|
|
_ensure_bool_property(
|
|
|
|
|
device_group,
|
|
|
|
|
"QetAutoPlaced",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Whether the device has been placed by the QET auto layout.",
|
|
|
|
|
created_now,
|
|
|
|
|
)
|
|
|
|
|
if created_now:
|
|
|
|
|
device_group.Placement = App.Placement()
|
|
|
|
|
return device_group, created_now
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remove_object_tree(doc, obj):
|
|
|
|
|
@ -184,20 +302,26 @@ def _supported_for_import(model_path):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _import_model_into_group(doc, device_group, model_path):
|
|
|
|
|
def _import_model_into_group(doc, device_group, model_path, merge=False, use_link_group=True):
|
|
|
|
|
before_names = _existing_object_names(doc)
|
|
|
|
|
try:
|
|
|
|
|
ImportGui.insert(name=model_path, docName=doc.Name, merge=False, useLinkGroup=True)
|
|
|
|
|
ImportGui.insert(
|
|
|
|
|
name=model_path,
|
|
|
|
|
docName=doc.Name,
|
|
|
|
|
merge=bool(merge),
|
|
|
|
|
useLinkGroup=bool(use_link_group),
|
|
|
|
|
)
|
|
|
|
|
except Exception:
|
|
|
|
|
for obj in _new_objects_since(doc, before_names):
|
|
|
|
|
_remove_object_tree(doc, obj)
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
imported_objects = _new_objects_since(doc, before_names)
|
|
|
|
|
for obj in imported_objects:
|
|
|
|
|
top_level_objects = _top_level_imported_objects(imported_objects)
|
|
|
|
|
for obj in top_level_objects:
|
|
|
|
|
if obj not in getattr(device_group, "Group", []):
|
|
|
|
|
device_group.addObject(obj)
|
|
|
|
|
return imported_objects
|
|
|
|
|
return top_level_objects
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _model_index(payload):
|
|
|
|
|
@ -209,10 +333,71 @@ def _model_index(payload):
|
|
|
|
|
return index
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _import_cabinet_model(doc, root_group, cabinet, report):
|
|
|
|
|
if not isinstance(cabinet, dict):
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
resolved_scene_path = _native_path(cabinet.get("resolved_scene_path", ""))
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"DeviceImport cabinet resolved_scene_path={0}".format(resolved_scene_path)
|
|
|
|
|
)
|
|
|
|
|
if not resolved_scene_path:
|
|
|
|
|
report["cabinet_skipped_missing_model"] += 1
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not os.path.isfile(resolved_scene_path):
|
|
|
|
|
report["cabinet_skipped_missing_file"] += 1
|
|
|
|
|
report["warnings"].append(
|
|
|
|
|
"机柜 3D 文件不存在:{0}".format(resolved_scene_path)
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if not _supported_for_import(resolved_scene_path):
|
|
|
|
|
report["cabinet_skipped_unsupported_format"] += 1
|
|
|
|
|
report["warnings"].append(
|
|
|
|
|
"机柜 3D 文件格式暂不支持:{0}".format(resolved_scene_path)
|
|
|
|
|
)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
cabinet_group = _ensure_cabinet_model_group(doc, root_group)
|
|
|
|
|
_clear_group_contents(doc, cabinet_group)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
cabinet_group,
|
|
|
|
|
"QetCabinetResolvedScenePath",
|
|
|
|
|
"QET Exchange",
|
|
|
|
|
"Resolved local cabinet scene path from QET exchange",
|
|
|
|
|
resolved_scene_path,
|
|
|
|
|
)
|
|
|
|
|
try:
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"DeviceImport importing cabinet model: {0}".format(
|
|
|
|
|
resolved_scene_path
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
_import_model_into_group(
|
|
|
|
|
doc,
|
|
|
|
|
cabinet_group,
|
|
|
|
|
resolved_scene_path,
|
|
|
|
|
merge=False,
|
|
|
|
|
use_link_group=True,
|
|
|
|
|
)
|
|
|
|
|
report["cabinet_imported"] += 1
|
|
|
|
|
_append_debug_log("DeviceImport cabinet import succeeded")
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
report["cabinet_skipped_import_error"] += 1
|
|
|
|
|
report["warnings"].append(
|
|
|
|
|
"机柜 3D 导入失败:{0}".format(exc)
|
|
|
|
|
)
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"DeviceImport cabinet import failed: {0}".format(exc)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def import_devices_from_payload(payload, scene_path=""):
|
|
|
|
|
_append_debug_log("DeviceImport.import_devices_from_payload entered")
|
|
|
|
|
doc = _ensure_document(scene_path)
|
|
|
|
|
root_group = _ensure_root_group(doc)
|
|
|
|
|
cabinet = payload.get("cabinet")
|
|
|
|
|
root_group = _ensure_root_group(doc, cabinet)
|
|
|
|
|
models_by_element = _model_index(payload)
|
|
|
|
|
|
|
|
|
|
report = {
|
|
|
|
|
@ -226,10 +411,17 @@ def import_devices_from_payload(payload, scene_path=""):
|
|
|
|
|
"skipped_missing_file": 0,
|
|
|
|
|
"skipped_unsupported_format": 0,
|
|
|
|
|
"skipped_import_error": 0,
|
|
|
|
|
"cabinet_imported": 0,
|
|
|
|
|
"cabinet_skipped_missing_model": 0,
|
|
|
|
|
"cabinet_skipped_missing_file": 0,
|
|
|
|
|
"cabinet_skipped_unsupported_format": 0,
|
|
|
|
|
"cabinet_skipped_import_error": 0,
|
|
|
|
|
"warnings": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for device in payload.get("devices", []):
|
|
|
|
|
_import_cabinet_model(doc, root_group, cabinet, report)
|
|
|
|
|
|
|
|
|
|
for index, device in enumerate(payload.get("devices", [])):
|
|
|
|
|
report["total_devices"] += 1
|
|
|
|
|
|
|
|
|
|
element_uuid = device.get("element_uuid", "").strip()
|
|
|
|
|
@ -265,8 +457,14 @@ def import_devices_from_payload(payload, scene_path=""):
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
existing_group = _find_device_group(doc, element_uuid)
|
|
|
|
|
device_group = _ensure_device_group(
|
|
|
|
|
doc, root_group, element_uuid, instance_id, resolved_model_path, display_tag
|
|
|
|
|
device_group, created_now = _ensure_device_group(
|
|
|
|
|
doc,
|
|
|
|
|
root_group,
|
|
|
|
|
element_uuid,
|
|
|
|
|
instance_id,
|
|
|
|
|
resolved_model_path,
|
|
|
|
|
display_tag,
|
|
|
|
|
index,
|
|
|
|
|
)
|
|
|
|
|
_clear_group_contents(doc, device_group)
|
|
|
|
|
|
|
|
|
|
@ -292,7 +490,7 @@ def import_devices_from_payload(payload, scene_path=""):
|
|
|
|
|
)
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
if existing_group is None:
|
|
|
|
|
if created_now or existing_group is None:
|
|
|
|
|
report["imported_devices"] += 1
|
|
|
|
|
else:
|
|
|
|
|
report["updated_devices"] += 1
|
|
|
|
|
@ -307,7 +505,8 @@ def import_devices_from_payload(payload, scene_path=""):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
_append_debug_log(
|
|
|
|
|
"DeviceImport finished: imported={0}, updated={1}, skipped_missing_model={2}, skipped_missing_file={3}, skipped_import_error={4}".format(
|
|
|
|
|
"DeviceImport finished: cabinet_imported={0}, imported={1}, updated={2}, skipped_missing_model={3}, skipped_missing_file={4}, skipped_import_error={5}".format(
|
|
|
|
|
report["cabinet_imported"],
|
|
|
|
|
report["imported_devices"],
|
|
|
|
|
report["updated_devices"],
|
|
|
|
|
report["skipped_missing_model"],
|
|
|
|
|
|