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

dev
zhanghao 11 hours ago
parent a06bb8fd64
commit 5977e89d44

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

@ -166,6 +166,30 @@ def _ensure_child_group(doc, parent_group, element_uuid, instance_id, name_prefi
def _ensure_document(scene_path):
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)
if existing_doc is not None:
_activate_document(existing_doc)

@ -371,6 +371,7 @@ def _normalize_terminals(payload):
"terminal_uuid": terminal_uuid,
"instance_id": _normalize_instance_id(item),
"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

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

@ -1,5 +1,6 @@
import importlib
import sys
import tempfile
import types
import unittest
from pathlib import Path
@ -39,6 +40,9 @@ def _install_fake_freecad(source_doc):
)
fake_freecad.ActiveDocument = None
fake_freecad.set_active_document_calls = []
fake_freecad.open_document_calls = []
fake_freecad.new_document_calls = []
fake_freecad.documents = {}
def set_active_document(name):
fake_freecad.set_active_document_calls.append(name)
@ -46,9 +50,24 @@ def _install_fake_freecad(source_doc):
def close_document(*args, **kwargs):
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.listDocuments = lambda: {}
fake_freecad.openDocument = lambda *args, **kwargs: source_doc
fake_freecad.listDocuments = lambda: dict(fake_freecad.documents)
fake_freecad.openDocument = open_document
fake_freecad.newDocument = new_document
fake_freecad.closeDocument = close_document
sys.modules["FreeCAD"] = fake_freecad
@ -230,6 +249,25 @@ def _reload_modules():
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):
source = FakeDocument("Source", r"D:\models\breaker.FCStd")
_install_fake_freecad(source)

Loading…
Cancel
Save