完善端子显示与FCStd导入处理

dev
zhanghao 13 hours ago
parent a06bb8fd64
commit 5977e89d44

@ -293,13 +293,13 @@
"group_uuid": "string", "group_uuid": "string",
"wire_mark": "string", "wire_mark": "string",
"wire_mark_is_manual": false, "wire_mark_is_manual": false,
"wire_style_id": 0,
"start_element_uuid": "string", "start_element_uuid": "string",
"start_terminal_uuid": "string", "start_terminal_uuid": "string",
"end_element_uuid": "string", "end_element_uuid": "string",
"end_terminal_uuid": "string", "end_terminal_uuid": "string",
"start_terminal_display": "string", "start_terminal_display": "string",
"end_terminal_display": "string", "end_terminal_display": "string"
"conductor_uuids": []
} }
``` ```
@ -307,18 +307,18 @@
| 字段 | 中文 | 必需 | 说明 | | 字段 | 中文 | 必需 | 说明 |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `wire_id` | 导线交换ID | 是 | JSON 交换层稳定标识;优先使用单根导线 UUID退回 `net_uuid + index` | | `wire_id` | 导线交换ID | 是 | JSON 交换层稳定标识;`DirectionInfo` 生成,格式为 `direction:${net_uuid}:${index}` |
| `net_uuid` | 网络UUID | 否 | 当前逻辑导线所属网络 | | `net_uuid` | 网络UUID | 否 | 当前逻辑导线所属网络 |
| `group_uuid` | 网络分组UUID | 否 | 当前逻辑导线所属网络分组 | | `group_uuid` | 网络分组UUID | 否 | 当前逻辑导线所属网络分组 |
| `wire_mark` | 导线标注 | 否 | 导线当前标注;为空时导出为 `无标注导线` | | `wire_mark` | 导线标注 | 否 | 导线当前标注;为空时导出为 `无标注导线` |
| `wire_mark_is_manual` | 导线标注是否手工 | 否 | 是否手工修改过导线标注 | | `wire_mark_is_manual` | 导线标注是否手工 | 否 | 是否手工修改过导线标注 |
| `wire_style_id` | 导线样式ID | 否 | 取 `start_terminal` 所连接导线的样式 ID |
| `start_element_uuid` | 起点设备UUID | 是 | 起点端子所属 2D 设备实例 | | `start_element_uuid` | 起点设备UUID | 是 | 起点端子所属 2D 设备实例 |
| `start_terminal_uuid` | 起点端子UUID | 是 | 起点 2D 端子实例 | | `start_terminal_uuid` | 起点端子UUID | 是 | 起点 2D 端子实例 |
| `end_element_uuid` | 终点设备UUID | 是 | 终点端子所属 2D 设备实例 | | `end_element_uuid` | 终点设备UUID | 是 | 终点端子所属 2D 设备实例 |
| `end_terminal_uuid` | 终点端子UUID | 是 | 终点 2D 端子实例 | | `end_terminal_uuid` | 终点端子UUID | 是 | 终点 2D 端子实例 |
| `start_terminal_display` | 起点端子显示号 | 否 | 起点端子在 QET 中的显示编号 | | `start_terminal_display` | 起点端子显示号 | 否 | 起点端子在 QET 中的显示编号 |
| `end_terminal_display` | 终点端子显示号 | 否 | 终点端子在 QET 中的显示编号 | | `end_terminal_display` | 终点端子显示号 | 否 | 终点端子在 QET 中的显示编号 |
| `conductor_uuids` | 几何导线UUID列表 | 否 | 当前逻辑导线对应的 2D 几何导线 UUID 列表 |
### 8.4 说明 ### 8.4 说明
@ -326,6 +326,12 @@
- 它是**导线标注** - 它是**导线标注**
- 不是设备实例标注,也不是符号设备标注 - 不是设备实例标注,也不是符号设备标注
- `wire_id` 代表一条 `DirectionInfo`
- 不再混入几何 `Conductor` UUID 作为导线主标识
- `wire_style_id` 只取 `start_terminal` 所连接导线的样式
- 不按整条几何路径聚合多个样式
- `wires` 是交换 JSON 的扩展层 - `wires` 是交换 JSON 的扩展层
- 不意味着第一版数据库绑定表要新增导线绑定表 - 不意味着第一版数据库绑定表要新增导线绑定表

@ -166,6 +166,30 @@ def _ensure_child_group(doc, parent_group, element_uuid, instance_id, name_prefi
def _ensure_document(scene_path): def _ensure_document(scene_path):
preferred_name = _safe_token(Path(scene_path).stem if scene_path else "QETScene")[:48] or "QETScene" preferred_name = _safe_token(Path(scene_path).stem if scene_path else "QETScene")[:48] or "QETScene"
normalized_scene_path = _native_path(scene_path)
if normalized_scene_path and os.path.isfile(normalized_scene_path):
normalized_target = os.path.normcase(os.path.normpath(normalized_scene_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:
_activate_document(candidate)
return candidate
try:
doc = App.openDocument(normalized_scene_path)
except Exception as exc:
raise DeviceImportError(
"Cannot open existing FreeCAD scene file: {0}".format(normalized_scene_path)
) from exc
if doc is None:
raise DeviceImportError(
"Cannot open existing FreeCAD scene file: {0}".format(normalized_scene_path)
)
_activate_document(doc)
return doc
existing_doc = DevicePreview.find_main_exchange_document(preferred_name) existing_doc = DevicePreview.find_main_exchange_document(preferred_name)
if existing_doc is not None: if existing_doc is not None:
_activate_document(existing_doc) _activate_document(existing_doc)

@ -371,6 +371,7 @@ def _normalize_terminals(payload):
"terminal_uuid": terminal_uuid, "terminal_uuid": terminal_uuid,
"instance_id": _normalize_instance_id(item), "instance_id": _normalize_instance_id(item),
"element_uuid": element_uuid.strip() if isinstance(element_uuid, str) else "", "element_uuid": element_uuid.strip() if isinstance(element_uuid, str) else "",
"terminal_display": _optional_string(item, "terminal_display", "terminal entry #{0}".format(index)),
} }
) )
return normalized return normalized

@ -35,8 +35,10 @@ def _normalize_terminal_entry(item, index):
instance_id = (item.get("instance_id") or "").strip() instance_id = (item.get("instance_id") or "").strip()
element_uuid = (item.get("element_uuid") or "").strip() element_uuid = (item.get("element_uuid") or "").strip()
terminal_display = (item.get("terminal_display") or "").strip()
slot_name_hint = ( slot_name_hint = (
item.get("slot_name_hint") item.get("slot_name_hint")
or item.get("terminal_display")
or item.get("terminal_label") or item.get("terminal_label")
or item.get("slot_name") or item.get("slot_name")
or item.get("display_tag") or item.get("display_tag")
@ -47,6 +49,7 @@ def _normalize_terminal_entry(item, index):
"terminal_uuid": terminal_uuid, "terminal_uuid": terminal_uuid,
"instance_id": instance_id, "instance_id": instance_id,
"element_uuid": element_uuid, "element_uuid": element_uuid,
"terminal_display": terminal_display,
"slot_name_hint": slot_name_hint, "slot_name_hint": slot_name_hint,
} }
@ -168,6 +171,13 @@ def _terminal_slot_label(slot, terminal_uuid):
return terminal_uuid return terminal_uuid
def _terminal_entry_label(entry, slot, terminal_uuid):
entry_label = (entry.get("terminal_display") or "").strip()
if entry_label:
return entry_label
return _terminal_slot_label(slot, terminal_uuid)
def _normalize_slot_name(value): def _normalize_slot_name(value):
return (value or "").strip().lower() return (value or "").strip().lower()
@ -237,13 +247,14 @@ def _slot_placement(slot):
return App.Placement(base, rotation) return App.Placement(base, rotation)
def _create_terminal_object(doc, terminal_uuid, slot, terminal_group, project_uuid, element_uuid, instance_id): def _create_terminal_object(doc, terminal_uuid, entry, slot, terminal_group, project_uuid, element_uuid, instance_id):
terminal_label = _terminal_entry_label(entry, slot, terminal_uuid)
name_hint = "QETTerminal_{0}".format(TerminalObjects.safe_token(terminal_uuid)) name_hint = "QETTerminal_{0}".format(TerminalObjects.safe_token(terminal_uuid))
terminal_obj = TerminalObjects.create_lcs_object( terminal_obj = TerminalObjects.create_lcs_object(
doc, doc,
name_hint, name_hint,
placement=_slot_placement(slot), placement=_slot_placement(slot),
label=_terminal_slot_label(slot, terminal_uuid), label=terminal_label,
) )
terminal_group.addObject(terminal_obj) terminal_group.addObject(terminal_obj)
TerminalObjects.set_terminal_semantics( TerminalObjects.set_terminal_semantics(
@ -252,7 +263,7 @@ def _create_terminal_object(doc, terminal_uuid, slot, terminal_group, project_uu
element_uuid, element_uuid,
terminal_uuid, terminal_uuid,
instance_id, instance_id,
label=_terminal_slot_label(slot, terminal_uuid), label=terminal_label,
slot_name=slot.get("name", ""), slot_name=slot.get("name", ""),
) )
_ensure_visible(terminal_obj) _ensure_visible(terminal_obj)
@ -401,7 +412,7 @@ def import_terminals_from_payload(payload, scene_path=""):
device_element_uuid, device_element_uuid,
terminal_uuid, terminal_uuid,
device_instance_id, device_instance_id,
label=_terminal_slot_label(slot, terminal_uuid), label=_terminal_entry_label(entry, slot, terminal_uuid),
slot_name=slot.get("name", ""), slot_name=slot.get("name", ""),
) )
try: try:
@ -414,6 +425,7 @@ def import_terminals_from_payload(payload, scene_path=""):
terminal_obj = _create_terminal_object( terminal_obj = _create_terminal_object(
doc, doc,
terminal_uuid, terminal_uuid,
entry,
slot, slot,
terminal_group, terminal_group,
project_uuid, project_uuid,
@ -428,7 +440,7 @@ def import_terminals_from_payload(payload, scene_path=""):
device_element_uuid, device_element_uuid,
terminal_uuid, terminal_uuid,
device_instance_id, device_instance_id,
label=_terminal_slot_label(slot, terminal_uuid), label=_terminal_entry_label(entry, slot, terminal_uuid),
slot_name=slot.get("name", ""), slot_name=slot.get("name", ""),
) )
try: try:

@ -1,5 +1,6 @@
import importlib import importlib
import sys import sys
import tempfile
import types import types
import unittest import unittest
from pathlib import Path from pathlib import Path
@ -39,6 +40,9 @@ def _install_fake_freecad(source_doc):
) )
fake_freecad.ActiveDocument = None fake_freecad.ActiveDocument = None
fake_freecad.set_active_document_calls = [] fake_freecad.set_active_document_calls = []
fake_freecad.open_document_calls = []
fake_freecad.new_document_calls = []
fake_freecad.documents = {}
def set_active_document(name): def set_active_document(name):
fake_freecad.set_active_document_calls.append(name) fake_freecad.set_active_document_calls.append(name)
@ -46,9 +50,24 @@ def _install_fake_freecad(source_doc):
def close_document(*args, **kwargs): def close_document(*args, **kwargs):
fake_freecad.ActiveDocument = None fake_freecad.ActiveDocument = None
def open_document(*args, **kwargs):
fake_freecad.open_document_calls.append((args, kwargs))
if source_doc is not None:
fake_freecad.documents[source_doc.Name] = source_doc
fake_freecad.ActiveDocument = source_doc
return source_doc
def new_document(name):
fake_freecad.new_document_calls.append(name)
doc = FakeDocument(name)
fake_freecad.documents[doc.Name] = doc
fake_freecad.ActiveDocument = doc
return doc
fake_freecad.setActiveDocument = set_active_document fake_freecad.setActiveDocument = set_active_document
fake_freecad.listDocuments = lambda: {} fake_freecad.listDocuments = lambda: dict(fake_freecad.documents)
fake_freecad.openDocument = lambda *args, **kwargs: source_doc fake_freecad.openDocument = open_document
fake_freecad.newDocument = new_document
fake_freecad.closeDocument = close_document fake_freecad.closeDocument = close_document
sys.modules["FreeCAD"] = fake_freecad sys.modules["FreeCAD"] = fake_freecad
@ -230,6 +249,25 @@ def _reload_modules():
class FcstdDeviceImportTest(unittest.TestCase): class FcstdDeviceImportTest(unittest.TestCase):
def test_ensure_document_opens_existing_scene_file_instead_of_creating_new_document(self):
with tempfile.TemporaryDirectory() as temp_dir:
scene_path = Path(temp_dir) / "QETScene.FCStd"
scene_path.write_text("fake fcstd placeholder", encoding="utf-8")
scene_doc = FakeDocument("QETScene", str(scene_path))
_install_fake_freecad(scene_doc)
app = sys.modules["FreeCAD"]
device_import, _ = _reload_modules()
doc = device_import._ensure_document(str(scene_path))
self.assertIs(doc, scene_doc)
self.assertEqual(1, len(app.open_document_calls))
self.assertEqual(str(scene_path), app.open_document_calls[0][0][0])
self.assertEqual([], app.new_document_calls)
self.assertIs(app.ActiveDocument, scene_doc)
self.assertIn("QETScene", app.set_active_document_calls)
def test_fcstd_import_preserves_template_slots_without_live_template_lcs(self): def test_fcstd_import_preserves_template_slots_without_live_template_lcs(self):
source = FakeDocument("Source", r"D:\models\breaker.FCStd") source = FakeDocument("Source", r"D:\models\breaker.FCStd")
_install_fake_freecad(source) _install_fake_freecad(source)

Loading…
Cancel
Save