|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
# FreeCADExchange GUI panel for guided manual 3D wiring.
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
|
|
|
|
import FreeCAD as App
|
|
|
|
|
@ -183,7 +184,9 @@ def _iter_terminal_objects(doc):
|
|
|
|
|
except Exception:
|
|
|
|
|
root = None
|
|
|
|
|
if root is not None:
|
|
|
|
|
return TerminalObjects.collect_terminal_objects(root)
|
|
|
|
|
terminals = TerminalObjects.collect_terminal_objects(root)
|
|
|
|
|
if terminals:
|
|
|
|
|
return terminals
|
|
|
|
|
return [
|
|
|
|
|
obj
|
|
|
|
|
for obj in list(getattr(doc, "Objects", []) or [])
|
|
|
|
|
@ -619,6 +622,74 @@ def _task_wire_kwargs(task):
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _json_array_value(text):
|
|
|
|
|
if not text:
|
|
|
|
|
return []
|
|
|
|
|
try:
|
|
|
|
|
value = json.loads(text)
|
|
|
|
|
except Exception:
|
|
|
|
|
return []
|
|
|
|
|
if isinstance(value, list):
|
|
|
|
|
return value
|
|
|
|
|
return []
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _selected_routed_wire():
|
|
|
|
|
for obj in _selection():
|
|
|
|
|
if WiringObjects.is_routed_wire_object(obj):
|
|
|
|
|
return obj
|
|
|
|
|
for picked in _selection_ex():
|
|
|
|
|
obj = getattr(picked, "Object", None)
|
|
|
|
|
if WiringObjects.is_routed_wire_object(obj):
|
|
|
|
|
return obj
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _editable_waypoints_from_wire(wire_obj):
|
|
|
|
|
payload = _json_array_value(getattr(wire_obj, "QetManualWaypointsJson", ""))
|
|
|
|
|
if not payload:
|
|
|
|
|
payload = [
|
|
|
|
|
item
|
|
|
|
|
for item in _json_array_value(getattr(wire_obj, "QetRouteNodesJson", ""))
|
|
|
|
|
if isinstance(item, dict) and item.get("role") == "waypoint"
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
waypoints = []
|
|
|
|
|
for item in payload:
|
|
|
|
|
waypoint = ManualWiring._coerce_waypoint(item)
|
|
|
|
|
if waypoint is not None:
|
|
|
|
|
waypoints.append(waypoint)
|
|
|
|
|
return waypoints
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _open_transaction(doc, name):
|
|
|
|
|
try:
|
|
|
|
|
if hasattr(doc, "openTransaction"):
|
|
|
|
|
doc.openTransaction(name)
|
|
|
|
|
return True
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _commit_transaction(doc, opened):
|
|
|
|
|
if not opened:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
doc.commitTransaction()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _abort_transaction(doc, opened):
|
|
|
|
|
if not opened:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
doc.abortTransaction()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class ManualWiringController:
|
|
|
|
|
def __init__(self, terminal_exit_length=DEFAULT_TERMINAL_EXIT_LENGTH):
|
|
|
|
|
self.terminal_exit_length = float(terminal_exit_length or 0.0)
|
|
|
|
|
@ -627,6 +698,7 @@ class ManualWiringController:
|
|
|
|
|
self.waypoints = []
|
|
|
|
|
self.preview_objects = []
|
|
|
|
|
self.last_wire = None
|
|
|
|
|
self.editing_wire = None
|
|
|
|
|
|
|
|
|
|
def set_terminal_exit_length(self, value):
|
|
|
|
|
self.terminal_exit_length = max(float(value or 0.0), 0.0)
|
|
|
|
|
@ -740,8 +812,17 @@ class ManualWiringController:
|
|
|
|
|
self.start_terminal = None
|
|
|
|
|
self.waypoints = []
|
|
|
|
|
self.last_wire = None
|
|
|
|
|
self.editing_wire = None
|
|
|
|
|
self._clear_preview_objects()
|
|
|
|
|
|
|
|
|
|
def _create_waypoint_previews(self):
|
|
|
|
|
self._clear_preview_objects()
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
for index, waypoint in enumerate(self.waypoints, start=1):
|
|
|
|
|
preview = _create_preview_point(doc, waypoint, index)
|
|
|
|
|
if preview is not None:
|
|
|
|
|
self.preview_objects.append(preview)
|
|
|
|
|
|
|
|
|
|
def available_tasks(self):
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
try:
|
|
|
|
|
@ -805,6 +886,71 @@ class ManualWiringController:
|
|
|
|
|
_remove_preview_object(getattr(App, "ActiveDocument", None), preview)
|
|
|
|
|
return waypoint
|
|
|
|
|
|
|
|
|
|
def load_selected_wire_for_edit(self):
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
wire = _selected_routed_wire()
|
|
|
|
|
if wire is None:
|
|
|
|
|
raise ManualWiringPanelError("请先选择一条已布好的 QET 导线。")
|
|
|
|
|
|
|
|
|
|
start_terminal = _find_terminal_by_uuid(
|
|
|
|
|
doc,
|
|
|
|
|
getattr(wire, "QetStartTerminalUuid", ""),
|
|
|
|
|
)
|
|
|
|
|
end_terminal = _find_terminal_by_uuid(
|
|
|
|
|
doc,
|
|
|
|
|
getattr(wire, "QetEndTerminalUuid", ""),
|
|
|
|
|
)
|
|
|
|
|
if start_terminal is None or end_terminal is None:
|
|
|
|
|
raise ManualWiringPanelError("已布导线的起点或终点工程端子未找到。")
|
|
|
|
|
|
|
|
|
|
self._reset_route_state()
|
|
|
|
|
self.editing_wire = wire
|
|
|
|
|
self.last_wire = wire
|
|
|
|
|
self.start_terminal = start_terminal
|
|
|
|
|
self.waypoints = _editable_waypoints_from_wire(wire)
|
|
|
|
|
self.terminal_exit_length = float(getattr(wire, "QetTerminalExitLength", 0.0) or 0.0)
|
|
|
|
|
self._create_waypoint_previews()
|
|
|
|
|
return wire
|
|
|
|
|
|
|
|
|
|
def update_loaded_wire(self):
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
if self.editing_wire is None:
|
|
|
|
|
raise ManualWiringPanelError("请先选择已布导线并点击“载入选中导线”。")
|
|
|
|
|
|
|
|
|
|
end_terminal = _find_terminal_by_uuid(
|
|
|
|
|
doc,
|
|
|
|
|
getattr(self.editing_wire, "QetEndTerminalUuid", ""),
|
|
|
|
|
)
|
|
|
|
|
if self.start_terminal is None or end_terminal is None:
|
|
|
|
|
raise ManualWiringPanelError("已布导线的起点或终点工程端子未找到。")
|
|
|
|
|
|
|
|
|
|
opened = _open_transaction(doc, "修改手动导线")
|
|
|
|
|
try:
|
|
|
|
|
wire = ManualWiring.update_manual_wire(
|
|
|
|
|
doc,
|
|
|
|
|
self.editing_wire,
|
|
|
|
|
self.start_terminal,
|
|
|
|
|
end_terminal,
|
|
|
|
|
waypoints=list(self.waypoints),
|
|
|
|
|
terminal_exit_length=self.terminal_exit_length,
|
|
|
|
|
)
|
|
|
|
|
_commit_transaction(doc, opened)
|
|
|
|
|
except Exception:
|
|
|
|
|
_abort_transaction(doc, opened)
|
|
|
|
|
raise
|
|
|
|
|
self.last_wire = wire
|
|
|
|
|
return wire
|
|
|
|
|
|
|
|
|
|
def undo_last_change(self):
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
if hasattr(doc, "undo"):
|
|
|
|
|
doc.undo()
|
|
|
|
|
return True
|
|
|
|
|
if Gui is not None and hasattr(Gui, "runCommand"):
|
|
|
|
|
Gui.runCommand("Std_Undo")
|
|
|
|
|
return True
|
|
|
|
|
raise ManualWiringPanelError("当前 FreeCAD 文档不支持撤销。")
|
|
|
|
|
|
|
|
|
|
def set_end_from_selection_and_generate(self):
|
|
|
|
|
doc = _active_document()
|
|
|
|
|
if self.start_terminal is None:
|
|
|
|
|
@ -823,17 +969,24 @@ class ManualWiringController:
|
|
|
|
|
if end_terminal is None:
|
|
|
|
|
raise ManualWiringPanelError("请先选择一个工程端子,再点击“设为终点并生成”。")
|
|
|
|
|
|
|
|
|
|
wire = ManualWiring.create_manual_wire(
|
|
|
|
|
doc,
|
|
|
|
|
self.start_terminal,
|
|
|
|
|
end_terminal,
|
|
|
|
|
waypoints=list(self.waypoints),
|
|
|
|
|
terminal_exit_length=self.terminal_exit_length,
|
|
|
|
|
**wire_kwargs,
|
|
|
|
|
)
|
|
|
|
|
opened = _open_transaction(doc, "生成手动导线")
|
|
|
|
|
try:
|
|
|
|
|
wire = ManualWiring.create_manual_wire(
|
|
|
|
|
doc,
|
|
|
|
|
self.start_terminal,
|
|
|
|
|
end_terminal,
|
|
|
|
|
waypoints=list(self.waypoints),
|
|
|
|
|
terminal_exit_length=self.terminal_exit_length,
|
|
|
|
|
**wire_kwargs,
|
|
|
|
|
)
|
|
|
|
|
self.last_wire = wire
|
|
|
|
|
if self.current_task is not None:
|
|
|
|
|
_set_task_route_status(self.current_task, "Routed")
|
|
|
|
|
_commit_transaction(doc, opened)
|
|
|
|
|
except Exception:
|
|
|
|
|
_abort_transaction(doc, opened)
|
|
|
|
|
raise
|
|
|
|
|
self.last_wire = wire
|
|
|
|
|
if self.current_task is not None:
|
|
|
|
|
_set_task_route_status(self.current_task, "Routed")
|
|
|
|
|
return wire
|
|
|
|
|
|
|
|
|
|
def diagnose_last_wire(self):
|
|
|
|
|
@ -870,6 +1023,11 @@ class ManualWiringController:
|
|
|
|
|
wire_text = "未生成"
|
|
|
|
|
if self.last_wire is not None:
|
|
|
|
|
wire_text = getattr(self.last_wire, "Label", "") or getattr(self.last_wire, "Name", "")
|
|
|
|
|
if self.editing_wire is not None:
|
|
|
|
|
wire_text = "编辑中:" + (
|
|
|
|
|
getattr(self.editing_wire, "Label", "")
|
|
|
|
|
or getattr(self.editing_wire, "Name", "")
|
|
|
|
|
)
|
|
|
|
|
waypoint_text = "无"
|
|
|
|
|
if self.waypoints:
|
|
|
|
|
waypoint_text = ";".join(
|
|
|
|
|
@ -924,6 +1082,9 @@ class ManualWiringTaskPanel:
|
|
|
|
|
self.waypoint_button = QtWidgets.QPushButton("添加折点")
|
|
|
|
|
self.delete_waypoint_button = QtWidgets.QPushButton("删除最后折点")
|
|
|
|
|
self.end_button = QtWidgets.QPushButton("设为终点并生成")
|
|
|
|
|
self.load_wire_button = QtWidgets.QPushButton("载入选中导线")
|
|
|
|
|
self.update_wire_button = QtWidgets.QPushButton("更新已布导线")
|
|
|
|
|
self.undo_button = QtWidgets.QPushButton("撤销上次修改")
|
|
|
|
|
self.diagnose_button = QtWidgets.QPushButton("检查最近导线")
|
|
|
|
|
self.diagnose_all_button = QtWidgets.QPushButton("检查全部导线")
|
|
|
|
|
self.clear_button = QtWidgets.QPushButton("清除草稿")
|
|
|
|
|
@ -954,6 +1115,11 @@ class ManualWiringTaskPanel:
|
|
|
|
|
layout.addWidget(self.waypoint_button)
|
|
|
|
|
layout.addWidget(self.delete_waypoint_button)
|
|
|
|
|
layout.addWidget(self.end_button)
|
|
|
|
|
edit_layout = QtWidgets.QHBoxLayout()
|
|
|
|
|
edit_layout.addWidget(self.load_wire_button)
|
|
|
|
|
edit_layout.addWidget(self.update_wire_button)
|
|
|
|
|
edit_layout.addWidget(self.undo_button)
|
|
|
|
|
layout.addLayout(edit_layout)
|
|
|
|
|
layout.addWidget(self.diagnose_button)
|
|
|
|
|
layout.addWidget(self.diagnose_all_button)
|
|
|
|
|
layout.addWidget(self.clear_button)
|
|
|
|
|
@ -980,6 +1146,9 @@ class ManualWiringTaskPanel:
|
|
|
|
|
self.waypoint_button.clicked.connect(self.add_waypoint)
|
|
|
|
|
self.delete_waypoint_button.clicked.connect(self.delete_last_waypoint)
|
|
|
|
|
self.end_button.clicked.connect(self.set_end_and_generate)
|
|
|
|
|
self.load_wire_button.clicked.connect(self.load_selected_wire)
|
|
|
|
|
self.update_wire_button.clicked.connect(self.update_loaded_wire)
|
|
|
|
|
self.undo_button.clicked.connect(self.undo_last_change)
|
|
|
|
|
self.diagnose_button.clicked.connect(self.diagnose_last_wire)
|
|
|
|
|
self.diagnose_all_button.clicked.connect(self.diagnose_all_wires)
|
|
|
|
|
self.clear_button.clicked.connect(self.clear)
|
|
|
|
|
@ -1169,6 +1338,34 @@ class ManualWiringTaskPanel:
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def load_selected_wire(self):
|
|
|
|
|
try:
|
|
|
|
|
wire = self.controller.load_selected_wire_for_edit()
|
|
|
|
|
self.exit_length_input.setValue(self.controller.terminal_exit_length)
|
|
|
|
|
self._refresh_waypoint_list()
|
|
|
|
|
self._set_status("已载入导线:{0}".format(getattr(wire, "Label", "") or getattr(wire, "Name", "")))
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def update_loaded_wire(self):
|
|
|
|
|
try:
|
|
|
|
|
try:
|
|
|
|
|
self.controller.set_terminal_exit_length(self.exit_length_input.value())
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
wire = self.controller.update_loaded_wire()
|
|
|
|
|
self._refresh_waypoint_list()
|
|
|
|
|
self._set_status("已更新导线:{0}".format(getattr(wire, "Label", "") or getattr(wire, "Name", "")))
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def undo_last_change(self):
|
|
|
|
|
try:
|
|
|
|
|
self.controller.undo_last_change()
|
|
|
|
|
self._set_status("已撤销上次布线修改。")
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
self._set_error(str(exc))
|
|
|
|
|
|
|
|
|
|
def diagnose_last_wire(self):
|
|
|
|
|
try:
|
|
|
|
|
diagnostics = self.controller.diagnose_last_wire()
|
|
|
|
|
|