diff --git a/CHANGELOG.md b/CHANGELOG.md index 60b3527..180849b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +- Disable plugin main dialog by default +- Add button for opening output dir upon successful completion +- Drop support for point layers in the main plugin dialog +- Fix node connection method always being set to 'edge distance' when running + via the main plugin dialog +- Start autogenerated node ids from one rather than zero + ## [2.0.0-rc3] - 2024-07-17 diff --git a/src/qgis_conefor/coneforinputsprocessor.py b/src/qgis_conefor/coneforinputsprocessor.py index 3de78bf..7dda013 100644 --- a/src/qgis_conefor/coneforinputsprocessor.py +++ b/src/qgis_conefor/coneforinputsprocessor.py @@ -30,6 +30,10 @@ def get_numeric_attribute( f"attribute {attribute_name!r} does not exist") +def autogenerate_feature_id(feature: qgis.core.QgsFeature) -> int: + return feature.id() + 1 + + def get_output_path(tentative_path: Path) -> Path: """ Rename the output name if it is already present in the directory. @@ -102,7 +106,7 @@ def generate_node_file_by_attribute( level=qgis.core.Qgis.Warning ) id_ = ( - feat.id() if node_id_field_name is None + autogenerate_feature_id(feat) if node_id_field_name is None else get_numeric_attribute(feat, node_id_field_name) ) attr = get_numeric_attribute(feat, node_attribute_field_name) @@ -161,7 +165,7 @@ def generate_node_file_by_area( geom = feat.geometry() feat_area = area_measurer.measureArea(geom) id_ = ( - feat.id() if node_id_field_name is None + autogenerate_feature_id(feat) if node_id_field_name is None else get_numeric_attribute(feat, node_id_field_name) ) if id_ is not None: @@ -208,7 +212,7 @@ def generate_connection_file_with_centroid_distances( seen_ids = set() for feat in feature_iterator_factory(): feat_id = ( - feat.id() if node_id_field_name is None + autogenerate_feature_id(feat) if node_id_field_name is None else get_numeric_attribute(feat, node_id_field_name) ) if feat_id is not None: @@ -223,7 +227,7 @@ def generate_connection_file_with_centroid_distances( break if pair_feat.id() > feat.id(): pair_feat_id = ( - pair_feat.id() if node_id_field_name is None + autogenerate_feature_id(pair_feat) if node_id_field_name is None else get_numeric_attribute(pair_feat, node_id_field_name) ) if pair_feat_id is not None: @@ -295,7 +299,7 @@ def generate_connection_file_with_edge_distances( seen_ids = set() for feat in feature_iterator_factory(): feat_id = ( - feat.id() if node_id_field_name is None + autogenerate_feature_id(feat) if node_id_field_name is None else get_numeric_attribute(feat, node_id_field_name) ) if feat_id is not None: @@ -312,7 +316,7 @@ def generate_connection_file_with_edge_distances( break if pair_feat.id() > feat.id(): pair_feat_id = ( - pair_feat.id() if node_id_field_name is None + autogenerate_feature_id(pair_feat) if node_id_field_name is None else get_numeric_attribute(pair_feat, node_id_field_name) ) if pair_feat_id is not None: diff --git a/src/qgis_conefor/main.py b/src/qgis_conefor/main.py index aaf0c4b..feac99c 100644 --- a/src/qgis_conefor/main.py +++ b/src/qgis_conefor/main.py @@ -10,6 +10,7 @@ import qgis.gui from processing.tools.dataobjects import createContext from qgis.PyQt import ( + QtCore, QtGui, QtWidgets, ) @@ -28,7 +29,10 @@ ConeforInputsPoint, ConeforInputsPolygon, ) -from .utilities import log +from .utilities import ( + log, + load_settings_key, +) class QgisConefor: @@ -38,7 +42,6 @@ class QgisConefor: action: QtWidgets.QAction dialog: Optional[QtWidgets.QDialog] processing_provider: ProcessingConeforProvider - inputs_from_points_algorithm: Optional[qgis.core.QgsProcessingAlgorithm] inputs_from_polygons_algorithm: Optional[qgis.core.QgsProcessingAlgorithm] edge_distance_processing_model: Optional[qgis.core.QgsProcessingAlgorithm] centroid_distance_processing_model: Optional[qgis.core.QgsProcessingAlgorithm] @@ -63,7 +66,6 @@ def __init__(self, iface: qgis.gui.QgisInterface): ) self.processing_provider = ProcessingConeforProvider() self.processing_context = None - self.inputs_from_points_algorithm = None self.inputs_from_polygons_algorithm = None self.edge_distance_processing_model = None self.centroid_distance_processing_model = None @@ -77,8 +79,6 @@ def init_processing(self): def initGui(self): self.init_processing() processing_registry = qgis.core.QgsApplication.processingRegistry() - self.inputs_from_points_algorithm = processing_registry.createAlgorithmById( - "conefor:inputsfrompoint") self.inputs_from_polygons_algorithm = processing_registry.createAlgorithmById( "conefor:inputsfrompolygon") self.edge_distance_processing_model = processing_registry.createAlgorithmById( @@ -96,6 +96,7 @@ def initGui(self): self.iface.mainWindow() ) self.action.triggered.connect(self.run) + self.action.setEnabled(False) self.iface.addPluginToVectorMenu(f"&{self._action_title}", self.action) self.iface.addVectorToolBarIcon(self.action) qgis_project = qgis.core.QgsProject.instance() @@ -216,53 +217,6 @@ def _enqueue_edge_distance_generation_task( task_manager = qgis.core.QgsApplication.taskManager() task_manager.addTask(task) - def _process_point_layer( - self, - layer_params: schemas.ConeforInputParameters, - create_distance_file: bool, - output_dir: str, - use_selected_features: bool - ): - process_id = schemas.PROCESSING_TASK_ID_SEPARATOR.join(( - str(uuid.uuid4()), - layer_params.layer.name() - )) - input_layer_param = qgis.core.QgsProcessingFeatureSourceDefinition( - source=layer_params.layer.id(), - selectedFeaturesOnly=use_selected_features, - featureLimit=-1, - geometryCheck=self.processing_context.invalidGeometryCheck(), - ) - task = qgis.core.QgsProcessingAlgRunnerTask( - algorithm=self.inputs_from_points_algorithm, - parameters={ - ConeforInputsPolygon.INPUT_NODE_IDENTIFIER_NAME[0]: ( - layer_params.id_attribute_field_name or ""), - ConeforInputsPolygon.INPUT_NODE_ATTRIBUTE_NAME[0]: ( - layer_params.attribute_field_name or ""), - ConeforInputsPolygon.INPUT_DISTANCE_THRESHOLD[0]: "", - ConeforInputsPolygon.INPUT_OUTPUT_DIRECTORY[0]: output_dir, - ConeforInputsPoint.INPUT_POINT_LAYER[0]: input_layer_param, - }, - context=self.processing_context - ) - task.executed.connect( - functools.partial( - self.finalize_task_execution, process_id, layer_params) - ) - self._processing_tasks[process_id] = task - task_manager = qgis.core.QgsApplication.taskManager() - log(f"About to enqueue task with process_id: {process_id!r}") - task_manager.addTask(task) - if create_distance_file: - self._enqueue_centroid_distance_generation_task( - layer_params, - schemas.PROCESSING_TASK_ID_SEPARATOR.join(( - process_id, - "centroid_distance", - )), - ) - def _process_polygon_layer( self, layer_params: schemas.ConeforInputParameters, @@ -280,6 +234,12 @@ def _process_polygon_layer( featureLimit=-1, geometryCheck=self.processing_context.invalidGeometryCheck(), ) + connection_method = ConeforInputsPolygon._NODE_DISTANCE_CHOICES.index( + layer_params.connections_method.value) + log( + f"About to use {connection_method!r} as the node " + f"connection distance value" + ) task = qgis.core.QgsProcessingAlgRunnerTask( algorithm=self.inputs_from_polygons_algorithm, parameters={ @@ -294,7 +254,7 @@ def _process_polygon_layer( ConeforInputsPolygon.INPUT_POLYGON_LAYER[0]: ( input_layer_param), ConeforInputsPolygon.INPUT_NODE_CONNECTION_DISTANCE_METHOD[0]: ( - layer_params.connections_method.value), + connection_method), }, context=self.processing_context ) @@ -329,7 +289,6 @@ def _process_polygon_layer( raise NotImplementedError() def prepare_conefor_inputs(self): - log(f"Inside prepare_conefor_inputs, now we need data to work with") layer_inputs = set() output_dir = str(self.dialog.output_dir_le.text()) only_selected_features = self.dialog.use_selected_features_chb.isChecked() @@ -358,15 +317,6 @@ def prepare_conefor_inputs(self): output_dir, only_selected_features, ) - - elif layer_to_process.layer.geometryType() == qgis.core.Qgis.GeometryType.Point: - self._process_point_layer( - layer_to_process, - self.dialog.create_distances_file_chb.isChecked(), - output_dir, - only_selected_features, - ) - raise NotImplementedError else: raise RuntimeError( f"layer: {layer_to_process.layer.name()!r} has invalid " @@ -437,9 +387,14 @@ def finalize_task_execution( level=qgis.core.Qgis.Critical ) else: - self.iface.messageBar().pushMessage( - "Conefor inputs", - "Plugin finished execution", + message_widget = self.iface.messageBar().createMessage( + "Conefor inputs", "Plugin finished execution") + open_output_dir_btn = QtWidgets.QPushButton(message_widget) + open_output_dir_btn.setText("Open output directory") + open_output_dir_btn.pressed.connect(self.open_output_dir) + message_widget.layout().addWidget(open_output_dir_btn) + self.iface.messageBar().pushWidget( + message_widget, level=qgis.core.Qgis.Info ) self._task_results = {} @@ -452,6 +407,11 @@ def check_for_removed_layers(self, removed_layer_ids: list[str]): log("inside check_for_removed_layers") self.start_analyzing_layers(disregard_ids=removed_layer_ids) + def open_output_dir(self): + output_dir = load_settings_key( + schemas.QgisConeforSettingsKey.OUTPUT_DIR) + QtGui.QDesktopServices.openUrl(QtCore.QUrl(f"file://{output_dir}")) + class NoUniqueFieldError(Exception): diff --git a/src/qgis_conefor/processing/algorithms/coneforinputs.py b/src/qgis_conefor/processing/algorithms/coneforinputs.py index 1aa1968..b8b7b7f 100644 --- a/src/qgis_conefor/processing/algorithms/coneforinputs.py +++ b/src/qgis_conefor/processing/algorithms/coneforinputs.py @@ -226,14 +226,7 @@ def processAlgorithm(self, parameters, context, feedback): class ConeforInputsPolygon(ConeforInputsBase): INPUT_POLYGON_LAYER = ("vector_layer", "Polygon layer",) - # INPUT_NODE_IDENTIFIER_NAME = ( - # "node_identifier", "Node identifier (will autogenerate if not set)") - # INPUT_NODE_ATTRIBUTE_NAME = ("node_attribute", "Node attribute (will calculate area if not set)") INPUT_NODE_CONNECTION_DISTANCE_METHOD = ("node_connection", "Node connection distance method") - # INPUT_DISTANCE_THRESHOLD = ("distance_threshold", "Distance threshold") - # INPUT_OUTPUT_DIRECTORY = ("output_dir", "Output directory for generated Conefor input files") - # OUTPUT_CONEFOR_NODES_FILE_PATH = ("output_path", "Conefor nodes file") - # OUTPUT_CONEFOR_CONNECTIONS_FILE_PATH = ("output_connections_path", "Conefor connections file") _NODE_DISTANCE_CHOICES = [ NodeConnectionType.EDGE_DISTANCE.value, NodeConnectionType.CENTROID_DISTANCE.value, @@ -326,7 +319,6 @@ def processAlgorithm(self, parameters, context, feedback): node_id_field_name = None else: node_id_field_name = raw_node_id_field_name - feedback.pushInfo(f"{self.parameterAsEnum(parameters, self.INPUT_NODE_CONNECTION_DISTANCE_METHOD[0], context)=} ") connections_distance_method = NodeConnectionType( self._NODE_DISTANCE_CHOICES[ self.parameterAsEnum( @@ -334,6 +326,7 @@ def processAlgorithm(self, parameters, context, feedback): ) ] ) + feedback.pushInfo(f"{connections_distance_method.value=} ") raw_distance_threshold = self.parameterAsString( parameters, self.INPUT_DISTANCE_THRESHOLD[0], context) if raw_distance_threshold == "": diff --git a/src/qgis_conefor/tasks.py b/src/qgis_conefor/tasks.py index be2f9d8..90ee8be 100644 --- a/src/qgis_conefor/tasks.py +++ b/src/qgis_conefor/tasks.py @@ -15,7 +15,6 @@ class LayerAnalyzerTask(qgis.core.QgsTask): relevant_layer_ids: dict[str, list[str]] _relevant_geometry_types = ( - qgis.core.Qgis.GeometryType.Point, qgis.core.Qgis.GeometryType.Polygon, )