Skip to content

Commit 78f8bda

Browse files
authored
Merge pull request #30 from srl-labs/fixed_pos
Fixed pos
2 parents fc66088 + ecbb9d0 commit 78f8bda

9 files changed

+623
-294
lines changed

clab2drawio.py

+67-19
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
from core.interactivity.interactive_manager import InteractiveManager
1212
from core.diagram.diagram_builder import DiagramBuilder
1313
from core.logging_config import configure_logging
14-
from prompt_toolkit.shortcuts import checkboxlist_dialog, yes_no_dialog
1514
import os
1615
import sys
1716
import logging
@@ -48,7 +47,7 @@ def main(
4847
loader = TopologyLoader()
4948
try:
5049
containerlab_data = loader.load(input_file)
51-
except TopologyLoaderError as e:
50+
except TopologyLoaderError:
5251
logger.error("Failed to load topology. Exiting.")
5352
sys.exit(1)
5453

@@ -75,7 +74,7 @@ def main(
7574

7675
try:
7776
styles = theme_manager.load_theme()
78-
except ThemeManagerError as e:
77+
except ThemeManagerError:
7978
logger.error("Failed to load theme. Exiting.")
8079
sys.exit(1)
8180
logger.debug("Theme loaded successfully, building diagram...")
@@ -84,7 +83,6 @@ def main(
8483
diagram.layout = layout
8584
diagram.styles = styles
8685

87-
nodes_from_clab = containerlab_data["topology"]["nodes"]
8886
# Determine the prefix
8987
prefix = containerlab_data.get("prefix", "clab")
9088
lab_name = containerlab_data.get("name", "")
@@ -106,14 +104,13 @@ def main(
106104
available_themes = theme_manager.list_available_themes()
107105
available_themes.sort()
108106

109-
110107
if interactive:
111108
logger.debug("Entering interactive mode...")
112109
processor = YAMLProcessor()
113110
interactor = InteractiveManager()
114111
interactor.run_interactive_mode(
115112
diagram=diagram,
116-
available_themes=available_themes,
113+
available_themes=available_themes,
117114
icon_to_group_mapping=styles["icon_to_group_mapping"],
118115
containerlab_data=containerlab_data,
119116
output_file=input_file,
@@ -123,30 +120,81 @@ def main(
123120
)
124121
# After wizard finishes:
125122
layout = interactor.final_summary.get("Layout", layout)
126-
chosen_theme = interactor.final_summary.get("Theme")
123+
chosen_theme = interactor.final_summary.get("Theme")
127124

128125
if chosen_theme:
129126
# 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+
)
131130
if os.path.exists(new_theme_path):
132131
logger.debug(f"Loading user-chosen theme: {chosen_theme}")
133132
theme_manager.config_path = new_theme_path
134133
styles = theme_manager.load_theme()
135134
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+
)
141147

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+
)
145182
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()
147195

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)
150198

151199
# Calculate the diagram size based on the positions of the nodes
152200
min_x = min(node.pos_x for node in nodes.values())

0 commit comments

Comments
 (0)