邱德佳 14 hours ago
commit a06bb8fd64

@ -100,6 +100,14 @@ def _dominant_axis(vector):
return axis return axis
def _point_payload(point):
return {
"x": float(getattr(point, "x", 0.0)),
"y": float(getattr(point, "y", 0.0)),
"z": float(getattr(point, "z", 0.0)),
}
def _coerce_waypoint(point_like): def _coerce_waypoint(point_like):
if isinstance(point_like, dict): if isinstance(point_like, dict):
point = _vector_from_point( point = _vector_from_point(
@ -129,7 +137,10 @@ def _coerce_waypoint(point_like):
"point": point, "point": point,
"support_axis": support_axis, "support_axis": support_axis,
"anchor_kind": (point_like.get("anchor_kind", "") or "").strip(), "anchor_kind": (point_like.get("anchor_kind", "") or "").strip(),
"carrier_kind": (point_like.get("carrier_kind", "") or "").strip(),
"carrier_axis": (point_like.get("carrier_axis", "") or "").strip().lower(),
"source_label": (point_like.get("source_label", "") or "").strip(), "source_label": (point_like.get("source_label", "") or "").strip(),
"source_object_name": (point_like.get("source_object_name", "") or "").strip(),
"subelement_name": (point_like.get("subelement_name", "") or "").strip(), "subelement_name": (point_like.get("subelement_name", "") or "").strip(),
} }
@ -140,7 +151,10 @@ def _coerce_waypoint(point_like):
"point": point, "point": point,
"support_axis": None, "support_axis": None,
"anchor_kind": "", "anchor_kind": "",
"carrier_kind": "",
"carrier_axis": "",
"source_label": "", "source_label": "",
"source_object_name": "",
"subelement_name": "", "subelement_name": "",
} }
@ -209,31 +223,329 @@ def _manual_waypoints_payload(waypoints):
}, },
"support_axis": waypoint.get("support_axis", ""), "support_axis": waypoint.get("support_axis", ""),
"anchor_kind": waypoint.get("anchor_kind", ""), "anchor_kind": waypoint.get("anchor_kind", ""),
"carrier_kind": waypoint.get("carrier_kind", ""),
"carrier_axis": waypoint.get("carrier_axis", ""),
"source_label": waypoint.get("source_label", ""), "source_label": waypoint.get("source_label", ""),
"source_object_name": waypoint.get("source_object_name", ""),
"subelement_name": waypoint.get("subelement_name", ""), "subelement_name": waypoint.get("subelement_name", ""),
} }
) )
return payload return payload
def _route_node_payload(
role,
point,
terminal=None,
waypoint=None,
waypoint_index=None,
):
payload = {
"role": role,
"point": _point_payload(point),
}
if terminal is not None:
payload["terminal_uuid"] = getattr(terminal, "QetTerminalUuid", "").strip()
payload["instance_id"] = getattr(terminal, "QetInstanceId", "").strip()
if waypoint is not None:
payload["waypoint_index"] = int(waypoint_index or 0)
payload["support_axis"] = waypoint.get("support_axis", "") or ""
payload["anchor_kind"] = waypoint.get("anchor_kind", "") or ""
payload["carrier_kind"] = waypoint.get("carrier_kind", "") or ""
payload["carrier_axis"] = waypoint.get("carrier_axis", "") or ""
payload["source_label"] = waypoint.get("source_label", "") or ""
payload["source_object_name"] = waypoint.get("source_object_name", "") or ""
payload["subelement_name"] = waypoint.get("subelement_name", "") or ""
return payload
def _manual_route_nodes(
start_terminal,
end_terminal,
normalized_waypoints=None,
terminal_exit_length=0.0,
):
start_origin = TerminalObjects.terminal_origin(start_terminal)
end_origin = TerminalObjects.terminal_origin(end_terminal)
exit_length = max(float(terminal_exit_length or 0.0), 0.0)
nodes = [
_route_node_payload("start_terminal", start_origin, terminal=start_terminal)
]
if exit_length > 0:
nodes.append(
_route_node_payload(
"start_exit",
_offset_point(
start_origin,
_terminal_exit_direction(start_terminal),
exit_length,
),
terminal=start_terminal,
)
)
for index, waypoint in enumerate(normalized_waypoints or [], start=1):
nodes.append(
_route_node_payload(
"waypoint",
waypoint["point"],
waypoint=waypoint,
waypoint_index=index,
)
)
if exit_length > 0:
nodes.append(
_route_node_payload(
"end_exit",
_offset_point(
end_origin,
_terminal_exit_direction(end_terminal),
exit_length,
),
terminal=end_terminal,
)
)
nodes.append(_route_node_payload("end_terminal", end_origin, terminal=end_terminal))
return nodes
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 _diagnostic(severity, code, message):
return {
"severity": severity,
"code": code,
"message": message,
}
def diagnose_manual_wire(wire_obj):
diagnostics = []
points = WiringObjects.wire_shape_points(wire_obj)
if len(points) < 2:
diagnostics.append(
_diagnostic("error", "wire_points_missing", "导线至少需要两个几何点。")
)
route_nodes = _json_array_value(getattr(wire_obj, "QetRouteNodesJson", ""))
if not route_nodes:
diagnostics.append(
_diagnostic("warning", "route_nodes_missing", "导线缺少语义路线节点。")
)
return diagnostics
roles = [str(node.get("role", "")) for node in route_nodes if isinstance(node, dict)]
if not roles or roles[0] != "start_terminal":
diagnostics.append(
_diagnostic("warning", "start_route_node_missing", "导线缺少起点端子路线节点。")
)
if not roles or roles[-1] != "end_terminal":
diagnostics.append(
_diagnostic("warning", "end_route_node_missing", "导线缺少终点端子路线节点。")
)
for node in route_nodes:
if not isinstance(node, dict):
continue
if node.get("role") != "waypoint":
continue
if node.get("carrier_kind") != "wire_duct":
continue
if not (node.get("source_object_name") or "").strip():
diagnostics.append(
_diagnostic(
"warning",
"wire_duct_source_missing",
"线槽折点缺少载体对象,不能可靠判断是否同一线槽。",
)
)
if not (node.get("carrier_axis") or "").strip():
diagnostics.append(
_diagnostic(
"warning",
"wire_duct_axis_missing",
"线槽折点缺少轴向信息,不能可靠沿线槽方向走线。",
)
)
return diagnostics
def _remove_from_group(group, obj):
if group is None or obj is None:
return
try:
if hasattr(group, "removeObject"):
group.removeObject(obj)
else:
group.Group = [candidate for candidate in group.Group if candidate is not obj]
except Exception:
pass
def _clear_manual_diagnostics(doc, diagnostic_group):
for obj in list(getattr(diagnostic_group, "Group", []) or []):
if (getattr(obj, "QetDiagnosticSource", "") or "").strip() != "ManualWiring":
continue
_remove_from_group(diagnostic_group, obj)
try:
if doc.getObject(getattr(obj, "Name", "")) is not None:
doc.removeObject(obj.Name)
except Exception:
pass
def _set_diagnostic_property(obj, prop_name, value, description):
TerminalObjects.ensure_string_property(
obj,
prop_name,
"QET Wiring Diagnostics",
description,
value,
)
def _create_diagnostic_object(doc, diagnostic_group, wire_obj, diagnostic, index):
name = "QETWireDiagnostic_{0}".format(index)
suffix = 1
base_name = name
while doc.getObject(name) is not None:
name = "{0}_{1}".format(base_name, suffix)
suffix += 1
obj = doc.addObject("App::FeaturePython", name)
message = diagnostic.get("message", "")
obj.Label = "{0}: {1}".format(diagnostic.get("severity", "warning"), message)
_set_diagnostic_property(obj, "QetDiagnosticSource", "ManualWiring", "Diagnostic source")
_set_diagnostic_property(obj, "QetDiagnosticSeverity", diagnostic.get("severity", ""), "Diagnostic severity")
_set_diagnostic_property(obj, "QetDiagnosticCode", diagnostic.get("code", ""), "Diagnostic code")
_set_diagnostic_property(obj, "QetDiagnosticMessage", message, "Diagnostic message")
_set_diagnostic_property(obj, "QetWireObjectName", getattr(wire_obj, "Name", ""), "Wire object name")
_set_diagnostic_property(obj, "QetWireLabel", getattr(wire_obj, "Label", ""), "Wire label")
diagnostic_group.addObject(obj)
return obj
def write_document_wire_diagnostics(doc, wires=None, project_uuid=""):
if doc is None:
raise ManualWiringError("No active FreeCAD document is available.")
if wires is None:
wires = WiringObjects.iter_routed_wire_objects(doc)
project_uuid = (
(project_uuid or "").strip()
or getattr(TerminalObjects.ensure_root_group(doc), "QetProjectUuid", "").strip()
)
diagnostic_group = WiringObjects.ensure_diagnostic_group(doc, project_uuid)
_clear_manual_diagnostics(doc, diagnostic_group)
created = []
issue_count = 0
for wire_obj in list(wires or []):
for diagnostic in diagnose_manual_wire(wire_obj):
issue_count += 1
created.append(
_create_diagnostic_object(
doc,
diagnostic_group,
wire_obj,
diagnostic,
issue_count,
)
)
try:
doc.recompute()
except Exception:
pass
return {
"wire_count": len(list(wires or [])),
"issue_count": issue_count,
"diagnostic_objects": created,
}
def _append_unique_point(points, point): def _append_unique_point(points, point):
if not points or not _vector_close(points[-1], point): if not points or not _vector_close(points[-1], point):
points.append(point) points.append(point)
def _append_orthogonal_segment(points, target_point, preferred_axis=None): def _append_orthogonal_segment(points, target_point, preferred_axis=None, leading_axis=None):
if not points: if not points:
points.append(target_point) points.append(target_point)
return return
segment = _orthogonal_segment_points( if leading_axis in {"x", "y", "z"}:
points[-1], axis_order = [leading_axis] + [
target_point, axis for axis in ("x", "y", "z") if axis != leading_axis
preferred_axis=preferred_axis, ]
) segment = _orthogonal_segment_points_for_axis_order(
points[-1],
target_point,
axis_order,
)
else:
segment = _orthogonal_segment_points(
points[-1],
target_point,
preferred_axis=preferred_axis,
)
for point in segment[1:]: for point in segment[1:]:
_append_unique_point(points, point) _append_unique_point(points, point)
def _orthogonal_segment_points_for_axis_order(start_point, end_point, axis_order):
if _vector_close(start_point, end_point):
return [start_point]
points = [start_point]
current = start_point
for axis in axis_order:
if axis not in {"x", "y", "z"}:
continue
target = _axis_value(end_point, axis)
if abs(_axis_value(current, axis) - target) <= 0.000001:
continue
current = _vector_with_axis(current, axis, target)
if not _vector_close(current, points[-1]):
points.append(current)
if not _vector_close(points[-1], end_point):
points.append(end_point)
return points
def _same_carrier_run(left, right):
if not left or not right:
return None
if (left.get("carrier_kind") or "") != "wire_duct":
return None
if (right.get("carrier_kind") or "") != "wire_duct":
return None
left_source = (left.get("source_object_name") or "").strip()
right_source = (right.get("source_object_name") or "").strip()
if not left_source or not right_source:
return None
if left_source != right_source:
return None
left_axis = (left.get("carrier_axis") or "").strip().lower()
right_axis = (right.get("carrier_axis") or "").strip().lower()
if left_axis and left_axis == right_axis and left_axis in {"x", "y", "z"}:
return left_axis
return None
def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit_length=0.0): def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit_length=0.0):
start_origin = TerminalObjects.terminal_origin(start_terminal) start_origin = TerminalObjects.terminal_origin(start_terminal)
end_origin = TerminalObjects.terminal_origin(end_terminal) end_origin = TerminalObjects.terminal_origin(end_terminal)
@ -251,16 +563,20 @@ def _terminal_points(start_terminal, end_terminal, waypoints=None, terminal_exit
) )
) )
previous_waypoint = None
for point_like in waypoints or []: for point_like in waypoints or []:
waypoint = _coerce_waypoint(point_like) waypoint = _coerce_waypoint(point_like)
if waypoint is None: if waypoint is None:
continue continue
normalized_waypoints.append(waypoint) normalized_waypoints.append(waypoint)
leading_axis = _same_carrier_run(previous_waypoint, waypoint)
_append_orthogonal_segment( _append_orthogonal_segment(
points, points,
waypoint["point"], waypoint["point"],
preferred_axis=waypoint.get("support_axis"), preferred_axis=waypoint.get("support_axis"),
leading_axis=leading_axis,
) )
previous_waypoint = waypoint
if exit_length > 0: if exit_length > 0:
end_exit = _offset_point( end_exit = _offset_point(
@ -293,6 +609,8 @@ def _set_wire_properties(
wire_mark="", wire_mark="",
wire_mark_is_manual=False, wire_mark_is_manual=False,
manual_waypoints=None, manual_waypoints=None,
route_nodes=None,
terminal_exit_length=0.0,
): ):
WiringObjects.set_routed_wire_semantics( WiringObjects.set_routed_wire_semantics(
obj, obj,
@ -325,6 +643,28 @@ def _set_wire_properties(
) )
except Exception: except Exception:
pass pass
try:
if "QetRouteNodesJson" not in getattr(obj, "PropertiesList", []):
obj.addProperty(
"App::PropertyString",
"QetRouteNodesJson",
"QET Wiring",
"Manual route semantic nodes",
)
obj.QetRouteNodesJson = json.dumps(route_nodes or [], ensure_ascii=False)
except Exception:
pass
try:
if "QetTerminalExitLength" not in getattr(obj, "PropertiesList", []):
obj.addProperty(
"App::PropertyFloat",
"QetTerminalExitLength",
"QET Wiring",
"Terminal exit length in millimeters",
)
obj.QetTerminalExitLength = max(float(terminal_exit_length or 0.0), 0.0)
except Exception:
pass
def _set_wire_points(obj, points): def _set_wire_points(obj, points):
@ -447,6 +787,12 @@ def create_manual_wire(
waypoints=waypoints, waypoints=waypoints,
terminal_exit_length=terminal_exit_length, terminal_exit_length=terminal_exit_length,
) )
route_nodes = _manual_route_nodes(
start_terminal,
end_terminal,
normalized_waypoints=normalized_waypoints,
terminal_exit_length=terminal_exit_length,
)
if len(points) < 2: if len(points) < 2:
raise ManualWiringError("A wire requires at least two points.") raise ManualWiringError("A wire requires at least two points.")
@ -464,6 +810,8 @@ def create_manual_wire(
wire_mark=wire_mark, wire_mark=wire_mark,
wire_mark_is_manual=wire_mark_is_manual, wire_mark_is_manual=wire_mark_is_manual,
manual_waypoints=normalized_waypoints, manual_waypoints=normalized_waypoints,
route_nodes=route_nodes,
terminal_exit_length=terminal_exit_length,
) )
if parent_group is None: if parent_group is None:

@ -30,6 +30,11 @@ except Exception:
COMMAND_NAME = "QET_Exchange_OpenManualWiringPanel" COMMAND_NAME = "QET_Exchange_OpenManualWiringPanel"
DEFAULT_TERMINAL_EXIT_LENGTH = 20.0 DEFAULT_TERMINAL_EXIT_LENGTH = 20.0
CARRIER_ROLE_LABELS = {
"wire_duct": "线槽",
"cabinet": "柜面",
"rail": "导轨",
}
class ManualWiringPanelError(RuntimeError): class ManualWiringPanelError(RuntimeError):
@ -184,7 +189,10 @@ def _point_label(point_like, index):
label = (point_like.get("source_label", "") or "").strip() label = (point_like.get("source_label", "") or "").strip()
subelement = (point_like.get("subelement_name", "") or "").strip() subelement = (point_like.get("subelement_name", "") or "").strip()
anchor_kind = (point_like.get("anchor_kind", "") or "").strip() anchor_kind = (point_like.get("anchor_kind", "") or "").strip()
carrier_label = _carrier_role_label(point_like.get("carrier_kind", ""))
parts = [] parts = []
if carrier_label:
parts.append(carrier_label)
if label: if label:
parts.append(label) parts.append(label)
if subelement: if subelement:
@ -216,6 +224,60 @@ def _dominant_axis(vector):
return axis return axis
def _carrier_kind_from_object(obj):
candidates = []
current = obj
if current is not None:
candidates.append(current)
candidates.extend(list(getattr(current, "InList", []) or []))
for candidate in candidates:
carrier_kind = (getattr(candidate, "QetCarrierKind", "") or "").strip()
if carrier_kind:
return carrier_kind
text_parts = []
for candidate in candidates:
text_parts.append(getattr(candidate, "Name", "") or "")
text_parts.append(getattr(candidate, "Label", "") or "")
text = " ".join(text_parts).lower()
if "线槽" in text or "duct" in text or "trunking" in text:
return "wire_duct"
if "导轨" in text or "rail" in text:
return "rail"
if "机柜" in text or "柜体" in text or "cabinet" in text or "panel" in text:
return "cabinet"
return ""
def _edge_carrier_axis(edge):
vertexes = list(getattr(edge, "Vertexes", []) or [])
if len(vertexes) >= 2:
start = getattr(vertexes[0], "Point", None)
end = getattr(vertexes[-1], "Point", None)
if start is not None and end is not None:
return _dominant_axis(
App.Vector(
float(getattr(end, "x", 0.0)) - float(getattr(start, "x", 0.0)),
float(getattr(end, "y", 0.0)) - float(getattr(start, "y", 0.0)),
float(getattr(end, "z", 0.0)) - float(getattr(start, "z", 0.0)),
)
)
return None
def _carrier_role_label(carrier_kind):
return CARRIER_ROLE_LABELS.get((carrier_kind or "").strip(), "")
def _selected_carrier_objects():
return [
obj
for obj in _selection()
if obj is not None and not TerminalObjects.is_terminal_object(obj)
]
def _selected_waypoint(): def _selected_waypoint():
for picked in _selection_ex(): for picked in _selection_ex():
picked_points = list(getattr(picked, "PickedPoints", []) or []) picked_points = list(getattr(picked, "PickedPoints", []) or [])
@ -226,6 +288,7 @@ def _selected_waypoint():
obj = getattr(picked, "Object", None) obj = getattr(picked, "Object", None)
support_axis = None support_axis = None
anchor_kind = "" anchor_kind = ""
carrier_axis = None
if point is None and sub_objects: if point is None and sub_objects:
point = _shape_center(sub_objects[0]) point = _shape_center(sub_objects[0])
@ -248,6 +311,9 @@ def _selected_waypoint():
support_axis = _dominant_axis(sub_object.normalAt(0.5)) support_axis = _dominant_axis(sub_object.normalAt(0.5))
except Exception: except Exception:
support_axis = None support_axis = None
carrier_axis = _edge_carrier_axis(sub_object)
elif anchor_kind == "edge":
carrier_axis = _edge_carrier_axis(sub_object)
elif anchor_kind == "vertex": elif anchor_kind == "vertex":
support_axis = None support_axis = None
@ -255,7 +321,10 @@ def _selected_waypoint():
"point": point, "point": point,
"support_axis": support_axis, "support_axis": support_axis,
"anchor_kind": anchor_kind, "anchor_kind": anchor_kind,
"carrier_kind": _carrier_kind_from_object(obj),
"carrier_axis": carrier_axis,
"source_label": getattr(obj, "Label", "") if obj is not None else "", "source_label": getattr(obj, "Label", "") if obj is not None else "",
"source_object_name": getattr(obj, "Name", "") if obj is not None else "",
"subelement_name": subelement_names[0] if subelement_names else "", "subelement_name": subelement_names[0] if subelement_names else "",
} }
return None return None
@ -385,6 +454,40 @@ class ManualWiringController:
self.terminal_exit_length = max(float(value or 0.0), 0.0) self.terminal_exit_length = max(float(value or 0.0), 0.0)
return self.terminal_exit_length return self.terminal_exit_length
def mark_selected_carriers(self, carrier_kind):
carrier_kind = (carrier_kind or "").strip()
if carrier_kind not in CARRIER_ROLE_LABELS:
raise ManualWiringPanelError("未知的布线载体类型:{0}".format(carrier_kind))
doc = _active_document()
selected = _selected_carrier_objects()
if not selected:
raise ManualWiringPanelError("请先选择线槽、柜面或导轨对象。")
project_uuid = getattr(TerminalObjects.ensure_root_group(doc), "QetProjectUuid", "").strip()
carrier_group = WiringObjects.ensure_carrier_group(doc, project_uuid)
role_label = _carrier_role_label(carrier_kind)
marked = []
for obj in selected:
TerminalObjects.ensure_string_property(
obj,
"QetCarrierKind",
"QET Wiring",
"3D wiring carrier kind",
carrier_kind,
)
TerminalObjects.ensure_string_property(
obj,
"QetCarrierRoleLabel",
"QET Wiring",
"3D wiring carrier role label",
role_label,
)
if obj not in getattr(carrier_group, "Group", []):
carrier_group.addObject(obj)
marked.append(obj)
return marked
def _clear_preview_objects(self): def _clear_preview_objects(self):
doc = getattr(App, "ActiveDocument", None) doc = getattr(App, "ActiveDocument", None)
if doc is None: if doc is None:
@ -493,6 +596,14 @@ class ManualWiringController:
_set_task_route_status(self.current_task, "Routed") _set_task_route_status(self.current_task, "Routed")
return wire return wire
def diagnose_last_wire(self):
if self.last_wire is None:
raise ManualWiringPanelError("当前还没有生成导线。")
return ManualWiring.diagnose_manual_wire(self.last_wire)
def diagnose_all_wires(self):
return ManualWiring.write_document_wire_diagnostics(_active_document())
def clear(self): def clear(self):
self._reset_route_state() self._reset_route_state()
@ -558,9 +669,14 @@ class ManualWiringTaskPanel:
self.exit_length_input.setSuffix(" mm") self.exit_length_input.setSuffix(" mm")
self.exit_length_input.setValue(self.controller.terminal_exit_length) self.exit_length_input.setValue(self.controller.terminal_exit_length)
self.start_button = QtWidgets.QPushButton("设为起点") self.start_button = QtWidgets.QPushButton("设为起点")
self.mark_duct_button = QtWidgets.QPushButton("标记为线槽")
self.mark_cabinet_button = QtWidgets.QPushButton("标记为柜面")
self.mark_rail_button = QtWidgets.QPushButton("标记为导轨")
self.waypoint_button = QtWidgets.QPushButton("添加折点") self.waypoint_button = QtWidgets.QPushButton("添加折点")
self.delete_waypoint_button = QtWidgets.QPushButton("删除最后折点") self.delete_waypoint_button = QtWidgets.QPushButton("删除最后折点")
self.end_button = QtWidgets.QPushButton("设为终点并生成") self.end_button = QtWidgets.QPushButton("设为终点并生成")
self.diagnose_button = QtWidgets.QPushButton("检查最近导线")
self.diagnose_all_button = QtWidgets.QPushButton("检查全部导线")
self.clear_button = QtWidgets.QPushButton("清除草稿") self.clear_button = QtWidgets.QPushButton("清除草稿")
self.save_button = QtWidgets.QPushButton("保存并回写") self.save_button = QtWidgets.QPushButton("保存并回写")
@ -571,10 +687,17 @@ class ManualWiringTaskPanel:
exit_layout.addWidget(QtWidgets.QLabel("端子出线长度")) exit_layout.addWidget(QtWidgets.QLabel("端子出线长度"))
exit_layout.addWidget(self.exit_length_input) exit_layout.addWidget(self.exit_length_input)
layout.addLayout(exit_layout) layout.addLayout(exit_layout)
carrier_layout = QtWidgets.QHBoxLayout()
carrier_layout.addWidget(self.mark_duct_button)
carrier_layout.addWidget(self.mark_cabinet_button)
carrier_layout.addWidget(self.mark_rail_button)
layout.addLayout(carrier_layout)
layout.addWidget(self.start_button) layout.addWidget(self.start_button)
layout.addWidget(self.waypoint_button) layout.addWidget(self.waypoint_button)
layout.addWidget(self.delete_waypoint_button) layout.addWidget(self.delete_waypoint_button)
layout.addWidget(self.end_button) layout.addWidget(self.end_button)
layout.addWidget(self.diagnose_button)
layout.addWidget(self.diagnose_all_button)
layout.addWidget(self.clear_button) layout.addWidget(self.clear_button)
layout.addWidget(self.save_button) layout.addWidget(self.save_button)
@ -589,10 +712,15 @@ class ManualWiringTaskPanel:
self.use_task_button.clicked.connect(self.use_selected_task) self.use_task_button.clicked.connect(self.use_selected_task)
self.reload_tasks_button.clicked.connect(self._refresh_task_list) self.reload_tasks_button.clicked.connect(self._refresh_task_list)
self.exit_length_input.valueChanged.connect(self.set_exit_length) self.exit_length_input.valueChanged.connect(self.set_exit_length)
self.mark_duct_button.clicked.connect(self.mark_wire_duct)
self.mark_cabinet_button.clicked.connect(self.mark_cabinet)
self.mark_rail_button.clicked.connect(self.mark_rail)
self.start_button.clicked.connect(self.set_start) self.start_button.clicked.connect(self.set_start)
self.waypoint_button.clicked.connect(self.add_waypoint) self.waypoint_button.clicked.connect(self.add_waypoint)
self.delete_waypoint_button.clicked.connect(self.delete_last_waypoint) self.delete_waypoint_button.clicked.connect(self.delete_last_waypoint)
self.end_button.clicked.connect(self.set_end_and_generate) self.end_button.clicked.connect(self.set_end_and_generate)
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) self.clear_button.clicked.connect(self.clear)
self.save_button.clicked.connect(self.save_and_write_back) self.save_button.clicked.connect(self.save_and_write_back)
@ -659,6 +787,29 @@ class ManualWiringTaskPanel:
except Exception as exc: except Exception as exc:
self._set_error(str(exc)) self._set_error(str(exc))
def mark_carrier(self, carrier_kind):
marked = self.controller.mark_selected_carriers(carrier_kind)
role_label = _carrier_role_label(carrier_kind) or carrier_kind
self._set_status("已将 {0} 个对象标记为{1}".format(len(marked), role_label))
def mark_wire_duct(self):
try:
self.mark_carrier("wire_duct")
except Exception as exc:
self._set_error(str(exc))
def mark_cabinet(self):
try:
self.mark_carrier("cabinet")
except Exception as exc:
self._set_error(str(exc))
def mark_rail(self):
try:
self.mark_carrier("rail")
except Exception as exc:
self._set_error(str(exc))
def set_start(self): def set_start(self):
try: try:
terminal = self.controller.set_start_from_selection() terminal = self.controller.set_start_from_selection()
@ -703,6 +854,39 @@ class ManualWiringTaskPanel:
except Exception as exc: except Exception as exc:
self._set_error(str(exc)) self._set_error(str(exc))
def diagnose_last_wire(self):
try:
diagnostics = self.controller.diagnose_last_wire()
if not diagnostics:
self._set_status("最近导线检查通过。")
return
text = "".join(
"{0}:{1}".format(item.get("severity", ""), item.get("message", ""))
for item in diagnostics[:3]
)
if len(diagnostics) > 3:
text += "..."
self._set_status("最近导线检查发现 {0} 个问题:{1}".format(len(diagnostics), text))
except Exception as exc:
self._set_error(str(exc))
def diagnose_all_wires(self):
try:
report = self.controller.diagnose_all_wires()
issue_count = int(report.get("issue_count", 0))
wire_count = int(report.get("wire_count", 0))
if issue_count <= 0:
self._set_status("全部导线检查通过:共 {0} 条导线。".format(wire_count))
return
self._set_status(
"全部导线检查完成:{0} 条导线,发现 {1} 个问题,已写入 Diagnostics。".format(
wire_count,
issue_count,
)
)
except Exception as exc:
self._set_error(str(exc))
def clear(self): def clear(self):
self.controller.clear() self.controller.clear()
self._refresh_waypoint_list() self._refresh_waypoint_list()

@ -291,6 +291,19 @@ def _point_from_vector(vector):
} }
def _json_array_property(obj, prop_name):
text = getattr(obj, prop_name, "")
if not text:
return []
try:
value = json.loads(text)
except Exception:
return []
if isinstance(value, list):
return value
return []
def wire_shape_points(wire_obj): def wire_shape_points(wire_obj):
if wire_obj is None: if wire_obj is None:
return [] return []
@ -347,6 +360,9 @@ def wire_payload_from_object(wire_obj):
"wire_mark": getattr(wire_obj, "QetWireMark", "").strip(), "wire_mark": getattr(wire_obj, "QetWireMark", "").strip(),
"wire_mark_is_manual": bool(getattr(wire_obj, "QetWireMarkIsManual", False)), "wire_mark_is_manual": bool(getattr(wire_obj, "QetWireMarkIsManual", False)),
"points": [], "points": [],
"manual_waypoints": [],
"route_nodes": [],
"terminal_exit_length": float(getattr(wire_obj, "QetTerminalExitLength", 0.0) or 0.0),
} }
points = [_point_from_vector(point) for point in wire_shape_points(wire_obj)] points = [_point_from_vector(point) for point in wire_shape_points(wire_obj)]
return { return {
@ -364,6 +380,9 @@ def wire_payload_from_object(wire_obj):
"wire_mark": getattr(wire_obj, "QetWireMark", "").strip(), "wire_mark": getattr(wire_obj, "QetWireMark", "").strip(),
"wire_mark_is_manual": bool(getattr(wire_obj, "QetWireMarkIsManual", False)), "wire_mark_is_manual": bool(getattr(wire_obj, "QetWireMarkIsManual", False)),
"points": points, "points": points,
"manual_waypoints": _json_array_property(wire_obj, "QetManualWaypointsJson"),
"route_nodes": _json_array_property(wire_obj, "QetRouteNodesJson"),
"terminal_exit_length": float(getattr(wire_obj, "QetTerminalExitLength", 0.0) or 0.0),
} }

@ -247,6 +247,106 @@ class ManualWiringPanelTest(unittest.TestCase):
), ),
) )
def test_controller_records_selected_wire_duct_waypoint_as_carrier_anchor(self):
selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
terminal_objects.ensure_root_group(doc, "project-1")
carrier = doc.addObject("Part::Feature", "WireDuct_A")
carrier.Label = "线槽A"
terminal_objects.ensure_string_property(
carrier,
"QetCarrierKind",
"QET Wiring",
"Carrier kind",
"wire_duct",
)
selection_state["selection_ex"] = [
types.SimpleNamespace(
PickedPoints=[app.Vector(100, 20, 30)],
SubObjects=[
types.SimpleNamespace(
ShapeType="Edge",
normalAt=lambda u: app.Vector(0, 1, 0),
)
],
SubElementNames=["Edge1"],
Object=carrier,
)
]
waypoint = panel.ManualWiringController().add_waypoint_from_selection()
self.assertEqual("edge", waypoint["anchor_kind"])
self.assertEqual("wire_duct", waypoint["carrier_kind"])
self.assertEqual("线槽A", waypoint["source_label"])
self.assertEqual("WireDuct_A", waypoint["source_object_name"])
def test_controller_records_wire_duct_edge_axis_for_waypoint(self):
selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
terminal_objects.ensure_root_group(doc, "project-1")
carrier = doc.addObject("Part::Feature", "WireDuct_A")
carrier.Label = "线槽A"
terminal_objects.ensure_string_property(
carrier,
"QetCarrierKind",
"QET Wiring",
"Carrier kind",
"wire_duct",
)
edge = types.SimpleNamespace(
ShapeType="Edge",
Vertexes=[
types.SimpleNamespace(Point=app.Vector(0, 10, 20)),
types.SimpleNamespace(Point=app.Vector(100, 10, 20)),
],
)
selection_state["selection_ex"] = [
types.SimpleNamespace(
PickedPoints=[app.Vector(40, 10, 20)],
SubObjects=[edge],
SubElementNames=["Edge1"],
Object=carrier,
)
]
waypoint = panel.ManualWiringController().add_waypoint_from_selection()
self.assertEqual("wire_duct", waypoint["carrier_kind"])
self.assertEqual("x", waypoint["carrier_axis"])
def test_controller_marks_selected_object_as_wire_duct_carrier(self):
selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
terminal_objects.ensure_root_group(doc, "project-1")
carrier = doc.addObject("Part::Feature", "WireDuct_A")
carrier.Label = "线槽A"
selection_state["selection"] = [carrier]
marked = panel.ManualWiringController().mark_selected_carriers("wire_duct")
carrier_group = doc.getObject("QETWiring_02_Carriers")
self.assertEqual([carrier], marked)
self.assertEqual("wire_duct", getattr(carrier, "QetCarrierKind", ""))
self.assertEqual("线槽", getattr(carrier, "QetCarrierRoleLabel", ""))
self.assertIn(carrier, carrier_group.Group)
def test_controller_deletes_last_waypoint_and_preview_point(self): def test_controller_deletes_last_waypoint_and_preview_point(self):
selection_state = _install_fake_freecad() selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules() terminal_objects, panel = _reload_modules()
@ -366,6 +466,104 @@ class ManualWiringPanelTest(unittest.TestCase):
[(point.x, point.y, point.z) for point in getattr(wire, "Points", [])], [(point.x, point.y, point.z) for point in getattr(wire, "Points", [])],
) )
def test_controller_diagnoses_last_generated_wire(self):
selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
controller = panel.ManualWiringController()
selection_state["selection"] = [start_terminal]
controller.set_start_from_selection()
selection_state["selection_ex"] = [
types.SimpleNamespace(
PickedPoints=[app.Vector(10, 10, 10)],
SubObjects=[],
SubElementNames=[],
Object=types.SimpleNamespace(Name="线槽无对象名", Label="线槽"),
)
]
controller.add_waypoint_from_selection()
controller.waypoints[0]["source_object_name"] = ""
selection_state["selection"] = [end_terminal]
controller.set_end_from_selection_and_generate()
diagnostics = controller.diagnose_last_wire()
self.assertTrue(
any(item["code"] == "wire_duct_source_missing" for item in diagnostics)
)
def test_controller_writes_all_wire_diagnostics(self):
selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
app.ActiveDocument = doc
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
controller = panel.ManualWiringController()
selection_state["selection"] = [start_terminal]
controller.set_start_from_selection()
controller.waypoints = [
{
"point": app.Vector(10, 10, 10),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
}
]
selection_state["selection"] = [end_terminal]
controller.set_end_from_selection_and_generate()
report = controller.diagnose_all_wires()
self.assertEqual(1, report["issue_count"])
self.assertEqual(1, len(doc.getObject("QETWiring_05_Diagnostics").Group))
def test_controller_generates_wire_from_selected_task(self): def test_controller_generates_wire_from_selected_task(self):
selection_state = _install_fake_freecad() selection_state = _install_fake_freecad()
terminal_objects, panel = _reload_modules() terminal_objects, panel = _reload_modules()

@ -1,6 +1,7 @@
import sys import sys
import types import types
import unittest import unittest
import json
from pathlib import Path from pathlib import Path
@ -306,6 +307,375 @@ class ManualWiringGroupTest(unittest.TestCase):
points[:5], points[:5],
) )
def test_manual_wire_records_semantic_route_nodes_for_later_carrier_routing(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
start_terminal.Placement = app.Placement(app.Vector(0, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(100, 20, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(50, 10, 30),
"support_axis": "z",
"anchor_kind": "edge",
"carrier_kind": "wire_duct",
"source_label": "线槽A",
"subelement_name": "Edge1",
}
],
terminal_exit_length=15.0,
)
self.assertEqual(15.0, getattr(wire, "QetTerminalExitLength", None))
route_nodes = json.loads(getattr(wire, "QetRouteNodesJson", "[]"))
self.assertEqual(
[
"start_terminal",
"start_exit",
"waypoint",
"end_exit",
"end_terminal",
],
[node["role"] for node in route_nodes],
)
self.assertEqual("wire_duct", route_nodes[2]["carrier_kind"])
self.assertEqual("edge", route_nodes[2]["anchor_kind"])
self.assertEqual("terminal-start", route_nodes[0]["terminal_uuid"])
self.assertEqual("terminal-end", route_nodes[-1]["terminal_uuid"])
def test_manual_wire_routes_along_same_wire_duct_axis_between_waypoints(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
start_terminal.Placement = app.Placement(app.Vector(0, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(30, 120, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 0, 20),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
"source_object_name": "WireDuct_A",
},
{
"point": app.Vector(20, 100, 20),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
"source_object_name": "WireDuct_A",
},
],
terminal_exit_length=0.0,
)
points = [(point.x, point.y, point.z) for point in wire.Shape]
self.assertEqual(
[
(10.0, 0.0, 20.0),
(20.0, 0.0, 20.0),
(20.0, 100.0, 20.0),
],
points[2:5],
)
route_nodes = json.loads(getattr(wire, "QetRouteNodesJson", "[]"))
self.assertEqual("x", route_nodes[1]["carrier_axis"])
self.assertEqual("x", route_nodes[2]["carrier_axis"])
def test_manual_wire_does_not_treat_unknown_wire_duct_sources_as_same_carrier(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
start_terminal.Placement = app.Placement(app.Vector(0, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(30, 120, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 0, 20),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
},
{
"point": app.Vector(20, 100, 20),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
},
],
terminal_exit_length=0.0,
)
points = [(point.x, point.y, point.z) for point in wire.Shape]
self.assertEqual((10.0, 100.0, 20.0), points[3])
def test_manual_wire_diagnostics_warn_for_wire_duct_waypoint_without_source(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 10, 10),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
}
],
)
diagnostics = manual_wiring.diagnose_manual_wire(wire)
self.assertTrue(
any(item["code"] == "wire_duct_source_missing" for item in diagnostics)
)
def test_manual_wire_diagnostics_pass_for_complete_manual_route(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 10, 10),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
"source_object_name": "WireDuct_A",
}
],
terminal_exit_length=20.0,
)
self.assertEqual([], manual_wiring.diagnose_manual_wire(wire))
def test_write_document_wire_diagnostics_creates_diagnostic_objects(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
wire = manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 10, 10),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
}
],
)
report = manual_wiring.write_document_wire_diagnostics(doc)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
self.assertEqual(1, report["issue_count"])
self.assertEqual(1, len(diagnostic_group.Group))
diagnostic = diagnostic_group.Group[0]
self.assertEqual("wire_duct_source_missing", diagnostic.QetDiagnosticCode)
self.assertEqual(wire.Name, diagnostic.QetWireObjectName)
self.assertIn("线槽折点", diagnostic.QetDiagnosticMessage)
def test_write_document_wire_diagnostics_replaces_previous_manual_results(self):
_install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules()
app = sys.modules["FreeCAD"]
doc = FakeDocument()
terminal_objects.ensure_root_group(doc, "project-1")
start_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalStart")
terminal_objects.set_terminal_semantics(
start_terminal,
"project-1",
"device-start",
"terminal-start",
"instance-start",
label="Start",
)
end_terminal = doc.addObject("Part::LocalCoordinateSystem", "TerminalEnd")
end_terminal.Placement = app.Placement(app.Vector(20, 0, 0), app.Rotation())
terminal_objects.set_terminal_semantics(
end_terminal,
"project-1",
"device-end",
"terminal-end",
"instance-end",
label="End",
)
manual_wiring.create_manual_wire(
doc,
start_terminal,
end_terminal,
waypoints=[
{
"point": app.Vector(10, 10, 10),
"carrier_kind": "wire_duct",
"carrier_axis": "x",
}
],
)
manual_wiring.write_document_wire_diagnostics(doc)
manual_wiring.write_document_wire_diagnostics(doc)
diagnostic_group = doc.getObject("QETWiring_05_Diagnostics")
self.assertEqual(1, len(diagnostic_group.Group))
def test_manual_wire_is_visible_in_routed_group_not_hidden_legacy_group(self): def test_manual_wire_is_visible_in_routed_group_not_hidden_legacy_group(self):
_install_fake_freecad() _install_fake_freecad()
_device_import, manual_wiring, terminal_objects = _reload_modules() _device_import, manual_wiring, terminal_objects = _reload_modules()

@ -330,6 +330,14 @@ class WiringTest(unittest.TestCase):
self.assertTrue(any(point.x == 4.0 and point.y == 5.0 and point.z == 6.0 for point in wire.Points)) self.assertTrue(any(point.x == 4.0 and point.y == 5.0 and point.z == 6.0 for point in wire.Points))
self.assertIn("QetManualWaypointsJson", getattr(wire, "PropertiesList", [])) self.assertIn("QetManualWaypointsJson", getattr(wire, "PropertiesList", []))
self.assertIn('"support_axis": "x"', getattr(wire, "QetManualWaypointsJson", "")) self.assertIn('"support_axis": "x"', getattr(wire, "QetManualWaypointsJson", ""))
payload = wiring_objects.wire_payload_from_object(wire)
self.assertEqual(20.0, payload["terminal_exit_length"])
self.assertEqual("Manual", payload["route_mode"])
self.assertEqual(
["start_terminal", "start_exit", "waypoint", "end_exit", "end_terminal"],
[node["role"] for node in payload["route_nodes"]],
)
self.assertEqual("face", payload["route_nodes"][2]["anchor_kind"])
def test_wire_writeback_omits_scene_routed_wire_payload(self): def test_wire_writeback_omits_scene_routed_wire_payload(self):
_install_fake_freecad() _install_fake_freecad()

Loading…
Cancel
Save