blender/release/scripts/startup/bl_operators/wm.py

2048 lines
60 KiB
Python
Raw Normal View History

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
2010-02-12 13:34:04 +00:00
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
2010-06-09 19:12:03 +00:00
# <pep8 compliant>
import bpy
from bpy.types import Operator
from bpy.props import (StringProperty,
BoolProperty,
IntProperty,
FloatProperty,
EnumProperty,
)
from bpy.app.translations import pgettext_tip as tip_
2012-01-01 08:52:54 +00:00
rna_path_prop = StringProperty(
name="Context Attributes",
2012-07-07 13:51:07 +00:00
description="RNA context string",
maxlen=1024,
)
rna_reverse_prop = BoolProperty(
name="Reverse",
description="Cycle backwards",
default=False,
)
rna_relative_prop = BoolProperty(
name="Relative",
description="Apply relative to the current value (delta)",
default=False,
)
2010-01-31 14:46:28 +00:00
def context_path_validate(context, data_path):
try:
value = eval("context.%s" % data_path) if data_path else Ellipsis
except AttributeError as e:
if str(e).startswith("'NoneType'"):
# One of the items in the rna path is None, just ignore this
value = Ellipsis
else:
2011-10-17 06:58:07 +00:00
# We have a real error in the rna path, don't ignore that
raise
return value
def operator_value_is_undo(value):
if value in {None, Ellipsis}:
return False
# typical properties or objects
id_data = getattr(value, "id_data", Ellipsis)
if id_data is None:
return False
elif id_data is Ellipsis:
# handle mathutils types
id_data = getattr(getattr(value, "owner", None), "id_data", None)
if id_data is None:
return False
# return True if its a non window ID type
return (isinstance(id_data, bpy.types.ID) and
(not isinstance(id_data, (bpy.types.WindowManager,
bpy.types.Screen,
bpy.types.Brush,
))))
def operator_path_is_undo(context, data_path):
# note that if we have data paths that use strings this could fail
2011-10-17 06:58:07 +00:00
# luckily we don't do this!
#
# When we cant find the data owner assume no undo is needed.
data_path_head = data_path.rpartition(".")[0]
if not data_path_head:
return False
value = context_path_validate(context, data_path_head)
return operator_value_is_undo(value)
def operator_path_undo_return(context, data_path):
return {'FINISHED'} if operator_path_is_undo(context, data_path) else {'CANCELLED'}
def operator_value_undo_return(value):
return {'FINISHED'} if operator_value_is_undo(value) else {'CANCELLED'}
def execute_context_assign(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
if getattr(self, "relative", False):
exec("context.%s += self.value" % data_path)
else:
exec("context.%s = self.value" % data_path)
return operator_path_undo_return(context, data_path)
class BRUSH_OT_active_index_set(Operator):
2012-06-19 22:17:19 +00:00
"""Set active sculpt/paint brush from it's number"""
bl_idname = "brush.active_index_set"
bl_label = "Set Brush Number"
mode = StringProperty(
name="Mode",
description="Paint mode to set brush for",
maxlen=1024,
)
index = IntProperty(
name="Number",
description="Brush number",
)
2011-01-01 07:20:34 +00:00
_attr_dict = {"sculpt": "use_paint_sculpt",
"vertex_paint": "use_paint_vertex",
"weight_paint": "use_paint_weight",
"image_paint": "use_paint_image",
}
def execute(self, context):
attr = self._attr_dict.get(self.mode)
if attr is None:
return {'CANCELLED'}
2012-01-17 17:57:20 +00:00
toolsettings = context.tool_settings
for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))):
if i == self.index:
2012-01-17 17:57:20 +00:00
getattr(toolsettings, self.mode).brush = brush
return {'FINISHED'}
return {'CANCELLED'}
2011-01-01 07:20:34 +00:00
class WM_OT_context_set_boolean(Operator):
"""Set a context value"""
bl_idname = "wm.context_set_boolean"
bl_label = "Context Set Boolean"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = BoolProperty(
name="Value",
description="Assignment value",
default=True,
)
execute = execute_context_assign
class WM_OT_context_set_int(Operator): # same as enum
"""Set a context value"""
bl_idname = "wm.context_set_int"
bl_label = "Context Set"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = IntProperty(
name="Value",
description="Assign value",
default=0,
)
relative = rna_relative_prop
execute = execute_context_assign
class WM_OT_context_scale_float(Operator):
"""Scale a float context value"""
bl_idname = "wm.context_scale_float"
bl_label = "Context Scale Float"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = FloatProperty(
name="Value",
description="Assign value",
default=1.0,
)
def execute(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
value = self.value
if value == 1.0: # nothing to do
return {'CANCELLED'}
exec("context.%s *= value" % data_path)
return operator_path_undo_return(context, data_path)
class WM_OT_context_scale_int(Operator):
"""Scale an int context value"""
bl_idname = "wm.context_scale_int"
bl_label = "Context Scale Int"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = FloatProperty(
name="Value",
description="Assign value",
default=1.0,
)
always_step = BoolProperty(
name="Always Step",
description="Always adjust the value by a minimum of 1 when 'value' is not 1.0",
default=True,
)
def execute(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
value = self.value
2010-09-07 15:17:42 +00:00
if value == 1.0: # nothing to do
return {'CANCELLED'}
if getattr(self, "always_step", False):
if value > 1.0:
add = "1"
func = "max"
else:
add = "-1"
func = "min"
exec("context.%s = %s(round(context.%s * value), context.%s + %s)" %
(data_path, func, data_path, data_path, add))
else:
exec("context.%s *= value" % data_path)
return operator_path_undo_return(context, data_path)
class WM_OT_context_set_float(Operator): # same as enum
"""Set a context value"""
bl_idname = "wm.context_set_float"
bl_label = "Context Set Float"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = FloatProperty(
name="Value",
description="Assignment value",
default=0.0,
)
relative = rna_relative_prop
execute = execute_context_assign
class WM_OT_context_set_string(Operator): # same as enum
"""Set a context value"""
bl_idname = "wm.context_set_string"
bl_label = "Context Set String"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = StringProperty(
name="Value",
description="Assign value",
maxlen=1024,
)
execute = execute_context_assign
class WM_OT_context_set_enum(Operator):
"""Set a context value"""
bl_idname = "wm.context_set_enum"
bl_label = "Context Set Enum"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = StringProperty(
name="Value",
description="Assignment value (as a string)",
maxlen=1024,
)
execute = execute_context_assign
class WM_OT_context_set_value(Operator):
"""Set a context value"""
bl_idname = "wm.context_set_value"
bl_label = "Context Set Value"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = StringProperty(
name="Value",
description="Assignment value (as a string)",
maxlen=1024,
)
def execute(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
exec("context.%s = %s" % (data_path, self.value))
return operator_path_undo_return(context, data_path)
class WM_OT_context_toggle(Operator):
"""Toggle a context value"""
bl_idname = "wm.context_toggle"
bl_label = "Context Toggle"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
def execute(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
exec("context.%s = not (context.%s)" % (data_path, data_path))
return operator_path_undo_return(context, data_path)
class WM_OT_context_toggle_enum(Operator):
"""Toggle a context value"""
bl_idname = "wm.context_toggle_enum"
bl_label = "Context Toggle Values"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value_1 = StringProperty(
name="Value",
description="Toggle enum",
maxlen=1024,
)
value_2 = StringProperty(
name="Value",
description="Toggle enum",
maxlen=1024,
)
def execute(self, context):
data_path = self.data_path
if context_path_validate(context, data_path) is Ellipsis:
return {'PASS_THROUGH'}
# failing silently is not ideal, but we don't want errors for shortcut
# keys that some values that are only available in a particular context
try:
exec("context.%s = ('%s', '%s')[context.%s != '%s']" %
(data_path, self.value_1,
self.value_2, data_path,
self.value_2,
))
except:
return {'PASS_THROUGH'}
return operator_path_undo_return(context, data_path)
class WM_OT_context_cycle_int(Operator):
2012-07-07 13:51:07 +00:00
"""Set a context value (useful for cycling active material, """ \
"""vertex keys, groups, etc.)"""
bl_idname = "wm.context_cycle_int"
bl_label = "Context Int Cycle"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
reverse = rna_reverse_prop
def execute(self, context):
data_path = self.data_path
value = context_path_validate(context, data_path)
if value is Ellipsis:
return {'PASS_THROUGH'}
if self.reverse:
value -= 1
else:
value += 1
exec("context.%s = value" % data_path)
if value != eval("context.%s" % data_path):
# relies on rna clamping integers out of the range
if self.reverse:
value = (1 << 31) - 1
else:
value = -1 << 31
exec("context.%s = value" % data_path)
return operator_path_undo_return(context, data_path)
class WM_OT_context_cycle_enum(Operator):
"""Toggle a context value"""
bl_idname = "wm.context_cycle_enum"
bl_label = "Context Enum Cycle"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
reverse = rna_reverse_prop
def execute(self, context):
data_path = self.data_path
value = context_path_validate(context, data_path)
if value is Ellipsis:
return {'PASS_THROUGH'}
orig_value = value
# Have to get rna enum values
rna_struct_str, rna_prop_str = data_path.rsplit('.', 1)
i = rna_prop_str.find('[')
2011-10-17 06:58:07 +00:00
# just in case we get "context.foo.bar[0]"
if i != -1:
rna_prop_str = rna_prop_str[0:i]
rna_struct = eval("context.%s.rna_type" % rna_struct_str)
rna_prop = rna_struct.properties[rna_prop_str]
if type(rna_prop) != bpy.types.EnumProperty:
raise Exception("expected an enum property")
enums = rna_struct.properties[rna_prop_str].enum_items.keys()
orig_index = enums.index(orig_value)
# Have the info we need, advance to the next item
if self.reverse:
if orig_index == 0:
advance_enum = enums[-1]
else:
2010-09-07 15:17:42 +00:00
advance_enum = enums[orig_index - 1]
else:
if orig_index == len(enums) - 1:
advance_enum = enums[0]
else:
advance_enum = enums[orig_index + 1]
# set the new value
exec("context.%s = advance_enum" % data_path)
return operator_path_undo_return(context, data_path)
class WM_OT_context_cycle_array(Operator):
"""Set a context array value """ \
"""(useful for cycling the active mesh edit mode)"""
bl_idname = "wm.context_cycle_array"
bl_label = "Context Array Cycle"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
reverse = rna_reverse_prop
def execute(self, context):
data_path = self.data_path
value = context_path_validate(context, data_path)
if value is Ellipsis:
return {'PASS_THROUGH'}
def cycle(array):
if self.reverse:
array.insert(0, array.pop())
else:
array.append(array.pop(0))
return array
exec("context.%s = cycle(context.%s[:])" % (data_path, data_path))
return operator_path_undo_return(context, data_path)
class WM_OT_context_menu_enum(Operator):
bl_idname = "wm.context_menu_enum"
bl_label = "Context Enum Menu"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
def execute(self, context):
data_path = self.data_path
value = context_path_validate(context, data_path)
if value is Ellipsis:
return {'PASS_THROUGH'}
base_path, prop_string = data_path.rsplit(".", 1)
value_base = context_path_validate(context, base_path)
prop = value_base.bl_rna.properties[prop_string]
def draw_cb(self, context):
layout = self.layout
layout.prop(value_base, prop_string, expand=True)
context.window_manager.popup_menu(draw_func=draw_cb, title=prop.name, icon=prop.icon)
return {'PASS_THROUGH'}
class WM_OT_context_set_id(Operator):
"""Set a context value to an ID data-block"""
bl_idname = "wm.context_set_id"
bl_label = "Set Library ID"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path_prop
value = StringProperty(
name="Value",
description="Assign value",
maxlen=1024,
)
def execute(self, context):
value = self.value
data_path = self.data_path
# match the pointer type from the target property to bpy.data.*
# so we lookup the correct list.
data_path_base, data_path_prop = data_path.rsplit(".", 1)
data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
data_prop_rna_type = data_prop_rna.fixed_type
id_iter = None
for prop in bpy.data.rna_type.properties:
if prop.rna_type.identifier == "CollectionProperty":
if prop.fixed_type == data_prop_rna_type:
id_iter = prop.identifier
break
if id_iter:
value_id = getattr(bpy.data, id_iter).get(value)
exec("context.%s = value_id" % data_path)
return operator_path_undo_return(context, data_path)
doc_id = StringProperty(
name="Doc ID",
maxlen=1024,
options={'HIDDEN'},
)
doc_new = StringProperty(
name="Edit Description",
maxlen=1024,
)
data_path_iter = StringProperty(
description="The data path relative to the context, must point to an iterable")
data_path_item = StringProperty(
description="The data path from each iterable to the value (int or float)")
class WM_OT_context_collection_boolean_set(Operator):
"""Set boolean values for a collection of items"""
bl_idname = "wm.context_collection_boolean_set"
bl_label = "Context Collection Boolean Set"
bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
data_path_iter = data_path_iter
data_path_item = data_path_item
type = EnumProperty(
name="Type",
items=(('TOGGLE', "Toggle", ""),
('ENABLE', "Enable", ""),
('DISABLE', "Disable", ""),
),
)
def execute(self, context):
data_path_iter = self.data_path_iter
data_path_item = self.data_path_item
items = list(getattr(context, data_path_iter))
items_ok = []
is_set = False
for item in items:
try:
value_orig = eval("item." + data_path_item)
except:
continue
2012-10-08 08:28:05 +00:00
if value_orig is True:
is_set = True
2012-10-08 08:28:05 +00:00
elif value_orig is False:
pass
else:
self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
(data_path_iter, data_path_item))
return {'CANCELLED'}
items_ok.append(item)
# avoid undo push when nothing to do
if not items_ok:
return {'CANCELLED'}
if self.type == 'ENABLE':
is_set = True
elif self.type == 'DISABLE':
is_set = False
else:
is_set = not is_set
exec_str = "item.%s = %s" % (data_path_item, is_set)
for item in items_ok:
exec(exec_str)
return operator_value_undo_return(item)
class WM_OT_context_modal_mouse(Operator):
"""Adjust arbitrary values with mouse input"""
bl_idname = "wm.context_modal_mouse"
bl_label = "Context Modal Mouse"
bl_options = {'GRAB_POINTER', 'BLOCKING', 'UNDO', 'INTERNAL'}
data_path_iter = data_path_iter
data_path_item = data_path_item
header_text = StringProperty(
name="Header Text",
description="Text to display in header during scale",
)
input_scale = FloatProperty(
description="Scale the mouse movement by this value before applying the delta",
default=0.01,
)
invert = BoolProperty(
description="Invert the mouse input",
default=False,
)
initial_x = IntProperty(options={'HIDDEN'})
def _values_store(self, context):
data_path_iter = self.data_path_iter
data_path_item = self.data_path_item
self._values = values = {}
for item in getattr(context, data_path_iter):
try:
value_orig = eval("item." + data_path_item)
except:
continue
# check this can be set, maybe this is library data.
try:
exec("item.%s = %s" % (data_path_item, value_orig))
except:
continue
values[item] = value_orig
def _values_delta(self, delta):
delta *= self.input_scale
if self.invert:
delta = - delta
data_path_item = self.data_path_item
for item, value_orig in self._values.items():
if type(value_orig) == int:
exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
else:
exec("item.%s = %f" % (data_path_item, value_orig + delta))
def _values_restore(self):
data_path_item = self.data_path_item
for item, value_orig in self._values.items():
exec("item.%s = %s" % (data_path_item, value_orig))
self._values.clear()
def _values_clear(self):
self._values.clear()
def modal(self, context, event):
event_type = event.type
if event_type == 'MOUSEMOVE':
delta = event.mouse_x - self.initial_x
self._values_delta(delta)
header_text = self.header_text
if header_text:
if len(self._values) == 1:
(item, ) = self._values.keys()
header_text = header_text % eval("item.%s" % self.data_path_item)
else:
header_text = (self.header_text % delta) + " (delta)"
context.area.header_text_set(header_text)
elif 'LEFTMOUSE' == event_type:
item = next(iter(self._values.keys()))
self._values_clear()
context.area.header_text_set()
return operator_value_undo_return(item)
elif event_type in {'RIGHTMOUSE', 'ESC'}:
self._values_restore()
context.area.header_text_set()
return {'CANCELLED'}
return {'RUNNING_MODAL'}
def invoke(self, context, event):
self._values_store(context)
if not self._values:
2010-03-06 01:40:29 +00:00
self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
(self.data_path_iter, self.data_path_item))
2010-03-06 01:40:29 +00:00
return {'CANCELLED'}
else:
self.initial_x = event.mouse_x
context.window_manager.modal_handler_add(self)
return {'RUNNING_MODAL'}
class WM_OT_url_open(Operator):
"Open a website in the web-browser"
bl_idname = "wm.url_open"
bl_label = ""
bl_options = {'INTERNAL'}
url = StringProperty(
name="URL",
description="URL to open",
)
def execute(self, context):
import webbrowser
webbrowser.open(self.url)
return {'FINISHED'}
class WM_OT_path_open(Operator):
"Open a path in a file browser"
bl_idname = "wm.path_open"
bl_label = ""
bl_options = {'INTERNAL'}
filepath = StringProperty(
subtype='FILE_PATH',
options={'SKIP_SAVE'},
)
def execute(self, context):
import sys
2011-09-23 13:47:29 +00:00
import os
import subprocess
filepath = self.filepath
if not filepath:
self.report({'ERROR'}, "File path was not set")
return {'CANCELLED'}
filepath = bpy.path.abspath(filepath)
filepath = os.path.normpath(filepath)
if not os.path.exists(filepath):
self.report({'ERROR'}, "File '%s' not found" % filepath)
return {'CANCELLED'}
if sys.platform[:3] == "win":
os.startfile(filepath)
elif sys.platform == "darwin":
subprocess.Popen(["open", filepath])
else:
try:
subprocess.Popen(["xdg-open", filepath])
except OSError:
# xdg-open *should* be supported by recent Gnome, KDE, Xfce
pass
return {'FINISHED'}
def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
id_split = doc_id.split(".")
url = rna = None
if len(id_split) == 1: # rna, class
if do_url:
url = "%s/bpy.types.%s.html" % (url_prefix, id_split[0])
else:
rna = "bpy.types.%s" % id_split[0]
elif len(id_split) == 2: # rna, class.prop
class_name, class_prop = id_split
# an operator (common case - just button referencing an op)
if hasattr(bpy.types, class_name.upper() + "_OT_" + class_prop):
if do_url:
url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop))
else:
rna = "bpy.ops.%s.%s" % (class_name, class_prop)
else:
rna_class = getattr(bpy.types, class_name)
# an operator setting (selected from a running operator), rare case
# note: Py defined operators are subclass of Operator,
# C defined operators are subclass of OperatorProperties.
# we may need to check on this at some point.
if issubclass(rna_class, (bpy.types.Operator, bpy.types.OperatorProperties)):
# note: ignore the prop name since we don't have a way to link into it
class_name, class_prop = class_name.split("_OT_", 1)
class_name = class_name.lower()
if do_url:
url = ("%s/bpy.ops.%s.html#bpy.ops.%s.%s" % (url_prefix, class_name, class_name, class_prop))
else:
rna = "bpy.ops.%s.%s" % (class_name, class_prop)
else:
# an RNA setting, common case
# detect if this is a inherited member and use that name instead
rna_parent = rna_class.bl_rna
rna_prop = rna_parent.properties[class_prop]
rna_parent = rna_parent.base
while rna_parent and rna_prop == rna_parent.properties.get(class_prop):
class_name = rna_parent.identifier
rna_parent = rna_parent.base
if do_url:
url = ("%s/bpy.types.%s.html#bpy.types.%s.%s" % (url_prefix, class_name, class_name, class_prop))
else:
rna = ("bpy.types.%s.%s" % (class_name, class_prop))
2012-06-19 22:17:19 +00:00
return url if do_url else rna
class WM_OT_doc_view_manual(Operator):
"""Load online manual"""
bl_idname = "wm.doc_view_manual"
bl_label = "View Manual"
doc_id = doc_id
@staticmethod
def _find_reference(rna_id, url_mapping, verbose=True):
if verbose:
print("online manual check for: '%s'... " % rna_id)
from fnmatch import fnmatch
for pattern, url_suffix in url_mapping:
if fnmatch(rna_id, pattern):
if verbose:
print(" match found: '%s' --> '%s'" % (pattern, url_suffix))
return url_suffix
if verbose:
print("match not found")
return None
@staticmethod
def _lookup_rna_url(rna_id, verbose=True):
for prefix, url_manual_mapping in bpy.utils.manual_map():
rna_ref = WM_OT_doc_view_manual._find_reference(rna_id, url_manual_mapping, verbose=verbose)
if rna_ref is not None:
url = prefix + rna_ref
return url
def execute(self, context):
rna_id = _wm_doc_get_id(self.doc_id, do_url=False)
if rna_id is None:
return {'PASS_THROUGH'}
url = self._lookup_rna_url(rna_id)
if url is None:
self.report({'WARNING'}, "No reference available %r, "
"Update info in 'rna_wiki_reference.py' "
" or callback to bpy.utils.manual_map()" %
self.doc_id)
return {'CANCELLED'}
else:
import webbrowser
webbrowser.open(url)
return {'FINISHED'}
class WM_OT_doc_view(Operator):
"""Load online reference docs"""
bl_idname = "wm.doc_view"
bl_label = "View Documentation"
doc_id = doc_id
if bpy.app.version_cycle == "release":
_prefix = ("http://www.blender.org/documentation/blender_python_api_%s%s_release" %
("_".join(str(v) for v in bpy.app.version[:2]), bpy.app.version_char))
else:
_prefix = ("http://www.blender.org/documentation/blender_python_api_%s" %
"_".join(str(v) for v in bpy.app.version))
def execute(self, context):
url = _wm_doc_get_id(self.doc_id, do_url=True, url_prefix=self._prefix)
if url is None:
return {'PASS_THROUGH'}
import webbrowser
webbrowser.open(url)
return {'FINISHED'}
'''
class WM_OT_doc_edit(Operator):
"""Edit online reference docs"""
bl_idname = "wm.doc_edit"
bl_label = "Edit Documentation"
doc_id = doc_id
doc_new = doc_new
_url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
def _send_xmlrpc(self, data_dict):
print("sending data:", data_dict)
import xmlrpc.client
user = "blenderuser"
pwd = "blender>user"
docblog = xmlrpc.client.ServerProxy(self._url)
docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
def execute(self, context):
doc_id = self.doc_id
doc_new = self.doc_new
class_name, class_prop = doc_id.split('.')
if not doc_new:
self.report({'ERROR'}, "No input given for '%s'" % doc_id)
return {'CANCELLED'}
# check if this is an operator
op_name = class_name.upper() + '_OT_' + class_prop
op_class = getattr(bpy.types, op_name, None)
# Upload this to the web server
upload = {}
if op_class:
rna = op_class.bl_rna
doc_orig = rna.description
if doc_orig == doc_new:
return {'RUNNING_MODAL'}
print("op - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
upload["title"] = 'OPERATOR %s:%s' % (doc_id, doc_orig)
else:
rna = getattr(bpy.types, class_name).bl_rna
doc_orig = rna.properties[class_prop].description
if doc_orig == doc_new:
return {'RUNNING_MODAL'}
print("rna - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
upload["title"] = 'RNA %s:%s' % (doc_id, doc_orig)
upload["description"] = doc_new
self._send_xmlrpc(upload)
return {'FINISHED'}
def draw(self, context):
layout = self.layout
layout.label(text="Descriptor ID: '%s'" % self.doc_id)
layout.prop(self, "doc_new", text="")
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)
'''
rna_path = StringProperty(
name="Property Edit",
description="Property data_path edit",
maxlen=1024,
options={'HIDDEN'},
)
rna_value = StringProperty(
name="Property Value",
description="Property value edit",
maxlen=1024,
)
rna_property = StringProperty(
name="Property Name",
description="Property name edit",
maxlen=1024,
)
rna_min = FloatProperty(
name="Min",
default=0.0,
precision=3,
)
rna_max = FloatProperty(
name="Max",
default=1.0,
precision=3,
)
class WM_OT_properties_edit(Operator):
bl_idname = "wm.properties_edit"
bl_label = "Edit Property"
# register only because invoke_props_popup requires.
bl_options = {'REGISTER', 'INTERNAL'}
data_path = rna_path
property = rna_property
value = rna_value
min = rna_min
max = rna_max
description = StringProperty(
name="Tooltip",
)
def execute(self, context):
2013-07-03 01:20:32 +00:00
from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear
data_path = self.data_path
value = self.value
prop = self.property
prop_old = getattr(self, "_last_prop", [None])[0]
if prop_old is None:
self.report({'ERROR'}, "Direct execution not supported")
return {'CANCELLED'}
try:
value_eval = eval(value)
# assert else None -> None, not "None", see [#33431]
assert(type(value_eval) in {str, float, int, bool, tuple, list})
except:
value_eval = value
# First remove
item = eval("context.%s" % data_path)
prop_type_old = type(item[prop_old])
rna_idprop_ui_prop_clear(item, prop_old)
exec_str = "del item[%r]" % prop_old
# print(exec_str)
exec(exec_str)
# Reassign
exec_str = "item[%r] = %s" % (prop, repr(value_eval))
# print(exec_str)
exec(exec_str)
self._last_prop[:] = [prop]
prop_type = type(item[prop])
prop_ui = rna_idprop_ui_prop_get(item, prop)
if prop_type in {float, int}:
prop_ui["soft_min"] = prop_ui["min"] = prop_type(self.min)
prop_ui["soft_max"] = prop_ui["max"] = prop_type(self.max)
2012-06-19 22:17:19 +00:00
prop_ui["description"] = self.description
# If we have changed the type of the property, update its potential anim curves!
if prop_type_old != prop_type:
data_path = '["%s"]' % bpy.utils.escape_identifier(prop)
done = set()
2014-02-18 21:02:59 +00:00
def _update(fcurves):
for fcu in fcurves:
if fcu not in done and fcu.data_path == data_path:
fcu.update_autoflags(item)
done.add(fcu)
def _update_strips(strips):
for st in strips:
if st.type == 'CLIP' and st.action:
_update(st.action.fcurves)
elif st.type == 'META':
_update_strips(st.strips)
adt = getattr(item, "animation_data", None)
if adt is not None:
if adt.action:
_update(adt.action.fcurves)
if adt.drivers:
_update(adt.drivers)
if adt.nla_tracks:
for nt in adt.nla_tracks:
_update_strips(nt.strips)
# otherwise existing buttons which reference freed
# memory may crash blender [#26510]
# context.area.tag_redraw()
for win in context.window_manager.windows:
for area in win.screen.areas:
area.tag_redraw()
return {'FINISHED'}
def invoke(self, context, event):
2013-07-03 01:20:32 +00:00
from rna_prop_ui import rna_idprop_ui_prop_get
data_path = self.data_path
if not data_path:
self.report({'ERROR'}, "Data path not set")
return {'CANCELLED'}
self._last_prop = [self.property]
item = eval("context.%s" % data_path)
# setup defaults
2011-10-17 06:58:07 +00:00
prop_ui = rna_idprop_ui_prop_get(item, self.property, False) # don't create
if prop_ui:
self.min = prop_ui.get("min", -1000000000)
self.max = prop_ui.get("max", 1000000000)
self.description = prop_ui.get("description", "")
wm = context.window_manager
return wm.invoke_props_dialog(self)
2010-03-14 23:26:17 +00:00
class WM_OT_properties_add(Operator):
bl_idname = "wm.properties_add"
bl_label = "Add Property"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path
def execute(self, context):
2013-07-03 01:20:32 +00:00
from rna_prop_ui import rna_idprop_ui_prop_get
data_path = self.data_path
item = eval("context.%s" % data_path)
def unique_name(names):
prop = "prop"
prop_new = prop
i = 1
while prop_new in names:
prop_new = prop + str(i)
i += 1
return prop_new
prop = unique_name(item.keys())
item[prop] = 1.0
# not essential, but without this we get [#31661]
prop_ui = rna_idprop_ui_prop_get(item, prop)
prop_ui["soft_min"] = prop_ui["min"] = 0.0
prop_ui["soft_max"] = prop_ui["max"] = 1.0
return {'FINISHED'}
class WM_OT_properties_context_change(Operator):
"Jump to a different tab inside the properties editor"
bl_idname = "wm.properties_context_change"
bl_label = ""
bl_options = {'INTERNAL'}
context = StringProperty(
name="Context",
maxlen=64,
)
def execute(self, context):
context.space_data.context = self.context
return {'FINISHED'}
class WM_OT_properties_remove(Operator):
"""Internal use (edit a property data_path)"""
bl_idname = "wm.properties_remove"
bl_label = "Remove Property"
bl_options = {'UNDO', 'INTERNAL'}
data_path = rna_path
property = rna_property
def execute(self, context):
data_path = self.data_path
item = eval("context.%s" % data_path)
del item[self.property]
return {'FINISHED'}
2010-09-07 15:17:42 +00:00
class WM_OT_keyconfig_activate(Operator):
bl_idname = "wm.keyconfig_activate"
bl_label = "Activate Keyconfig"
filepath = StringProperty(
subtype='FILE_PATH',
)
def execute(self, context):
if bpy.utils.keyconfig_set(self.filepath, report=self.report):
return {'FINISHED'}
else:
return {'CANCELLED'}
2013-06-27 03:05:19 +00:00
class WM_OT_appconfig_default(Operator):
bl_idname = "wm.appconfig_default"
bl_label = "Default Application Configuration"
def execute(self, context):
2011-09-23 13:47:29 +00:00
import os
context.window_manager.keyconfigs.active = context.window_manager.keyconfigs.default
filepath = os.path.join(bpy.utils.preset_paths("interaction")[0], "blender.py")
if os.path.exists(filepath):
bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
return {'FINISHED'}
class WM_OT_appconfig_activate(Operator):
bl_idname = "wm.appconfig_activate"
bl_label = "Activate Application Configuration"
filepath = StringProperty(
subtype='FILE_PATH',
)
def execute(self, context):
2011-09-23 13:47:29 +00:00
import os
bpy.utils.keyconfig_set(self.filepath)
filepath = self.filepath.replace("keyconfig", "interaction")
if os.path.exists(filepath):
bpy.ops.script.execute_preset(filepath=filepath, menu_idname="USERPREF_MT_interaction_presets")
return {'FINISHED'}
2011-01-01 07:20:34 +00:00
class WM_OT_sysinfo(Operator):
"""Generate System Info"""
bl_idname = "wm.sysinfo"
bl_label = "System Info"
def execute(self, context):
import sys_info
sys_info.write_sysinfo(self)
return {'FINISHED'}
class WM_OT_copy_prev_settings(Operator):
"""Copy settings from previous version"""
bl_idname = "wm.copy_prev_settings"
bl_label = "Copy Previous Settings"
def execute(self, context):
2011-09-23 13:47:29 +00:00
import os
import shutil
ver = bpy.app.version
ver_old = ((ver[0] * 100) + ver[1]) - 1
path_src = bpy.utils.resource_path('USER', ver_old // 100, ver_old % 100)
path_dst = bpy.utils.resource_path('USER')
if os.path.isdir(path_dst):
self.report({'ERROR'}, "Target path %r exists" % path_dst)
elif not os.path.isdir(path_src):
self.report({'ERROR'}, "Source path %r exists" % path_src)
else:
shutil.copytree(path_src, path_dst, symlinks=True)
# reload recent-files.txt
bpy.ops.wm.read_history()
2011-10-17 06:58:07 +00:00
# don't loose users work if they open the splash later.
if bpy.data.is_saved is bpy.data.is_dirty is False:
bpy.ops.wm.read_homefile()
else:
self.report({'INFO'}, "Reload Start-Up file to restore settings")
return {'FINISHED'}
return {'CANCELLED'}
2012-01-01 08:52:54 +00:00
class WM_OT_blenderplayer_start(Operator):
"""Launch the blender-player with the current blend-file"""
Cucumber, first batch of merge - UI changes and custom exit key --------------------------------------------------------------- This was a test drive to see how painful the merge will be. Next batches are: - use desktop option for fullscreen - multisampling option - bullet collision mask - python - storage (vbo, dl, ...) - lighting [lighting still needs review] [python could use review, although it should be straightforward] [storage should be tested more I think] Merged /branches/soc-2011-cucumber:r 36991,37059,37157,37416,37497-37499,37501,37522,39036,40593 36991: ==UI== * Made some options available in Blender Game that were only available in Blender Render (camera resolution, animation fps) * Created a panel for the embedded player * Renamed the FPS option for the standalone player to Refresh Rate * Moved framing options to display * Made a button to launch the blender player from within blender (only tested on windows for now) 37059: ==UI== * Added the option to change the exit key for the BGE. The UI currently just sets a number, and this feature most likely does not work for blenderplayer yet. More work on this to come. * Removed the physics settings from the scene panel for the BGE. * Added an Add menu in the logic brick header. 37157: Making the bake options available in Blender Game 37416: Making the exit key UI element accept key presses instead of numbers. It still does not work for the Blenderplayer, and it does not limit the input to key presses (other events don't work for exiting) 37497: Some more work on getting the exit key to work in the Blenderplayer. Input is now restricted to keyboard events only for the exit key UI. 37498: Some clean up from the last commit. The exit key setting affects the Blenderplayer now. 37499: Cleaning up some duplicate code. Now the reverseTranslateTable for converting blender key codes to ketsji key codes is only defined in BL_BlenderDataConverter. 37501: Centralizing the exit key methods to the keyboard devices. This should make it easier to get exit key control to the python API. [37517: committed previously] 37522: Moved control of the exit key away from the keyboard devices, and moved it to ketsjiengine. Added setExitKey and getExitKey to the python API 39036: A couple of the doversions were in the wrong spot. This should fix some issues with the exit key not being set. [not committed entirely, see below]] 40552: space_logic.py (* fixed an error in space_logic.py *) 40593: launch blenderplayer from ui not working in OSX fix - by Daniel Stokes and me ######################################################## code left behind (to be included in next commit): ######################################################## { /* Initialize default values for collision masks */ Object *ob; for(ob=main->object.first; ob; ob=ob->id.next) ob->col_group = ob->col_mask = 1; }
2011-12-20 03:11:56 +00:00
bl_idname = "wm.blenderplayer_start"
bl_label = "Start Game In Player"
2012-01-01 08:52:54 +00:00
Cucumber, first batch of merge - UI changes and custom exit key --------------------------------------------------------------- This was a test drive to see how painful the merge will be. Next batches are: - use desktop option for fullscreen - multisampling option - bullet collision mask - python - storage (vbo, dl, ...) - lighting [lighting still needs review] [python could use review, although it should be straightforward] [storage should be tested more I think] Merged /branches/soc-2011-cucumber:r 36991,37059,37157,37416,37497-37499,37501,37522,39036,40593 36991: ==UI== * Made some options available in Blender Game that were only available in Blender Render (camera resolution, animation fps) * Created a panel for the embedded player * Renamed the FPS option for the standalone player to Refresh Rate * Moved framing options to display * Made a button to launch the blender player from within blender (only tested on windows for now) 37059: ==UI== * Added the option to change the exit key for the BGE. The UI currently just sets a number, and this feature most likely does not work for blenderplayer yet. More work on this to come. * Removed the physics settings from the scene panel for the BGE. * Added an Add menu in the logic brick header. 37157: Making the bake options available in Blender Game 37416: Making the exit key UI element accept key presses instead of numbers. It still does not work for the Blenderplayer, and it does not limit the input to key presses (other events don't work for exiting) 37497: Some more work on getting the exit key to work in the Blenderplayer. Input is now restricted to keyboard events only for the exit key UI. 37498: Some clean up from the last commit. The exit key setting affects the Blenderplayer now. 37499: Cleaning up some duplicate code. Now the reverseTranslateTable for converting blender key codes to ketsji key codes is only defined in BL_BlenderDataConverter. 37501: Centralizing the exit key methods to the keyboard devices. This should make it easier to get exit key control to the python API. [37517: committed previously] 37522: Moved control of the exit key away from the keyboard devices, and moved it to ketsjiengine. Added setExitKey and getExitKey to the python API 39036: A couple of the doversions were in the wrong spot. This should fix some issues with the exit key not being set. [not committed entirely, see below]] 40552: space_logic.py (* fixed an error in space_logic.py *) 40593: launch blenderplayer from ui not working in OSX fix - by Daniel Stokes and me ######################################################## code left behind (to be included in next commit): ######################################################## { /* Initialize default values for collision masks */ Object *ob; for(ob=main->object.first; ob; ob=ob->id.next) ob->col_group = ob->col_mask = 1; }
2011-12-20 03:11:56 +00:00
def execute(self, context):
import os
Cucumber, first batch of merge - UI changes and custom exit key --------------------------------------------------------------- This was a test drive to see how painful the merge will be. Next batches are: - use desktop option for fullscreen - multisampling option - bullet collision mask - python - storage (vbo, dl, ...) - lighting [lighting still needs review] [python could use review, although it should be straightforward] [storage should be tested more I think] Merged /branches/soc-2011-cucumber:r 36991,37059,37157,37416,37497-37499,37501,37522,39036,40593 36991: ==UI== * Made some options available in Blender Game that were only available in Blender Render (camera resolution, animation fps) * Created a panel for the embedded player * Renamed the FPS option for the standalone player to Refresh Rate * Moved framing options to display * Made a button to launch the blender player from within blender (only tested on windows for now) 37059: ==UI== * Added the option to change the exit key for the BGE. The UI currently just sets a number, and this feature most likely does not work for blenderplayer yet. More work on this to come. * Removed the physics settings from the scene panel for the BGE. * Added an Add menu in the logic brick header. 37157: Making the bake options available in Blender Game 37416: Making the exit key UI element accept key presses instead of numbers. It still does not work for the Blenderplayer, and it does not limit the input to key presses (other events don't work for exiting) 37497: Some more work on getting the exit key to work in the Blenderplayer. Input is now restricted to keyboard events only for the exit key UI. 37498: Some clean up from the last commit. The exit key setting affects the Blenderplayer now. 37499: Cleaning up some duplicate code. Now the reverseTranslateTable for converting blender key codes to ketsji key codes is only defined in BL_BlenderDataConverter. 37501: Centralizing the exit key methods to the keyboard devices. This should make it easier to get exit key control to the python API. [37517: committed previously] 37522: Moved control of the exit key away from the keyboard devices, and moved it to ketsjiengine. Added setExitKey and getExitKey to the python API 39036: A couple of the doversions were in the wrong spot. This should fix some issues with the exit key not being set. [not committed entirely, see below]] 40552: space_logic.py (* fixed an error in space_logic.py *) 40593: launch blenderplayer from ui not working in OSX fix - by Daniel Stokes and me ######################################################## code left behind (to be included in next commit): ######################################################## { /* Initialize default values for collision masks */ Object *ob; for(ob=main->object.first; ob; ob=ob->id.next) ob->col_group = ob->col_mask = 1; }
2011-12-20 03:11:56 +00:00
import sys
import subprocess
gs = context.scene.game_settings
# these remain the same every execution
blender_bin_path = bpy.app.binary_path
blender_bin_dir = os.path.dirname(blender_bin_path)
ext = os.path.splitext(blender_bin_path)[-1]
player_path = os.path.join(blender_bin_dir, "blenderplayer" + ext)
# done static vars
Cucumber, first batch of merge - UI changes and custom exit key --------------------------------------------------------------- This was a test drive to see how painful the merge will be. Next batches are: - use desktop option for fullscreen - multisampling option - bullet collision mask - python - storage (vbo, dl, ...) - lighting [lighting still needs review] [python could use review, although it should be straightforward] [storage should be tested more I think] Merged /branches/soc-2011-cucumber:r 36991,37059,37157,37416,37497-37499,37501,37522,39036,40593 36991: ==UI== * Made some options available in Blender Game that were only available in Blender Render (camera resolution, animation fps) * Created a panel for the embedded player * Renamed the FPS option for the standalone player to Refresh Rate * Moved framing options to display * Made a button to launch the blender player from within blender (only tested on windows for now) 37059: ==UI== * Added the option to change the exit key for the BGE. The UI currently just sets a number, and this feature most likely does not work for blenderplayer yet. More work on this to come. * Removed the physics settings from the scene panel for the BGE. * Added an Add menu in the logic brick header. 37157: Making the bake options available in Blender Game 37416: Making the exit key UI element accept key presses instead of numbers. It still does not work for the Blenderplayer, and it does not limit the input to key presses (other events don't work for exiting) 37497: Some more work on getting the exit key to work in the Blenderplayer. Input is now restricted to keyboard events only for the exit key UI. 37498: Some clean up from the last commit. The exit key setting affects the Blenderplayer now. 37499: Cleaning up some duplicate code. Now the reverseTranslateTable for converting blender key codes to ketsji key codes is only defined in BL_BlenderDataConverter. 37501: Centralizing the exit key methods to the keyboard devices. This should make it easier to get exit key control to the python API. [37517: committed previously] 37522: Moved control of the exit key away from the keyboard devices, and moved it to ketsjiengine. Added setExitKey and getExitKey to the python API 39036: A couple of the doversions were in the wrong spot. This should fix some issues with the exit key not being set. [not committed entirely, see below]] 40552: space_logic.py (* fixed an error in space_logic.py *) 40593: launch blenderplayer from ui not working in OSX fix - by Daniel Stokes and me ######################################################## code left behind (to be included in next commit): ######################################################## { /* Initialize default values for collision masks */ Object *ob; for(ob=main->object.first; ob; ob=ob->id.next) ob->col_group = ob->col_mask = 1; }
2011-12-20 03:11:56 +00:00
if sys.platform == "darwin":
player_path = os.path.join(blender_bin_dir, "../../../blenderplayer.app/Contents/MacOS/blenderplayer")
2012-01-01 08:52:54 +00:00
if not os.path.exists(player_path):
self.report({'ERROR'}, "Player path: %r not found" % player_path)
return {'CANCELLED'}
filepath = bpy.data.filepath + '~' if bpy.data.is_saved else os.path.join(bpy.app.tempdir, "game.blend")
bpy.ops.wm.save_as_mainfile('EXEC_DEFAULT', filepath=filepath, copy=True)
# start the command line call with the player path
args = [player_path]
# handle some UI options as command line arguments
args.extend([
"-g", "show_framerate", "=", "%d" % gs.show_framerate_profile,
"-g", "show_profile", "=", "%d" % gs.show_framerate_profile,
"-g", "show_properties", "=", "%d" % gs.show_debug_properties,
"-g", "ignore_deprecation_warnings", "=", "%d" % (not gs.use_deprecation_warnings),
])
# finish the call with the path to the blend file
args.append(filepath)
subprocess.call(args)
os.remove(filepath)
Cucumber, first batch of merge - UI changes and custom exit key --------------------------------------------------------------- This was a test drive to see how painful the merge will be. Next batches are: - use desktop option for fullscreen - multisampling option - bullet collision mask - python - storage (vbo, dl, ...) - lighting [lighting still needs review] [python could use review, although it should be straightforward] [storage should be tested more I think] Merged /branches/soc-2011-cucumber:r 36991,37059,37157,37416,37497-37499,37501,37522,39036,40593 36991: ==UI== * Made some options available in Blender Game that were only available in Blender Render (camera resolution, animation fps) * Created a panel for the embedded player * Renamed the FPS option for the standalone player to Refresh Rate * Moved framing options to display * Made a button to launch the blender player from within blender (only tested on windows for now) 37059: ==UI== * Added the option to change the exit key for the BGE. The UI currently just sets a number, and this feature most likely does not work for blenderplayer yet. More work on this to come. * Removed the physics settings from the scene panel for the BGE. * Added an Add menu in the logic brick header. 37157: Making the bake options available in Blender Game 37416: Making the exit key UI element accept key presses instead of numbers. It still does not work for the Blenderplayer, and it does not limit the input to key presses (other events don't work for exiting) 37497: Some more work on getting the exit key to work in the Blenderplayer. Input is now restricted to keyboard events only for the exit key UI. 37498: Some clean up from the last commit. The exit key setting affects the Blenderplayer now. 37499: Cleaning up some duplicate code. Now the reverseTranslateTable for converting blender key codes to ketsji key codes is only defined in BL_BlenderDataConverter. 37501: Centralizing the exit key methods to the keyboard devices. This should make it easier to get exit key control to the python API. [37517: committed previously] 37522: Moved control of the exit key away from the keyboard devices, and moved it to ketsjiengine. Added setExitKey and getExitKey to the python API 39036: A couple of the doversions were in the wrong spot. This should fix some issues with the exit key not being set. [not committed entirely, see below]] 40552: space_logic.py (* fixed an error in space_logic.py *) 40593: launch blenderplayer from ui not working in OSX fix - by Daniel Stokes and me ######################################################## code left behind (to be included in next commit): ######################################################## { /* Initialize default values for collision masks */ Object *ob; for(ob=main->object.first; ob; ob=ob->id.next) ob->col_group = ob->col_mask = 1; }
2011-12-20 03:11:56 +00:00
return {'FINISHED'}
2012-01-01 08:52:54 +00:00
class WM_OT_keyconfig_test(Operator):
"Test key-config for conflicts"
bl_idname = "wm.keyconfig_test"
bl_label = "Test Key Configuration for Conflicts"
def execute(self, context):
from bpy_extras import keyconfig_utils
wm = context.window_manager
kc = wm.keyconfigs.default
if keyconfig_utils.keyconfig_test(kc):
print("CONFLICT")
return {'FINISHED'}
class WM_OT_keyconfig_import(Operator):
"Import key configuration from a python script"
bl_idname = "wm.keyconfig_import"
bl_label = "Import Key Configuration..."
filepath = StringProperty(
subtype='FILE_PATH',
default="keymap.py",
)
filter_folder = BoolProperty(
name="Filter folders",
default=True,
options={'HIDDEN'},
)
filter_text = BoolProperty(
name="Filter text",
default=True,
options={'HIDDEN'},
)
filter_python = BoolProperty(
name="Filter python",
default=True,
options={'HIDDEN'},
)
keep_original = BoolProperty(
name="Keep original",
description="Keep original file after copying to configuration folder",
default=True,
)
def execute(self, context):
import os
from os.path import basename
import shutil
if not self.filepath:
self.report({'ERROR'}, "Filepath not set")
return {'CANCELLED'}
config_name = basename(self.filepath)
path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", "keyconfig"), create=True)
path = os.path.join(path, config_name)
try:
if self.keep_original:
shutil.copy(self.filepath, path)
else:
shutil.move(self.filepath, path)
except Exception as e:
self.report({'ERROR'}, "Installing keymap failed: %s" % e)
return {'CANCELLED'}
# sneaky way to check we're actually running the code.
if bpy.utils.keyconfig_set(path, report=self.report):
return {'FINISHED'}
else:
return {'CANCELLED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
# This operator is also used by interaction presets saving - AddPresetBase
class WM_OT_keyconfig_export(Operator):
"Export key configuration to a python script"
bl_idname = "wm.keyconfig_export"
bl_label = "Export Key Configuration..."
filepath = StringProperty(
subtype='FILE_PATH',
default="keymap.py",
)
filter_folder = BoolProperty(
name="Filter folders",
default=True,
options={'HIDDEN'},
)
filter_text = BoolProperty(
name="Filter text",
default=True,
options={'HIDDEN'},
)
filter_python = BoolProperty(
name="Filter python",
default=True,
options={'HIDDEN'},
)
def execute(self, context):
from bpy_extras import keyconfig_utils
if not self.filepath:
raise Exception("Filepath not set")
2012-07-04 21:41:05 +00:00
if not self.filepath.endswith(".py"):
self.filepath += ".py"
wm = context.window_manager
keyconfig_utils.keyconfig_export(wm,
wm.keyconfigs.active,
self.filepath,
)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
class WM_OT_keymap_restore(Operator):
"Restore key map(s)"
bl_idname = "wm.keymap_restore"
bl_label = "Restore Key Map(s)"
all = BoolProperty(
name="All Keymaps",
description="Restore all keymaps to default",
)
def execute(self, context):
wm = context.window_manager
if self.all:
for km in wm.keyconfigs.user.keymaps:
km.restore_to_default()
else:
km = context.keymap
km.restore_to_default()
return {'FINISHED'}
class WM_OT_keyitem_restore(Operator):
"Restore key map item"
bl_idname = "wm.keyitem_restore"
bl_label = "Restore Key Map Item"
item_id = IntProperty(
name="Item Identifier",
description="Identifier of the item to remove",
)
@classmethod
def poll(cls, context):
keymap = getattr(context, "keymap", None)
return keymap
def execute(self, context):
km = context.keymap
kmi = km.keymap_items.from_id(self.item_id)
if (not kmi.is_user_defined) and kmi.is_user_modified:
km.restore_item_to_default(kmi)
return {'FINISHED'}
class WM_OT_keyitem_add(Operator):
"Add key map item"
bl_idname = "wm.keyitem_add"
bl_label = "Add Key Map Item"
def execute(self, context):
km = context.keymap
if km.is_modal:
km.keymap_items.new_modal("", 'A', 'PRESS')
else:
km.keymap_items.new("none", 'A', 'PRESS')
# clear filter and expand keymap so we can see the newly added item
if context.space_data.filter_text != "":
context.space_data.filter_text = ""
km.show_expanded_items = True
km.show_expanded_children = True
return {'FINISHED'}
class WM_OT_keyitem_remove(Operator):
"Remove key map item"
bl_idname = "wm.keyitem_remove"
bl_label = "Remove Key Map Item"
item_id = IntProperty(
name="Item Identifier",
description="Identifier of the item to remove",
)
@classmethod
def poll(cls, context):
return hasattr(context, "keymap")
def execute(self, context):
km = context.keymap
kmi = km.keymap_items.from_id(self.item_id)
km.keymap_items.remove(kmi)
return {'FINISHED'}
class WM_OT_keyconfig_remove(Operator):
"Remove key config"
bl_idname = "wm.keyconfig_remove"
bl_label = "Remove Key Config"
@classmethod
def poll(cls, context):
wm = context.window_manager
keyconf = wm.keyconfigs.active
return keyconf and keyconf.is_user_defined
def execute(self, context):
wm = context.window_manager
keyconfig = wm.keyconfigs.active
wm.keyconfigs.remove(keyconfig)
return {'FINISHED'}
class WM_OT_operator_cheat_sheet(Operator):
bl_idname = "wm.operator_cheat_sheet"
bl_label = "Operator Cheat Sheet"
def execute(self, context):
op_strings = []
tot = 0
for op_module_name in dir(bpy.ops):
op_module = getattr(bpy.ops, op_module_name)
for op_submodule_name in dir(op_module):
op = getattr(op_module, op_submodule_name)
text = repr(op)
if text.split("\n")[-1].startswith("bpy.ops."):
op_strings.append(text)
tot += 1
op_strings.append('')
textblock = bpy.data.texts.new("OperatorList.txt")
textblock.write('# %d Operators\n\n' % tot)
textblock.write('\n'.join(op_strings))
self.report({'INFO'}, "See OperatorList.txt textblock")
return {'FINISHED'}
2012-01-01 08:52:54 +00:00
# -----------------------------------------------------------------------------
# Addon Operators
class WM_OT_addon_enable(Operator):
"Enable an addon"
bl_idname = "wm.addon_enable"
bl_label = "Enable Addon"
module = StringProperty(
name="Module",
description="Module name of the addon to enable",
)
def execute(self, context):
2011-09-23 13:47:29 +00:00
import addon_utils
2013-06-27 03:05:19 +00:00
err_str = ""
2013-06-27 03:05:19 +00:00
def err_cb():
import traceback
nonlocal err_str
err_str = traceback.format_exc()
print(err_str)
2011-09-23 13:47:29 +00:00
mod = addon_utils.enable(self.module, handle_error=err_cb)
if mod:
info = addon_utils.module_bl_info(mod)
info_ver = info.get("blender", (0, 0, 0))
if info_ver > bpy.app.version:
self.report({'WARNING'},
("This script was written Blender "
"version %d.%d.%d and might not "
"function (correctly), "
"though it is enabled" %
info_ver))
return {'FINISHED'}
else:
if err_str:
self.report({'ERROR'}, err_str)
return {'CANCELLED'}
class WM_OT_addon_disable(Operator):
"Disable an addon"
bl_idname = "wm.addon_disable"
bl_label = "Disable Addon"
module = StringProperty(
name="Module",
description="Module name of the addon to disable",
)
def execute(self, context):
2011-09-23 13:47:29 +00:00
import addon_utils
err_str = ""
2013-06-27 03:05:19 +00:00
def err_cb():
import traceback
nonlocal err_str
err_str = traceback.format_exc()
print(err_str)
addon_utils.disable(self.module, handle_error=err_cb)
if err_str:
self.report({'ERROR'}, err_str)
return {'FINISHED'}
2012-06-19 22:17:19 +00:00
class WM_OT_theme_install(Operator):
2012-12-15 16:27:12 +00:00
"Load and apply a Blender XML theme file"
bl_idname = "wm.theme_install"
2012-06-19 22:17:19 +00:00
bl_label = "Install Theme..."
overwrite = BoolProperty(
name="Overwrite",
description="Remove existing theme file if exists",
default=True,
)
filepath = StringProperty(
subtype='FILE_PATH',
)
filter_folder = BoolProperty(
name="Filter folders",
default=True,
options={'HIDDEN'},
)
filter_glob = StringProperty(
default="*.xml",
options={'HIDDEN'},
)
def execute(self, context):
import os
import shutil
import traceback
2012-06-19 22:17:19 +00:00
xmlfile = self.filepath
2012-06-19 22:17:19 +00:00
path_themes = bpy.utils.user_resource('SCRIPTS', "presets/interface_theme", create=True)
if not path_themes:
self.report({'ERROR'}, "Failed to get themes path")
return {'CANCELLED'}
path_dest = os.path.join(path_themes, os.path.basename(xmlfile))
if not self.overwrite:
if os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
return {'CANCELLED'}
try:
shutil.copyfile(xmlfile, path_dest)
2012-06-19 22:17:19 +00:00
bpy.ops.script.execute_preset(filepath=path_dest, menu_idname="USERPREF_MT_interface_theme_presets")
except:
traceback.print_exc()
return {'CANCELLED'}
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
class WM_OT_addon_refresh(Operator):
"Scan addon directories for new modules"
bl_idname = "wm.addon_refresh"
bl_label = "Refresh"
def execute(self, context):
import addon_utils
addon_utils.modules_refresh()
return {'FINISHED'}
class WM_OT_addon_install(Operator):
"Install an addon"
bl_idname = "wm.addon_install"
bl_label = "Install from File..."
overwrite = BoolProperty(
name="Overwrite",
description="Remove existing addons with the same ID",
default=True,
)
target = EnumProperty(
name="Target Path",
items=(('DEFAULT', "Default", ""),
('PREFS', "User Prefs", "")),
)
filepath = StringProperty(
subtype='FILE_PATH',
)
filter_folder = BoolProperty(
name="Filter folders",
default=True,
options={'HIDDEN'},
)
filter_python = BoolProperty(
name="Filter python",
default=True,
options={'HIDDEN'},
)
filter_glob = StringProperty(
default="*.py;*.zip",
options={'HIDDEN'},
)
@staticmethod
def _module_remove(path_addons, module):
import os
module = os.path.splitext(module)[0]
for f in os.listdir(path_addons):
f_base = os.path.splitext(f)[0]
if f_base == module:
f_full = os.path.join(path_addons, f)
if os.path.isdir(f_full):
os.rmdir(f_full)
else:
os.remove(f_full)
def execute(self, context):
2011-09-23 13:47:29 +00:00
import addon_utils
import traceback
import zipfile
import shutil
import os
pyfile = self.filepath
if self.target == 'DEFAULT':
2011-10-17 06:58:07 +00:00
# don't use bpy.utils.script_paths("addons") because we may not be able to write to it.
path_addons = bpy.utils.user_resource('SCRIPTS', "addons", create=True)
else:
path_addons = context.user_preferences.filepaths.script_directory
if path_addons:
path_addons = os.path.join(path_addons, "addons")
if not path_addons:
self.report({'ERROR'}, "Failed to get addons path")
return {'CANCELLED'}
if not os.path.isdir(path_addons):
try:
os.makedirs(path_addons, exist_ok=True)
except:
import traceback
traceback.print_exc()
# Check if we are installing from a target path,
# doing so causes 2+ addons of same name or when the same from/to
# location is used, removal of the file!
addon_path = ""
pyfile_dir = os.path.dirname(pyfile)
for addon_path in addon_utils.paths():
if os.path.samefile(pyfile_dir, addon_path):
self.report({'ERROR'}, "Source file is in the addon search path: %r" % addon_path)
return {'CANCELLED'}
del addon_path
del pyfile_dir
# done checking for exceptional case
addons_old = {mod.__name__ for mod in addon_utils.modules()}
#check to see if the file is in compressed format (.zip)
if zipfile.is_zipfile(pyfile):
try:
file_to_extract = zipfile.ZipFile(pyfile, 'r')
except:
traceback.print_exc()
return {'CANCELLED'}
if self.overwrite:
for f in file_to_extract.namelist():
WM_OT_addon_install._module_remove(path_addons, f)
else:
for f in file_to_extract.namelist():
path_dest = os.path.join(path_addons, os.path.basename(f))
if os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
return {'CANCELLED'}
try: # extract the file to "addons"
file_to_extract.extractall(path_addons)
except:
traceback.print_exc()
return {'CANCELLED'}
else:
path_dest = os.path.join(path_addons, os.path.basename(pyfile))
if self.overwrite:
WM_OT_addon_install._module_remove(path_addons, os.path.basename(pyfile))
elif os.path.exists(path_dest):
self.report({'WARNING'}, "File already installed to %r\n" % path_dest)
return {'CANCELLED'}
#if not compressed file just copy into the addon path
try:
shutil.copyfile(pyfile, path_dest)
except:
traceback.print_exc()
return {'CANCELLED'}
addons_new = {mod.__name__ for mod in addon_utils.modules()} - addons_old
addons_new.discard("modules")
# disable any addons we may have enabled previously and removed.
2011-10-17 06:58:07 +00:00
# this is unlikely but do just in case. bug [#23978]
for new_addon in addons_new:
addon_utils.disable(new_addon)
# possible the zip contains multiple addons, we could disallow this
# but for now just use the first
for mod in addon_utils.modules(refresh=False):
if mod.__name__ in addons_new:
info = addon_utils.module_bl_info(mod)
# show the newly installed addon.
context.window_manager.addon_filter = 'All'
context.window_manager.addon_search = info["name"]
break
2011-10-17 06:58:07 +00:00
# in case a new module path was created to install this addon.
bpy.utils.refresh_script_paths()
# print message
msg = tip_("Modules Installed from %r into %r (%s)") % (pyfile, path_addons, ", ".join(sorted(addons_new)))
print(msg)
self.report({'INFO'}, msg)
return {'FINISHED'}
def invoke(self, context, event):
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
class WM_OT_addon_remove(Operator):
2013-09-30 19:30:56 +00:00
"Delete the addon from the file system"
bl_idname = "wm.addon_remove"
bl_label = "Remove Addon"
module = StringProperty(
name="Module",
description="Module name of the addon to remove",
)
@staticmethod
def path_from_addon(module):
import os
2011-09-23 13:47:29 +00:00
import addon_utils
for mod in addon_utils.modules():
if mod.__name__ == module:
filepath = mod.__file__
if os.path.exists(filepath):
if os.path.splitext(os.path.basename(filepath))[0] == "__init__":
return os.path.dirname(filepath), True
else:
return filepath, False
return None, False
def execute(self, context):
2011-09-23 13:47:29 +00:00
import addon_utils
import os
2011-09-23 13:47:29 +00:00
path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
if path is None:
self.report({'WARNING'}, "Addon path %r could not be found" % path)
return {'CANCELLED'}
2011-10-17 06:58:07 +00:00
# in case its enabled
addon_utils.disable(self.module)
import shutil
if isdir:
shutil.rmtree(path)
else:
os.remove(path)
addon_utils.modules_refresh()
context.area.tag_redraw()
return {'FINISHED'}
# lame confirmation check
def draw(self, context):
self.layout.label(text="Remove Addon: %r?" % self.module)
path, isdir = WM_OT_addon_remove.path_from_addon(self.module)
self.layout.label(text="Path: %r" % path)
def invoke(self, context, event):
wm = context.window_manager
return wm.invoke_props_dialog(self, width=600)
class WM_OT_addon_expand(Operator):
"Display more information on this addon"
bl_idname = "wm.addon_expand"
bl_label = ""
bl_options = {'INTERNAL'}
module = StringProperty(
name="Module",
description="Module name of the addon to expand",
)
def execute(self, context):
2011-09-23 13:47:29 +00:00
import addon_utils
module_name = self.module
# unlikely to fail, module should have already been imported
try:
# mod = __import__(module_name)
2011-09-23 13:47:29 +00:00
mod = addon_utils.addons_fake_modules.get(module_name)
except:
import traceback
traceback.print_exc()
return {'CANCELLED'}
info = addon_utils.module_bl_info(mod)
info["show_expanded"] = not info["show_expanded"]
return {'FINISHED'}