You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
242 lines
6.9 KiB
Python
242 lines
6.9 KiB
Python
import os
|
|
from pathlib import Path
|
|
|
|
import FreeCAD as App
|
|
import FreeCADGui as Gui
|
|
|
|
|
|
DEVICE_GROUP_PREFIX = "QETDevice_"
|
|
PREVIEW_DOC_PREFIX = "QETPreview_"
|
|
ROOT_GROUP_NAME = "QETExchangeDevices"
|
|
|
|
|
|
def _debug_log_path():
|
|
local_app_data = os.environ.get("LOCALAPPDATA", "").strip()
|
|
if local_app_data:
|
|
return os.path.join(local_app_data, "QETDeps", "freecad_exchange_bootstrap.log")
|
|
return os.path.join(str(Path.home()), "AppData", "Local", "QETDeps", "freecad_exchange_bootstrap.log")
|
|
|
|
|
|
def _append_debug_log(message):
|
|
try:
|
|
log_path = _debug_log_path()
|
|
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
|
with open(log_path, "a", encoding="utf-8") as handle:
|
|
handle.write(message + "\n")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _safe_token(value):
|
|
text = (value or "").strip()
|
|
if not text:
|
|
return "unknown"
|
|
|
|
chars = []
|
|
for ch in text:
|
|
if ch.isalnum():
|
|
chars.append(ch)
|
|
else:
|
|
chars.append("_")
|
|
return "".join(chars)
|
|
|
|
|
|
def is_preview_document_name(doc_name):
|
|
return str(doc_name or "").startswith(PREVIEW_DOC_PREFIX)
|
|
|
|
|
|
def _get_document_if_exists(doc_name):
|
|
try:
|
|
return App.getDocument(doc_name)
|
|
except Exception:
|
|
return None
|
|
|
|
|
|
def find_main_exchange_document(preferred_name=""):
|
|
preferred_name = (preferred_name or "").strip()
|
|
if preferred_name:
|
|
preferred_doc = _get_document_if_exists(preferred_name)
|
|
if preferred_doc is not None and not is_preview_document_name(preferred_doc.Name):
|
|
return preferred_doc
|
|
|
|
active_doc = App.ActiveDocument
|
|
if (
|
|
active_doc is not None
|
|
and not is_preview_document_name(active_doc.Name)
|
|
and active_doc.getObject(ROOT_GROUP_NAME) is not None
|
|
):
|
|
return active_doc
|
|
|
|
for candidate in App.listDocuments().values():
|
|
if (
|
|
candidate is not None
|
|
and not is_preview_document_name(candidate.Name)
|
|
and candidate.getObject(ROOT_GROUP_NAME) is not None
|
|
):
|
|
return candidate
|
|
|
|
if active_doc is not None and not is_preview_document_name(active_doc.Name):
|
|
return active_doc
|
|
|
|
return None
|
|
|
|
|
|
def is_qet_device_object(obj):
|
|
if obj is None:
|
|
return False
|
|
if getattr(obj, "TypeId", "") != "App::Part":
|
|
return False
|
|
if not getattr(obj, "Name", "").startswith(DEVICE_GROUP_PREFIX):
|
|
return False
|
|
return "QetElementUuid" in getattr(obj, "PropertiesList", [])
|
|
|
|
|
|
def find_parent_qet_device_object(obj):
|
|
"""
|
|
从当前选中对象向上查找所属的 QETDevice_xxx 设备组。
|
|
|
|
作用:
|
|
1. 双击 QETDevice_xxx 本身,可以预览;
|
|
2. 双击 QETDevice_xxx 下面的子实体,也可以自动找到父设备并预览。
|
|
"""
|
|
if obj is None:
|
|
_append_debug_log("DevicePreview resolve parent: selected object is None")
|
|
return None
|
|
|
|
if is_qet_device_object(obj):
|
|
_append_debug_log(
|
|
"DevicePreview resolve parent: selected object is direct device {0}".format(
|
|
getattr(obj, "Name", "")
|
|
)
|
|
)
|
|
return obj
|
|
|
|
visited = set()
|
|
stack = list(getattr(obj, "InList", []) or [])
|
|
|
|
while stack:
|
|
parent = stack.pop()
|
|
if parent is None:
|
|
continue
|
|
|
|
parent_name = getattr(parent, "Name", "")
|
|
if parent_name in visited:
|
|
continue
|
|
|
|
visited.add(parent_name)
|
|
|
|
if is_qet_device_object(parent):
|
|
_append_debug_log(
|
|
"DevicePreview resolve parent: found parent device {0} from child {1}".format(
|
|
getattr(parent, "Name", ""),
|
|
getattr(obj, "Name", ""),
|
|
)
|
|
)
|
|
return parent
|
|
|
|
stack.extend(list(getattr(parent, "InList", []) or []))
|
|
|
|
_append_debug_log(
|
|
"DevicePreview resolve parent: no device parent found for {0}".format(
|
|
getattr(obj, "Name", "")
|
|
)
|
|
)
|
|
return None
|
|
|
|
|
|
def _preview_document_name(device_group):
|
|
display_tag = getattr(device_group, "QetDisplayTag", "")
|
|
element_uuid = getattr(device_group, "QetElementUuid", "")
|
|
display_token = _safe_token(display_tag)[:24]
|
|
uuid_token = _safe_token(element_uuid)[:8] or _safe_token(device_group.Name)[-8:]
|
|
if display_token:
|
|
return "{0}{1}_{2}".format(PREVIEW_DOC_PREFIX, display_token, uuid_token)
|
|
return "{0}{1}".format(PREVIEW_DOC_PREFIX, uuid_token)
|
|
|
|
|
|
def _clear_document_objects(doc):
|
|
while getattr(doc, "Objects", []):
|
|
doc.removeObject(doc.Objects[-1].Name)
|
|
|
|
|
|
def _activate_document(doc):
|
|
App.setActiveDocument(doc.Name)
|
|
App.ActiveDocument = doc
|
|
try:
|
|
Gui.ActiveDocument = Gui.getDocument(doc.Name)
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _fit_active_view():
|
|
try:
|
|
active_view = Gui.activeDocument().activeView()
|
|
active_view.viewIsometric()
|
|
Gui.SendMsgToActiveView("ViewFit")
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
def _set_visibility_recursive(obj, visible):
|
|
if obj is None:
|
|
return
|
|
|
|
try:
|
|
view_object = getattr(obj, "ViewObject", None)
|
|
if view_object is not None and hasattr(view_object, "Visibility"):
|
|
view_object.Visibility = bool(visible)
|
|
except Exception:
|
|
pass
|
|
|
|
for child in list(getattr(obj, "Group", []) or []):
|
|
_set_visibility_recursive(child, visible)
|
|
|
|
|
|
def _open_device_preview(device_group):
|
|
doc_name = _preview_document_name(device_group)
|
|
_append_debug_log(
|
|
"DevicePreview opening preview doc={0} for device={1}".format(
|
|
doc_name,
|
|
getattr(device_group, "Name", ""),
|
|
)
|
|
)
|
|
preview_doc = _get_document_if_exists(doc_name)
|
|
if preview_doc is None:
|
|
preview_doc = App.newDocument(doc_name)
|
|
_append_debug_log("DevicePreview created new preview document")
|
|
else:
|
|
_append_debug_log("DevicePreview reusing existing preview document")
|
|
|
|
_clear_document_objects(preview_doc)
|
|
copied_device = preview_doc.copyObject(device_group, True)
|
|
copied_device.Label = device_group.Label
|
|
if hasattr(copied_device, "Placement"):
|
|
copied_device.Placement = App.Placement()
|
|
_set_visibility_recursive(copied_device, True)
|
|
|
|
preview_doc.recompute()
|
|
_activate_document(preview_doc)
|
|
_fit_active_view()
|
|
_append_debug_log(
|
|
"DevicePreview preview ready: doc={0}, copied_name={1}, copied_label={2}".format(
|
|
preview_doc.Name,
|
|
getattr(copied_device, "Name", ""),
|
|
getattr(copied_device, "Label", ""),
|
|
)
|
|
)
|
|
return preview_doc
|
|
|
|
|
|
def open_preview_for_device_object(obj):
|
|
if not is_qet_device_object(obj):
|
|
return None
|
|
if is_preview_document_name(getattr(getattr(obj, "Document", None), "Name", "")):
|
|
return None
|
|
_append_debug_log(
|
|
"DevicePreview open requested element_uuid={0}, label={1}".format(
|
|
getattr(obj, "QetElementUuid", ""),
|
|
getattr(obj, "Label", ""),
|
|
)
|
|
)
|
|
return _open_device_preview(obj)
|