How to access variable of other user Class(blender python)? - python

I want to access variable of other Class.
Static variable of other Class was very good accessed.
But dynimic changed variable value of ther Class was not good accessed.
Why I can't get the changed variable value?
bl_info = {
"name": "New Object",
"author": "Your Name Here",
"version": (1, 0),
"blender": (2, 75, 0),
"location": "View3D > Add > Mesh > New Object",
"description": "Adds a new Mesh Object",
"warning": "",
"wiki_url": "",
"category": "Add Mesh",
}
import bpy
class SelectFace(bpy.types.Operator):
bl_idname = "object.d0"
bl_label = "Select Face"
selected_faces = 2
def __init__(self):
self.selected_faces = 3
def execute(self, context):
print("self.selected_faces: ", self.selected_faces)
self.selected_faces += 1
bpy.ops.object.d1('INVOKE_DEFAULT')
return {'FINISHED'}
class OperationAboutSelectedFaces(bpy.types.Operator):
""" Test dialog. """
bl_idname = "object.d1"
bl_label = "Test dialog"
F_num = bpy.props.IntProperty(name="be Selected face", default=1)
#classmethod
def poll(self, context):
obj = context.object
return(obj and obj.type == 'MESH' and context.mode == 'OBJECT')
def invoke(self, context, event):
# This block code is Not Work! --- TypeError: bpy_struct.__new__(type): expected a single argument.
testInstance = SelectFace() # why not work?
print("testInstance.selected_faces: ", testInstance.selected_faces)
self.F_num = testInstance.selected_faces
# This block code is nice Work!
testInstance = SelectFace.selected_faces
print("testInstance: ", testInstance)
self.F_num = testInstance
return context.window_manager.invoke_props_dialog(self)
def execute(self, context):
context.active_object.data.polygons [self.F_num].select = True
return {'FINISHED'}
def register():
bpy.utils.register_class(SelectFace)
bpy.utils.register_class(OperationAboutSelectedFaces)
def unregister():
bpy.utils.unregister_class(SelectFace)
bpy.utils.unregister_class(OperationAboutSelectedFaces)
if __name__ == "__main__":
register()
bpy.ops.object.d0()

An operator in blender is used to perform an action. While we use a class to define that action and related properties, we shouldn't treat them as normal python classes. The properties of an operator should be used to adjust the action peformed, not to hold variable data.
As the operators properties control the result of the operator, they are used by blender to perform undo/redo steps. These properties are also adjustable by the user using the operator properties panel by pressing F6 and can also be found at the bottom of the toolbar region.
Add bl_options = {'REGISTER', 'UNDO'} to your operator to allow a user to adjust your operator. You can also customise the display within this panel by giving your operator a draw(self,context) method.
To control how an operator performs it's task when we call it directly, we can add the properties to the operator call -
bpy.ops.object.d1(F_num=4, val2=3.6)
If you are adding an operator button to a panel you can use -
row.operator('object.d1').F_num = 4
or if you need to set multiple values you can use -
op = row.operator('object.d1')
op.F_num = 4
op.val2 = 3.6
The example you provided uses a property that appears to only be valid for one object, if the user selects another object it will no longer be valid. This property would work better as an object property, you can add a property to the object class (or several others listed as subclasses of ID) by adding it in your addons register() and removing it in unregister()
def register():
bpy.types.Object.selected_faces = bpy.props.IntProperty()
def unregister():
del bpy.types.Object.selected_faces
For that example you could even count the selected faces when you needed the value -
selected_faces_count = len([f for f in obj.data.polygons if f.select])

I assume that
testInstance = SelectFace() # why not work?
is the real question.
see:
https://www.blender.org/api/blender_python_api_2_60a_release/info_overview.html
seems it is not expected that you write code that creates an instance of an bpy.types.Operator. Perhaps Blender handles bpy.types.Operator sub class creation in its own way.
"Notice these classes don’t define an init(self) function. While init() and del() will be called if defined, the class instances lifetime only spans the execution. So a panel for example will have a new instance for every redraw, for this reason there is rarely a cause to store variables in the panel instance. Instead, persistent variables should be stored in Blenders data so that the state can be restored when blender is restarted."
see also, Property Definitions: https://www.blender.org/api/blender_python_api_2_66a_release/bpy.props.html

Related

Can I dynamically generate boolean options on a custom Blender operator?

I'm trying to write a custom operator for Blender that:
Gets the objects currently in the scene (may be lots)
Filters them based on some criteria
Prompts the user to select/deselect any of the filtered objects (using checkboxes or similar)
Does something with the final selection.
I'm stuck on number 3. I'd like to show the user a window with checkboxes beside each of the filtered options, but to do that I'd have to be able to generate the properties dynamically.
The closest thing I've found so far is a bpy.props.EnumProperty, which takes callable to set its items. But it only supports 1 selection, whereas I need the user to be able to select multiple options.
Example:
def filter_objects(self, context):
return [obj for obj in bpy.data.objects if obj.name.startswith('A')]
class TurnObjectsBlue(bpy.types.Operator):
'TurnObjectsBlue'
bl_idname = 'object.turnobjectsblue'
bl_label = 'TurnObjectsBlue'
bl_options = {'REGISTER'}
# MultiSelectCheckboxes doesn't exist :(
chosen_objects: bpy.props.MultiSelectCheckboxes(
name='Select Objects',
)
def execute(self, context):
from coolmodule import turn_blue
for obj in self.user_selected_objects:
turn_blue(obj)
return {'FINISHED'}
def invoke(self, context, event):
return context.window_manager.invoke_props_dialog(self)
It looks like you want a CollectionProperty. These are basically native blender lists that can store blender properties, and can have as many items as you like.
To set one up, start by creating a new class that will represent one of the items in the CollectionProperty.
It should inherit from bpy.types.PropertyGroup, and in it you can define any properties that you want to be able to access (also make sure to register that class):
class MyCollectionItem(bpy.types.PropertyGroup):
# This will tell us which object this item is for
object: bpy.props.PointerProperty(type=bpy.types.Object)
# This is whether this item (and by extension the object) is enabled/disabled
is_item_selected: bpy.props.BoolProperty()
def register():
# Don't forget to register it!
bpy.utils.register_class(MyCollectionItem)
Then you can rewrite your operator to do something like this:
class TurnObjectsBlue(bpy.types.Operator):
'TurnObjectsBlue'
bl_idname = 'object.turnobjectsblue'
bl_label = 'TurnObjectsBlue'
bl_options = {'REGISTER'}
# This acts as a list, where each item is an instance of MyCollectionItem
chosen_objects: bpy.props.CollectionProperty(
type=MyCollectionItem,
)
def execute(self, context):
from coolmodule import turn_blue
# Loop through all items in the CollectionProperty, and if they are selected, do something
for item in self.chosen_objects:
if item.is_item_selected:
turn_blue(item.object)
return {'FINISHED'}
def invoke(self, context, event):
objects = filter_objects(context)
# For each object, add a new item to the CollectionProperty, and set it's object property
for object in objects:
# Note how items are created with CollectionProperty.add()
obj_item = self.chosen_objects.add()
obj_item.object = object
return context.window_manager.invoke_props_dialog(self)
def draw(self, context):
layout = self.layout
# Loop through all of the CollectionProperty items and draw the "is_item_selected" property
for item in self.chosen_objects:
layout.prop(item, "is_item_selected")
While collection properties can act like Blender lists, they work fairly differently to python lists, so I'd suggest reading a bit about them to try and understand how they work:
https://docs.blender.org/api/current/bpy.props.html#collection-example
Hope that helps!

Maya Python UI - Show an Objects ClassParent Class When Selected

Hello I need some help I'm a novice when it comes to advanced python coding, I've been trying to solve this problem but I'm unable to find an answer. I'm trying to find a way so that when someone clicks on an object inside Maya for example a basic sphere it will print the object class and parent class into each of the textFields; then should a user select something else like a cube it will instead show that object class&parent class. I know I need a function which will activate when an object is selected then place the value into the textFields, but I can't figure out how to do it. If anyone has a solution it would be greatly appreciated :)
import maya.cmds as cmds
from functools import partial
class drawUI(): #Function that will draw the entire window
#check to see if window exists
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
ObjParClass = cmds.textField(editable = False)
cmds.showWindow("UI_MainWindow") #shows window
The main stumbling block is that you're using the class setup in a very unusual way. The usual pattern is to have an __init__ method that creates a new copy ("instance") of the class; in you case you're executing the code when the class is defined rather than when you invoke it. The usual class shell looks like:
class Something (object) # derive from object in python 2.7, ie, in maya
def __init__(self):
self.variable = 1 # set persistent variables for the object
do_something() # other setup runs now, when a new Something is created
def method(self):
print self.variable # you can use any variables defined in 'self'
The usual way to create a reactive UI in maya is to use a scriptJob. These will fire a script callback when certain events happen inside of Maya. The easiest event for your purpose is SelectionChanged which fires when the selection changes.
The other thing you want to do is figure out how to pack this into a class. Sometimes it's a good idea to create your class object and then give it a show() or layout() method that creates the actual UI -- a lot depends on how much you need to set the object up before showing the window.
In this case I put the behavior into the __init__ so it will create the UI when you make the object. The __init__ also creates the scriptJob and parents it to the window (so it will disappear when the window does).
class drawUI(object):
def __init__(self):
if cmds.window("UI_MainWindow", exists = True):
cmds.deleteUI("UI_MainWindow")
#create actual window
self.window = cmds.window("UI_MainWindow", title = "User Interface Test", w = 500, h = 700, mnb = False, mxb = False, sizeable = False)
cmds.columnLayout("UI_MainLayout", adjustableColumn=True)
cmds.text(label="Object's Class:")
self.ObjClass = cmds.textField(text = cmds.objectType,editable = False)
cmds.text(label="Object's Parent Class:")
self.ObjParClass = cmds.textField(editable = False)
cmds.showWindow(self.window)
cmds.scriptJob (e = ("SelectionChanged", self.update_UI), p= self.window)
def update_UI(self, *_, **__):
sel = cmds.ls(selection=True) or []
c = ["-none-", "-none-"]
if sel:
c = cmds.nodeType(sel[0], i = True)
cmds.textField(self.ObjClass, e=True, text = c[-1])
cmds.textField(self.ObjParClass, e=True, text = ", ".join(c[:-1]))
test = drawUI()
By using self.update_UI without arguments in the scriptJob, we make sure that the function that fires knows which object it's updating. Using the self variables for the textfields lets us update the UI that goes with the window without worrying about remembering names in some other scope.
More details here

Replace text in textField with object name - Pymel

I've realized that there were similar questions located
here:
textfield query and prefix replacing
and
here:
Python - Change the textField after browsing - MAYA
However, these do not address the issue if you have two definitions and need the text in the textField to be queried (actually CHANGE the text in the textField).
I know from experience that doing what I have below in MelScript actually works, but for the sake of Python, and learning how to do it in Python, it seems to not work. Am I missing something? Do I need a lambda to get the name of the object selected and query the textField?
I have an example (a snip-bit of what needs to be fixed):
from pymel.core import *
def mainWindow():
window('myWin')
columnLayout(adj=1)
button('retopoplz', ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],
l='START RETOPOLOGY', c='Retopo(TextToMakeLive)')
TextToMakeLive = textField(ann='Mesh Selected', bgc=[.2,0,0],
edit=0, tx='NONE')
setParent('..')
showWindow('myWin')
def Retopo(TextToMakeLive):
#This tool selects the object to retopologize
MakeLiveField = textField(TextToMakeLive, q=1, tx=1)
MakeSelectionLive = (ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if MakeSelectionLive == 1:
TextToMakeLive = textField(TextToMakeLive, ed=1,
tx=MakeSelectionLive,
bgc=[0,.2,0])
shape = ls(s=MakeSelectionLive[0])
setAttr((shape + '.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer', num=1, nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
mainWindow()
GUI objects can always be edited -- including changing their commands -- as long as you store their names. So your mainWindow() could return the name(s) of gui controls you wanted to edit again and a second function could use those names to change the looks or behaviors of the created objects.
However, this is all much easier if you use a python class to 'remember' the names of the objects and any other state information: it's easy for the class to 'see' all the relevant info and state. Here's your original converted to classes:
from pymel.core import *
class RetopoWindow(object):
def __init__(self):
self.window = window('myWin')
columnLayout(adj=1)
button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY', c = self.do_retopo)
self.TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
def show(self):
showWindow(self.window)
def do_retopo(self, *_):
#This tool selects the object to retopologize
MakeLiveField= textField(self.TextToMakeLive,q=1,tx=1)
MakeSelectionLive=(ls(sl=1))
if MakeSelectionLive is None:
warning('Please select an object to retopologize')
if len( MakeSelectionLive) == 1:
TextToMakeLive=textField(self.TextToMakeLive,ed=1,tx=MakeSelectionLive,bgc=[0,.2,0])
shape=ls(s=MakeSelectionLive[0])
setAttr((shape+'.backfaceCulling'),3)
createDisplayLayer(n='RetopoLayer',num=1,nr=1)
makeLive(shape)
print('Retopology Activated!')
else:
warning('Select only ONE Object')
RetopoWindow().show()
As for the callbacks: useful reference here
You need to assign the command flag AFTER you have created your textField to be queried.
So you would do:
my_button = button('retopoplz',ann='Select a Mesh to Retopologize', bgc=[.15,.15,.15],l='START RETOPOLOGY')
TextToMakeLive=textField(ann='Mesh Selected', bgc=[.2,0,0],edit=0,tx='NONE')
button(my_button, e=True, c=windows.Callback(Retopo, TextToMakeLive))
You were along the right thought chain when you suggested lambda. Pymel's Callback can be more advantageous over lambda here. Check out the docs: http://download.autodesk.com/global/docs/maya2014/zh_cn/PyMel/generated/classes/pymel.core.windows/pymel.core.windows.Callback.html

jsonpickle force using __dict__ due to type evolution

This question is related to type evolution with jsonpickle (python)
Current state description:
I need to store an object to a JSON file using jsonpickle in python.
The object class CarState is generated by a script from another software component thus I can't change the class itself. This script automatically generates the __getstate__ and __setstate__ methods for the class that jsonpickle uses for serializing the object. The __getstate__ returns just a list of the values for each member variable, without the field names.
Therefore jsonpickle doesn't store the field name, but only the values within the JSON data (see code example below)
The Problem:
Let's say my program needs to extend the class CarState for a new version (Version 2) by an additional field (CarStateNewVersion). Now If it loads the JSON data from version 1, the data isn't assigned to the correct fields.
Here's an example code demonstrating the problem.
The class CarState is generated by the script and simplified here to show the problem. In Version 2 I update the class CarState with a new field (in the code snipped inserted as CarStateNewVersion to keep it simple)
#!/usr/bin/env python
import jsonpickle as jp
# Class using slots and implementing the __getstate__ method
# Let's say this is in program version 1
class CarState(object):
__slots__ = ['company','type']
_slot_types = ['string','string']
def __init__(self):
self.company = ""
self.type = ""
def __getstate__(self):
return [getattr(self, x) for x in self.__slots__]
def __setstate__(self, state):
for x, val in zip(self.__slots__, state):
setattr(self, x, val)
# Class using slots and implementing the __getstate__ method
# For program version 2 a new field 'year' is needed
class CarStateNewVersion(object):
__slots__ = ['company','year','type']
_slot_types = ['string','string','string']
def __init__(self):
self.company = ""
self.type = ""
self.year = "1900"
def __getstate__(self):
return [getattr(self, x) for x in self.__slots__]
def __setstate__(self, state):
for x, val in zip(self.__slots__, state):
setattr(self, x, val)
# Class using slots without the __getstate__ method
# Let's say this is in program version 1
class CarDict(object):
__slots__ = ['company','type']
_slot_types = ['string','string']
def __init__(self):
self.company = ""
self.type = ""
# Class using slots without the __getstate__ method
# For program version 2 a new field 'year' is needed
class CarDictNewVersion(object):
__slots__ = ['company','year','type']
_slot_types = ['string','string','string']
def __init__(self):
self.company = ""
self.type = ""
self.year = "1900"
if __name__ == "__main__":
# Version 1 stores the data
carDict = CarDict()
carDict.company = "Ford"
carDict.type = "Mustang"
print jp.encode(carDict)
# {"py/object": "__main__.CarDict", "company": "Ford", "type": "Mustang"}
# Now version 2 tries to load the data
carDictNewVersion = jp.decode('{"py/object": "__main__.CarDictNewVersion", "company": "Ford", "type": "Mustang"}')
# OK!
# carDictNewVersion.company = Ford
# carDictNewVersion.year = undefined
# carDictNewVersion.type = Mustang
# Version 1 stores the data
carState = CarState()
carState.company = "Ford"
carState.type = "Mustang"
print jp.encode(carState)
# {"py/object": "__main__.CarState", "py/state": ["Ford", "Mustang"]}
# Now version 2 tries to load the data
carStateNewVersion = jp.decode('{"py/object": "__main__.CarStateNewVersion", "py/state": ["Ford", "Mustang"]}')
# !!!! ERROR !!!!
# carDictNewVersion.company = Ford
# carDictNewVersion.year = Mustang
# carDictNewVersion.type = undefined
try:
carDictNewVersion.year
except:
carDictNewVersion.year = 1900
As you can see for the CarDict and CarDictNewVersion class, if __getstate__ isn't implemented, there's no problem with the newly added field because the JSON text also contains field names.
Question:
Is there a possibility to tell jsonpickle to not use __getstate__ and use the __dict__ instead to include the field names within the JSON data?
Or is there another possibility to somehow include the field names?
NOTE: I can't change the CarState class nor the containing __getstate__ method since it is generated through a script from another software component.
I can only change the code within the main method.
Or is there another serialization tool for python which creates human readable output and includes field names?
Additional Background info:
The class is generated using message definitions in ROS, namely by genpy
, and the generated class inherits from the Message class which implements the __getstate__ (see https://github.com/ros/genpy/blob/indigo-devel/src/genpy/message.py#L308)
Subclass CarState to implement your own pickle protocol methods, or register a handler with jsonpickle.

Python Accessing Dict That Has Defualt Values That Have Been Updated

So I have this class:
class hero():
def __init__(self, name="Jimmy", prof="Warrior", weapon="Sword"):
"""Constructor for hero"""
self.name = name
self.prof = prof
self.weapon = weapon
self.herodict = {
"Name": self.name,
"Class": self.prof,
"Weapon": self.weapon
}
self.herotext = {
"Welcome": "Greetings, hero. What is thine name? ",
"AskClass": "A fine name, {Name}. What is your class? ",
"AskWeapon": "A {Class}, hmm? What shalt thy weapon be? ",
}
def setHeroDicts(self, textkey, herokey):
n = raw_input(self.herotext[textkey].format(**self.herodict))
if n == "":
n = self.herodict[herokey]
self.herodict[herokey] = n
#print self.herodict[herokey]
def heroMake(self):
h = hero()
h.setHeroDicts("Welcome", "Name")
h.setHeroDicts("AskClass", "Class")
h.setHeroDicts("AskWeapon", "Weapon")
And in another class I have this executing
def Someclass(self):
h = hero()
print h.herodict["Class"]
h.heroMake()
print h.getClass()
if "Mage" in h.herodict["Class"]:
print "OMG MAGE"
elif "Warrior" in h.herodict["Class"]:
print "Warrior!"
else:
print "NONE"
So if I input nothing each time, it will result in a blank user input, and give the default values. But if I put an input, then it will change the herodict values to what I customize. My problem is, if I try and access those updated values in Someclass it only gives me the default values instead of the new ones. How do I go about accessing the updated values?
The main issue with your class is that you are creating a new object within heromake instead of using the existing one. You can fix this by replacing h with self (so that each time you are calling setHeroDicts on the object):
def heromake(self):
self.setHeroDicts("Welcome", "Name")
self.setHeroDicts("AskClass", "Class")
self.setHeroDicts("AskWeapon", "Weapon")
The first argument to a method is always set to the instance itself, so if you want to interact with the instance or mutate it, you need to use it directly. When you do h = hero() in your original code, you create a whole new hero object, manipulate it and then it disappears when control passes back to your function.
A few other notes: you should name your classes with CamelCase, so it's easier to tell they are classes (e.g., you should really have class Hero) and in python 2, you need to make your classes descend from object (so class Hero(object)). Finally, you are duplicating nearly the entire point of having classes with your herodict, you should consider accessing the attributes of the object directly, instead of having the intermediary herodict (e.g., instead of doing h.herodict["Class"] you could do h.prof directly.

Categories

Resources