feature/支持 FCstd格式的3d模型-zh-0521

dev
zhanghao 6 days ago
parent 14f7bae6bd
commit 50b50cad9f

@ -81,6 +81,10 @@ def _top_level_imported_objects(imported_objects):
return [obj for obj in imported_objects if obj.Name not in child_names]
def _top_level_document_objects(doc):
return _top_level_imported_objects(list(getattr(doc, "Objects", []) or []))
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)
@ -419,6 +423,9 @@ def _supported_for_import(model_path):
def _import_model_into_group(doc, device_group, model_path, merge=False, use_link_group=True):
if Path(model_path).suffix.lower() == ".fcstd":
return _import_fcstd_into_group(doc, device_group, model_path)
before_names = _existing_object_names(doc)
try:
ImportGui.insert(
@ -440,6 +447,41 @@ def _import_model_into_group(doc, device_group, model_path, merge=False, use_lin
return top_level_objects
def _open_fcstd_source_document(model_path):
normalized_target = os.path.normcase(os.path.normpath(model_path))
for candidate in App.listDocuments().values():
candidate_path = getattr(candidate, "FileName", "") or ""
if candidate_path and os.path.normcase(os.path.normpath(candidate_path)) == normalized_target:
return candidate, False
source_doc = App.openDocument(model_path, hidden=True, temporary=True)
return source_doc, True
def _import_fcstd_into_group(doc, device_group, model_path):
source_doc = None
should_close = False
try:
source_doc, should_close = _open_fcstd_source_document(model_path)
if source_doc is None:
raise DeviceImportError("Cannot open FCStd file")
top_level_objects = _top_level_document_objects(source_doc)
copied_objects = []
for source_obj in top_level_objects:
copied_obj = doc.copyObject(source_obj, True)
if copied_obj not in getattr(device_group, "Group", []):
device_group.addObject(copied_obj)
copied_objects.append(copied_obj)
return copied_objects
finally:
if should_close and source_doc is not None:
try:
App.closeDocument(source_doc.Name)
except Exception:
pass
def _model_index(payload):
index = {}
for item in payload.get("device_models", []):

@ -7,8 +7,14 @@ import FreeCAD as App
import FreeCADGui as Gui
import DeviceImport
import DevicePreview
import ExchangeWriteBack
import TerminalImport
try:
import ExchangeWriteBack
except Exception:
ExchangeWriteBack = None
try:
import TerminalImport
except Exception:
TerminalImport = None
try:
from PySide6 import QtCore, QtWidgets
@ -84,6 +90,16 @@ def _show_error(title, message):
QtWidgets.QMessageBox.critical(_get_main_window(), title, message)
def _terminal_report_not_available():
return {
"imported_terminals": 0,
"updated_terminals": 0,
"removed_terminals": 0,
"skipped_terminals": 0,
"warnings": ["3D terminal import module is not available."],
}
def _qt_class_name(widget):
try:
return widget.metaObject().className()
@ -721,34 +737,42 @@ def _run_scheduled_device_import(attempt=0):
)
)
try:
terminal_report = TerminalImport.import_terminals_from_payload(payload, scene_path)
except TerminalImport.TerminalImportError as exc:
_append_debug_log("terminal import failed: {0}".format(exc))
_show_error("QET Exchange", str(exc))
App.Console.PrintError("[FreeCADExchange] {0}\n".format(exc))
return
except Exception as exc:
_append_debug_log("unexpected terminal import exception: {0}".format(exc))
_append_debug_log(traceback.format_exc())
_show_error("QET Exchange", "Failed to import 3D terminals:\n{0}".format(exc))
App.Console.PrintError(
"[FreeCADExchange] Failed to import terminals: {0}\n".format(exc)
)
return
if TerminalImport is None:
_append_debug_log("terminal import skipped: TerminalImport module unavailable")
terminal_report = _terminal_report_not_available()
else:
try:
terminal_report = TerminalImport.import_terminals_from_payload(payload, scene_path)
except TerminalImport.TerminalImportError as exc:
_append_debug_log("terminal import failed: {0}".format(exc))
_show_error("QET Exchange", str(exc))
App.Console.PrintError("[FreeCADExchange] {0}\n".format(exc))
return
except Exception as exc:
_append_debug_log("unexpected terminal import exception: {0}".format(exc))
_append_debug_log(traceback.format_exc())
_show_error("QET Exchange", "Failed to import 3D terminals:\n{0}".format(exc))
App.Console.PrintError(
"[FreeCADExchange] Failed to import terminals: {0}\n".format(exc)
)
return
setattr(App, STATE_TERMINAL_IMPORT_REPORT, terminal_report)
try:
writeback_report = ExchangeWriteBack.write_back_document(
App.ActiveDocument, scene_path=scene_path, payload=payload
)
except Exception as exc:
_append_debug_log("write-back failed after import: {0}".format(exc))
_append_debug_log(traceback.format_exc())
if ExchangeWriteBack is None:
_append_debug_log("write-back skipped: ExchangeWriteBack module unavailable")
writeback_report = None
else:
setattr(App, STATE_WRITEBACK_REPORT, writeback_report)
try:
writeback_report = ExchangeWriteBack.write_back_document(
App.ActiveDocument, scene_path=scene_path, payload=payload
)
except Exception as exc:
_append_debug_log("write-back failed after import: {0}".format(exc))
_append_debug_log(traceback.format_exc())
writeback_report = None
else:
setattr(App, STATE_WRITEBACK_REPORT, writeback_report)
App.Console.PrintMessage(
"[FreeCADExchange] Imported terminals: {0}, updated: {1}, removed: {2}\n".format(

@ -43,6 +43,39 @@ def _normalize_terminal_entry(item, index):
}
def _payload_device_lookup(payload):
by_element_uuid = set()
by_instance_id = set()
for item in payload.get("devices", []) or []:
if not isinstance(item, dict):
continue
element_uuid = (item.get("element_uuid") or "").strip()
instance_id = (item.get("instance_id") or "").strip()
if element_uuid:
by_element_uuid.add(element_uuid)
if instance_id:
by_instance_id.add(instance_id)
return {
"element_uuids": by_element_uuid,
"instance_ids": by_instance_id,
}
def _terminal_belongs_to_payload_devices(entry, device_lookup):
instance_id = entry["instance_id"]
element_uuid = entry["element_uuid"]
if instance_id and instance_id in device_lookup["instance_ids"]:
return True
if element_uuid and element_uuid in device_lookup["element_uuids"]:
return True
return False
def _ensure_visible(obj):
try:
if getattr(obj, "ViewObject", None) is not None:
@ -179,6 +212,8 @@ def import_terminals_from_payload(payload, scene_path=""):
if not isinstance(terminal_entries, list):
raise TerminalImportError("Field 'terminals' must be a list.")
device_lookup = _payload_device_lookup(payload)
report = {
"document_name": doc.Name,
"scene_path": scene_path or "",
@ -190,6 +225,7 @@ def import_terminals_from_payload(payload, scene_path=""):
"reused_template_hints": 0,
"skipped_missing_device": 0,
"skipped_invalid_entry": 0,
"skipped_unmatched_parent": 0,
"warnings": [],
}
@ -203,6 +239,10 @@ def import_terminals_from_payload(payload, scene_path=""):
report["warnings"].append(str(exc))
continue
if not _terminal_belongs_to_payload_devices(entry, device_lookup):
report["skipped_unmatched_parent"] += 1
continue
device_group = _locate_device_group(doc, entry)
if device_group is None:
report["skipped_missing_device"] += 1
@ -316,10 +356,11 @@ def import_terminals_from_payload(payload, scene_path=""):
pass
_append_debug_log(
"TerminalImport finished: imported={0}, updated={1}, removed={2}".format(
"TerminalImport finished: imported={0}, updated={1}, removed={2}, skipped_unmatched_parent={3}".format(
report["imported_terminals"],
report["updated_terminals"],
report["removed_terminals"],
report["skipped_unmatched_parent"],
)
)
return report

Loading…
Cancel
Save