12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865 |
- # ##### 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,
- # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- #
- # ##### END GPL LICENSE BLOCK #####
- # <pep8 compliant>
- import bpy
- from bpy.types import (
- Menu,
- Operator,
- )
- from bpy.props import (
- BoolProperty,
- EnumProperty,
- FloatProperty,
- IntProperty,
- StringProperty,
- )
- # FIXME, we need a way to detect key repeat events.
- # unfortunately checking event previous values isn't reliable.
- use_toolbar_release_hack = True
- rna_path_prop = StringProperty(
- name="Context Attributes",
- description="RNA context string",
- maxlen=1024,
- )
- rna_reverse_prop = BoolProperty(
- name="Reverse",
- description="Cycle backwards",
- default=False,
- )
- rna_wrap_prop = BoolProperty(
- name="Wrap",
- description="Wrap back to the first/last values",
- default=False,
- )
- rna_relative_prop = BoolProperty(
- name="Relative",
- description="Apply relative to the current value (delta)",
- default=False,
- )
- rna_space_type_prop = EnumProperty(
- name="Type",
- items=tuple(
- (e.identifier, e.name, "", e. value)
- for e in bpy.types.Space.bl_rna.properties["type"].enum_items
- ),
- default='EMPTY',
- )
- # Note, this can be used for more operators,
- # currently not used for all "WM_OT_context_" operators.
- rna_module_prop = StringProperty(
- name="Module",
- description="Optionally override the context with a module",
- maxlen=1024,
- )
- def context_path_validate(context, data_path):
- try:
- value = eval("context.%s" % data_path) if data_path else Ellipsis
- except AttributeError as ex:
- if str(ex).startswith("'NoneType'"):
- # One of the items in the rna path is None, just ignore this
- value = Ellipsis
- else:
- # 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
- # luckily we don't do this!
- #
- # When we can't 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 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
- 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
- module: rna_module_prop
- def execute(self, context):
- data_path = self.data_path
- module = self.module
- if not module:
- base = context
- else:
- from importlib import import_module
- base = import_module(self.module)
- if context_path_validate(base, data_path) is Ellipsis:
- return {'PASS_THROUGH'}
- exec("base.%s = not (base.%s)" % (data_path, data_path))
- return operator_path_undo_return(base, 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):
- """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
- wrap: rna_wrap_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 self.wrap:
- 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
- wrap: rna_wrap_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('[')
- # 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.
- #
- # When wrap's disabled we may set the value to its self,
- # this is done to ensure update callbacks run.
- if self.reverse:
- if orig_index == 0:
- advance_enum = enums[-1] if self.wrap else enums[0]
- else:
- advance_enum = enums[orig_index - 1]
- else:
- if orig_index == len(enums) - 1:
- advance_enum = enums[0] if self.wrap else enums[-1]
- 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 {'FINISHED'}
- class WM_OT_context_pie_enum(Operator):
- bl_idname = "wm.context_pie_enum"
- bl_label = "Context Enum Pie"
- bl_options = {'UNDO', 'INTERNAL'}
- data_path: rna_path_prop
- def invoke(self, context, event):
- wm = context.window_manager
- 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)
- wm.popup_menu_pie(draw_func=draw_cb, title=prop.name, icon=prop.icon, event=event)
- return {'FINISHED'}
- class WM_OT_operator_pie_enum(Operator):
- bl_idname = "wm.operator_pie_enum"
- bl_label = "Operator Enum Pie"
- bl_options = {'UNDO', 'INTERNAL'}
- data_path: StringProperty(
- name="Operator",
- description="Operator name (in python as string)",
- maxlen=1024,
- )
- prop_string: StringProperty(
- name="Property",
- description="Property name (as a string)",
- maxlen=1024,
- )
- def invoke(self, context, event):
- wm = context.window_manager
- data_path = self.data_path
- prop_string = self.prop_string
- # same as eval("bpy.ops." + data_path)
- op_mod_str, ob_id_str = data_path.split(".", 1)
- op = getattr(getattr(bpy.ops, op_mod_str), ob_id_str)
- del op_mod_str, ob_id_str
- try:
- op_rna = op.get_rna_type()
- except KeyError:
- self.report({'ERROR'}, "Operator not found: bpy.ops.%s" % data_path)
- return {'CANCELLED'}
- def draw_cb(self, context):
- layout = self.layout
- pie = layout.menu_pie()
- pie.operator_enum(data_path, prop_string)
- wm.popup_menu_pie(draw_func=draw_cb, title=op_rna.name, event=event)
- return {'FINISHED'}
- 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'},
- )
- 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
- if value_orig is True:
- is_set = True
- 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_CURSOR', '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(None)
- return operator_value_undo_return(item)
- elif event_type in {'RIGHTMOUSE', 'ESC'}:
- self._values_restore()
- context.area.header_text_set(None)
- return {'CANCELLED'}
- return {'RUNNING_MODAL'}
- def invoke(self, context, event):
- self._values_store(context)
- if not self._values:
- self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
- (self.data_path_iter, self.data_path_item))
- 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
- 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.check_call(["open", filepath])
- else:
- try:
- subprocess.check_call(["xdg-open", filepath])
- except:
- # xdg-open *should* be supported by recent Gnome, KDE, Xfce
- import traceback
- traceback.print_exc()
- return {'FINISHED'}
- def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
- def operator_exists_pair(a, b):
- # Not fast, this is only for docs.
- return b in dir(getattr(bpy.ops, a))
- def operator_exists_single(a):
- a, b = a.partition("_OT_")[::2]
- return operator_exists_pair(a.lower(), b)
- 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 operator_exists_pair(class_name, 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)
- elif operator_exists_single(class_name):
- # 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
- rna_class = getattr(bpy.types, class_name)
- # detect if this is a inherited member and use that name instead
- rna_parent = rna_class.bl_rna
- rna_prop = rna_parent.properties.get(class_prop)
- if rna_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)
- else:
- # We assume this is custom property, only try to generate generic url/rna_id...
- if do_url:
- url = ("%s/bpy.types.bpy_struct.html#bpy.types.bpy_struct.items" % (url_prefix,))
- else:
- rna = "bpy.types.bpy_struct"
- 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 fnmatchcase
- # XXX, for some reason all RNA ID's are stored lowercase
- # Adding case into all ID's isn't worth the hassle so force lowercase.
- rna_id = rna_id.lower()
- for pattern, url_suffix in url_mapping:
- if fnmatchcase(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_manual_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):
- """Open online reference docs in a web browser"""
- bl_idname = "wm.doc_view"
- bl_label = "View Documentation"
- doc_id: doc_id
- if bpy.app.version_cycle in {"release", "rc"}:
- _prefix = ("https://docs.blender.org/api/%d.%d%s" %
- (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char))
- else:
- _prefix = ("https://docs.blender.org/api/master")
- 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'}
- 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_default = StringProperty(
- name="Default Value",
- description="Default value of the property. Important for NLA mixing",
- maxlen=1024,
- )
- rna_property = StringProperty(
- name="Property Name",
- description="Property name edit",
- maxlen=1024,
- )
- rna_min = FloatProperty(
- name="Min",
- default=-10000.0,
- precision=3,
- )
- rna_max = FloatProperty(
- name="Max",
- default=10000.0,
- precision=3,
- )
- rna_use_soft_limits = BoolProperty(
- name="Use Soft Limits",
- )
- rna_is_overridable_library = BoolProperty(
- name="Is Library Overridable",
- default=False,
- )
- 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
- default: rna_default
- min: rna_min
- max: rna_max
- use_soft_limits: rna_use_soft_limits
- is_overridable_library: rna_is_overridable_library
- soft_min: rna_min
- soft_max: rna_max
- description: StringProperty(
- name="Tooltip",
- )
- def _cmp_props_get(self):
- # Changing these properties will refresh the UI
- return {
- "use_soft_limits": self.use_soft_limits,
- "soft_range": (self.soft_min, self.soft_max),
- "hard_range": (self.min, self.max),
- }
- def get_value_eval(self):
- try:
- value_eval = eval(self.value)
- # assert else None -> None, not "None", see [#33431]
- assert(type(value_eval) in {str, float, int, bool, tuple, list})
- except:
- value_eval = self.value
- return value_eval
- def get_default_eval(self):
- try:
- default_eval = eval(self.default)
- # assert else None -> None, not "None", see [#33431]
- assert(type(default_eval) in {str, float, int, bool, tuple, list})
- except:
- default_eval = self.default
- return default_eval
- def execute(self, context):
- from rna_prop_ui import (
- rna_idprop_ui_prop_get,
- rna_idprop_ui_prop_clear,
- rna_idprop_ui_prop_update,
- )
- data_path = self.data_path
- 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'}
- value_eval = self.get_value_eval()
- default_eval = self.get_default_eval()
- # 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)
- exec_str = "item.property_overridable_library_set('[\"%s\"]', %s)" % (prop, self.is_overridable_library)
- exec(exec_str)
- rna_idprop_ui_prop_update(item, prop)
- 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["min"] = prop_type(self.min)
- prop_ui["max"] = prop_type(self.max)
- if type(default_eval) in {float, int} and default_eval != 0:
- prop_ui["default"] = prop_type(default_eval)
- if self.use_soft_limits:
- prop_ui["soft_min"] = prop_type(self.soft_min)
- prop_ui["soft_max"] = prop_type(self.soft_max)
- else:
- prop_ui["soft_min"] = prop_type(self.min)
- prop_ui["soft_max"] = prop_type(self.max)
- 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()
- 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):
- 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)
- # retrieve overridable static
- exec_str = "item.is_property_overridable_library('[\"%s\"]')" % (self.property)
- self.is_overridable_library = bool(eval(exec_str))
- # default default value
- prop_type = type(self.get_value_eval())
- if prop_type in {int, float}:
- self.default = str(prop_type(0))
- else:
- self.default = ""
- # setup defaults
- 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", "")
- defval = prop_ui.get("default", None)
- if defval is not None:
- self.default = str(defval)
- self.soft_min = prop_ui.get("soft_min", self.min)
- self.soft_max = prop_ui.get("soft_max", self.max)
- self.use_soft_limits = (
- self.min != self.soft_min or
- self.max != self.soft_max
- )
- # store for comparison
- self._cmp_props = self._cmp_props_get()
- wm = context.window_manager
- return wm.invoke_props_dialog(self)
- def check(self, _context):
- cmp_props = self._cmp_props_get()
- changed = False
- if self._cmp_props != cmp_props:
- if cmp_props["use_soft_limits"]:
- if cmp_props["soft_range"] != self._cmp_props["soft_range"]:
- self.min = min(self.min, self.soft_min)
- self.max = max(self.max, self.soft_max)
- changed = True
- if cmp_props["hard_range"] != self._cmp_props["hard_range"]:
- self.soft_min = max(self.min, self.soft_min)
- self.soft_max = min(self.max, self.soft_max)
- changed = True
- else:
- if cmp_props["soft_range"] != cmp_props["hard_range"]:
- self.soft_min = self.min
- self.soft_max = self.max
- changed = True
- changed |= (cmp_props["use_soft_limits"] != self._cmp_props["use_soft_limits"])
- if changed:
- cmp_props = self._cmp_props_get()
- self._cmp_props = cmp_props
- return changed
- def draw(self, _context):
- layout = self.layout
- layout.prop(self, "property")
- layout.prop(self, "value")
- row = layout.row()
- row.enabled = type(self.get_value_eval()) in {int, float}
- row.prop(self, "default")
- row = layout.row(align=True)
- row.prop(self, "min")
- row.prop(self, "max")
- row = layout.row()
- row.prop(self, "use_soft_limits")
- if bpy.app.use_override_library:
- row.prop(self, "is_overridable_library")
- row = layout.row(align=True)
- row.enabled = self.use_soft_limits
- row.prop(self, "soft_min", text="Soft Min")
- row.prop(self, "soft_max", text="Soft Max")
- layout.prop(self, "description")
- 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):
- from rna_prop_ui import (
- rna_idprop_ui_create,
- )
- 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(),
- *type(item).bl_rna.properties.keys(),
- })
- rna_idprop_ui_create(item, prop, default=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):
- from rna_prop_ui import (
- rna_idprop_ui_prop_clear,
- rna_idprop_ui_prop_update,
- )
- data_path = self.data_path
- item = eval("context.%s" % data_path)
- prop = self.property
- rna_idprop_ui_prop_update(item, prop)
- del item[prop]
- rna_idprop_ui_prop_clear(item, prop)
- return {'FINISHED'}
- class WM_OT_sysinfo(Operator):
- """Generate system information, saved into a text file"""
- bl_idname = "wm.sysinfo"
- bl_label = "Save System Info"
- filepath: StringProperty(
- subtype='FILE_PATH',
- options={'SKIP_SAVE'},
- )
- def execute(self, _context):
- import sys_info
- sys_info.write_sysinfo(self.filepath)
- return {'FINISHED'}
- def invoke(self, context, _event):
- import os
- if not self.filepath:
- self.filepath = os.path.join(
- os.path.expanduser("~"), "system-info.txt")
- wm = context.window_manager
- wm.fileselect_add(self)
- return {'RUNNING_MODAL'}
- class WM_OT_operator_cheat_sheet(Operator):
- """List all the Operators in a text-block, useful for scripting"""
- 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'}
- # -----------------------------------------------------------------------------
- # Add-on Operators
- class WM_OT_owner_enable(Operator):
- """Enable workspace owner ID"""
- bl_idname = "wm.owner_enable"
- bl_label = "Enable Add-on"
- owner_id: StringProperty(
- name="UI Tag",
- )
- def execute(self, context):
- workspace = context.workspace
- workspace.owner_ids.new(self.owner_id)
- return {'FINISHED'}
- class WM_OT_owner_disable(Operator):
- """Enable workspace owner ID"""
- bl_idname = "wm.owner_disable"
- bl_label = "Disable UI Tag"
- owner_id: StringProperty(
- name="UI Tag",
- )
- def execute(self, context):
- workspace = context.workspace
- owner_id = workspace.owner_ids[self.owner_id]
- workspace.owner_ids.remove(owner_id)
- return {'FINISHED'}
- class WM_OT_tool_set_by_id(Operator):
- """Set the tool by name (for keymaps)"""
- bl_idname = "wm.tool_set_by_id"
- bl_label = "Set Tool By Name"
- name: StringProperty(
- name="Identifier",
- description="Identifier of the tool",
- )
- cycle: BoolProperty(
- name="Cycle",
- description="Cycle through tools in this group",
- default=False,
- options={'SKIP_SAVE'},
- )
- space_type: rna_space_type_prop
- if use_toolbar_release_hack:
- def invoke(self, context, event):
- # Hack :S
- if not self.properties.is_property_set("name"):
- WM_OT_toolbar._key_held = False
- return {'PASS_THROUGH'}
- elif (WM_OT_toolbar._key_held == event.type) and (event.value != 'RELEASE'):
- return {'PASS_THROUGH'}
- WM_OT_toolbar._key_held = None
- return self.execute(context)
- def execute(self, context):
- from bl_ui.space_toolsystem_common import (
- activate_by_id,
- activate_by_id_or_cycle,
- )
- if self.properties.is_property_set("space_type"):
- space_type = self.space_type
- else:
- space_type = context.space_data.type
- fn = activate_by_id_or_cycle if self.cycle else activate_by_id
- if fn(context, space_type, self.name):
- return {'FINISHED'}
- else:
- self.report({'WARNING'}, f"Tool {self.name!r:s} not found for space {space_type!r:s}.")
- return {'CANCELLED'}
- class WM_OT_tool_set_by_index(Operator):
- """Set the tool by index (for keymaps)"""
- bl_idname = "wm.tool_set_by_index"
- bl_label = "Set Tool By Index"
- index: IntProperty(
- name="Index in toolbar",
- default=0,
- )
- cycle: BoolProperty(
- name="Cycle",
- description="Cycle through tools in this group",
- default=False,
- options={'SKIP_SAVE'},
- )
- expand: BoolProperty(
- description="Include tool sub-groups",
- default=True,
- )
- space_type: rna_space_type_prop
- def execute(self, context):
- from bl_ui.space_toolsystem_common import (
- activate_by_id,
- activate_by_id_or_cycle,
- item_from_index,
- item_from_flat_index,
- )
- if self.properties.is_property_set("space_type"):
- space_type = self.space_type
- else:
- space_type = context.space_data.type
- fn = item_from_flat_index if self.expand else item_from_index
- item = fn(context, space_type, self.index)
- if item is None:
- # Don't report, since the number of tools may change.
- return {'CANCELLED'}
- # Same as: WM_OT_tool_set_by_id
- fn = activate_by_id_or_cycle if self.cycle else activate_by_id
- if fn(context, space_type, item.idname):
- return {'FINISHED'}
- else:
- # Since we already have the tool, this can't happen.
- raise Exception("Internal error setting tool")
- class WM_OT_toolbar(Operator):
- bl_idname = "wm.toolbar"
- bl_label = "Toolbar"
- @classmethod
- def poll(cls, context):
- return context.space_data is not None
- if use_toolbar_release_hack:
- _key_held = None
- def invoke(self, context, event):
- WM_OT_toolbar._key_held = event.type
- return self.execute(context)
- def execute(self, context):
- from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
- from bl_keymap_utils import keymap_from_toolbar
- space_type = context.space_data.type
- cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
- if cls is None:
- return {'CANCELLED'}
- wm = context.window_manager
- keymap = keymap_from_toolbar.generate(context, space_type)
- def draw_menu(popover, context):
- layout = popover.layout
- layout.operator_context = 'INVOKE_REGION_WIN'
- cls.draw_cls(layout, context, detect_layout=False, scale_y=1.0)
- wm.popover(draw_menu, ui_units_x=8, keymap=keymap)
- return {'FINISHED'}
- class WM_MT_splash(Menu):
- bl_label = "Splash"
- def draw_setup(self, context):
- wm = context.window_manager
- # prefs = context.preferences
- layout = self.layout
- layout.operator_context = 'EXEC_DEFAULT'
- layout.label(text="Quick Setup")
- split = layout.split(factor=0.25)
- split.label()
- split = split.split(factor=2.0 / 3.0)
- col = split.column()
- col.label()
- sub = col.split(factor=0.35)
- row = sub.row()
- row.alignment = 'RIGHT'
- row.label(text="Shortcuts")
- text = bpy.path.display_name(wm.keyconfigs.active.name)
- if not text:
- text = "Blender"
- sub.menu("USERPREF_MT_keyconfigs", text=text)
- kc = wm.keyconfigs.active
- kc_prefs = kc.preferences
- has_select_mouse = hasattr(kc_prefs, "select_mouse")
- if has_select_mouse:
- sub = col.split(factor=0.35)
- row = sub.row()
- row.alignment = 'RIGHT'
- row.label(text="Select With")
- sub.row().prop(kc_prefs, "select_mouse", expand=True)
- has_select_mouse = True
- has_spacebar_action = hasattr(kc_prefs, "spacebar_action")
- if has_spacebar_action:
- sub = col.split(factor=0.35)
- row = sub.row()
- row.alignment = 'RIGHT'
- row.label(text="Spacebar")
- sub.row().prop(kc_prefs, "spacebar_action", expand=True)
- has_select_mouse = True
- col.separator()
- sub = col.split(factor=0.35)
- row = sub.row()
- row.alignment = 'RIGHT'
- row.label(text="Theme")
- label = bpy.types.USERPREF_MT_interface_theme_presets.bl_label
- if label == "Presets":
- label = "Blender Dark"
- sub.menu("USERPREF_MT_interface_theme_presets", text=label)
- # We need to make switching to a language easier first
- #sub = col.split(factor=0.35)
- #row = sub.row()
- #row.alignment = 'RIGHT'
- # row.label(text="Language:")
- #prefs = context.preferences
- #sub.prop(prefs.system, "language", text="")
- # Keep height constant
- if not has_select_mouse:
- col.label()
- if not has_spacebar_action:
- col.label()
- layout.label()
- row = layout.row()
- sub = row.row()
- if bpy.types.PREFERENCES_OT_copy_prev.poll(context):
- old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
- sub.operator("preferences.copy_prev", text="Load %d.%d Settings" % old_version)
- sub.operator("wm.save_userpref", text="Save New Settings")
- else:
- sub.label()
- sub.label()
- sub.operator("wm.save_userpref", text="Next")
- layout.separator()
- layout.separator()
- def draw(self, context):
- # Draw setup screen if no preferences have been saved yet.
- import os
- userconfig_path = bpy.utils.user_resource('CONFIG')
- userdef_path = os.path.join(userconfig_path, "userpref.blend")
- if not os.path.isfile(userdef_path):
- self.draw_setup(context)
- return
- # Pass
- layout = self.layout
- layout.operator_context = 'EXEC_DEFAULT'
- layout.emboss = 'PULLDOWN_MENU'
- split = layout.split()
- # Templates
- col1 = split.column()
- col1.label(text="New File")
- bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
- # Recent
- col2 = split.column()
- col2_title = col2.row()
- found_recent = col2.template_recent_files()
- if found_recent:
- col2_title.label(text="Recent Files")
- else:
- if bpy.app.version_cycle in {'rc', 'release'}:
- manual_version = '%d.%d' % bpy.app.version[:2]
- else:
- manual_version = 'dev'
- # Links if no recent files
- col2_title.label(text="Getting Started")
- col2.operator(
- "wm.url_open", text="Manual", icon='URL'
- ).url = "https://docs.blender.org/manual/en/" + manual_version + "/"
- col2.operator(
- "wm.url_open", text="Release Notes", icon='URL',
- ).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
- col2.operator(
- "wm.url_open", text="Blender Website", icon='URL',
- ).url = "https://www.blender.org"
- col2.operator(
- "wm.url_open", text="Credits", icon='URL',
- ).url = "https://www.blender.org/about/credits/"
- layout.separator()
- split = layout.split()
- col1 = split.column()
- sub = col1.row()
- sub.operator_context = 'INVOKE_DEFAULT'
- sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
- col1.operator("wm.recover_last_session", icon='RECOVER_LAST')
- col2 = split.column()
- col2.operator(
- "wm.url_open", text="Release Notes", icon='URL',
- ).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
- col2.operator(
- "wm.url_open", text="Development Fund", icon='FUND'
- ).url = "https://fund.blender.org"
- layout.separator()
- layout.separator()
- class WM_OT_drop_blend_file(Operator):
- bl_idname = "wm.drop_blend_file"
- bl_label = "Handle dropped .blend file"
- bl_options = {'INTERNAL'}
- filepath: StringProperty()
- def invoke(self, context, _event):
- context.window_manager.popup_menu(self.draw_menu, title=bpy.path.basename(self.filepath), icon='QUESTION')
- return {'FINISHED'}
- def draw_menu(self, menu, _context):
- layout = menu.layout
- col = layout.column()
- col.operator_context = 'INVOKE_DEFAULT'
- props = col.operator("wm.open_mainfile", text="Open", icon='FILE_FOLDER')
- props.filepath = self.filepath
- props.display_file_selector = False
- layout.separator()
- col = layout.column()
- col.operator_context = 'INVOKE_DEFAULT'
- col.operator("wm.link", text="Link...", icon='LINK_BLEND').filepath = self.filepath
- col.operator("wm.append", text="Append...", icon='APPEND_BLEND').filepath = self.filepath
- classes = (
- WM_OT_context_collection_boolean_set,
- WM_OT_context_cycle_array,
- WM_OT_context_cycle_enum,
- WM_OT_context_cycle_int,
- WM_OT_context_menu_enum,
- WM_OT_context_modal_mouse,
- WM_OT_context_pie_enum,
- WM_OT_context_scale_float,
- WM_OT_context_scale_int,
- WM_OT_context_set_boolean,
- WM_OT_context_set_enum,
- WM_OT_context_set_float,
- WM_OT_context_set_id,
- WM_OT_context_set_int,
- WM_OT_context_set_string,
- WM_OT_context_set_value,
- WM_OT_context_toggle,
- WM_OT_context_toggle_enum,
- WM_OT_doc_view,
- WM_OT_doc_view_manual,
- WM_OT_drop_blend_file,
- WM_OT_operator_cheat_sheet,
- WM_OT_operator_pie_enum,
- WM_OT_path_open,
- WM_OT_properties_add,
- WM_OT_properties_context_change,
- WM_OT_properties_edit,
- WM_OT_properties_remove,
- WM_OT_sysinfo,
- WM_OT_owner_disable,
- WM_OT_owner_enable,
- WM_OT_url_open,
- WM_OT_tool_set_by_id,
- WM_OT_tool_set_by_index,
- WM_OT_toolbar,
- WM_MT_splash,
- )
|