|
|
|
|
@ -22,6 +22,11 @@ ROUTE_CARRIER_KIND_WIRING_CUT_OUT = "WiringCutOut"
|
|
|
|
|
ROUTE_CARRIER_KIND_AUXILIARY_PATH = "AuxiliaryPath"
|
|
|
|
|
ROUTE_CARRIER_KIND_ROUTING_RANGE = "RoutingRange"
|
|
|
|
|
ROUTE_CARRIER_KIND_TERMINAL_ACCESS = "TerminalAccess"
|
|
|
|
|
MANAGED_ROUTE_SOURCE_KINDS = {
|
|
|
|
|
ROUTE_CARRIER_KIND_WIRE_DUCT,
|
|
|
|
|
ROUTE_CARRIER_KIND_WIRING_CUT_OUT,
|
|
|
|
|
ROUTE_CARRIER_KIND_ROUTING_RANGE,
|
|
|
|
|
}
|
|
|
|
|
PROPERTY_GROUP = "QET Routing"
|
|
|
|
|
DEFAULT_NODE_TOLERANCE = 0.001
|
|
|
|
|
DEFAULT_SURFACE_LANE_SPACING = 100.0
|
|
|
|
|
@ -872,10 +877,11 @@ def _detach_from_groups(doc, obj):
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clear_route_carriers(doc):
|
|
|
|
|
"""Delete generated route carriers while keeping terminals and routed wires."""
|
|
|
|
|
def _remove_route_carriers(doc, carriers):
|
|
|
|
|
removed = 0
|
|
|
|
|
for carrier in list(collect_route_carriers(doc)):
|
|
|
|
|
for carrier in list(carriers or []):
|
|
|
|
|
if carrier is None or not is_route_carrier(carrier):
|
|
|
|
|
continue
|
|
|
|
|
_detach_from_groups(doc, carrier)
|
|
|
|
|
try:
|
|
|
|
|
if doc.getObject(getattr(carrier, "Name", "")) is not None:
|
|
|
|
|
@ -883,6 +889,12 @@ def clear_route_carriers(doc):
|
|
|
|
|
removed += 1
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return removed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def clear_route_carriers(doc):
|
|
|
|
|
"""Delete generated route carriers while keeping terminals and routed wires."""
|
|
|
|
|
removed = _remove_route_carriers(doc, collect_route_carriers(doc))
|
|
|
|
|
try:
|
|
|
|
|
doc.recompute()
|
|
|
|
|
except Exception:
|
|
|
|
|
@ -1528,6 +1540,40 @@ def _live_source_carriers(doc, source):
|
|
|
|
|
return carriers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _source_kind_value(source):
|
|
|
|
|
return (getattr(source, "QetRoutingSourceKind", "") or "").strip()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _set_route_carrier_source_metadata(carrier, source, source_kind=""):
|
|
|
|
|
if carrier is None or source is None:
|
|
|
|
|
return
|
|
|
|
|
source_name = (getattr(source, "Name", "") or "").strip()
|
|
|
|
|
if not source_name:
|
|
|
|
|
return
|
|
|
|
|
kind = (source_kind or _source_kind_value(source)).strip()
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
carrier,
|
|
|
|
|
"QetRouteSourceName",
|
|
|
|
|
PROPERTY_GROUP,
|
|
|
|
|
"FreeCAD source object name that generated this route carrier",
|
|
|
|
|
source_name,
|
|
|
|
|
)
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
carrier,
|
|
|
|
|
"QetRouteSourceLabel",
|
|
|
|
|
PROPERTY_GROUP,
|
|
|
|
|
"FreeCAD source object label that generated this route carrier",
|
|
|
|
|
getattr(source, "Label", "") or source_name,
|
|
|
|
|
)
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
carrier,
|
|
|
|
|
"QetRouteSourceKind",
|
|
|
|
|
PROPERTY_GROUP,
|
|
|
|
|
"Routing source kind that generated this route carrier",
|
|
|
|
|
kind,
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _remember_source_carriers(source, carriers):
|
|
|
|
|
live_names = [
|
|
|
|
|
getattr(carrier, "Name", "")
|
|
|
|
|
@ -1535,6 +1581,9 @@ def _remember_source_carriers(source, carriers):
|
|
|
|
|
if carrier is not None and getattr(carrier, "Name", "")
|
|
|
|
|
]
|
|
|
|
|
if live_names:
|
|
|
|
|
source_kind = _source_kind_value(source)
|
|
|
|
|
for carrier in carriers or []:
|
|
|
|
|
_set_route_carrier_source_metadata(carrier, source, source_kind=source_kind)
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
source,
|
|
|
|
|
"QetRouteCarrierNamesJson",
|
|
|
|
|
@ -1591,6 +1640,7 @@ def _mark_wiring_cut_out_source(source, carrier):
|
|
|
|
|
"Generated route carrier for this source",
|
|
|
|
|
getattr(carrier, "Name", ""),
|
|
|
|
|
)
|
|
|
|
|
_remember_source_carriers(source, [carrier])
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@ -1613,6 +1663,7 @@ def _mark_terminal_access_source(source, carrier):
|
|
|
|
|
"Generated route carrier for this source",
|
|
|
|
|
getattr(carrier, "Name", ""),
|
|
|
|
|
)
|
|
|
|
|
_remember_source_carriers(source, [carrier])
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
@ -1622,6 +1673,77 @@ def _live_source_carrier(doc, source):
|
|
|
|
|
return carriers[0] if carriers else None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _source_is_valid_for_kind(source, source_kind):
|
|
|
|
|
if source_kind == ROUTE_CARRIER_KIND_WIRE_DUCT:
|
|
|
|
|
return _is_wire_duct_candidate(source)
|
|
|
|
|
if source_kind == ROUTE_CARRIER_KIND_ROUTING_RANGE:
|
|
|
|
|
return _is_support_surface_candidate(source)
|
|
|
|
|
if source_kind == ROUTE_CARRIER_KIND_WIRING_CUT_OUT:
|
|
|
|
|
return _is_wiring_cut_out_candidate(source)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _clear_invalid_source_route_metadata(source):
|
|
|
|
|
for property_name in (
|
|
|
|
|
"QetRouteCarrierName",
|
|
|
|
|
"QetRouteCarrierNamesJson",
|
|
|
|
|
"QetRoutingObstacleMode",
|
|
|
|
|
):
|
|
|
|
|
if property_name not in getattr(source, "PropertiesList", []) and not getattr(source, property_name, ""):
|
|
|
|
|
continue
|
|
|
|
|
TerminalObjects.ensure_string_property(
|
|
|
|
|
source,
|
|
|
|
|
property_name,
|
|
|
|
|
PROPERTY_GROUP,
|
|
|
|
|
"Cleared invalid routing source metadata",
|
|
|
|
|
"",
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _document_object_by_name(doc, name):
|
|
|
|
|
if doc is None or not name:
|
|
|
|
|
return None
|
|
|
|
|
try:
|
|
|
|
|
return doc.getObject(name)
|
|
|
|
|
except Exception:
|
|
|
|
|
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cleanup_invalid_source_carriers(doc):
|
|
|
|
|
"""Remove generated carriers whose FreeCAD source object is missing or invalid."""
|
|
|
|
|
if doc is None:
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
removed = 0
|
|
|
|
|
for carrier in list(collect_route_carriers(doc)):
|
|
|
|
|
source_name = (getattr(carrier, "QetRouteSourceName", "") or "").strip()
|
|
|
|
|
source_kind = (getattr(carrier, "QetRouteSourceKind", "") or "").strip()
|
|
|
|
|
if source_kind not in MANAGED_ROUTE_SOURCE_KINDS or not source_name:
|
|
|
|
|
continue
|
|
|
|
|
if _document_object_by_name(doc, source_name) is None:
|
|
|
|
|
removed += _remove_route_carriers(doc, [carrier])
|
|
|
|
|
|
|
|
|
|
for source in list(getattr(doc, "Objects", []) or []):
|
|
|
|
|
if source is None or is_route_carrier(source):
|
|
|
|
|
continue
|
|
|
|
|
source_kind = _source_kind_value(source)
|
|
|
|
|
if source_kind not in MANAGED_ROUTE_SOURCE_KINDS:
|
|
|
|
|
continue
|
|
|
|
|
if not _route_source_carrier_names(source):
|
|
|
|
|
continue
|
|
|
|
|
if _source_is_valid_for_kind(source, source_kind):
|
|
|
|
|
continue
|
|
|
|
|
removed += _remove_route_carriers(doc, _live_source_carriers(doc, source))
|
|
|
|
|
_clear_invalid_source_route_metadata(source)
|
|
|
|
|
|
|
|
|
|
if removed:
|
|
|
|
|
try:
|
|
|
|
|
doc.recompute()
|
|
|
|
|
except Exception:
|
|
|
|
|
pass
|
|
|
|
|
return removed
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detect_wire_duct_sources(doc, min_aspect=DEFAULT_AUTO_WIRE_DUCT_MIN_ASPECT):
|
|
|
|
|
"""Return document objects that look like wire ducts based on semantics/name and shape."""
|
|
|
|
|
sources = []
|
|
|
|
|
@ -1672,6 +1794,7 @@ def prepare_layout_space_sources_from_document(doc, project_uuid=""):
|
|
|
|
|
raise RoutingNetworkError("No FreeCAD document is available.")
|
|
|
|
|
|
|
|
|
|
WiringObjects.ensure_wiring_root_group(doc, project_uuid)
|
|
|
|
|
cleanup_invalid_source_carriers(doc)
|
|
|
|
|
|
|
|
|
|
wire_duct_sources = detect_wire_duct_sources(doc)
|
|
|
|
|
support_surface_sources = detect_support_surface_sources(doc)
|
|
|
|
|
@ -1713,6 +1836,7 @@ def create_wire_duct_carriers_from_document(
|
|
|
|
|
min_aspect=DEFAULT_AUTO_WIRE_DUCT_MIN_ASPECT,
|
|
|
|
|
):
|
|
|
|
|
"""Auto-detect wire duct objects in the document and create WireDuct centerlines."""
|
|
|
|
|
cleanup_invalid_source_carriers(doc)
|
|
|
|
|
created = []
|
|
|
|
|
for index, source in enumerate(detect_wire_duct_sources(doc, min_aspect=min_aspect), start=1):
|
|
|
|
|
bbox = _bound_box_from_object(source)
|
|
|
|
|
@ -1765,6 +1889,7 @@ def create_wire_duct_carriers_from_document(
|
|
|
|
|
|
|
|
|
|
def create_wiring_cut_out_carriers_from_document(doc, project_uuid=""):
|
|
|
|
|
"""Create pass-through route carriers for wiring cut-out objects."""
|
|
|
|
|
cleanup_invalid_source_carriers(doc)
|
|
|
|
|
created = []
|
|
|
|
|
for source in detect_wiring_cut_out_sources(doc):
|
|
|
|
|
bbox = _bound_box_from_object(source)
|
|
|
|
|
@ -1808,6 +1933,7 @@ def create_surface_carriers_from_document(
|
|
|
|
|
margin=DEFAULT_SURFACE_MARGIN,
|
|
|
|
|
):
|
|
|
|
|
"""Auto-detect thin support panels and create low-priority RoutingRange grids."""
|
|
|
|
|
cleanup_invalid_source_carriers(doc)
|
|
|
|
|
created = []
|
|
|
|
|
for source in detect_support_surface_sources(doc):
|
|
|
|
|
bbox = _bound_box_from_object(source)
|
|
|
|
|
@ -2076,6 +2202,7 @@ def create_wire_duct_carriers_from_selection(
|
|
|
|
|
min_aspect=1.5,
|
|
|
|
|
):
|
|
|
|
|
"""Create WireDuct centerline carriers from selected duct-like solids."""
|
|
|
|
|
cleanup_invalid_source_carriers(doc)
|
|
|
|
|
created = []
|
|
|
|
|
for index, source in enumerate(_wire_duct_sources_from_selection(selection_ex), start=1):
|
|
|
|
|
bbox = _bound_box_from_object(source)
|
|
|
|
|
|