Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ae425a0
Initial plan
Copilot Dec 16, 2025
fc7b8eb
feat: add debug manager and directory setting
Copilot Dec 16, 2025
dd79214
chore: refine debug handling and options
Copilot Dec 16, 2025
ff55f3f
chore: address review follow ups
Copilot Dec 16, 2025
351a96a
fix: convert unit_name_field to 'UNITNAME'
lachlangrose Dec 17, 2025
2ec3fcd
fix: updating unload to prevent error when missing dock widgets
lachlangrose Dec 17, 2025
24cc539
fix: rename unit_name_column to unit_name_field
lachlangrose Dec 17, 2025
56ceb77
fix: update stratigraphic column with calculated thicknesses
lachlangrose Dec 17, 2025
5fba7c8
fix: update stratigraphic unit to prevent missing widget error
lachlangrose Dec 17, 2025
4a0ba19
style: formatting
lachlangrose Dec 17, 2025
752f217
Merge branch 'dev-0.1.12' into copilot/add-structured-logging-feature
lachlangrose Dec 17, 2025
9148640
chore: log layer sources in debug params
Copilot Dec 17, 2025
91d94e6
feat: export layers and add offline runner script
Copilot Dec 17, 2025
e590b19
fix: give debug manager the logger
lachlangrose Dec 18, 2025
c487dec
fix: add generic exporter for m2l objects
lachlangrose Dec 18, 2025
c74aedc
fix: pass debug manager to api for exporting packages
lachlangrose Dec 18, 2025
118aecc
move export to debug manager
lachlangrose Jan 6, 2026
e74bcc7
adding rebuild with debounce when geometry properties are changed
lachlangrose Jan 7, 2026
75ff03e
call update feature when rebuild is required
lachlangrose Jan 8, 2026
7208085
adding progress bar when model update is required
lachlangrose Jan 8, 2026
440fd2e
keep track of source feature for meshes
lachlangrose Jan 8, 2026
3a22d1a
update feature in viewer when it changes
lachlangrose Jan 14, 2026
52cccd1
fix: debug mode changes logging
lachlangrose Jan 14, 2026
37e0319
adding copilot fixes
lachlangrose Jan 14, 2026
cc9a0de
Merge branch 'dev-0.1.12' into fault-feature-panel-upgrade
lachlangrose Jan 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
from PyQt5.QtCore import Qt
from PyQt5.QtCore import QObject, Qt, QThread, pyqtSignal, pyqtSlot
from PyQt5.QtWidgets import (
QMenu,
QMessageBox,
QProgressDialog,
QPushButton,
QSplitter,
QTreeWidget,
Expand All@@ -9,26 +11,47 @@
QWidget,
)

from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)
from LoopStructural.modelling.features import FeatureType

# Import the AddFaultDialog
from .add_fault_dialog import AddFaultDialog
from .add_foliation_dialog import AddFoliationDialog
from .add_unconformity_dialog import AddUnconformityDialog
from .feature_details_panel import (
FaultFeatureDetailsPanel,
FoldedFeatureDetailsPanel,
FoliationFeatureDetailsPanel,
StructuralFrameFeatureDetailsPanel,
)


class GeologicalModelTab(QWidget):
def __init__(self, parent=None, *, model_manager=None, data_manager=None):
super().__init__(parent)
self.model_manager = model_manager
self.data_manager = data_manager
self.model_manager.observers.append(self.update_feature_list)
# Register update observer using Observable API if available
if self.model_manager is not None:
try:
# listen for model-level updates
self._disp_model = self.model_manager.attach(
self.update_feature_list, 'model_updated'
)
# show progress when model updates start/finish (covers indirect calls)
self._disp_update_start = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_started(),
'model_update_started',
)
self._disp_update_finish = self.model_manager.attach(
lambda _obs, _ev, *a, **k: self._on_model_update_finished(),
'model_update_finished',
)
except Exception:
# fallback to legacy list
try:
self.model_manager.observers.append(self.update_feature_list)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# If the model manager does not expose a legacy 'observers' list or
# another non-critical error occurs here, ignore it and continue
# without registering the legacy model-level observer.

Copilot uses AI. Check for mistakes.
pass
# Main layout
mainLayout = QVBoxLayout(self)

Expand DownExpand Up@@ -75,6 +98,10 @@ def __init__(self, parent=None, *, model_manager=None, data_manager=None):
# Connect feature selection to update details panel
self.featureList.itemClicked.connect(self.on_feature_selected)

# thread handle to keep worker alive while running
self._model_update_thread = None
self._model_update_worker = None

def show_add_feature_menu(self, *args):
menu = QMenu(self)
add_fault = menu.addAction("Add Fault")
Expand All@@ -89,6 +116,7 @@ def show_add_feature_menu(self, *args):
self.open_add_foliation_dialog()
elif action == add_unconformity:
self.open_add_unconformity_dialog()

def open_add_fault_dialog(self):
dialog = AddFaultDialog(self)
if dialog.exec_() == dialog.Accepted:
Expand All@@ -102,16 +130,95 @@ def open_add_foliation_dialog(self):
)
if dialog.exec_() == dialog.Accepted:
pass

def open_add_unconformity_dialog(self):
dialog = AddUnconformityDialog(
self, data_manager=self.data_manager, model_manager=self.model_manager
)
if dialog.exec_() == dialog.Accepted:
pass

def initialize_model(self):
self.model_manager.update_model()
# Run update_model in a background thread to avoid blocking the UI.
if not self.model_manager:
return

# create progress dialog (indeterminate)
progress = QProgressDialog("Updating geological model...", "Cancel", 0, 0, self)
progress.setWindowModality(Qt.ApplicationModal)
progress.setWindowTitle("Updating Model")
progress.setCancelButton(None)
progress.setMinimumDuration(0)
progress.show()

# worker and thread
thread = QThread(self)
worker = _ModelUpdateWorker(self.model_manager)
worker.moveToThread(thread)

# When thread starts run worker.run
thread.started.connect(worker.run)

# on worker finished, notify observers on main thread and cleanup
def _on_finished():
try:
# notify observers now on main thread
try:
self.model_manager.notify('model_updated')
except Exception:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
finally:
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
# cleanup worker/thread
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_error(tb):
try:
progress.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
QMessageBox.critical(
self,
"Model update failed",
f"An error occurred while updating the model:\n{tb}",
)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Suggested change
exceptException:
exceptException:
# Best-effort display of error dialog; ignore failures in QMessageBox.

Copilot uses AI. Check for mistakes.
pass
# ensure thread cleanup
try:
worker.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
thread.quit()
thread.wait(2000)
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

worker.finished.connect(_on_finished)
worker.error.connect(_on_error)
thread.finished.connect(thread.deleteLater)
self._model_update_thread = thread
self._model_update_worker = worker
thread.start()

def update_feature_list(self):
def update_feature_list(self, *args, **kwargs):
self.featureList.clear() # Clear the feature list before populating it
for feature in self.model_manager.features():
if feature.name.startswith("__"):
Expand DownExpand Up@@ -153,6 +260,43 @@ def on_feature_selected(self, item):
splitter.widget(1).deleteLater() # Remove the existing widget
splitter.addWidget(self.featureDetailsPanel) # Add the new widget

def _on_model_update_started(self):
"""Show a non-blocking indeterminate progress dialog for model updates.

This method is invoked via the Observable notifications and ensures the
user sees that a background or foreground update is in progress.
"""
print("Model update started - showing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is None:
self._progress_dialog = QProgressDialog(
"Updating geological model...", None, 0, 0, self
)
self._progress_dialog.setWindowTitle("Updating Model")
self._progress_dialog.setWindowModality(Qt.ApplicationModal)
self._progress_dialog.setCancelButton(None)
self._progress_dialog.setMinimumDuration(0)
self._progress_dialog.show()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def _on_model_update_finished(self):
"""Close the progress dialog shown for model updates."""
print("Model update finished - closing progress dialog")

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Debug print statement left in production code. This should use proper logging instead of print().

Copilot uses AI. Check for mistakes.
try:
if getattr(self, '_progress_dialog', None) is not None:
try:
self._progress_dialog.close()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
try:
self._progress_dialog.deleteLater()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
self._progress_dialog = None
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass

def show_feature_context_menu(self, pos):
# Show context menu only for items
item = self.featureList.itemAt(pos)
Expand DownExpand Up@@ -197,10 +341,50 @@ def delete_feature(self, item):

# Notify observers to refresh UI
try:
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:
pass
# Prefer notify API
try:
self.model_manager.notify('model_updated')
except Exception:
# fallback to legacy observers list
for obs in getattr(self.model_manager, 'observers', []):
try:
obs()
except Exception:

CopilotAIJan 14, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'except' clause does nothing but pass and there is no explanatory comment.

Copilot uses AI. Check for mistakes.
pass
except Exception:
pass


class _ModelUpdateWorker(QObject):
"""Worker that runs model_manager.update_model in a background thread.

Emits finished when done and error with a string if an exception occurs.
"""

finished = pyqtSignal()
error = pyqtSignal(str)

def __init__(self, model_manager):
super().__init__()
self.model_manager = model_manager

@pyqtSlot()
def run(self):
try:
# perform the expensive update
# run update without notifying observers from the background thread
try:
self.model_manager.update_model(notify_observers=False)
except TypeError:
# fallback if update_model signature not available
self.model_manager.update_model()
except Exception as e:
try:
import traceback

tb = traceback.format_exc()
except Exception:
tb = str(e)
self.error.emit(tb)
finally:
self.finished.emit()
Loading