Skip to content

Commit 91d94e6

Browse files
Copilotlachlangrose
andcommitted
feat: export layers and add offline runner script
Co-authored-by: lachlangrose <7371904+lachlangrose@users.noreply.github.com>
1 parent 9148640 commit 91d94e6

File tree

5 files changed

+214
-16
lines changed

5 files changed

+214
-16
lines changed

loopstructural/debug_manager.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ def log_params(self, context_label: str, params: Any):
157157
message=f"[map2loop] Params saved to: {file_path}",
158158
log_level=0,
159159
)
160+
self._ensure_runner_script()
160161
except Exception as err:
161162
self.plugin.log(
162163
message=f"[map2loop] Failed to save params for {context_label}: {err}",
@@ -183,3 +184,78 @@ def save_debug_file(self, filename: str, content_bytes: bytes):
183184
log_level=2,
184185
)
185186
return None
187+
188+
def _ensure_runner_script(self):
189+
"""Create a reusable runner script in the debug directory."""
190+
try:
191+
debug_dir = self.get_effective_debug_dir()
192+
script_path = debug_dir / "run_map2loop.py"
193+
if script_path.exists():
194+
return
195+
script_content = """#!/usr/bin/env python3
196+
import argparse
197+
import json
198+
from pathlib import Path
199+
200+
import geopandas as gpd
201+
202+
from loopstructural.main import m2l_api
203+
204+
205+
def load_layer(layer_info):
206+
if isinstance(layer_info, dict):
207+
export_path = layer_info.get("export_path")
208+
if export_path:
209+
return gpd.read_file(export_path)
210+
return layer_info
211+
212+
213+
def load_params(path):
214+
params = json.loads(Path(path).read_text())
215+
# convert exported layers to GeoDataFrames
216+
for key, value in list(params.items()):
217+
params[key] = load_layer(value)
218+
return params
219+
220+
221+
def run(params):
222+
if "sampler_type" in params:
223+
result = m2l_api.sample_contacts(**params)
224+
print("Sampler result:", result)
225+
elif "sorting_algorithm" in params:
226+
result = m2l_api.sort_stratigraphic_column(**params)
227+
print("Sorter result:", result)
228+
elif "calculator_type" in params:
229+
result = m2l_api.calculate_thickness(**params)
230+
print("Thickness result:", result)
231+
elif "geology_layer" in params and "unit_name_field" in params:
232+
result = m2l_api.extract_basal_contacts(**params)
233+
print("Basal contacts result:", result)
234+
else:
235+
print("Unknown params shape; inspect manually:", params.keys())
236+
237+
238+
def main():
239+
parser = argparse.ArgumentParser()
240+
parser.add_argument(
241+
"params",
242+
nargs="?",
243+
default=None,
244+
help="Path to params JSON (defaults to first *_params.json in this folder)",
245+
)
246+
args = parser.parse_args()
247+
base = Path(__file__).parent
248+
params_path = Path(args.params) if args.params else next(base.glob("*_params.json"))
249+
params = load_params(params_path)
250+
run(params)
251+
252+
253+
if __name__ == "__main__":
254+
main()
255+
"""
256+
script_path.write_text(script_content, encoding="utf-8")
257+
except Exception as err:
258+
self.plugin.log(
259+
message=f"[map2loop] Failed to create runner script: {err}",
260+
log_level=1,
261+
)

loopstructural/gui/map2loop_tools/basal_contacts_widget.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from PyQt5.QtWidgets import QMessageBox, QWidget
66
from qgis.PyQt import uic
7+
from qgis.core import QgsProject, QgsVectorFileWriter
78

89
from ...main.helpers import ColumnMatcher, get_layer_names
910
from ...main.m2l_api import extract_basal_contacts
@@ -67,22 +68,50 @@ def set_debug_manager(self, debug_manager):
6768
"""Attach a debug manager instance."""
6869
self._debug = debug_manager
6970

70-
def _serialize_layer(self, layer):
71+
def _export_layer_for_debug(self, layer, name_prefix: str):
72+
if not (self._debug and self._debug.is_debug()):
73+
return None
7174
try:
75+
debug_dir = self._debug.get_effective_debug_dir()
76+
out_path = debug_dir / f"{name_prefix}.gpkg"
77+
options = QgsVectorFileWriter.SaveVectorOptions()
78+
options.driverName = "GPKG"
79+
options.layerName = layer.name()
80+
res = QgsVectorFileWriter.writeAsVectorFormatV3(
81+
layer,
82+
str(out_path),
83+
QgsProject.instance().transformContext(),
84+
options,
85+
)
86+
if res[0] == QgsVectorFileWriter.NoError:
87+
return str(out_path)
88+
except Exception as err:
89+
self._debug.plugin.log(
90+
message=f"[map2loop] Failed to export layer '{name_prefix}': {err}",
91+
log_level=2,
92+
)
93+
return None
94+
95+
def _serialize_layer(self, layer, name_prefix: str):
96+
try:
97+
export_path = self._export_layer_for_debug(layer, name_prefix)
7298
return {
7399
"name": layer.name(),
74100
"id": layer.id(),
75101
"provider": layer.providerType() if hasattr(layer, "providerType") else None,
76102
"source": layer.source() if hasattr(layer, "source") else None,
103+
"export_path": export_path,
77104
}
78105
except Exception:
79106
return str(layer)
80107

81-
def _serialize_params_for_logging(self, params):
108+
def _serialize_params_for_logging(self, params, context_label: str):
82109
serialized = {}
83110
for key, value in params.items():
84111
if hasattr(value, "source") or hasattr(value, "id"):
85-
serialized[key] = self._serialize_layer(value)
112+
serialized[key] = self._serialize_layer(
113+
value, f"{context_label}_{key}"
114+
)
86115
else:
87116
serialized[key] = value
88117
return serialized
@@ -92,7 +121,9 @@ def _log_params(self, context_label: str):
92121
try:
93122
self._debug.log_params(
94123
context_label=context_label,
95-
params=self._serialize_params_for_logging(self.get_parameters()),
124+
params=self._serialize_params_for_logging(
125+
self.get_parameters(), context_label
126+
),
96127
)
97128
except Exception:
98129
pass

loopstructural/gui/map2loop_tools/sampler_widget.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from PyQt5.QtWidgets import QMessageBox, QWidget
66
from qgis.PyQt import uic
7+
from qgis.core import QgsProject, QgsVectorFileWriter
78

89
from loopstructural.toolbelt.preferences import PlgOptionsManager
910

@@ -60,22 +61,50 @@ def set_debug_manager(self, debug_manager):
6061
"""Attach a debug manager instance."""
6162
self._debug = debug_manager
6263

63-
def _serialize_layer(self, layer):
64+
def _export_layer_for_debug(self, layer, name_prefix: str):
65+
if not (self._debug and self._debug.is_debug()):
66+
return None
6467
try:
68+
debug_dir = self._debug.get_effective_debug_dir()
69+
out_path = debug_dir / f"{name_prefix}.gpkg"
70+
options = QgsVectorFileWriter.SaveVectorOptions()
71+
options.driverName = "GPKG"
72+
options.layerName = layer.name()
73+
res = QgsVectorFileWriter.writeAsVectorFormatV3(
74+
layer,
75+
str(out_path),
76+
QgsProject.instance().transformContext(),
77+
options,
78+
)
79+
if res[0] == QgsVectorFileWriter.NoError:
80+
return str(out_path)
81+
except Exception as err:
82+
self._debug.plugin.log(
83+
message=f"[map2loop] Failed to export layer '{name_prefix}': {err}",
84+
log_level=2,
85+
)
86+
return None
87+
88+
def _serialize_layer(self, layer, name_prefix: str):
89+
try:
90+
export_path = self._export_layer_for_debug(layer, name_prefix)
6591
return {
6692
"name": layer.name(),
6793
"id": layer.id(),
6894
"provider": layer.providerType() if hasattr(layer, "providerType") else None,
6995
"source": layer.source() if hasattr(layer, "source") else None,
96+
"export_path": export_path,
7097
}
7198
except Exception:
7299
return str(layer)
73100

74-
def _serialize_params_for_logging(self, params):
101+
def _serialize_params_for_logging(self, params, context_label: str):
75102
serialized = {}
76103
for key, value in params.items():
77104
if hasattr(value, "source") or hasattr(value, "id"):
78-
serialized[key] = self._serialize_layer(value)
105+
serialized[key] = self._serialize_layer(
106+
value, f"{context_label}_{key}"
107+
)
79108
else:
80109
serialized[key] = value
81110
return serialized
@@ -85,7 +114,9 @@ def _log_params(self, context_label: str):
85114
try:
86115
self._debug.log_params(
87116
context_label=context_label,
88-
params=self._serialize_params_for_logging(self.get_parameters()),
117+
params=self._serialize_params_for_logging(
118+
self.get_parameters(), context_label
119+
),
89120
)
90121
except Exception:
91122
pass

loopstructural/gui/map2loop_tools/sorter_widget.py

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from PyQt5.QtWidgets import QMessageBox, QWidget
66
from qgis.core import QgsRasterLayer
77
from qgis.PyQt import uic
8+
from qgis.core import QgsProject, QgsVectorFileWriter
89

910
from loopstructural.main.helpers import get_layer_names
1011
from loopstructural.main.m2l_api import PARAMETERS_DICTIONARY, SORTER_LIST
@@ -81,22 +82,50 @@ def set_debug_manager(self, debug_manager):
8182
"""Attach a debug manager instance."""
8283
self._debug = debug_manager
8384

84-
def _serialize_layer(self, layer):
85+
def _export_layer_for_debug(self, layer, name_prefix: str):
86+
if not (self._debug and self._debug.is_debug()):
87+
return None
8588
try:
89+
debug_dir = self._debug.get_effective_debug_dir()
90+
out_path = debug_dir / f"{name_prefix}.gpkg"
91+
options = QgsVectorFileWriter.SaveVectorOptions()
92+
options.driverName = "GPKG"
93+
options.layerName = layer.name()
94+
res = QgsVectorFileWriter.writeAsVectorFormatV3(
95+
layer,
96+
str(out_path),
97+
QgsProject.instance().transformContext(),
98+
options,
99+
)
100+
if res[0] == QgsVectorFileWriter.NoError:
101+
return str(out_path)
102+
except Exception as err:
103+
self._debug.plugin.log(
104+
message=f"[map2loop] Failed to export layer '{name_prefix}': {err}",
105+
log_level=2,
106+
)
107+
return None
108+
109+
def _serialize_layer(self, layer, name_prefix: str):
110+
try:
111+
export_path = self._export_layer_for_debug(layer, name_prefix)
86112
return {
87113
"name": layer.name(),
88114
"id": layer.id(),
89115
"provider": layer.providerType() if hasattr(layer, "providerType") else None,
90116
"source": layer.source() if hasattr(layer, "source") else None,
117+
"export_path": export_path,
91118
}
92119
except Exception:
93120
return str(layer)
94121

95-
def _serialize_params_for_logging(self, params):
122+
def _serialize_params_for_logging(self, params, context_label: str):
96123
serialized = {}
97124
for key, value in params.items():
98125
if hasattr(value, "source") or hasattr(value, "id"):
99-
serialized[key] = self._serialize_layer(value)
126+
serialized[key] = self._serialize_layer(
127+
value, f"{context_label}_{key}"
128+
)
100129
else:
101130
serialized[key] = value
102131
return serialized
@@ -106,7 +135,9 @@ def _log_params(self, context_label: str):
106135
try:
107136
self._debug.log_params(
108137
context_label=context_label,
109-
params=self._serialize_params_for_logging(self.get_parameters()),
138+
params=self._serialize_params_for_logging(
139+
self.get_parameters(), context_label
140+
),
110141
)
111142
except Exception:
112143
pass

loopstructural/gui/map2loop_tools/thickness_calculator_widget.py

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from PyQt5.QtWidgets import QLabel, QMessageBox, QWidget
66
from qgis.PyQt import uic
7+
from qgis.core import QgsProject, QgsVectorFileWriter
78

89
from loopstructural.toolbelt.preferences import PlgOptionsManager
910

@@ -73,22 +74,50 @@ def set_debug_manager(self, debug_manager):
7374
"""Attach a debug manager instance."""
7475
self._debug = debug_manager
7576

76-
def _serialize_layer(self, layer):
77+
def _export_layer_for_debug(self, layer, name_prefix: str):
78+
if not (self._debug and self._debug.is_debug()):
79+
return None
7780
try:
81+
debug_dir = self._debug.get_effective_debug_dir()
82+
out_path = debug_dir / f"{name_prefix}.gpkg"
83+
options = QgsVectorFileWriter.SaveVectorOptions()
84+
options.driverName = "GPKG"
85+
options.layerName = layer.name()
86+
res = QgsVectorFileWriter.writeAsVectorFormatV3(
87+
layer,
88+
str(out_path),
89+
QgsProject.instance().transformContext(),
90+
options,
91+
)
92+
if res[0] == QgsVectorFileWriter.NoError:
93+
return str(out_path)
94+
except Exception as err:
95+
self._debug.plugin.log(
96+
message=f"[map2loop] Failed to export layer '{name_prefix}': {err}",
97+
log_level=2,
98+
)
99+
return None
100+
101+
def _serialize_layer(self, layer, name_prefix: str):
102+
try:
103+
export_path = self._export_layer_for_debug(layer, name_prefix)
78104
return {
79105
"name": layer.name(),
80106
"id": layer.id(),
81107
"provider": layer.providerType() if hasattr(layer, "providerType") else None,
82108
"source": layer.source() if hasattr(layer, "source") else None,
109+
"export_path": export_path,
83110
}
84111
except Exception:
85112
return str(layer)
86113

87-
def _serialize_params_for_logging(self, params):
114+
def _serialize_params_for_logging(self, params, context_label: str):
88115
serialized = {}
89116
for key, value in params.items():
90117
if hasattr(value, "source") or hasattr(value, "id"):
91-
serialized[key] = self._serialize_layer(value)
118+
serialized[key] = self._serialize_layer(
119+
value, f"{context_label}_{key}"
120+
)
92121
else:
93122
serialized[key] = value
94123
return serialized
@@ -97,7 +126,7 @@ def _log_params(self, context_label: str, params=None):
97126
if getattr(self, "_debug", None):
98127
try:
99128
payload = params if params is not None else self.get_parameters()
100-
payload = self._serialize_params_for_logging(payload)
129+
payload = self._serialize_params_for_logging(payload, context_label)
101130
self._debug.log_params(context_label=context_label, params=payload)
102131
except Exception:
103132
pass

0 commit comments

Comments
 (0)