PyQGIS, custom processing algorithm: How to use selected features only? - algorithm

I want to create a custom processing algorithm with PyQGIS, which is able to take a vector layer as input (in this case of type point) and then do something with it's features. It's working well as long as I just choose the whole layer. But it doesn't work if I'm trying to work on selected features only.
I'm using QgsProcessingParameterFeatureSource to be able to work on selected features only. The option is shown and I can enable the checkbox. But when I'm executing the algorithm, I get NoneType as return of parameterAsVectorLayer.
Below you'll find a minimal working example to reproduce the problem:
from qgis.PyQt.QtCore import QCoreApplication
from qgis.core import (
QgsProcessing,
QgsProcessingAlgorithm,
QgsProcessingParameterFeatureSource
)
name = "selectedonly"
display_name = "Selected features only example"
group = "Test"
group_id = "test"
short_help_string = "Minimal working example code for showing my problem."
class ExampleProcessingAlgorithm(QgsProcessingAlgorithm):
def tr(self, string):
return QCoreApplication.translate('Processing', string)
def createInstance(self):
return ExampleProcessingAlgorithm()
def name(self):
return name
def displayName(self):
return self.tr(display_name)
def group(self):
return self.tr(group)
def groupId(self):
return group_id
def shortHelpString(self):
return self.tr(short_help_string)
def initAlgorithm(self, config=None):
self.addParameter(
QgsProcessingParameterFeatureSource(
'INPUT',
self.tr('Some point vector layer.'),
types=[QgsProcessing.TypeVectorPoint]
)
)
def processAlgorithm(self, parameters, context, feedback):
layer = self.parameterAsVectorLayer(
parameters,
'INPUT',
context
)
return {"OUTPUT": layer}
If I'm working on the whole layer, the output is {'OUTPUT': <QgsVectorLayer: 'Neuer Temporärlayer' (memory)>}, which is what I would expect.
If I'm working on selected features only, my output is {'OUTPUT': None}, which doesn't makes sense to me. I selected some of the features before executing of course.
I'm using QGIS-version 3.22 LTR, if it's relevant.
Can anybody tell me what I'm doing wrong?

I would suggest you trying to use the method 'parameterAsSource' in the 'processAlgorithm' method.
layer = self.parameterAsSource(
parameters,
'INPUT',
context
)

Related

Kv related question - How to bound an on_press/release function to the viewclass of the recycleview?

I've been working on a project which required me to learn kv.
what I'm trying to do is use recycleview to display a list of people that are a part of a dataset I built and allow easy edit of the dataset.
what I've done is read the documentation and simply use the first example from there (with a slight change, the viewclass being a togglebutton:
[The Example][1]
so as for my question, what I want to do is simply bound an on_press/release function to the viewclass objects, for example what I want to do is to bound a function to all of the toggle buttons which appends the button's text to a list when It's being pressed and removes the name from the list when It's being released.
[1]: https://i.stack.imgur.com/55FlM.png
You can do that by adding the on_press to the data:
class RV(RecycleView):
def __init__(self, **kwargs):
self.list = []
super(RV, self).__init__(**kwargs)
self.data = [{'text': str(x), 'on_press':partial(self.toggle, str(x))} for x in range(100)]
def toggle(self, name):
print('toggle')
if name in self.list:
self.list.remove(name)
else:
self.list.append(name)
print('list is now:', self.list)

rails string substitution or similar solution in controller

I'm building a site with users in all 50 states. We need to display information for each user that is specific to their situation, e.g., the number of events they completed in that state. Each state's view (a partial) displays state-specific information and, therefore, relies upon state-specific calculations in a state-specific model. We'd like to do something similar to this:
##{user.state} = #{user.state.capitalize}.new(current_user)
in the users_controller instead of
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
.... [and the remaining 49 states]
#wisconsin = Wisconsin.new(current_user) if (#user.state == 'wisconsin')
to trigger the Illinois.rb model and, in turn, drive the view defined in the users_controller by
def user_state_view
#user = current_user
#events = Event.all
#illinois = Illinois.new(current_user) if (#user.state == 'illinois')
end
I'm struggling to find a better way to do this / refactor it. Thanks!
I would avoid dynamically defining instance variables if you can help it. It can be done with instance_variable_set but it's unnecessary. There's no reason you need to define the variable as #illinois instead of just #user_state or something like that. Here is one way to do it.
First make a static list of states:
def states
%{wisconsin arkansas new_york etc}
end
then make a dictionary which maps those states to their classes:
def state_classes
states.reduce({}) do |memo, state|
memo[state] = state.camelize.constantize
memo
end
end
# = { 'illinois' => Illinois, 'wisconsin' => Wisconsin, 'new_york' => NewYork, etc }
It's important that you hard-code a list of state identifiers somewhere, because it's not a good practice to pass arbitrary values to contantize.
Then instantiating the correct class is a breeze:
#user_state = state_classes[#user.state].new(current_user)
there are definitely other ways to do this (for example, it could be added on the model layer instead)

PySide - How to connect a signal to a widget that was created through a method being called inside another method

I'm fairly new to programming so please be patient with me. I'm trying to create a simple script in Autodesk Maya. I've created a method that sets up two check-boxes side by side (See below)...
def checkboxLayout(self, name1, text1, name2, text2, parentLayout, initState):
##create and add horizontal layout
layout = QtGui.QHBoxLayout()
parentLayout.addLayout(layout)
width = 75
name1 = QtGui.QCheckBox(text1)
layout.addWidget(name1)
name1.setMinimumWidth(width)
name1.setMaximumWidth(width)
name1.setChecked(initState)
name2 = QtGui.QCheckBox(text2)
layout.addWidget(name2)
name2.setMinimumWidth(width)
name2.setMaximumWidth(width)
And later on I've called this method twice in order to save me having to write out the same big block of code twice...
def createLayout(self):
##Layout
mainLayout = QtGui.QVBoxLayout()
mainLayout.addWidget(self.title)
mainLayout.addSpacerItem(self.titleSpacer)
mainLayout.addWidget(self.oldLabel)
self.checkboxLayout("selection_CB", "Selection", "name_CB", "Name", mainLayout, True)
mainLayout.addWidget(self.textbox1)
mainLayout.addSpacerItem(self.midSpacer)
mainLayout.addWidget(self.newLabel)
mainLayout.addWidget(self.textbox2)
self.checkboxLayout("delHistory_CB", "Delete\nHistory", "freezeTrans_CB", "Freeze\nTransforms", mainLayout, False)
self.buttonLayout = QtGui.QGridLayout()
mainLayout.addLayout(self.buttonLayout)
self.buttonLayout.addWidget(self.cancelButton, 0, 0)
self.buttonLayout.addWidget(self.okButton, 0, 1)
self.setLayout(mainLayout)
My problem is that when I try to connect a signal to it, it won't work. All the tutorials I've watched so far have only connected signals to widgets that WERE NOT created by calling a method inside another method (I realize that probably isn't the correct terminology but like I said, I'm new to this :( ) I'll post the code that I've written to try and connect the signal below. My guess was that I had to specify the method that created the check box, but I couldn't get that to work either. So how do I get this signal connected? Also feel free to correct my terminology :) Thanks to anyone who can help :)
def createConnections(self):
self.selection_CB.toggled.connect(self.checkboxLine1_ChangeState)
Where and how are you setting the variable self.selection_CB?
In your checkboxLayout function you can include a return for your check box like so:
`return [name1, name2]`
then simply assign them as you're calling the function and connect the events from there:
self.check1, self.check2 = self.checkboxLayout("selection_CB", "Selection", "name_CB", "Name", mainLayout, True)
Or if they are always being connected to the same functions, then why not just do the connection straight from checkboxLayout?
name1.stateChanged.connect(self.checkboxLine1_ChangeState)

TurboGears 2.3 #validte in two steps

I'm using TurboGears 2.3 and working on validating forms with formencode and need some guidance
I have a form which covers 2 different objects. They are a almost the same, but with some difference
When i submit my form, I want to validate 2 things
Some basic data
Some specific data for the specific object
Here are my schemas:
class basicQuestionSchema(Schema):
questionType = validators.OneOf(['selectQuestion', 'yesNoQuestion', 'amountQuestion'])
allow_extra_fields = True
class amount_or_yes_no_question_Schema(Schema):
questionText = validators.NotEmpty()
product_id_radio = object_exist_by_id(entity=Product, not_empty=True)
allow_extra_fields = True
class selectQuestionSchema(Schema):
questionText = validators.NotEmpty()
product_ids = validators.NotEmpty()
allow_extra_fields = True
And here are my controller's methods:
#expose()
#validate(validators=basicQuestionSchema(), error_handler=questionEditError)
def saveQuestion(self,**kw):
type = kw['questionType']
if type == 'selectQuestion':
self.save_select_question(**kw)
else:
self.save_amount_or_yes_no_question(**kw)
#validate(validators=selectQuestionSchema(),error_handler=questionEditError)
def save_select_question(self,**kw):
...
Do stuff
...
#validate(validators=amount_or_yes_no_question_Schema(),error_handler=questionEditError)
def save_amount_or_yes_no_question(self,**kw):
...
Do other stuff
...
What I wanted to do was validate twice, with different schemas. This doesn't work, as only the first #validate is validated, and the other are not (maybe ignored)
So, what am i doning wrong?
Thanks for the help
#validate is part of the request flow, so when manually calling a controller it is not executed (it is not a standard python decorator, all TG2 decorators actually only register an hook using tg.hooks so they are bound to request flow).
What you are trying to achieve should be done during validation phase itself, you can then call save_select_question and save_amount_or_yes_no_question as plain object methods after validation.
See http://runnable.com/VF_2-W1dWt9_fkPr/conditional-validation-in-turbogears-for-python for a working example of conditional validation.

Qt GUI Table / Spreadsheet type layout in Ruby

I am trying to design a GUI that will output data in a spreadsheet type of format, rows and columns.
The cells will be populated with data that will be fetched by another object at predefined intervals. Being able to change individual cell color would be ideal to highlight any cells that have changed.
After some research it seems like QtBindings gem for ruby is the most powerful GUI choice for this but I can't seem to find any documentation or examples that would help me with what I am trying to accomplish. Any advice in the form of code or examples would be more than helpful. Thank you.
Update:: after some research and brute force, I came up with this code:
class PositionModel < Qt::AbstractTableModel
slots 'timerhit()'
def initialize(risk)
super()
#timer = Qt::Timer.new(self)
connect(#timer, SIGNAL('timeout()'), self, SLOT('timerhit()'))
#timer.start(1000)
#risk = risk
#risk_a = #risk.to_a
#pp #risk_a
end
def timerhit()
emit dataChanged(createIndex(0,0), createIndex(0,0))
#emit dataChanged()
end
def rowCount(parent)
#risk_a.size
end
def columnCount(parent)
1
end
def data(index, role)
col = index.column
row = index.row
if role == Qt::DisplayRole
return Qt::Variant.new( #risk_a[row] )
else
return Qt::Variant.new()
end
end
end
app = Qt::Application.new(ARGV)
model = PositionModel.new(##risk)
table = Qt::TableView.new
table.model = model
table.setSortingEnabled(true)
table.show
It seems to be working well, and more importantly is a solid foundation for what I ultimately want to accomplish. However, I I tried to enable sorting by clicking on a column header, but it doesnt seem to be working. Does anyone know why?
Two words: Use QTableView or QTableWidget.
Populating Table Widget from Text File in Qt
How change row color with Null items?
Converting the c++ code to ruby qt should be trivial. Also the C++ Qt docs are awesome! Good luck.
Hope that helps.

Resources