11
11
from core .interactivity .interactive_manager import InteractiveManager
12
12
from core .diagram .diagram_builder import DiagramBuilder
13
13
from core .logging_config import configure_logging
14
- from prompt_toolkit .shortcuts import checkboxlist_dialog , yes_no_dialog
15
14
import os
16
15
import sys
17
16
import logging
@@ -48,7 +47,7 @@ def main(
48
47
loader = TopologyLoader ()
49
48
try :
50
49
containerlab_data = loader .load (input_file )
51
- except TopologyLoaderError as e :
50
+ except TopologyLoaderError :
52
51
logger .error ("Failed to load topology. Exiting." )
53
52
sys .exit (1 )
54
53
@@ -75,7 +74,7 @@ def main(
75
74
76
75
try :
77
76
styles = theme_manager .load_theme ()
78
- except ThemeManagerError as e :
77
+ except ThemeManagerError :
79
78
logger .error ("Failed to load theme. Exiting." )
80
79
sys .exit (1 )
81
80
logger .debug ("Theme loaded successfully, building diagram..." )
@@ -84,7 +83,6 @@ def main(
84
83
diagram .layout = layout
85
84
diagram .styles = styles
86
85
87
- nodes_from_clab = containerlab_data ["topology" ]["nodes" ]
88
86
# Determine the prefix
89
87
prefix = containerlab_data .get ("prefix" , "clab" )
90
88
lab_name = containerlab_data .get ("name" , "" )
@@ -106,14 +104,13 @@ def main(
106
104
available_themes = theme_manager .list_available_themes ()
107
105
available_themes .sort ()
108
106
109
-
110
107
if interactive :
111
108
logger .debug ("Entering interactive mode..." )
112
109
processor = YAMLProcessor ()
113
110
interactor = InteractiveManager ()
114
111
interactor .run_interactive_mode (
115
112
diagram = diagram ,
116
- available_themes = available_themes ,
113
+ available_themes = available_themes ,
117
114
icon_to_group_mapping = styles ["icon_to_group_mapping" ],
118
115
containerlab_data = containerlab_data ,
119
116
output_file = input_file ,
@@ -123,30 +120,81 @@ def main(
123
120
)
124
121
# After wizard finishes:
125
122
layout = interactor .final_summary .get ("Layout" , layout )
126
- chosen_theme = interactor .final_summary .get ("Theme" )
123
+ chosen_theme = interactor .final_summary .get ("Theme" )
127
124
128
125
if chosen_theme :
129
126
# Load that theme or switch to it
130
- new_theme_path = os .path .join (os .path .dirname (theme_path ), f"{ chosen_theme } .yaml" )
127
+ new_theme_path = os .path .join (
128
+ os .path .dirname (theme_path ), f"{ chosen_theme } .yaml"
129
+ )
131
130
if os .path .exists (new_theme_path ):
132
131
logger .debug (f"Loading user-chosen theme: { chosen_theme } " )
133
132
theme_manager .config_path = new_theme_path
134
133
styles = theme_manager .load_theme ()
135
134
else :
136
- logger .warning (f"User chose theme '{ chosen_theme } ' but no file found. Keeping old theme." )
137
-
138
- logger .debug ("Assigning graph levels..." )
139
- graph_manager = GraphLevelManager ()
140
- graph_manager .assign_graphlevels (diagram , verbose = False )
135
+ logger .warning (
136
+ f"User chose theme '{ chosen_theme } ' but no file found. Keeping old theme."
137
+ )
138
+
139
+ # Check if any nodes have predefined positions from the YAML
140
+ has_predefined_positions = any (
141
+ node .pos_x is not None
142
+ and node .pos_y is not None
143
+ and str (node .pos_x ).strip () != ""
144
+ and str (node .pos_y ).strip () != ""
145
+ for node in nodes .values ()
146
+ )
141
147
142
- # Choose layout based on layout argument
143
- if layout == "vertical" :
144
- layout_manager = VerticalLayout ()
148
+ if has_predefined_positions :
149
+ logger .debug ("Using predefined positions from YAML file with scaling..." )
150
+
151
+ # Scale factor to ensure adequate spacing between nodes
152
+ x_scale = (styles .get ("padding_x" , 150 ) / 100 ) * 1.5
153
+ y_scale = (styles .get ("padding_y" , 150 ) / 100 ) * 1.5
154
+
155
+ # Convert string positions to float and apply scaling
156
+ for node in nodes .values ():
157
+ try :
158
+ if (
159
+ node .pos_x is not None
160
+ and node .pos_y is not None
161
+ and str (node .pos_x ).strip () != ""
162
+ and str (node .pos_y ).strip () != ""
163
+ ):
164
+ node .pos_x = int (node .pos_x ) * x_scale
165
+ node .pos_y = int (node .pos_y ) * y_scale
166
+ except (ValueError , TypeError ):
167
+ logger .debug (
168
+ f"Could not convert position for node { node .name } , will use layout position"
169
+ )
170
+ node .pos_x = None
171
+ node .pos_y = None
172
+
173
+ # When fixed positions are available, we still assign graph levels for connectivity purposes
174
+ # but instruct it to skip warnings and not override positions
175
+ logger .debug (
176
+ "Using predefined positions - graph levels will only be used for connectivity"
177
+ )
178
+ graph_manager = GraphLevelManager ()
179
+ graph_manager .assign_graphlevels (
180
+ diagram , verbose = False , skip_warnings = True , respect_fixed_positions = True
181
+ )
145
182
else :
146
- layout_manager = HorizontalLayout ()
183
+ # No fixed positions, proceed with normal graph level assignment
184
+ logger .debug ("No predefined positions found - assigning graph levels normally" )
185
+ graph_manager = GraphLevelManager ()
186
+ graph_manager .assign_graphlevels (diagram , verbose = False )
187
+
188
+ # Only apply layout manager if we don't have predefined positions
189
+ if not has_predefined_positions :
190
+ # Choose layout based on layout argument
191
+ if layout == "vertical" :
192
+ layout_manager = VerticalLayout ()
193
+ else :
194
+ layout_manager = HorizontalLayout ()
147
195
148
- logger .debug (f"Applying { layout } layout..." )
149
- layout_manager .apply (diagram , verbose = verbose )
196
+ logger .debug (f"Applying { layout } layout..." )
197
+ layout_manager .apply (diagram , verbose = verbose )
150
198
151
199
# Calculate the diagram size based on the positions of the nodes
152
200
min_x = min (node .pos_x for node in nodes .values ())
0 commit comments