Loop doesn't work, 3-lines python code - python

this question is about blender, python scripting
I'm completely new in this, so please excuse me for any stupid/newbie question/comment.
I made it simple (3 lines code) to make it easy addressing the problem.
what I need is a code that adds a new uv map for each object within loop function.
But this code instead is adding multiple new UV maps to only one object.
import bpy
for x in bpy.context.selected_objects:
bpy.ops.mesh.uv_texture_add()
what's wrong I'm doing here??
Thanks

Similar to what Sambler said, I always use:
for active in bpy.context.selected_objects:
bpy.context.scene.objects.active = active
...
These two lines I use more than any other when programming for Blender (except import bpy of course).
I think I first learned this here if you'd like a good intro on how this works:
https://cgcookiemarkets.com/2014/12/11/writing-first-blender-script/
In the article he uses:
# Create a list of all the selected objects
selected = bpy.context.selected_objects
# Iterate through all selected objects
for obj in selected:
bpy.context.scene.objects.active = obj
...
His comments explain it pretty well, but I will take it a step further. As you know, Blender lacks built-in multi-object editing, so you have selected objects and one active object. The active object is what you can and will edit if you try to set its values from python or Blender's gui itself. So although we are writing it slightly differently each time, the effect is the same. We loop over all selected objects with the for active in bpy.context.selected_objects, then we set the active object to be the next one in the loop that iterates over all the objects that are selected with bpy.context.scene.objects.active = active. As a result, whatever we do in the loop gets done once for every object in the selection and any operation we do on the object in question gets done on all of the objects. What would happen if we only used the first line and put our code in the for loop?
for active in bpy.context.selected_objects:
...
Whatever we do in the loop gets done once for every object in the selection but any operation we do on the object in question gets done on only the active object, but as many times as there are selected objects. This is why we need to set the active object from within the loop.

The uv_texture_add operator is one that only works on the current active object. You can change the active object by setting scene.objects.active
import bpy
for x in bpy.context.selected_objects:
bpy.context.scene.objects.active = x
bpy.ops.mesh.uv_texture_add()

note: I am not really familiar with blender
It seems that bpy.ops operations depend on the state of bpy.context. The context can also be overridden per-operation.
I assume that uv_texture_add() only works on a single object at a time?
Try something like this:
import bpy
for x in bpy.context.selected_objects:
override = { "selected_objects": x }
bpy.ops.mesh.uv_texture_add(override)
That should run the operations as if only one object was selected at a time.
Source:
https://www.blender.org/api/blender_python_api_2_63_17/bpy.ops.html#overriding-context

Related

Setting value of a iterable property of a Python COM object

I am using pywin32 to automate some tasks in software that has an Automation Server technology interface (formerly OLE Automation Server).
This software comes with a somewhat detailed manual with code examples in VBA, C++ or Matlab but no Python. I have built a Python library that can do most of the functionalities built into the software but there are some parts I cannot do in Python.
I cannot change the value of a property if this property is contained in a iterable COM object.
What I can do:
[Documentation for Visibility property]
import win32com.client
app = win32com.client.Dispatch('NAME_OF_APP')
app.Visibility = True
As an example, with this code, I can change the visibility parameter of the software: if it runs with or without GUI.
What I cannot do:
[Documentation for getting and setting current device]
import win32com.client
app = win32com.client.Dispatch('NAME_OF_APP')
app.CurrentDevice(0) = 'NAME OF DEVICE'
I then get the following error:
SyntaxError: cannot assign to function call here. Maybe you meant '==' instead of '='?
This error makes sense to me but I cannot find a way to set any of these software properties when they come in the form of an iterable object. As soon as I have to specify an index, I don't know how to set the value.
From what I understand, in C++ we are able to change the value because of pointers but how can we achieve the same thing in Python? Is it possible or do I have to use some C++ code in parallel to my Python to run my library? I don't know anything in C++ so if I could avoid doing that, it would be good.
What I have tried
Of course, the 1st thing I tried was to change () to [] or {} which logically didn't work.
Then I used the Evaluate function in PyCharms to see what was hiding behind my app.CurrentDevice. I was hoping to find sub-attributes that I could then set but I don't see anything inside the object:
[Result of Evaluate on the CurrentDevice object]
Finally, I have tried the following:
import win32com.client
app = win32com.client.Dispatch('NAME_OF_APP')
curr_device = app.CurrentDevice(0)
curr_device = 'NAME OF DEVICE'
I wanted to affect the object to a variable and then change the value but of course, this only rewrites the variable curr-device with 'NAME OF DEVICE' but loses any link to COM Object.
I feel like my questions are similar to the following unanswered question:
How can I set the value of an indexed property of a COM object in Python?
It looks as if win32com is struggling to set the property if there is an additional argument to the put function, which is a little surprising.
First thing to do is to use
app = win32com.client.gencache.EnsureDispatch('NAME_OF_APP')
This creates a Python wrapper for the COM object (rather than just firing function calls at the object and hoping). This may in itself clear up your issue.
If not, here is a quite ugly way of working around. As you have identified, the relevant part of the type library is:
[id(0x00000018),propput, helpstring("property CurrentDevice")]
HRESULT CurrentDevice([in] long lAcq, [in] VARIANT pVal);
And you can use this to set the property at a low level.
win32com dispatch objects are a wrapper for the PyIDispatch object. All dispatch objects support the Invoke method, and you can use this to call the function yourself. NB. Since I don't have access to your COM object, I can't test, so this answer may need some tweaking (!).
The PyIDispatch documentation
Try:
import win32com.client as wc
import pythoncom
app = wc.gencache.EnsureDispatch('NAME OF APP')
app.Visibility=TRUE
newVal = wc.VARIANT(pythoncom.VT_VARIANT,'NAME OF DEVICE')
app._oleobj_.Invoke(24,0,pythoncom.INVOKE_PROPERTYPUT,0,0,newVal)
There are a lot of 'magic' numbers here, but basically:
24 = 0x00000018 in decimal: this is the Id of the property
0 = the LCID, the Locale Id ... I always set it to 0
pythoncom.INVOKE_PROPERTYPUT = the type of call.
0 = whether you care about the return type (you probably don't = False)
0 = first parameter, lAcq, as in CurrentDevice(0)
newVal = second paramter,pVal, the new device name as a VARIANT
I haven't tried this, but pythoncom is pretty good about converting VARIANT types, so you might not need the VARIANT creation, and can just use NAME OF DEVICE directly as the parameter.

Is there a way to create nested class attributes in Python?

I've been struggling with creating a class for my image processing code in Python.
The code requires a whole bunch of different parameters (set in a params.txt file) which can easily be grouped into different categories. For example, some are paths, some are related to the experimental geometry, some are just switches for turning certain image processing features on/off etc etc.
If my "structure" (not sure how I should create it yet) is created as P, I would like to have something like,
P = my_param_object()
P.load_parameters('path/to/params.txt')
and then, from the main code, I can access whatever elements I need like so,
print(P.paths.basepath())
'/some/path/to/data'
print(P.paths.year())
2019
print(P.filenames.lightfield())
'andor_lightfield.dat'
print(P.geometry.dist_lens_to_sample())
1.5
print(P.settings.subtract_background())
False
print(P.settings.apply_threshold())
True
I already tried creating my own class to do this but everything is just in one massive block. I don't know how to create nested parts for the class. So for example, I have a setting and a function called "load_background". This makes sense because the load_background function always loads a specific filename in a specific location but should only do so if the load_background parameter is set to True
From within the class, I tried doing something like
self.setting_load_background = False
def method_load_background(self):
myutils.load_dat(self.background_fname())
but that's very ugly. It would be nicer to have,
if P.settings.load_background() == True:
P.load_background()
else:
P.generate_random_background()

Variable gets written with no intention

Not sure if this is the right place for my question, but I'm dealing with something pretty weird.
In my script, I have a class data() that is just a container for all sorts of constants and sort of data types. One of these data types is a dictionary that looks like this:
testStatus = { 'checkpoint': None,
'tests_Executed': [],
'tests_Passed': [],
'tests_FailedFromRegression': [],
'tests_FailedFromRetest': [],
'tests_PassedFromRetest': [] }
My intention is to use this dictionary as a data type for what I call, last test status and current test status. Somewhere in the constructor of my main class, I have something like this:
self.lastTestStatus = self.testStatus
self.currentTestStatus = self.testStatus
The weird part happens in my run() function of my main class. This is the main worker function of the class. After getting some previously saved status, and building a list with all previously tested items, self.currentTestStatus gets written even if I'm not touching it. The code looks like this:
self.getTestStatus()
#All good after this line.
#This is a function that uses self.lastTestStatus to save the previous status.
#After running this line, self.lastTestStatus["tests_FailedFromRegression"] will hold a list with some items. This is just script testing data.
previouslyTested = []
previouslyTested = self.lastTestStatus["tests_Passed"]
#All good after these two lines.
previouslyTested.extend(self.lastTestStatus["tests_FailedFromRegression"])
#At this point, self.currentTestStatus["tests_Passed"] gets the same value as self.lastTestStatus["tests_FailedFromRegression"] has.
previouslyTested.extend(self.lastTestStatus["tests_FailedFromRetest"])
previouslyTested.extend(self.lastTestStatus["tests_PassedFromRetest"])
Any idea what exactly am I doing wrong here? If I use a testStatus2 for my current status, which is identical with testStatus, everything's fine.
I'm using Python 2.7.10 32bit with Spyder 3.0.0dev.
Thanks a lot!
Just so we have an answer ---
self.lastTestStatus and self.currentTestStatusare references to the same object. When you mutate one, you mutate the other, since they are in fact the same object. Instead do
import copy
self.lastTestStatus = copy.deepcopy(self.testStatus)
self.currentTestStatus = copy.deepcopy(self.testStatus)
in order to copy the dictionaries and the lists they hold -- docs.

How to programatically create a detailed event like z3c.form does?

I have a simple event handler that looks for what has actually been changed (it's registered for a IObjectModifiedEvent events), the code looks like:
def on_change_do_something(obj, event):
modified = False
# check if the publication has changed
for change in event.descriptions:
if change.interface == IPublication:
modified = True
break
if modified:
# do something
So my question is: how can I programmatically generate those descriptions? I'm using plone.app.dexterity everywhere, so z3c.form is doing that automagically when using a form, but I want to test it with a unittest.
event.description is nominally an IModificationDescription object, which is essentially a list of IAttributes objects: each Attributes object having an interface (e.g. schema) and attributes (e.g. list of field names) modified.
Simplest solution is to create a zope.lifecycleevent.Attributes object for each field changed, and pass as arguments to the event constructor -- example:
# imports elided...
changelog = [
Attributes(IFoo, 'some_fieldname_here'),
Attributes(IMyBehaviorHere, 'some_behavior_provided_fieldname_here',
]
notify(ObjectModifiedEvent(context, *changelog)
I may also misunderstood something, but you may simple fire the event in your code, with the same parameters like z3c.form (Similar to the comment from #keul)?
After a short search in a Plone 4.3.x, I found this in z3c.form.form:
def applyChanges(self, data):
content = self.getContent()
changes = applyChanges(self, content, data)
# ``changes`` is a dictionary; if empty, there were no changes
if changes:
# Construct change-descriptions for the object-modified event
descriptions = []
for interface, names in changes.items():
descriptions.append(
zope.lifecycleevent.Attributes(interface, *names))
# Send out a detailed object-modified event
zope.event.notify(
zope.lifecycleevent.ObjectModifiedEvent(content, *descriptions))
return changes
You need two testcases, one which does nothing and one which goes thru your code.
applyChanges is in the same module (z3c.form.form) it iterates over the form fields and computes a dict with all changes.
You should set a break point there to inspect how the dict is build.
Afterwards you can do the same in your test case.
This way you can write readable test cases.
def test_do_something_in_event(self)
content = self.get_my_content()
descriptions = self.get_event_descriptions()
zope.event.notify(zope.lifecycleevent.ObjectModifiedEvent(content, *descriptions))
self.assertSomething(...)
IMHO mocking whole logic away may be a bad idea for future, if the code changes and probably works completely different, your test will be still fine.

Which is the difference in assigning a variable and working directly in array,in IPython

I'm playing around with IPython and remote control:
in: from IPython Import parallel as p
in: rc=p.client(profile=myprofile)
There is a behavior difference between
in: rc[0].block
out: False
in: rc[0].block=True
in: rc[0].block
out: False
and
in: view=rc[0]
in: view.block
out: False
in: view.block=True
in: view.block
out: True
I realy can't understand what append ? Why is that ? what do assignement do ?
Assignment itself does nothing. rc here is your client. Doing rc[0], or any sort of indexing, generates and returns a DirectView object that's a view with whatever engines you specify in []. This is a shorthand for generating the views: it's not actually just getting a specific object.
Thus, those views aren't unique. The best way to explain it, I think, is with an example. Say you have 2 engines. You want to run some tasks on only engine one, and want the tasks to block. You want to run others on only engine one, but don't want them to block. You want to run yet more on engines 1 and 2, and don't want them to block. Then you could do:
view_1_block = rc[0]
view_1_block.block = True
view_2_noblock = rc[0]
view_2_noblock.block = False
view_3_noblock = rc[[0,1]]
view_3_noblock.block = False
Then, you can use these to run tasks in whatever way you'd like, eg
view_1_block.map(lambda x:x**10, range(32)) # blocks, returns results, runs only on 1
view_3_noblock.map(lambda x:x**10, range(32)) # does not block, returns AsyncResult, runs on 1 and 2
There's no actual magic being used here. When you run rc[0] twice, it generates two views. The second view is not the same as the first. When you assign rc[0] to a variable, and then use that variable, you're working with one view, and not creating a new one.
iPython, like Numpy and Scipy, has quite a few shorthand notations that don't necessarily fit Python's idioms perfectly. This is especially the case with [] and getitem. A purer Python way of writing this could would be to use the much more unwieldy rc.direct_view(1), and so on, which would make clear that this wasn't just getting an item, and was actually creating a view.

Categories

Resources