@ -65,6 +65,7 @@ class FakeViewObject:
self . Visibility = True
self . LineWidth = None
self . LineColor = None
self . DrawStyle = None
class FakeObject :
@ -158,9 +159,11 @@ class FakeEdge:
class FakeFace :
ShapeType = " Face "
def __init__ ( self , bbox , normal ):
def __init__ ( self , bbox , normal , vertices = None , center = None ):
self . BoundBox = bbox
self . _normal = normal
self . Vertexes = [ FakeVertex ( point ) for point in ( vertices or [ ] ) ]
self . CenterOfMass = center
def normalAt ( self , _u , _v ) :
return self . _normal
@ -176,6 +179,7 @@ class FakeSelectionItem:
def _reload_modules ( ) :
for name in [
" TerminalObjects " ,
" TemplateSemantics " ,
" WiringObjects " ,
" RoutingNetwork " ,
" AutoRouting " ,
@ -266,6 +270,96 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( " Routed " , result [ " route_status " ] )
self . assertTrue ( any ( point . y == 30.0 for point in result [ " points " ] ) )
def test_route_carrier_styles_make_generated_objects_distinguishable ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
wire_duct = routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 0 , 0 ) , app . Vector ( 100 , 0 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
routing_range = routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 10 , 0 ) , app . Vector ( 100 , 10 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " RoutingRange " ,
)
terminal_access = routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 20 , 0 ) , app . Vector ( 100 , 20 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " TerminalAccess " ,
)
self . assertEqual ( ( 1.0 , 0.55 , 0.0 ) , wire_duct . ViewObject . LineColor )
self . assertEqual ( 4.0 , wire_duct . ViewObject . LineWidth )
self . assertEqual ( ( 0.0 , 0.65 , 0.35 ) , routing_range . ViewObject . LineColor )
self . assertEqual ( " Solid " , routing_range . ViewObject . DrawStyle )
self . assertEqual ( ( 0.65 , 0.2 , 1.0 ) , terminal_access . ViewObject . LineColor )
self . assertEqual ( " Solid " , terminal_access . ViewObject . DrawStyle )
def test_route_graph_connects_crossing_carriers_at_intersection ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 50 , 50 , 0 ) )
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 0 , 0 ) , app . Vector ( 100 , 0 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 50 , - 50 , 0 ) , app . Vector ( 50 , 50 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
network = routing_network . build_route_graph ( doc )
result = auto_routing . route_between_terminals ( doc , start , end )
self . assertEqual ( 5 , len ( network [ " nodes " ] ) )
self . assertEqual ( " network-dijkstra-v1 " , result [ " algorithm " ] )
self . assertIn ( ( 50.0 , 0.0 , 0.0 ) , [ ( point . x , point . y , point . z ) for point in result [ " points " ] ] )
def test_route_graph_connects_overlapping_collinear_carriers ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 120 , 0 , 0 ) )
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 0 , 0 ) , app . Vector ( 80 , 0 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 40 , 0 , 0 ) , app . Vector ( 120 , 0 , 0 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
network = routing_network . build_route_graph ( doc )
result = auto_routing . route_between_terminals ( doc , start , end )
self . assertEqual ( " network-dijkstra-v1 " , result [ " algorithm " ] )
self . assertIn ( ( 40.0 , 0.0 , 0.0 ) , [ ( point . x , point . y , point . z ) for point in result [ " points " ] ] )
self . assertIn ( ( 80.0 , 0.0 , 0.0 ) , [ ( point . x , point . y , point . z ) for point in result [ " points " ] ] )
self . assertGreaterEqual ( network [ " segment_count " ] , 3 )
def test_auto_route_prefers_wire_duct_over_auxiliary_range ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
@ -325,6 +419,277 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( " network-dijkstra-v1 " , result [ " algorithm " ] )
self . assertTrue ( any ( point . z == 4.0 for point in result [ " points " ] ) )
def test_auto_detect_support_surface_creates_routing_range ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
panel = doc . addObject ( " Part::Feature " , " MountingPlateA " )
panel . Label = " 安装板A "
panel . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , 0 , 5 , 0 , 100 ) )
cabinet = doc . addObject ( " Part::Feature " , " Cabinet " )
cabinet . Label = " 3D机柜 "
cabinet . Shape = FakeShape ( FakeBoundBox ( 0 , 300 , 0 , 80 , 0 , 400 ) )
duct = doc . addObject ( " Part::Feature " , " WireDuctA " )
duct . Label = " Wire Duct A "
duct . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , - 10 , 10 , 5 , 25 ) )
created = routing_network . create_surface_carriers_from_document (
doc ,
project_uuid = " project-1 " ,
spacing = 60.0 ,
offset = 5.0 ,
margin = 0.0 ,
)
created_again = routing_network . create_surface_carriers_from_document (
doc ,
project_uuid = " project-1 " ,
spacing = 60.0 ,
offset = 5.0 ,
margin = 0.0 ,
)
self . assertEqual ( 6 , len ( created ) )
self . assertEqual ( 0 , len ( created_again ) )
self . assertTrue ( all ( carrier . QetRouteCarrierKind == " RoutingRange " for carrier in created ) )
self . assertEqual ( " RoutingRange " , panel . QetRoutingSourceKind )
self . assertEqual ( " SupportSurface " , panel . QetRoutingObstacleMode )
self . assertFalse ( hasattr ( cabinet , " QetRoutingSourceKind " ) )
self . assertFalse ( hasattr ( duct , " QetRoutingSourceKind " ) )
def test_auto_route_can_use_auto_detected_support_surface ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 10 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 120 , 10 , 0 ) )
panel = doc . addObject ( " Part::Feature " , " MountingPlateA " )
panel . Label = " Mounting Plate A "
panel . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , 0 , 5 , 0 , 100 ) )
created = routing_network . create_surface_carriers_from_document (
doc ,
project_uuid = " project-1 " ,
spacing = 60.0 ,
offset = 5.0 ,
margin = 0.0 ,
)
result = auto_routing . route_between_terminals ( doc , start , end )
self . assertGreater ( len ( created ) , 0 )
self . assertEqual ( " network-dijkstra-v1 " , result [ " algorithm " ] )
self . assertEqual ( " Routed " , result [ " route_status " ] )
self . assertEqual ( 0 , result [ " collision_count " ] )
self . assertTrue ( any ( point . y == 10.0 for point in result [ " points " ] ) )
def test_generate_layout_space_auto_detects_support_surface ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
panel = doc . addObject ( " Part::Feature " , " MountingPlateA " )
panel . Label = " Mounting Plate A "
panel . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , 0 , 5 , 0 , 100 ) )
result = auto_routing_panel . AutoRoutingController ( ) . generate_layout_space ( )
self . assertGreater ( result [ " surface_carriers " ] , 0 )
self . assertEqual ( " document " , result [ " source_mode " ] )
def test_generate_routing_paths_uses_selected_wire_duct_entity ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
gui = sys . modules [ " FreeCADGui " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
duct = doc . addObject ( " Part::Feature " , " UnlabeledLongDuct " )
duct . Shape = FakeShape ( FakeBoundBox ( 0 , 160 , - 10 , 10 , 0 , 20 ) )
gui . Selection = types . SimpleNamespace (
getSelection = lambda : [ ] ,
getSelectionEx = lambda : [ FakeSelectionItem ( obj = duct ) ] ,
)
result = auto_routing_panel . AutoRoutingController ( ) . generate_routing_paths ( )
self . assertEqual ( 1 , result [ " wire_duct_carriers " ] )
self . assertEqual ( " selection " , result [ " source_mode " ] )
def test_generate_layout_space_uses_whole_document_not_selected_face_workflow ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
gui = sys . modules [ " FreeCADGui " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
panel = doc . addObject ( " Part::Feature " , " MountingPlateA " )
panel . Label = " Mounting Plate A "
panel . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , 0 , 5 , 0 , 100 ) )
gui . Selection = types . SimpleNamespace (
getSelection = lambda : [ ] ,
getSelectionEx = lambda : [ FakeSelectionItem ( obj = panel ) ] ,
)
result = auto_routing_panel . AutoRoutingController ( ) . generate_layout_space ( )
self . assertGreater ( result [ " surface_carriers " ] , 0 )
self . assertEqual ( " document " , result [ " source_mode " ] )
def test_generate_layout_space_adds_terminal_access_to_route_network ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
_terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
_terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 100 , 0 , 0 ) )
duct = doc . addObject ( " Part::Feature " , " WireDuctA " )
duct . Label = " Wire Duct A "
duct . Shape = FakeShape ( FakeBoundBox ( 0 , 100 , - 5 , 5 , 15 , 25 ) )
result = auto_routing_panel . AutoRoutingController ( ) . generate_layout_space ( )
result_again = auto_routing_panel . AutoRoutingController ( ) . generate_layout_space ( )
access_carriers = [
carrier
for carrier in routing_network . collect_route_carriers ( doc )
if getattr ( carrier , " QetRouteCarrierKind " , " " ) == " TerminalAccess "
]
self . assertEqual ( 1 , result [ " wire_duct_carriers " ] )
self . assertEqual ( 2 , result [ " terminal_access_carriers " ] )
self . assertEqual ( 0 , result_again [ " wire_duct_carriers " ] )
self . assertEqual ( 2 , result_again [ " terminal_access_carriers " ] )
self . assertEqual ( 2 , len ( access_carriers ) )
self . assertGreater ( result [ " network " ] [ " segments " ] , 0 )
def test_generate_layout_space_skips_far_terminal_access_to_protect_view_bbox ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
_terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
duct = doc . addObject ( " Part::Feature " , " WireDuctFar " )
duct . Label = " Wire Duct Far "
duct . Shape = FakeShape ( FakeBoundBox ( 5000 , 5100 , - 5 , 5 , 15 , 25 ) )
result = auto_routing_panel . AutoRoutingController ( ) . generate_layout_space ( )
self . assertEqual ( 1 , result [ " wire_duct_carriers " ] )
self . assertEqual ( 0 , result [ " terminal_access_carriers " ] )
def test_route_all_prepares_layout_space_like_one_click_routing ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , _auto_routing = _reload_modules ( )
auto_routing_panel = importlib . import_module ( " AutoRoutingPanel " )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
_terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
_terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 100 , 0 , 0 ) )
duct = doc . addObject ( " Part::Feature " , " WireDuctA " )
duct . Label = " Wire Duct A "
duct . Shape = FakeShape ( FakeBoundBox ( 0 , 100 , - 5 , 5 , 15 , 25 ) )
app . _qet_exchange_payload = {
" project_uuid " : " project-1 " ,
" wires " : [
{
" wire_id " : " wire-1 " ,
" start_terminal_uuid " : " terminal-start " ,
" end_terminal_uuid " : " terminal-end " ,
}
] ,
}
report = auto_routing_panel . AutoRoutingController ( ) . route_all ( )
self . assertEqual ( 1 , report [ " routed " ] )
self . assertEqual ( 1 , report [ " prepared_layout " ] [ " wire_duct_carriers " ] )
self . assertEqual ( 2 , report [ " prepared_layout " ] [ " terminal_access_carriers " ] )
def test_auto_route_rejects_far_network_entry_to_avoid_huge_render_bbox ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 100 , 0 , 0 ) )
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 5000 , 0 , 20 ) , app . Vector ( 5100 , 0 , 20 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
with self . assertRaises ( auto_routing . AutoRoutingError ) :
auto_routing . route_between_terminals ( doc , start , end )
def test_route_between_terminals_fails_without_network ( self ) :
_install_fake_freecad ( )
terminal_objects , wiring_objects , _routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
app . ActiveDocument = doc
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 120 , 30 , 0 ) )
with self . assertRaises ( auto_routing . AutoRoutingError ) :
auto_routing . route_between_terminals ( doc , start , end )
self . assertEqual ( 0 , len ( wiring_objects . iter_routed_wire_objects ( doc ) ) )
def test_surface_carrier_grid_uses_actual_rotated_face_plane ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
normal = app . Vector ( 0 , 1 , 1 )
vertices = [
app . Vector ( 0 , 0 , 0 ) ,
app . Vector ( 100 , 0 , 0 ) ,
app . Vector ( 0 , 50 , - 50 ) ,
app . Vector ( 100 , 50 , - 50 ) ,
]
face = FakeFace (
FakeBoundBox ( 0 , 100 , 0 , 50 , - 50 , 0 ) ,
normal ,
vertices = vertices ,
center = app . Vector ( 50 , 25 , - 25 ) ,
)
created = routing_network . create_surface_carriers_from_selection (
doc ,
[ FakeSelectionItem ( [ face ] ) ] ,
project_uuid = " project-1 " ,
spacing = 50.0 ,
offset = 10.0 ,
margin = 0.0 ,
)
self . assertGreater ( len ( created ) , 0 )
first_point = created [ 0 ] . Points [ 0 ]
for carrier in created :
for point in carrier . Points :
# The rotated face is y + z = 0; after a 10 mm normal offset,
# all generated points must stay on one parallel plane.
self . assertAlmostEqual ( first_point . y + first_point . z , point . y + point . z , places = 6 )
def test_route_path_creation_ignores_whole_solid_object_edges ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
@ -346,6 +711,34 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( [ ] , created )
def test_route_path_creation_projects_line_to_selected_face ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
face = FakeFace (
FakeBoundBox ( 0 , 100 , 0 , 100 , 0 , 0 ) ,
app . Vector ( 0 , 0 , 1 ) ,
)
draft_line = doc . addObject ( " Part::Feature " , " DraftLine " )
draft_line . Shape = FakeShape (
FakeBoundBox ( 10 , 90 , 10 , 90 , 25 , 35 ) ,
edges = [ FakeEdge ( app . Vector ( 10 , 10 , 25 ) , app . Vector ( 90 , 90 , 35 ) ) ] ,
)
created = routing_network . create_carriers_from_selection (
doc ,
[
FakeSelectionItem ( [ face ] ) ,
FakeSelectionItem ( obj = draft_line ) ,
] ,
project_uuid = " project-1 " ,
)
self . assertEqual ( 1 , len ( created ) )
self . assertEqual ( [ 2.0 , 2.0 ] , [ point . z for point in created [ 0 ] . Points ] )
def test_wire_duct_entity_generates_centerline_and_marks_source_pass_through ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
@ -367,6 +760,33 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( " PassThrough " , duct . QetRoutingObstacleMode )
self . assertEqual ( [ ( 20.0 , 0.0 , 15.0 ) , ( 100.0 , 0.0 , 15.0 ) ] , [ ( p . x , p . y , p . z ) for p in carrier . Points ] )
def test_auto_detect_wire_ducts_ignores_cabinet_models ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , _auto_routing = _reload_modules ( )
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
duct = doc . addObject ( " Part::Feature " , " WireDuctA " )
duct . Label = " 线槽A "
duct . Shape = FakeShape ( FakeBoundBox ( 0 , 120 , - 10 , 10 , 5 , 25 ) )
cabinet = doc . addObject ( " Part::Feature " , " Cabinet " )
cabinet . Label = " 3D机柜 "
cabinet . Shape = FakeShape ( FakeBoundBox ( 0 , 300 , 0 , 80 , 0 , 400 ) )
created = routing_network . create_wire_duct_carriers_from_document (
doc ,
project_uuid = " project-1 " ,
)
created_again = routing_network . create_wire_duct_carriers_from_document (
doc ,
project_uuid = " project-1 " ,
)
self . assertEqual ( 1 , len ( created ) )
self . assertEqual ( 0 , len ( created_again ) )
self . assertEqual ( " WireDuct " , created [ 0 ] . QetRouteCarrierKind )
self . assertEqual ( " PassThrough " , duct . QetRoutingObstacleMode )
self . assertFalse ( hasattr ( cabinet , " QetRoutingObstacleMode " ) )
def test_wire_duct_source_is_not_reported_as_collision ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
@ -390,6 +810,43 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( " Routed " , result [ " route_status " ] )
self . assertEqual ( 0 , result [ " collision_count " ] )
def test_auto_route_uses_alternate_carrier_to_avoid_obstacle ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
start = _terminal ( doc , terminal_objects , " TerminalStart " , " terminal-start " , app . Vector ( 0 , 0 , 0 ) )
end = _terminal ( doc , terminal_objects , " TerminalEnd " , " terminal-end " , app . Vector ( 100 , 0 , 0 ) )
routing_network . create_route_carrier (
doc ,
[ app . Vector ( 0 , 0 , 20 ) , app . Vector ( 100 , 0 , 20 ) ] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
routing_network . create_route_carrier (
doc ,
[
app . Vector ( 0 , 0 , 20 ) ,
app . Vector ( 0 , 50 , 20 ) ,
app . Vector ( 100 , 50 , 20 ) ,
app . Vector ( 100 , 0 , 20 ) ,
] ,
project_uuid = " project-1 " ,
kind = " WireDuct " ,
)
obstacle = doc . addObject ( " Part::Feature " , " CabinetObstacle " )
obstacle . Shape = FakeShape ( FakeBoundBox ( 40 , 60 , - 10 , 10 , 15 , 25 ) )
result = auto_routing . route_between_terminals ( doc , start , end )
self . assertEqual ( " network-dijkstra-v1 " , result [ " algorithm " ] )
self . assertEqual ( " Routed " , result [ " route_status " ] )
self . assertEqual ( 0 , result [ " collision_count " ] )
self . assertTrue ( result [ " network " ] [ " obstacle_aware " ] )
self . assertGreaterEqual ( result [ " network " ] [ " blocked_segments " ] , 1 )
self . assertIn ( 50.0 , [ point . y for point in result [ " points " ] ] )
def test_auto_route_marks_collision_warning_against_obstacle_bbox ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , auto_routing = _reload_modules ( )
@ -433,6 +890,179 @@ class AutoRoutingTest(unittest.TestCase):
self . assertEqual ( 0 , report [ " routed " ] )
self . assertEqual ( 1 , report [ " skipped_missing_terminal " ] )
self . assertEqual ( 1 , report [ " available_terminals " ] )
self . assertEqual ( 0 , report [ " local_terminals " ] )
self . assertEqual ( [ " terminal-missing " ] , report [ " missing_endpoint_uuids " ] )
self . assertEqual ( " terminal-start " , report [ " missing_endpoint_samples " ] [ 0 ] [ " start_terminal_uuid " ] )
self . assertTrue ( report [ " missing_endpoint_samples " ] [ 0 ] [ " start_found " ] )
self . assertFalse ( report [ " missing_endpoint_samples " ] [ 0 ] [ " end_found " ] )
def test_route_all_report_calls_out_local_unbound_terminals ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
terminal_objects . ensure_root_group ( doc , " project-1 " )
_terminal (
doc ,
terminal_objects ,
" LocalTerminal " ,
" local:instance-1:p1 " ,
app . Vector ( 0 , 0 , 0 ) ,
)
payload = {
" wires " : [
{
" wire_id " : " wire-1 " ,
" start_terminal_uuid " : " qet-terminal-start " ,
" end_terminal_uuid " : " qet-terminal-end " ,
}
]
}
report = auto_routing . route_all_from_payload ( doc , payload )
message = auto_routing . format_route_all_report ( report )
self . assertEqual ( 0 , report [ " routed " ] )
self . assertEqual ( 1 , report [ " available_terminals " ] )
self . assertEqual ( 1 , report [ " local_terminals " ] )
self . assertIn ( " 端子匹配失败 " , message )
self . assertIn ( " local: " , message )
def test_bind_wire_task_terminals_from_payload_does_not_create_wires ( self ) :
_install_fake_freecad ( )
terminal_objects , wiring_objects , _routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
root = terminal_objects . ensure_root_group ( doc , " project-1 " )
device = doc . addObject ( " App::DocumentObjectGroup " , " QETDevice_device_a " )
root . addObject ( device )
terminal_objects . ensure_string_property ( device , " QetElementUuid " , " QET Exchange " , " " , " device-a " )
terminal_objects . ensure_string_property ( device , " QetInstanceId " , " QET Exchange " , " " , " instance-a " )
terminal_objects . ensure_string_property ( device , " QetProjectUuid " , " QET Exchange " , " " , " project-1 " )
terminal_group = terminal_objects . ensure_terminal_group (
doc ,
device ,
project_uuid = " project-1 " ,
instance_id = " instance-a " ,
)
for slot_name , point in (
( " P1 " , app . Vector ( 0 , 0 , 0 ) ) ,
( " P2 " , app . Vector ( 100 , 0 , 0 ) ) ,
) :
terminal = terminal_objects . create_lcs_object (
doc ,
" QETTerminal_instance_a_ {0} " . format ( slot_name ) ,
placement = app . Placement ( point , app . Rotation ( ) ) ,
label = slot_name ,
)
terminal_group . addObject ( terminal )
terminal_objects . set_terminal_semantics (
terminal ,
" project-1 " ,
" device-a " ,
" local:instance-a: {0} " . format ( slot_name ) ,
" instance-a " ,
label = slot_name ,
slot_name = slot_name ,
)
payload = {
" project_uuid " : " project-1 " ,
" wires " : [
{
" wire_id " : " wire-1 " ,
" start_element_uuid " : " device-a " ,
" start_instance_id " : " instance-a " ,
" start_terminal_uuid " : " qet-terminal-p1 " ,
" start_terminal_display " : " P1 " ,
" end_element_uuid " : " device-a " ,
" end_instance_id " : " instance-a " ,
" end_terminal_uuid " : " qet-terminal-p2 " ,
" end_terminal_display " : " P2 " ,
}
] ,
}
report = auto_routing . bind_wire_task_terminals_from_payload ( doc , payload )
indexed = auto_routing . index_terminals ( doc )
self . assertEqual ( 2 , report [ " bound " ] )
self . assertEqual ( 0 , report [ " created " ] )
self . assertEqual ( 0 , report [ " local_terminals " ] )
self . assertEqual ( [ ] , wiring_objects . iter_routed_wire_objects ( doc ) )
self . assertEqual ( " qet " , indexed [ " qet-terminal-p1 " ] . QetTerminalBindingMode )
def test_route_all_rebinds_local_template_terminals_from_wire_endpoints ( self ) :
_install_fake_freecad ( )
terminal_objects , _wiring_objects , _routing_network , auto_routing = _reload_modules ( )
app = sys . modules [ " FreeCAD " ]
doc = FakeDocument ( )
root = terminal_objects . ensure_root_group ( doc , " project-1 " )
device = doc . addObject ( " App::DocumentObjectGroup " , " QETDevice_device_a " )
root . addObject ( device )
terminal_objects . ensure_string_property ( device , " QetElementUuid " , " QET Exchange " , " " , " device-a " )
terminal_objects . ensure_string_property ( device , " QetInstanceId " , " QET Exchange " , " " , " instance-a " )
terminal_objects . ensure_string_property ( device , " QetProjectUuid " , " QET Exchange " , " " , " project-1 " )
terminal_group = terminal_objects . ensure_terminal_group (
doc ,
device ,
project_uuid = " project-1 " ,
instance_id = " instance-a " ,
)
for slot_name , point in (
( " P1 " , app . Vector ( 0 , 0 , 0 ) ) ,
( " P2 " , app . Vector ( 100 , 0 , 0 ) ) ,
) :
terminal = terminal_objects . create_lcs_object (
doc ,
" QETTerminal_instance_a_ {0} " . format ( slot_name ) ,
placement = app . Placement ( point , app . Rotation ( ) ) ,
label = slot_name ,
)
terminal_group . addObject ( terminal )
terminal_objects . set_terminal_semantics (
terminal ,
" project-1 " ,
" device-a " ,
" local:instance-a: {0} " . format ( slot_name ) ,
" instance-a " ,
label = slot_name ,
slot_name = slot_name ,
)
payload = {
" project_uuid " : " project-1 " ,
" wires " : [
{
" wire_id " : " wire-1 " ,
" start_element_uuid " : " device-a " ,
" start_instance_id " : " instance-a " ,
" start_terminal_uuid " : " qet-terminal-p1 " ,
" start_terminal_display " : " P1 " ,
" end_element_uuid " : " device-a " ,
" end_instance_id " : " instance-a " ,
" end_terminal_uuid " : " qet-terminal-p2 " ,
" end_terminal_display " : " P2 " ,
}
] ,
}
report = auto_routing . route_all_from_payload (
doc ,
payload ,
options = { " allow_floating_fallback " : True } ,
)
indexed = auto_routing . index_terminals ( doc )
self . assertEqual ( 1 , report [ " routed " ] )
self . assertEqual ( 2 , report [ " auto_bound_terminals " ] )
self . assertEqual ( 0 , report [ " local_terminals " ] )
self . assertIn ( " qet-terminal-p1 " , indexed )
self . assertIn ( " qet-terminal-p2 " , indexed )
self . assertEqual ( " qet " , indexed [ " qet-terminal-p1 " ] . QetTerminalBindingMode )
def test_clear_route_carriers_keeps_routed_wires ( self ) :
_install_fake_freecad ( )