|
|
|
|
@ -1,7 +1,6 @@
|
|
|
|
|
# FreeCADExchange wire task import helpers.
|
|
|
|
|
|
|
|
|
|
import json
|
|
|
|
|
import re
|
|
|
|
|
|
|
|
|
|
import FreeCAD as App
|
|
|
|
|
|
|
|
|
|
@ -43,100 +42,6 @@ def _conductor_uuids(item):
|
|
|
|
|
return [str(value).strip() for value in values if str(value).strip()]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _wire_style_payload(item):
|
|
|
|
|
wire_style = item.get("wire_style", {})
|
|
|
|
|
if not isinstance(wire_style, dict):
|
|
|
|
|
return {}
|
|
|
|
|
return dict(wire_style)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _canonical_wire_style_json(wire_style):
|
|
|
|
|
if not isinstance(wire_style, dict):
|
|
|
|
|
wire_style = {}
|
|
|
|
|
return json.dumps(wire_style, ensure_ascii=False, sort_keys=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _style_text(wire_style, key):
|
|
|
|
|
if not isinstance(wire_style, dict):
|
|
|
|
|
return ""
|
|
|
|
|
value = wire_style.get(key, "")
|
|
|
|
|
if value is None:
|
|
|
|
|
return ""
|
|
|
|
|
return str(value).strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _style_float_text(wire_style, key):
|
|
|
|
|
text = _style_text(wire_style, key)
|
|
|
|
|
if not text:
|
|
|
|
|
return ""
|
|
|
|
|
try:
|
|
|
|
|
return "{0:g}".format(float(text))
|
|
|
|
|
except Exception:
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _parse_line_color(value):
|
|
|
|
|
text = str(value or "").strip()
|
|
|
|
|
if not text:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
match = re.fullmatch(r"#?([0-9a-fA-F]{6})", text)
|
|
|
|
|
if match:
|
|
|
|
|
raw = match.group(1)
|
|
|
|
|
return tuple(int(raw[index:index + 2], 16) / 255.0 for index in (0, 2, 4))
|
|
|
|
|
|
|
|
|
|
if "," in text:
|
|
|
|
|
try:
|
|
|
|
|
numbers = [float(part.strip()) for part in text.split(",")]
|
|
|
|
|
except Exception:
|
|
|
|
|
numbers = []
|
|
|
|
|
if len(numbers) == 3:
|
|
|
|
|
if max(numbers) > 1.0:
|
|
|
|
|
numbers = [number / 255.0 for number in numbers]
|
|
|
|
|
return tuple(max(0.0, min(1.0, number)) for number in numbers)
|
|
|
|
|
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _line_width_value(wire_style):
|
|
|
|
|
try:
|
|
|
|
|
value = float(_style_text(wire_style, "line_width") or 0)
|
|
|
|
|
except Exception:
|
|
|
|
|
value = 0.0
|
|
|
|
|
if value <= 0.0:
|
|
|
|
|
return None
|
|
|
|
|
return max(0.1, min(20.0, value))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_style_value(wire_style):
|
|
|
|
|
text = _style_text(wire_style, "line_type").lower()
|
|
|
|
|
if not text:
|
|
|
|
|
return "Solid"
|
|
|
|
|
normalized = text.replace("_", "").replace("-", "").replace(" ", "")
|
|
|
|
|
if normalized in {"dashline", "dashed", "dash", "dashes", "虚线"}:
|
|
|
|
|
return "Dashed"
|
|
|
|
|
if normalized in {"dotline", "dotted", "dot", "dots", "点线"}:
|
|
|
|
|
return "Dotted"
|
|
|
|
|
if normalized in {"dashdotline", "dashdot", "点划线"}:
|
|
|
|
|
return "Dashdot"
|
|
|
|
|
return "Solid"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _geometry_style_fields(wire_style):
|
|
|
|
|
return {
|
|
|
|
|
key: _style_text(wire_style, key)
|
|
|
|
|
for key in ("diameter_mm", "bend_radius_mm", "bend_radius_factor", "area_or_spec")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _json_dict(text):
|
|
|
|
|
try:
|
|
|
|
|
value = json.loads(text or "{}")
|
|
|
|
|
except Exception:
|
|
|
|
|
return {}
|
|
|
|
|
return value if isinstance(value, dict) else {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _device_display_map(payload):
|
|
|
|
|
labels = {}
|
|
|
|
|
for item in payload.get("devices", []) or []:
|
|
|
|
|
@ -236,7 +141,6 @@ def _normalize_wire_entry(item, index, device_labels=None, endpoint_instances=No
|
|
|
|
|
_endpoint_text(start_device_label, start_terminal_display, start_terminal_uuid),
|
|
|
|
|
_endpoint_text(end_device_label, end_terminal_display, end_terminal_uuid),
|
|
|
|
|
)
|
|
|
|
|
wire_style = _wire_style_payload(item)
|
|
|
|
|
return {
|
|
|
|
|
"wire_uuid": wire_uuid,
|
|
|
|
|
"wire_label": wire_label,
|
|
|
|
|
@ -245,7 +149,6 @@ def _normalize_wire_entry(item, index, device_labels=None, endpoint_instances=No
|
|
|
|
|
"wire_mark": wire_mark,
|
|
|
|
|
"wire_mark_is_manual": _bool_value(item, "wire_mark_is_manual"),
|
|
|
|
|
"wire_style_id": _int_text_value(item, "wire_style_id"),
|
|
|
|
|
"wire_style": wire_style,
|
|
|
|
|
"start_element_uuid": start_element_uuid,
|
|
|
|
|
"start_terminal_uuid": start_terminal_uuid,
|
|
|
|
|
"start_instance_id": start_instance_id,
|
|
|
|
|
@ -296,137 +199,10 @@ def _ensure_string_property(obj, prop_name, value, description="QET wire task pr
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _ensure_bool_property(obj, prop_name, value, description="QET wire task property"):
|
|
|
|
|
TerminalObjects.ensure_bool_property(
|
|
|
|
|
obj,
|
|
|
|
|
prop_name,
|
|
|
|
|
"QET Wiring",
|
|
|
|
|
description,
|
|
|
|
|
value,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_wire_style_properties(obj, wire_style_id, wire_style):
|
|
|
|
|
style = wire_style if isinstance(wire_style, dict) else {}
|
|
|
|
|
new_style_json = _canonical_wire_style_json(style)
|
|
|
|
|
old_style_json = (getattr(obj, "QetWireStyleJson", "") or "").strip()
|
|
|
|
|
old_style = _json_dict(old_style_json)
|
|
|
|
|
changed = old_style_json != new_style_json
|
|
|
|
|
|
|
|
|
|
_ensure_string_property(obj, "QetWireStyleId", str(wire_style_id or "").strip())
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetWireStyleJson",
|
|
|
|
|
new_style_json,
|
|
|
|
|
"QET wire style JSON from 2D payload",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(obj, "QetWireStyleName", _style_text(style, "name"))
|
|
|
|
|
_ensure_string_property(obj, "QetWireLineColor", _style_text(style, "line_color"))
|
|
|
|
|
_ensure_string_property(obj, "QetWireLineWidth", _style_float_text(style, "line_width"))
|
|
|
|
|
_ensure_string_property(obj, "QetWireDiameterMm", _style_float_text(style, "diameter_mm"))
|
|
|
|
|
_ensure_string_property(obj, "QetWireBendRadiusMm", _style_float_text(style, "bend_radius_mm"))
|
|
|
|
|
|
|
|
|
|
if old_style_json and changed and _geometry_style_fields(old_style) != _geometry_style_fields(style):
|
|
|
|
|
_ensure_bool_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetWireStyleAffectsGeometry",
|
|
|
|
|
True,
|
|
|
|
|
"Whether latest QET wire style changes geometry-sensitive fields",
|
|
|
|
|
)
|
|
|
|
|
elif "QetWireStyleAffectsGeometry" not in getattr(obj, "PropertiesList", []):
|
|
|
|
|
_ensure_bool_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetWireStyleAffectsGeometry",
|
|
|
|
|
False,
|
|
|
|
|
"Whether latest QET wire style changes geometry-sensitive fields",
|
|
|
|
|
)
|
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _apply_wire_style_view(obj, wire_style):
|
|
|
|
|
changed = False
|
|
|
|
|
view = getattr(obj, "ViewObject", None)
|
|
|
|
|
if view is None:
|
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
color = _parse_line_color(_style_text(wire_style, "line_color"))
|
|
|
|
|
if color is not None:
|
|
|
|
|
for prop_name in ("LineColor", "ShapeColor", "PointColor"):
|
|
|
|
|
if not hasattr(view, prop_name):
|
|
|
|
|
continue
|
|
|
|
|
try:
|
|
|
|
|
if tuple(getattr(view, prop_name, ())) != tuple(color):
|
|
|
|
|
setattr(view, prop_name, color)
|
|
|
|
|
changed = True
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
draw_style = _draw_style_value(wire_style)
|
|
|
|
|
if hasattr(view, "DrawStyle"):
|
|
|
|
|
try:
|
|
|
|
|
if str(getattr(view, "DrawStyle", "") or "") != draw_style:
|
|
|
|
|
view.DrawStyle = draw_style
|
|
|
|
|
changed = True
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
if hasattr(view, "DisplayMode"):
|
|
|
|
|
try:
|
|
|
|
|
if str(getattr(view, "DisplayMode", "") or "") != "Wireframe":
|
|
|
|
|
view.DisplayMode = "Wireframe"
|
|
|
|
|
changed = True
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
width = _line_width_value(wire_style)
|
|
|
|
|
if width is not None and hasattr(view, "LineWidth"):
|
|
|
|
|
try:
|
|
|
|
|
if float(getattr(view, "LineWidth", 0.0) or 0.0) != width:
|
|
|
|
|
view.LineWidth = width
|
|
|
|
|
changed = True
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
_ensure_bool_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetWireStyleApplied",
|
|
|
|
|
bool(isinstance(wire_style, dict) and wire_style),
|
|
|
|
|
"Whether the QET wire style has been applied to the visible 3D wire",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetAppliedWireLineColor",
|
|
|
|
|
_style_text(wire_style, "line_color"),
|
|
|
|
|
"Applied QET wire line color text",
|
|
|
|
|
)
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetAppliedWireDrawStyle",
|
|
|
|
|
draw_style,
|
|
|
|
|
"Applied QET wire draw style",
|
|
|
|
|
)
|
|
|
|
|
if color is not None:
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetAppliedWireLineColorRgb",
|
|
|
|
|
",".join(str(value) for value in color),
|
|
|
|
|
"Applied QET wire RGB color",
|
|
|
|
|
)
|
|
|
|
|
if width is not None:
|
|
|
|
|
_ensure_string_property(
|
|
|
|
|
obj,
|
|
|
|
|
"QetAppliedWireLineWidth",
|
|
|
|
|
str(width),
|
|
|
|
|
"Applied QET wire line width",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return changed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_task_extra_properties(task, entry):
|
|
|
|
|
_ensure_string_property(task, "QetStartElementUuid", entry["start_element_uuid"])
|
|
|
|
|
_ensure_string_property(task, "QetEndElementUuid", entry["end_element_uuid"])
|
|
|
|
|
_set_wire_style_properties(task, entry["wire_style_id"], entry["wire_style"])
|
|
|
|
|
_ensure_string_property(task, "QetWireStyleId", entry["wire_style_id"])
|
|
|
|
|
_ensure_string_property(task, "QetStartTerminalDisplay", entry["start_terminal_display"])
|
|
|
|
|
_ensure_string_property(task, "QetEndTerminalDisplay", entry["end_terminal_display"])
|
|
|
|
|
_ensure_string_property(task, "QetStartDeviceLabel", entry["start_device_label"])
|
|
|
|
|
@ -530,67 +306,3 @@ def import_wire_tasks_from_payload(payload, doc=None):
|
|
|
|
|
report["updated_tasks"] += 1
|
|
|
|
|
|
|
|
|
|
return report
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def apply_wire_styles_from_payload(payload, doc=None):
|
|
|
|
|
if doc is None:
|
|
|
|
|
doc = getattr(App, "ActiveDocument", None)
|
|
|
|
|
if doc is None:
|
|
|
|
|
raise WiringImportError("No active FreeCAD document is available.")
|
|
|
|
|
if not isinstance(payload, dict):
|
|
|
|
|
raise WiringImportError("Exchange payload must be an object.")
|
|
|
|
|
|
|
|
|
|
style_by_wire = {}
|
|
|
|
|
for item in payload.get("wires", []) or []:
|
|
|
|
|
if not isinstance(item, dict):
|
|
|
|
|
continue
|
|
|
|
|
wire_uuid = (
|
|
|
|
|
_string_value(item, "wire_id")
|
|
|
|
|
or _string_value(item, "wire_uuid")
|
|
|
|
|
or _string_value(item, "id")
|
|
|
|
|
)
|
|
|
|
|
if not wire_uuid:
|
|
|
|
|
continue
|
|
|
|
|
style_by_wire[wire_uuid] = {
|
|
|
|
|
"wire_style_id": _int_text_value(item, "wire_style_id"),
|
|
|
|
|
"wire_style": _wire_style_payload(item),
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
report = {
|
|
|
|
|
"total_styles": len(style_by_wire),
|
|
|
|
|
"routed_wires_seen": 0,
|
|
|
|
|
"routed_wires_matched": 0,
|
|
|
|
|
"updated_properties": 0,
|
|
|
|
|
"updated_view": 0,
|
|
|
|
|
"updated_samples": [],
|
|
|
|
|
"warnings": [],
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for obj in list(WiringObjects.iter_routed_wire_objects(doc)):
|
|
|
|
|
report["routed_wires_seen"] += 1
|
|
|
|
|
wire_uuid = (getattr(obj, "QetWireUuid", "") or "").strip()
|
|
|
|
|
if not wire_uuid or wire_uuid not in style_by_wire:
|
|
|
|
|
continue
|
|
|
|
|
report["routed_wires_matched"] += 1
|
|
|
|
|
entry = style_by_wire[wire_uuid]
|
|
|
|
|
prop_changed = _set_wire_style_properties(
|
|
|
|
|
obj,
|
|
|
|
|
entry["wire_style_id"],
|
|
|
|
|
entry["wire_style"],
|
|
|
|
|
)
|
|
|
|
|
view_changed = _apply_wire_style_view(obj, entry["wire_style"])
|
|
|
|
|
if prop_changed:
|
|
|
|
|
report["updated_properties"] += 1
|
|
|
|
|
if view_changed:
|
|
|
|
|
report["updated_view"] += 1
|
|
|
|
|
if len(report["updated_samples"]) < 8:
|
|
|
|
|
report["updated_samples"].append(
|
|
|
|
|
{
|
|
|
|
|
"wire_uuid": wire_uuid,
|
|
|
|
|
"line_color": _style_text(entry["wire_style"], "line_color"),
|
|
|
|
|
"line_width": _style_text(entry["wire_style"], "line_width"),
|
|
|
|
|
"view_changed": bool(view_changed),
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return report
|
|
|
|
|
|