3939 QgsFeatureRequest ,
4040 QgsFeature ,
4141 QgsExpression ,
42- QgsWkbTypes )
42+ QgsWkbTypes ,
43+ QgsGeometry )
4344from processing .gui .Postprocessing import handleAlgorithmResults
4445from processing .tools import dataobjects
4546from qgis .utils import iface
@@ -86,27 +87,53 @@ def make_feature_compatible(new_features, input_layer):
8687
8788 result_features = []
8889 for new_f in new_features :
89- if (new_f .geometry ().wkbType () != input_layer .wkbType () and
90- QgsWkbTypes .isMultiType (input_layer .wkbType ()) and not
91- new_f .geometry ().isMultipart ()):
92- new_geom = new_f .geometry ()
93- new_geom .convertToMultiType ()
94- new_f .setGeometry (new_geom )
95- if len (new_f .fields ()) > len (input_layer .fields ()):
90+ if new_f .geometry ().wkbType () != input_layer .wkbType ():
91+ # Single -> Multi
92+ if (QgsWkbTypes .isMultiType (input_layer .wkbType ()) and not
93+ new_f .geometry ().isMultipart ()):
94+ new_geom = new_f .geometry ()
95+ new_geom .convertToMultiType ()
96+ new_f .setGeometry (new_geom )
97+ # Drop Z/M
98+ if ((QgsWkbTypes .hasZ (new_f .geometry ().wkbType ()) and not QgsWkbTypes .hasZ (input_layer .wkbType ())) or
99+ (QgsWkbTypes .hasM (new_f .geometry ().wkbType ()) and not QgsWkbTypes .hasM (input_layer .wkbType ()))):
100+ # FIXME: there must be a better way!!!
101+ if new_f .geometry ().type () == QgsWkbTypes .PointGeometry :
102+ if new_f .geometry ().isMultipart ():
103+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asMultiPoint ().asWkt ())
104+ else :
105+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asPoint ().asWkt ())
106+ elif new_f .geometry ().type () == QgsWkbTypes .PolygonGeometry :
107+ if new_f .geometry ().isMultipart ():
108+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asMultiPolygon ().asWkt ())
109+ else :
110+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asPolygon ().asWkt ())
111+ elif new_f .geometry ().type () == QgsWkbTypes .LineGeometry : # Linestring
112+ if new_f .geometry ().isMultipart ():
113+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asPolyline ().asWkt ())
114+ else :
115+ new_geom = QgsGeometry .fromWkt (new_f .geometry ().asMultiPolyline ().asWkt ())
116+ else :
117+ new_geom = QgsGeometry ()
118+ new_f .setGeometry (new_geom )
119+ if len (new_f .attributes ()) > len (input_layer .fields ()):
96120 f = QgsFeature (input_layer .fields ())
97121 f .setGeometry (new_f .geometry ())
98122 f .setAttributes (new_f .attributes ()[:len (input_layer .fields ())])
123+ new_f = f
99124 result_features .append (new_f )
100125 return result_features
101126
102127
103- def execute_in_place_run (alg , parameters , context = None , feedback = None ):
128+ def execute_in_place_run (alg , active_layer , parameters , context = None , feedback = None , raise_exceptions = False ):
104129 """Executes an algorithm modifying features in-place in the input layer.
105130
106131 The input layer must be editable or an exception is raised.
107132
108133 :param alg: algorithm to run
109134 :type alg: QgsProcessingAlgorithm
135+ :param active_layer: the editable layer
136+ :type active_layer: QgsVectoLayer
110137 :param parameters: parameters of the algorithm
111138 :type parameters: dict
112139 :param context: context, defaults to None
@@ -123,16 +150,11 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
123150 if context is None :
124151 context = dataobjects .createContext (feedback )
125152
126- # It would be nicer to get the layer from INPUT with
127- # alg.parameterAsVectorLayer(parameters, 'INPUT', context)
128- # but it does not work.
129- active_layer_id , ok = parameters ['INPUT' ].source .value (context .expressionContext ())
130- if ok :
131- active_layer = QgsProject .instance ().mapLayer (active_layer_id )
132- if active_layer is None or not active_layer .isEditable ():
133- raise QgsProcessingException (tr ("Layer is not editable or layer with id '%s' could not be found in the current project." ) % active_layer_id )
134- else :
135- return False , {}
153+ if active_layer is None or not active_layer .isEditable ():
154+ raise QgsProcessingException (tr ("Layer is not editable or layer is None." ))
155+
156+ if not alg .supportInPlaceEdit (active_layer ):
157+ raise QgsProcessingException (tr ("Selected algorithm and parameter configuration are not compatible with in-place modifications." ))
136158
137159 parameters ['OUTPUT' ] = 'memory:'
138160
@@ -147,7 +169,13 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
147169
148170 # Checks whether the algorithm has a processFeature method
149171 if hasattr (alg , 'processFeature' ): # in-place feature editing
172+ # Make a clone or it will crash the second time the dialog
173+ # is opened and run
174+ alg = alg .create ()
150175 alg .prepare (parameters , context , feedback )
176+ # Check again for compatibility after prepare
177+ if not alg .supportInPlaceEdit (active_layer ):
178+ raise QgsProcessingException (tr ("Selected algorithm and parameter configuration are not compatible with in-place modifications." ))
151179 field_idxs = range (len (active_layer .fields ()))
152180 feature_iterator = active_layer .getFeatures (QgsFeatureRequest (active_layer .selectedFeatureIds ())) if parameters ['INPUT' ].selectedFeaturesOnly else active_layer .getFeatures ()
153181 for f in feature_iterator :
@@ -166,7 +194,8 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
166194 active_layer .deleteFeature (f .id ())
167195 # Get the new ids
168196 old_ids = set ([f .id () for f in active_layer .getFeatures (req )])
169- active_layer .addFeatures (new_features )
197+ if not active_layer .addFeatures (new_features ):
198+ raise QgsProcessingException (tr ("Error adding processed features back into the layer." ))
170199 new_ids = set ([f .id () for f in active_layer .getFeatures (req )])
171200 new_feature_ids += list (new_ids - old_ids )
172201
@@ -188,21 +217,24 @@ def execute_in_place_run(alg, parameters, context=None, feedback=None):
188217 new_ids = set ([f .id () for f in active_layer .getFeatures (req )])
189218 new_feature_ids += list (new_ids - old_ids )
190219
220+ active_layer .endEditCommand ()
221+
191222 if ok and new_feature_ids :
192223 active_layer .selectByIds (new_feature_ids )
193224 elif not ok :
194225 active_layer .rollback ()
195226
196- active_layer .endEditCommand ()
197-
198227 return ok , results
199228
200229 except QgsProcessingException as e :
230+ active_layer .endEditCommand ()
231+ if raise_exceptions :
232+ raise e
201233 QgsMessageLog .logMessage (str (sys .exc_info ()[0 ]), 'Processing' , Qgis .Critical )
202234 if feedback is not None :
203- feedback .reportError (e . msg )
235+ feedback .reportError (getattr ( e , ' msg' , str ( e )) )
204236
205- return False , {}
237+ return False , {}
206238
207239
208240def execute_in_place (alg , parameters , context = None , feedback = None ):
@@ -224,7 +256,7 @@ def execute_in_place(alg, parameters, context=None, feedback=None):
224256 """
225257
226258 parameters ['INPUT' ] = QgsProcessingFeatureSourceDefinition (iface .activeLayer ().id (), True )
227- ok , results = execute_in_place_run (alg , parameters , context = context , feedback = feedback )
259+ ok , results = execute_in_place_run (alg , iface . activeLayer (), parameters , context = context , feedback = feedback )
228260 if ok :
229261 iface .activeLayer ().triggerRepaint ()
230262 return ok , results
0 commit comments