wm.py 55 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865
  1. # ##### BEGIN GPL LICENSE BLOCK #####
  2. #
  3. # This program is free software; you can redistribute it and/or
  4. # modify it under the terms of the GNU General Public License
  5. # as published by the Free Software Foundation; either version 2
  6. # of the License, or (at your option) any later version.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software Foundation,
  15. # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  16. #
  17. # ##### END GPL LICENSE BLOCK #####
  18. # <pep8 compliant>
  19. import bpy
  20. from bpy.types import (
  21. Menu,
  22. Operator,
  23. )
  24. from bpy.props import (
  25. BoolProperty,
  26. EnumProperty,
  27. FloatProperty,
  28. IntProperty,
  29. StringProperty,
  30. )
  31. # FIXME, we need a way to detect key repeat events.
  32. # unfortunately checking event previous values isn't reliable.
  33. use_toolbar_release_hack = True
  34. rna_path_prop = StringProperty(
  35. name="Context Attributes",
  36. description="RNA context string",
  37. maxlen=1024,
  38. )
  39. rna_reverse_prop = BoolProperty(
  40. name="Reverse",
  41. description="Cycle backwards",
  42. default=False,
  43. )
  44. rna_wrap_prop = BoolProperty(
  45. name="Wrap",
  46. description="Wrap back to the first/last values",
  47. default=False,
  48. )
  49. rna_relative_prop = BoolProperty(
  50. name="Relative",
  51. description="Apply relative to the current value (delta)",
  52. default=False,
  53. )
  54. rna_space_type_prop = EnumProperty(
  55. name="Type",
  56. items=tuple(
  57. (e.identifier, e.name, "", e. value)
  58. for e in bpy.types.Space.bl_rna.properties["type"].enum_items
  59. ),
  60. default='EMPTY',
  61. )
  62. # Note, this can be used for more operators,
  63. # currently not used for all "WM_OT_context_" operators.
  64. rna_module_prop = StringProperty(
  65. name="Module",
  66. description="Optionally override the context with a module",
  67. maxlen=1024,
  68. )
  69. def context_path_validate(context, data_path):
  70. try:
  71. value = eval("context.%s" % data_path) if data_path else Ellipsis
  72. except AttributeError as ex:
  73. if str(ex).startswith("'NoneType'"):
  74. # One of the items in the rna path is None, just ignore this
  75. value = Ellipsis
  76. else:
  77. # We have a real error in the rna path, don't ignore that
  78. raise
  79. return value
  80. def operator_value_is_undo(value):
  81. if value in {None, Ellipsis}:
  82. return False
  83. # typical properties or objects
  84. id_data = getattr(value, "id_data", Ellipsis)
  85. if id_data is None:
  86. return False
  87. elif id_data is Ellipsis:
  88. # handle mathutils types
  89. id_data = getattr(getattr(value, "owner", None), "id_data", None)
  90. if id_data is None:
  91. return False
  92. # return True if its a non window ID type
  93. return (isinstance(id_data, bpy.types.ID) and
  94. (not isinstance(id_data, (bpy.types.WindowManager,
  95. bpy.types.Screen,
  96. bpy.types.Brush,
  97. ))))
  98. def operator_path_is_undo(context, data_path):
  99. # note that if we have data paths that use strings this could fail
  100. # luckily we don't do this!
  101. #
  102. # When we can't find the data owner assume no undo is needed.
  103. data_path_head = data_path.rpartition(".")[0]
  104. if not data_path_head:
  105. return False
  106. value = context_path_validate(context, data_path_head)
  107. return operator_value_is_undo(value)
  108. def operator_path_undo_return(context, data_path):
  109. return {'FINISHED'} if operator_path_is_undo(context, data_path) else {'CANCELLED'}
  110. def operator_value_undo_return(value):
  111. return {'FINISHED'} if operator_value_is_undo(value) else {'CANCELLED'}
  112. def execute_context_assign(self, context):
  113. data_path = self.data_path
  114. if context_path_validate(context, data_path) is Ellipsis:
  115. return {'PASS_THROUGH'}
  116. if getattr(self, "relative", False):
  117. exec("context.%s += self.value" % data_path)
  118. else:
  119. exec("context.%s = self.value" % data_path)
  120. return operator_path_undo_return(context, data_path)
  121. class WM_OT_context_set_boolean(Operator):
  122. """Set a context value"""
  123. bl_idname = "wm.context_set_boolean"
  124. bl_label = "Context Set Boolean"
  125. bl_options = {'UNDO', 'INTERNAL'}
  126. data_path: rna_path_prop
  127. value: BoolProperty(
  128. name="Value",
  129. description="Assignment value",
  130. default=True,
  131. )
  132. execute = execute_context_assign
  133. class WM_OT_context_set_int(Operator): # same as enum
  134. """Set a context value"""
  135. bl_idname = "wm.context_set_int"
  136. bl_label = "Context Set"
  137. bl_options = {'UNDO', 'INTERNAL'}
  138. data_path: rna_path_prop
  139. value: IntProperty(
  140. name="Value",
  141. description="Assign value",
  142. default=0,
  143. )
  144. relative: rna_relative_prop
  145. execute = execute_context_assign
  146. class WM_OT_context_scale_float(Operator):
  147. """Scale a float context value"""
  148. bl_idname = "wm.context_scale_float"
  149. bl_label = "Context Scale Float"
  150. bl_options = {'UNDO', 'INTERNAL'}
  151. data_path: rna_path_prop
  152. value: FloatProperty(
  153. name="Value",
  154. description="Assign value",
  155. default=1.0,
  156. )
  157. def execute(self, context):
  158. data_path = self.data_path
  159. if context_path_validate(context, data_path) is Ellipsis:
  160. return {'PASS_THROUGH'}
  161. value = self.value
  162. if value == 1.0: # nothing to do
  163. return {'CANCELLED'}
  164. exec("context.%s *= value" % data_path)
  165. return operator_path_undo_return(context, data_path)
  166. class WM_OT_context_scale_int(Operator):
  167. """Scale an int context value"""
  168. bl_idname = "wm.context_scale_int"
  169. bl_label = "Context Scale Int"
  170. bl_options = {'UNDO', 'INTERNAL'}
  171. data_path: rna_path_prop
  172. value: FloatProperty(
  173. name="Value",
  174. description="Assign value",
  175. default=1.0,
  176. )
  177. always_step: BoolProperty(
  178. name="Always Step",
  179. description="Always adjust the value by a minimum of 1 when 'value' is not 1.0",
  180. default=True,
  181. )
  182. def execute(self, context):
  183. data_path = self.data_path
  184. if context_path_validate(context, data_path) is Ellipsis:
  185. return {'PASS_THROUGH'}
  186. value = self.value
  187. if value == 1.0: # nothing to do
  188. return {'CANCELLED'}
  189. if getattr(self, "always_step", False):
  190. if value > 1.0:
  191. add = "1"
  192. func = "max"
  193. else:
  194. add = "-1"
  195. func = "min"
  196. exec("context.%s = %s(round(context.%s * value), context.%s + %s)" %
  197. (data_path, func, data_path, data_path, add))
  198. else:
  199. exec("context.%s *= value" % data_path)
  200. return operator_path_undo_return(context, data_path)
  201. class WM_OT_context_set_float(Operator): # same as enum
  202. """Set a context value"""
  203. bl_idname = "wm.context_set_float"
  204. bl_label = "Context Set Float"
  205. bl_options = {'UNDO', 'INTERNAL'}
  206. data_path: rna_path_prop
  207. value: FloatProperty(
  208. name="Value",
  209. description="Assignment value",
  210. default=0.0,
  211. )
  212. relative: rna_relative_prop
  213. execute = execute_context_assign
  214. class WM_OT_context_set_string(Operator): # same as enum
  215. """Set a context value"""
  216. bl_idname = "wm.context_set_string"
  217. bl_label = "Context Set String"
  218. bl_options = {'UNDO', 'INTERNAL'}
  219. data_path: rna_path_prop
  220. value: StringProperty(
  221. name="Value",
  222. description="Assign value",
  223. maxlen=1024,
  224. )
  225. execute = execute_context_assign
  226. class WM_OT_context_set_enum(Operator):
  227. """Set a context value"""
  228. bl_idname = "wm.context_set_enum"
  229. bl_label = "Context Set Enum"
  230. bl_options = {'UNDO', 'INTERNAL'}
  231. data_path: rna_path_prop
  232. value: StringProperty(
  233. name="Value",
  234. description="Assignment value (as a string)",
  235. maxlen=1024,
  236. )
  237. execute = execute_context_assign
  238. class WM_OT_context_set_value(Operator):
  239. """Set a context value"""
  240. bl_idname = "wm.context_set_value"
  241. bl_label = "Context Set Value"
  242. bl_options = {'UNDO', 'INTERNAL'}
  243. data_path: rna_path_prop
  244. value: StringProperty(
  245. name="Value",
  246. description="Assignment value (as a string)",
  247. maxlen=1024,
  248. )
  249. def execute(self, context):
  250. data_path = self.data_path
  251. if context_path_validate(context, data_path) is Ellipsis:
  252. return {'PASS_THROUGH'}
  253. exec("context.%s = %s" % (data_path, self.value))
  254. return operator_path_undo_return(context, data_path)
  255. class WM_OT_context_toggle(Operator):
  256. """Toggle a context value"""
  257. bl_idname = "wm.context_toggle"
  258. bl_label = "Context Toggle"
  259. bl_options = {'UNDO', 'INTERNAL'}
  260. data_path: rna_path_prop
  261. module: rna_module_prop
  262. def execute(self, context):
  263. data_path = self.data_path
  264. module = self.module
  265. if not module:
  266. base = context
  267. else:
  268. from importlib import import_module
  269. base = import_module(self.module)
  270. if context_path_validate(base, data_path) is Ellipsis:
  271. return {'PASS_THROUGH'}
  272. exec("base.%s = not (base.%s)" % (data_path, data_path))
  273. return operator_path_undo_return(base, data_path)
  274. class WM_OT_context_toggle_enum(Operator):
  275. """Toggle a context value"""
  276. bl_idname = "wm.context_toggle_enum"
  277. bl_label = "Context Toggle Values"
  278. bl_options = {'UNDO', 'INTERNAL'}
  279. data_path: rna_path_prop
  280. value_1: StringProperty(
  281. name="Value",
  282. description="Toggle enum",
  283. maxlen=1024,
  284. )
  285. value_2: StringProperty(
  286. name="Value",
  287. description="Toggle enum",
  288. maxlen=1024,
  289. )
  290. def execute(self, context):
  291. data_path = self.data_path
  292. if context_path_validate(context, data_path) is Ellipsis:
  293. return {'PASS_THROUGH'}
  294. # failing silently is not ideal, but we don't want errors for shortcut
  295. # keys that some values that are only available in a particular context
  296. try:
  297. exec("context.%s = ('%s', '%s')[context.%s != '%s']" %
  298. (data_path, self.value_1,
  299. self.value_2, data_path,
  300. self.value_2,
  301. ))
  302. except:
  303. return {'PASS_THROUGH'}
  304. return operator_path_undo_return(context, data_path)
  305. class WM_OT_context_cycle_int(Operator):
  306. """Set a context value (useful for cycling active material, """ \
  307. """vertex keys, groups, etc.)"""
  308. bl_idname = "wm.context_cycle_int"
  309. bl_label = "Context Int Cycle"
  310. bl_options = {'UNDO', 'INTERNAL'}
  311. data_path: rna_path_prop
  312. reverse: rna_reverse_prop
  313. wrap: rna_wrap_prop
  314. def execute(self, context):
  315. data_path = self.data_path
  316. value = context_path_validate(context, data_path)
  317. if value is Ellipsis:
  318. return {'PASS_THROUGH'}
  319. if self.reverse:
  320. value -= 1
  321. else:
  322. value += 1
  323. exec("context.%s = value" % data_path)
  324. if self.wrap:
  325. if value != eval("context.%s" % data_path):
  326. # relies on rna clamping integers out of the range
  327. if self.reverse:
  328. value = (1 << 31) - 1
  329. else:
  330. value = -1 << 31
  331. exec("context.%s = value" % data_path)
  332. return operator_path_undo_return(context, data_path)
  333. class WM_OT_context_cycle_enum(Operator):
  334. """Toggle a context value"""
  335. bl_idname = "wm.context_cycle_enum"
  336. bl_label = "Context Enum Cycle"
  337. bl_options = {'UNDO', 'INTERNAL'}
  338. data_path: rna_path_prop
  339. reverse: rna_reverse_prop
  340. wrap: rna_wrap_prop
  341. def execute(self, context):
  342. data_path = self.data_path
  343. value = context_path_validate(context, data_path)
  344. if value is Ellipsis:
  345. return {'PASS_THROUGH'}
  346. orig_value = value
  347. # Have to get rna enum values
  348. rna_struct_str, rna_prop_str = data_path.rsplit('.', 1)
  349. i = rna_prop_str.find('[')
  350. # just in case we get "context.foo.bar[0]"
  351. if i != -1:
  352. rna_prop_str = rna_prop_str[0:i]
  353. rna_struct = eval("context.%s.rna_type" % rna_struct_str)
  354. rna_prop = rna_struct.properties[rna_prop_str]
  355. if type(rna_prop) != bpy.types.EnumProperty:
  356. raise Exception("expected an enum property")
  357. enums = rna_struct.properties[rna_prop_str].enum_items.keys()
  358. orig_index = enums.index(orig_value)
  359. # Have the info we need, advance to the next item.
  360. #
  361. # When wrap's disabled we may set the value to its self,
  362. # this is done to ensure update callbacks run.
  363. if self.reverse:
  364. if orig_index == 0:
  365. advance_enum = enums[-1] if self.wrap else enums[0]
  366. else:
  367. advance_enum = enums[orig_index - 1]
  368. else:
  369. if orig_index == len(enums) - 1:
  370. advance_enum = enums[0] if self.wrap else enums[-1]
  371. else:
  372. advance_enum = enums[orig_index + 1]
  373. # set the new value
  374. exec("context.%s = advance_enum" % data_path)
  375. return operator_path_undo_return(context, data_path)
  376. class WM_OT_context_cycle_array(Operator):
  377. """Set a context array value """ \
  378. """(useful for cycling the active mesh edit mode)"""
  379. bl_idname = "wm.context_cycle_array"
  380. bl_label = "Context Array Cycle"
  381. bl_options = {'UNDO', 'INTERNAL'}
  382. data_path: rna_path_prop
  383. reverse: rna_reverse_prop
  384. def execute(self, context):
  385. data_path = self.data_path
  386. value = context_path_validate(context, data_path)
  387. if value is Ellipsis:
  388. return {'PASS_THROUGH'}
  389. def cycle(array):
  390. if self.reverse:
  391. array.insert(0, array.pop())
  392. else:
  393. array.append(array.pop(0))
  394. return array
  395. exec("context.%s = cycle(context.%s[:])" % (data_path, data_path))
  396. return operator_path_undo_return(context, data_path)
  397. class WM_OT_context_menu_enum(Operator):
  398. bl_idname = "wm.context_menu_enum"
  399. bl_label = "Context Enum Menu"
  400. bl_options = {'UNDO', 'INTERNAL'}
  401. data_path: rna_path_prop
  402. def execute(self, context):
  403. data_path = self.data_path
  404. value = context_path_validate(context, data_path)
  405. if value is Ellipsis:
  406. return {'PASS_THROUGH'}
  407. base_path, prop_string = data_path.rsplit(".", 1)
  408. value_base = context_path_validate(context, base_path)
  409. prop = value_base.bl_rna.properties[prop_string]
  410. def draw_cb(self, context):
  411. layout = self.layout
  412. layout.prop(value_base, prop_string, expand=True)
  413. context.window_manager.popup_menu(draw_func=draw_cb, title=prop.name, icon=prop.icon)
  414. return {'FINISHED'}
  415. class WM_OT_context_pie_enum(Operator):
  416. bl_idname = "wm.context_pie_enum"
  417. bl_label = "Context Enum Pie"
  418. bl_options = {'UNDO', 'INTERNAL'}
  419. data_path: rna_path_prop
  420. def invoke(self, context, event):
  421. wm = context.window_manager
  422. data_path = self.data_path
  423. value = context_path_validate(context, data_path)
  424. if value is Ellipsis:
  425. return {'PASS_THROUGH'}
  426. base_path, prop_string = data_path.rsplit(".", 1)
  427. value_base = context_path_validate(context, base_path)
  428. prop = value_base.bl_rna.properties[prop_string]
  429. def draw_cb(self, context):
  430. layout = self.layout
  431. layout.prop(value_base, prop_string, expand=True)
  432. wm.popup_menu_pie(draw_func=draw_cb, title=prop.name, icon=prop.icon, event=event)
  433. return {'FINISHED'}
  434. class WM_OT_operator_pie_enum(Operator):
  435. bl_idname = "wm.operator_pie_enum"
  436. bl_label = "Operator Enum Pie"
  437. bl_options = {'UNDO', 'INTERNAL'}
  438. data_path: StringProperty(
  439. name="Operator",
  440. description="Operator name (in python as string)",
  441. maxlen=1024,
  442. )
  443. prop_string: StringProperty(
  444. name="Property",
  445. description="Property name (as a string)",
  446. maxlen=1024,
  447. )
  448. def invoke(self, context, event):
  449. wm = context.window_manager
  450. data_path = self.data_path
  451. prop_string = self.prop_string
  452. # same as eval("bpy.ops." + data_path)
  453. op_mod_str, ob_id_str = data_path.split(".", 1)
  454. op = getattr(getattr(bpy.ops, op_mod_str), ob_id_str)
  455. del op_mod_str, ob_id_str
  456. try:
  457. op_rna = op.get_rna_type()
  458. except KeyError:
  459. self.report({'ERROR'}, "Operator not found: bpy.ops.%s" % data_path)
  460. return {'CANCELLED'}
  461. def draw_cb(self, context):
  462. layout = self.layout
  463. pie = layout.menu_pie()
  464. pie.operator_enum(data_path, prop_string)
  465. wm.popup_menu_pie(draw_func=draw_cb, title=op_rna.name, event=event)
  466. return {'FINISHED'}
  467. class WM_OT_context_set_id(Operator):
  468. """Set a context value to an ID data-block"""
  469. bl_idname = "wm.context_set_id"
  470. bl_label = "Set Library ID"
  471. bl_options = {'UNDO', 'INTERNAL'}
  472. data_path: rna_path_prop
  473. value: StringProperty(
  474. name="Value",
  475. description="Assign value",
  476. maxlen=1024,
  477. )
  478. def execute(self, context):
  479. value = self.value
  480. data_path = self.data_path
  481. # match the pointer type from the target property to bpy.data.*
  482. # so we lookup the correct list.
  483. data_path_base, data_path_prop = data_path.rsplit(".", 1)
  484. data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
  485. data_prop_rna_type = data_prop_rna.fixed_type
  486. id_iter = None
  487. for prop in bpy.data.rna_type.properties:
  488. if prop.rna_type.identifier == "CollectionProperty":
  489. if prop.fixed_type == data_prop_rna_type:
  490. id_iter = prop.identifier
  491. break
  492. if id_iter:
  493. value_id = getattr(bpy.data, id_iter).get(value)
  494. exec("context.%s = value_id" % data_path)
  495. return operator_path_undo_return(context, data_path)
  496. doc_id = StringProperty(
  497. name="Doc ID",
  498. maxlen=1024,
  499. options={'HIDDEN'},
  500. )
  501. data_path_iter = StringProperty(
  502. description="The data path relative to the context, must point to an iterable")
  503. data_path_item = StringProperty(
  504. description="The data path from each iterable to the value (int or float)")
  505. class WM_OT_context_collection_boolean_set(Operator):
  506. """Set boolean values for a collection of items"""
  507. bl_idname = "wm.context_collection_boolean_set"
  508. bl_label = "Context Collection Boolean Set"
  509. bl_options = {'UNDO', 'REGISTER', 'INTERNAL'}
  510. data_path_iter: data_path_iter
  511. data_path_item: data_path_item
  512. type: EnumProperty(
  513. name="Type",
  514. items=(
  515. ('TOGGLE', "Toggle", ""),
  516. ('ENABLE', "Enable", ""),
  517. ('DISABLE', "Disable", ""),
  518. ),
  519. )
  520. def execute(self, context):
  521. data_path_iter = self.data_path_iter
  522. data_path_item = self.data_path_item
  523. items = list(getattr(context, data_path_iter))
  524. items_ok = []
  525. is_set = False
  526. for item in items:
  527. try:
  528. value_orig = eval("item." + data_path_item)
  529. except:
  530. continue
  531. if value_orig is True:
  532. is_set = True
  533. elif value_orig is False:
  534. pass
  535. else:
  536. self.report({'WARNING'}, "Non boolean value found: %s[ ].%s" %
  537. (data_path_iter, data_path_item))
  538. return {'CANCELLED'}
  539. items_ok.append(item)
  540. # avoid undo push when nothing to do
  541. if not items_ok:
  542. return {'CANCELLED'}
  543. if self.type == 'ENABLE':
  544. is_set = True
  545. elif self.type == 'DISABLE':
  546. is_set = False
  547. else:
  548. is_set = not is_set
  549. exec_str = "item.%s = %s" % (data_path_item, is_set)
  550. for item in items_ok:
  551. exec(exec_str)
  552. return operator_value_undo_return(item)
  553. class WM_OT_context_modal_mouse(Operator):
  554. """Adjust arbitrary values with mouse input"""
  555. bl_idname = "wm.context_modal_mouse"
  556. bl_label = "Context Modal Mouse"
  557. bl_options = {'GRAB_CURSOR', 'BLOCKING', 'UNDO', 'INTERNAL'}
  558. data_path_iter: data_path_iter
  559. data_path_item: data_path_item
  560. header_text: StringProperty(
  561. name="Header Text",
  562. description="Text to display in header during scale",
  563. )
  564. input_scale: FloatProperty(
  565. description="Scale the mouse movement by this value before applying the delta",
  566. default=0.01,
  567. )
  568. invert: BoolProperty(
  569. description="Invert the mouse input",
  570. default=False,
  571. )
  572. initial_x: IntProperty(options={'HIDDEN'})
  573. def _values_store(self, context):
  574. data_path_iter = self.data_path_iter
  575. data_path_item = self.data_path_item
  576. self._values = values = {}
  577. for item in getattr(context, data_path_iter):
  578. try:
  579. value_orig = eval("item." + data_path_item)
  580. except:
  581. continue
  582. # check this can be set, maybe this is library data.
  583. try:
  584. exec("item.%s = %s" % (data_path_item, value_orig))
  585. except:
  586. continue
  587. values[item] = value_orig
  588. def _values_delta(self, delta):
  589. delta *= self.input_scale
  590. if self.invert:
  591. delta = - delta
  592. data_path_item = self.data_path_item
  593. for item, value_orig in self._values.items():
  594. if type(value_orig) == int:
  595. exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
  596. else:
  597. exec("item.%s = %f" % (data_path_item, value_orig + delta))
  598. def _values_restore(self):
  599. data_path_item = self.data_path_item
  600. for item, value_orig in self._values.items():
  601. exec("item.%s = %s" % (data_path_item, value_orig))
  602. self._values.clear()
  603. def _values_clear(self):
  604. self._values.clear()
  605. def modal(self, context, event):
  606. event_type = event.type
  607. if event_type == 'MOUSEMOVE':
  608. delta = event.mouse_x - self.initial_x
  609. self._values_delta(delta)
  610. header_text = self.header_text
  611. if header_text:
  612. if len(self._values) == 1:
  613. (item, ) = self._values.keys()
  614. header_text = header_text % eval("item.%s" % self.data_path_item)
  615. else:
  616. header_text = (self.header_text % delta) + " (delta)"
  617. context.area.header_text_set(header_text)
  618. elif 'LEFTMOUSE' == event_type:
  619. item = next(iter(self._values.keys()))
  620. self._values_clear()
  621. context.area.header_text_set(None)
  622. return operator_value_undo_return(item)
  623. elif event_type in {'RIGHTMOUSE', 'ESC'}:
  624. self._values_restore()
  625. context.area.header_text_set(None)
  626. return {'CANCELLED'}
  627. return {'RUNNING_MODAL'}
  628. def invoke(self, context, event):
  629. self._values_store(context)
  630. if not self._values:
  631. self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
  632. (self.data_path_iter, self.data_path_item))
  633. return {'CANCELLED'}
  634. else:
  635. self.initial_x = event.mouse_x
  636. context.window_manager.modal_handler_add(self)
  637. return {'RUNNING_MODAL'}
  638. class WM_OT_url_open(Operator):
  639. """Open a website in the web-browser"""
  640. bl_idname = "wm.url_open"
  641. bl_label = ""
  642. bl_options = {'INTERNAL'}
  643. url: StringProperty(
  644. name="URL",
  645. description="URL to open",
  646. )
  647. def execute(self, _context):
  648. import webbrowser
  649. webbrowser.open(self.url)
  650. return {'FINISHED'}
  651. class WM_OT_path_open(Operator):
  652. """Open a path in a file browser"""
  653. bl_idname = "wm.path_open"
  654. bl_label = ""
  655. bl_options = {'INTERNAL'}
  656. filepath: StringProperty(
  657. subtype='FILE_PATH',
  658. options={'SKIP_SAVE'},
  659. )
  660. def execute(self, _context):
  661. import sys
  662. import os
  663. import subprocess
  664. filepath = self.filepath
  665. if not filepath:
  666. self.report({'ERROR'}, "File path was not set")
  667. return {'CANCELLED'}
  668. filepath = bpy.path.abspath(filepath)
  669. filepath = os.path.normpath(filepath)
  670. if not os.path.exists(filepath):
  671. self.report({'ERROR'}, "File '%s' not found" % filepath)
  672. return {'CANCELLED'}
  673. if sys.platform[:3] == "win":
  674. os.startfile(filepath)
  675. elif sys.platform == "darwin":
  676. subprocess.check_call(["open", filepath])
  677. else:
  678. try:
  679. subprocess.check_call(["xdg-open", filepath])
  680. except:
  681. # xdg-open *should* be supported by recent Gnome, KDE, Xfce
  682. import traceback
  683. traceback.print_exc()
  684. return {'FINISHED'}
  685. def _wm_doc_get_id(doc_id, do_url=True, url_prefix=""):
  686. def operator_exists_pair(a, b):
  687. # Not fast, this is only for docs.
  688. return b in dir(getattr(bpy.ops, a))
  689. def operator_exists_single(a):
  690. a, b = a.partition("_OT_")[::2]
  691. return operator_exists_pair(a.lower(), b)
  692. id_split = doc_id.split(".")
  693. url = rna = None
  694. if len(id_split) == 1: # rna, class
  695. if do_url:
  696. url = "%s/bpy.types.%s.html" % (url_prefix, id_split[0])
  697. else:
  698. rna = "bpy.types.%s" % id_split[0]
  699. elif len(id_split) == 2: # rna, class.prop
  700. class_name, class_prop = id_split
  701. # an operator (common case - just button referencing an op)
  702. if operator_exists_pair(class_name, class_prop):
  703. if do_url:
  704. url = (
  705. "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
  706. (url_prefix, class_name, class_name, class_prop)
  707. )
  708. else:
  709. rna = "bpy.ops.%s.%s" % (class_name, class_prop)
  710. elif operator_exists_single(class_name):
  711. # note: ignore the prop name since we don't have a way to link into it
  712. class_name, class_prop = class_name.split("_OT_", 1)
  713. class_name = class_name.lower()
  714. if do_url:
  715. url = (
  716. "%s/bpy.ops.%s.html#bpy.ops.%s.%s" %
  717. (url_prefix, class_name, class_name, class_prop)
  718. )
  719. else:
  720. rna = "bpy.ops.%s.%s" % (class_name, class_prop)
  721. else:
  722. # an RNA setting, common case
  723. rna_class = getattr(bpy.types, class_name)
  724. # detect if this is a inherited member and use that name instead
  725. rna_parent = rna_class.bl_rna
  726. rna_prop = rna_parent.properties.get(class_prop)
  727. if rna_prop:
  728. rna_parent = rna_parent.base
  729. while rna_parent and rna_prop == rna_parent.properties.get(class_prop):
  730. class_name = rna_parent.identifier
  731. rna_parent = rna_parent.base
  732. if do_url:
  733. url = (
  734. "%s/bpy.types.%s.html#bpy.types.%s.%s" %
  735. (url_prefix, class_name, class_name, class_prop)
  736. )
  737. else:
  738. rna = "bpy.types.%s.%s" % (class_name, class_prop)
  739. else:
  740. # We assume this is custom property, only try to generate generic url/rna_id...
  741. if do_url:
  742. url = ("%s/bpy.types.bpy_struct.html#bpy.types.bpy_struct.items" % (url_prefix,))
  743. else:
  744. rna = "bpy.types.bpy_struct"
  745. return url if do_url else rna
  746. class WM_OT_doc_view_manual(Operator):
  747. """Load online manual"""
  748. bl_idname = "wm.doc_view_manual"
  749. bl_label = "View Manual"
  750. doc_id: doc_id
  751. @staticmethod
  752. def _find_reference(rna_id, url_mapping, verbose=True):
  753. if verbose:
  754. print("online manual check for: '%s'... " % rna_id)
  755. from fnmatch import fnmatchcase
  756. # XXX, for some reason all RNA ID's are stored lowercase
  757. # Adding case into all ID's isn't worth the hassle so force lowercase.
  758. rna_id = rna_id.lower()
  759. for pattern, url_suffix in url_mapping:
  760. if fnmatchcase(rna_id, pattern):
  761. if verbose:
  762. print(" match found: '%s' --> '%s'" % (pattern, url_suffix))
  763. return url_suffix
  764. if verbose:
  765. print("match not found")
  766. return None
  767. @staticmethod
  768. def _lookup_rna_url(rna_id, verbose=True):
  769. for prefix, url_manual_mapping in bpy.utils.manual_map():
  770. rna_ref = WM_OT_doc_view_manual._find_reference(rna_id, url_manual_mapping, verbose=verbose)
  771. if rna_ref is not None:
  772. url = prefix + rna_ref
  773. return url
  774. def execute(self, _context):
  775. rna_id = _wm_doc_get_id(self.doc_id, do_url=False)
  776. if rna_id is None:
  777. return {'PASS_THROUGH'}
  778. url = self._lookup_rna_url(rna_id)
  779. if url is None:
  780. self.report(
  781. {'WARNING'},
  782. "No reference available %r, "
  783. "Update info in 'rna_manual_reference.py' "
  784. "or callback to bpy.utils.manual_map()" %
  785. self.doc_id
  786. )
  787. return {'CANCELLED'}
  788. else:
  789. import webbrowser
  790. webbrowser.open(url)
  791. return {'FINISHED'}
  792. class WM_OT_doc_view(Operator):
  793. """Open online reference docs in a web browser"""
  794. bl_idname = "wm.doc_view"
  795. bl_label = "View Documentation"
  796. doc_id: doc_id
  797. if bpy.app.version_cycle in {"release", "rc"}:
  798. _prefix = ("https://docs.blender.org/api/%d.%d%s" %
  799. (bpy.app.version[0], bpy.app.version[1], bpy.app.version_char))
  800. else:
  801. _prefix = ("https://docs.blender.org/api/master")
  802. def execute(self, _context):
  803. url = _wm_doc_get_id(self.doc_id, do_url=True, url_prefix=self._prefix)
  804. if url is None:
  805. return {'PASS_THROUGH'}
  806. import webbrowser
  807. webbrowser.open(url)
  808. return {'FINISHED'}
  809. rna_path = StringProperty(
  810. name="Property Edit",
  811. description="Property data_path edit",
  812. maxlen=1024,
  813. options={'HIDDEN'},
  814. )
  815. rna_value = StringProperty(
  816. name="Property Value",
  817. description="Property value edit",
  818. maxlen=1024,
  819. )
  820. rna_default = StringProperty(
  821. name="Default Value",
  822. description="Default value of the property. Important for NLA mixing",
  823. maxlen=1024,
  824. )
  825. rna_property = StringProperty(
  826. name="Property Name",
  827. description="Property name edit",
  828. maxlen=1024,
  829. )
  830. rna_min = FloatProperty(
  831. name="Min",
  832. default=-10000.0,
  833. precision=3,
  834. )
  835. rna_max = FloatProperty(
  836. name="Max",
  837. default=10000.0,
  838. precision=3,
  839. )
  840. rna_use_soft_limits = BoolProperty(
  841. name="Use Soft Limits",
  842. )
  843. rna_is_overridable_library = BoolProperty(
  844. name="Is Library Overridable",
  845. default=False,
  846. )
  847. class WM_OT_properties_edit(Operator):
  848. bl_idname = "wm.properties_edit"
  849. bl_label = "Edit Property"
  850. # register only because invoke_props_popup requires.
  851. bl_options = {'REGISTER', 'INTERNAL'}
  852. data_path: rna_path
  853. property: rna_property
  854. value: rna_value
  855. default: rna_default
  856. min: rna_min
  857. max: rna_max
  858. use_soft_limits: rna_use_soft_limits
  859. is_overridable_library: rna_is_overridable_library
  860. soft_min: rna_min
  861. soft_max: rna_max
  862. description: StringProperty(
  863. name="Tooltip",
  864. )
  865. def _cmp_props_get(self):
  866. # Changing these properties will refresh the UI
  867. return {
  868. "use_soft_limits": self.use_soft_limits,
  869. "soft_range": (self.soft_min, self.soft_max),
  870. "hard_range": (self.min, self.max),
  871. }
  872. def get_value_eval(self):
  873. try:
  874. value_eval = eval(self.value)
  875. # assert else None -> None, not "None", see [#33431]
  876. assert(type(value_eval) in {str, float, int, bool, tuple, list})
  877. except:
  878. value_eval = self.value
  879. return value_eval
  880. def get_default_eval(self):
  881. try:
  882. default_eval = eval(self.default)
  883. # assert else None -> None, not "None", see [#33431]
  884. assert(type(default_eval) in {str, float, int, bool, tuple, list})
  885. except:
  886. default_eval = self.default
  887. return default_eval
  888. def execute(self, context):
  889. from rna_prop_ui import (
  890. rna_idprop_ui_prop_get,
  891. rna_idprop_ui_prop_clear,
  892. rna_idprop_ui_prop_update,
  893. )
  894. data_path = self.data_path
  895. prop = self.property
  896. prop_old = getattr(self, "_last_prop", [None])[0]
  897. if prop_old is None:
  898. self.report({'ERROR'}, "Direct execution not supported")
  899. return {'CANCELLED'}
  900. value_eval = self.get_value_eval()
  901. default_eval = self.get_default_eval()
  902. # First remove
  903. item = eval("context.%s" % data_path)
  904. prop_type_old = type(item[prop_old])
  905. rna_idprop_ui_prop_clear(item, prop_old)
  906. exec_str = "del item[%r]" % prop_old
  907. # print(exec_str)
  908. exec(exec_str)
  909. # Reassign
  910. exec_str = "item[%r] = %s" % (prop, repr(value_eval))
  911. # print(exec_str)
  912. exec(exec_str)
  913. exec_str = "item.property_overridable_library_set('[\"%s\"]', %s)" % (prop, self.is_overridable_library)
  914. exec(exec_str)
  915. rna_idprop_ui_prop_update(item, prop)
  916. self._last_prop[:] = [prop]
  917. prop_type = type(item[prop])
  918. prop_ui = rna_idprop_ui_prop_get(item, prop)
  919. if prop_type in {float, int}:
  920. prop_ui["min"] = prop_type(self.min)
  921. prop_ui["max"] = prop_type(self.max)
  922. if type(default_eval) in {float, int} and default_eval != 0:
  923. prop_ui["default"] = prop_type(default_eval)
  924. if self.use_soft_limits:
  925. prop_ui["soft_min"] = prop_type(self.soft_min)
  926. prop_ui["soft_max"] = prop_type(self.soft_max)
  927. else:
  928. prop_ui["soft_min"] = prop_type(self.min)
  929. prop_ui["soft_max"] = prop_type(self.max)
  930. prop_ui["description"] = self.description
  931. # If we have changed the type of the property, update its potential anim curves!
  932. if prop_type_old != prop_type:
  933. data_path = '["%s"]' % bpy.utils.escape_identifier(prop)
  934. done = set()
  935. def _update(fcurves):
  936. for fcu in fcurves:
  937. if fcu not in done and fcu.data_path == data_path:
  938. fcu.update_autoflags(item)
  939. done.add(fcu)
  940. def _update_strips(strips):
  941. for st in strips:
  942. if st.type == 'CLIP' and st.action:
  943. _update(st.action.fcurves)
  944. elif st.type == 'META':
  945. _update_strips(st.strips)
  946. adt = getattr(item, "animation_data", None)
  947. if adt is not None:
  948. if adt.action:
  949. _update(adt.action.fcurves)
  950. if adt.drivers:
  951. _update(adt.drivers)
  952. if adt.nla_tracks:
  953. for nt in adt.nla_tracks:
  954. _update_strips(nt.strips)
  955. # otherwise existing buttons which reference freed
  956. # memory may crash blender [#26510]
  957. # context.area.tag_redraw()
  958. for win in context.window_manager.windows:
  959. for area in win.screen.areas:
  960. area.tag_redraw()
  961. return {'FINISHED'}
  962. def invoke(self, context, _event):
  963. from rna_prop_ui import rna_idprop_ui_prop_get
  964. data_path = self.data_path
  965. if not data_path:
  966. self.report({'ERROR'}, "Data path not set")
  967. return {'CANCELLED'}
  968. self._last_prop = [self.property]
  969. item = eval("context.%s" % data_path)
  970. # retrieve overridable static
  971. exec_str = "item.is_property_overridable_library('[\"%s\"]')" % (self.property)
  972. self.is_overridable_library = bool(eval(exec_str))
  973. # default default value
  974. prop_type = type(self.get_value_eval())
  975. if prop_type in {int, float}:
  976. self.default = str(prop_type(0))
  977. else:
  978. self.default = ""
  979. # setup defaults
  980. prop_ui = rna_idprop_ui_prop_get(item, self.property, False) # don't create
  981. if prop_ui:
  982. self.min = prop_ui.get("min", -1000000000)
  983. self.max = prop_ui.get("max", 1000000000)
  984. self.description = prop_ui.get("description", "")
  985. defval = prop_ui.get("default", None)
  986. if defval is not None:
  987. self.default = str(defval)
  988. self.soft_min = prop_ui.get("soft_min", self.min)
  989. self.soft_max = prop_ui.get("soft_max", self.max)
  990. self.use_soft_limits = (
  991. self.min != self.soft_min or
  992. self.max != self.soft_max
  993. )
  994. # store for comparison
  995. self._cmp_props = self._cmp_props_get()
  996. wm = context.window_manager
  997. return wm.invoke_props_dialog(self)
  998. def check(self, _context):
  999. cmp_props = self._cmp_props_get()
  1000. changed = False
  1001. if self._cmp_props != cmp_props:
  1002. if cmp_props["use_soft_limits"]:
  1003. if cmp_props["soft_range"] != self._cmp_props["soft_range"]:
  1004. self.min = min(self.min, self.soft_min)
  1005. self.max = max(self.max, self.soft_max)
  1006. changed = True
  1007. if cmp_props["hard_range"] != self._cmp_props["hard_range"]:
  1008. self.soft_min = max(self.min, self.soft_min)
  1009. self.soft_max = min(self.max, self.soft_max)
  1010. changed = True
  1011. else:
  1012. if cmp_props["soft_range"] != cmp_props["hard_range"]:
  1013. self.soft_min = self.min
  1014. self.soft_max = self.max
  1015. changed = True
  1016. changed |= (cmp_props["use_soft_limits"] != self._cmp_props["use_soft_limits"])
  1017. if changed:
  1018. cmp_props = self._cmp_props_get()
  1019. self._cmp_props = cmp_props
  1020. return changed
  1021. def draw(self, _context):
  1022. layout = self.layout
  1023. layout.prop(self, "property")
  1024. layout.prop(self, "value")
  1025. row = layout.row()
  1026. row.enabled = type(self.get_value_eval()) in {int, float}
  1027. row.prop(self, "default")
  1028. row = layout.row(align=True)
  1029. row.prop(self, "min")
  1030. row.prop(self, "max")
  1031. row = layout.row()
  1032. row.prop(self, "use_soft_limits")
  1033. if bpy.app.use_override_library:
  1034. row.prop(self, "is_overridable_library")
  1035. row = layout.row(align=True)
  1036. row.enabled = self.use_soft_limits
  1037. row.prop(self, "soft_min", text="Soft Min")
  1038. row.prop(self, "soft_max", text="Soft Max")
  1039. layout.prop(self, "description")
  1040. class WM_OT_properties_add(Operator):
  1041. bl_idname = "wm.properties_add"
  1042. bl_label = "Add Property"
  1043. bl_options = {'UNDO', 'INTERNAL'}
  1044. data_path: rna_path
  1045. def execute(self, context):
  1046. from rna_prop_ui import (
  1047. rna_idprop_ui_create,
  1048. )
  1049. data_path = self.data_path
  1050. item = eval("context.%s" % data_path)
  1051. def unique_name(names):
  1052. prop = "prop"
  1053. prop_new = prop
  1054. i = 1
  1055. while prop_new in names:
  1056. prop_new = prop + str(i)
  1057. i += 1
  1058. return prop_new
  1059. prop = unique_name({
  1060. *item.keys(),
  1061. *type(item).bl_rna.properties.keys(),
  1062. })
  1063. rna_idprop_ui_create(item, prop, default=1.0)
  1064. return {'FINISHED'}
  1065. class WM_OT_properties_context_change(Operator):
  1066. """Jump to a different tab inside the properties editor"""
  1067. bl_idname = "wm.properties_context_change"
  1068. bl_label = ""
  1069. bl_options = {'INTERNAL'}
  1070. context: StringProperty(
  1071. name="Context",
  1072. maxlen=64,
  1073. )
  1074. def execute(self, context):
  1075. context.space_data.context = self.context
  1076. return {'FINISHED'}
  1077. class WM_OT_properties_remove(Operator):
  1078. """Internal use (edit a property data_path)"""
  1079. bl_idname = "wm.properties_remove"
  1080. bl_label = "Remove Property"
  1081. bl_options = {'UNDO', 'INTERNAL'}
  1082. data_path: rna_path
  1083. property: rna_property
  1084. def execute(self, context):
  1085. from rna_prop_ui import (
  1086. rna_idprop_ui_prop_clear,
  1087. rna_idprop_ui_prop_update,
  1088. )
  1089. data_path = self.data_path
  1090. item = eval("context.%s" % data_path)
  1091. prop = self.property
  1092. rna_idprop_ui_prop_update(item, prop)
  1093. del item[prop]
  1094. rna_idprop_ui_prop_clear(item, prop)
  1095. return {'FINISHED'}
  1096. class WM_OT_sysinfo(Operator):
  1097. """Generate system information, saved into a text file"""
  1098. bl_idname = "wm.sysinfo"
  1099. bl_label = "Save System Info"
  1100. filepath: StringProperty(
  1101. subtype='FILE_PATH',
  1102. options={'SKIP_SAVE'},
  1103. )
  1104. def execute(self, _context):
  1105. import sys_info
  1106. sys_info.write_sysinfo(self.filepath)
  1107. return {'FINISHED'}
  1108. def invoke(self, context, _event):
  1109. import os
  1110. if not self.filepath:
  1111. self.filepath = os.path.join(
  1112. os.path.expanduser("~"), "system-info.txt")
  1113. wm = context.window_manager
  1114. wm.fileselect_add(self)
  1115. return {'RUNNING_MODAL'}
  1116. class WM_OT_operator_cheat_sheet(Operator):
  1117. """List all the Operators in a text-block, useful for scripting"""
  1118. bl_idname = "wm.operator_cheat_sheet"
  1119. bl_label = "Operator Cheat Sheet"
  1120. def execute(self, _context):
  1121. op_strings = []
  1122. tot = 0
  1123. for op_module_name in dir(bpy.ops):
  1124. op_module = getattr(bpy.ops, op_module_name)
  1125. for op_submodule_name in dir(op_module):
  1126. op = getattr(op_module, op_submodule_name)
  1127. text = repr(op)
  1128. if text.split("\n")[-1].startswith("bpy.ops."):
  1129. op_strings.append(text)
  1130. tot += 1
  1131. op_strings.append('')
  1132. textblock = bpy.data.texts.new("OperatorList.txt")
  1133. textblock.write('# %d Operators\n\n' % tot)
  1134. textblock.write('\n'.join(op_strings))
  1135. self.report({'INFO'}, "See OperatorList.txt textblock")
  1136. return {'FINISHED'}
  1137. # -----------------------------------------------------------------------------
  1138. # Add-on Operators
  1139. class WM_OT_owner_enable(Operator):
  1140. """Enable workspace owner ID"""
  1141. bl_idname = "wm.owner_enable"
  1142. bl_label = "Enable Add-on"
  1143. owner_id: StringProperty(
  1144. name="UI Tag",
  1145. )
  1146. def execute(self, context):
  1147. workspace = context.workspace
  1148. workspace.owner_ids.new(self.owner_id)
  1149. return {'FINISHED'}
  1150. class WM_OT_owner_disable(Operator):
  1151. """Enable workspace owner ID"""
  1152. bl_idname = "wm.owner_disable"
  1153. bl_label = "Disable UI Tag"
  1154. owner_id: StringProperty(
  1155. name="UI Tag",
  1156. )
  1157. def execute(self, context):
  1158. workspace = context.workspace
  1159. owner_id = workspace.owner_ids[self.owner_id]
  1160. workspace.owner_ids.remove(owner_id)
  1161. return {'FINISHED'}
  1162. class WM_OT_tool_set_by_id(Operator):
  1163. """Set the tool by name (for keymaps)"""
  1164. bl_idname = "wm.tool_set_by_id"
  1165. bl_label = "Set Tool By Name"
  1166. name: StringProperty(
  1167. name="Identifier",
  1168. description="Identifier of the tool",
  1169. )
  1170. cycle: BoolProperty(
  1171. name="Cycle",
  1172. description="Cycle through tools in this group",
  1173. default=False,
  1174. options={'SKIP_SAVE'},
  1175. )
  1176. space_type: rna_space_type_prop
  1177. if use_toolbar_release_hack:
  1178. def invoke(self, context, event):
  1179. # Hack :S
  1180. if not self.properties.is_property_set("name"):
  1181. WM_OT_toolbar._key_held = False
  1182. return {'PASS_THROUGH'}
  1183. elif (WM_OT_toolbar._key_held == event.type) and (event.value != 'RELEASE'):
  1184. return {'PASS_THROUGH'}
  1185. WM_OT_toolbar._key_held = None
  1186. return self.execute(context)
  1187. def execute(self, context):
  1188. from bl_ui.space_toolsystem_common import (
  1189. activate_by_id,
  1190. activate_by_id_or_cycle,
  1191. )
  1192. if self.properties.is_property_set("space_type"):
  1193. space_type = self.space_type
  1194. else:
  1195. space_type = context.space_data.type
  1196. fn = activate_by_id_or_cycle if self.cycle else activate_by_id
  1197. if fn(context, space_type, self.name):
  1198. return {'FINISHED'}
  1199. else:
  1200. self.report({'WARNING'}, f"Tool {self.name!r:s} not found for space {space_type!r:s}.")
  1201. return {'CANCELLED'}
  1202. class WM_OT_tool_set_by_index(Operator):
  1203. """Set the tool by index (for keymaps)"""
  1204. bl_idname = "wm.tool_set_by_index"
  1205. bl_label = "Set Tool By Index"
  1206. index: IntProperty(
  1207. name="Index in toolbar",
  1208. default=0,
  1209. )
  1210. cycle: BoolProperty(
  1211. name="Cycle",
  1212. description="Cycle through tools in this group",
  1213. default=False,
  1214. options={'SKIP_SAVE'},
  1215. )
  1216. expand: BoolProperty(
  1217. description="Include tool sub-groups",
  1218. default=True,
  1219. )
  1220. space_type: rna_space_type_prop
  1221. def execute(self, context):
  1222. from bl_ui.space_toolsystem_common import (
  1223. activate_by_id,
  1224. activate_by_id_or_cycle,
  1225. item_from_index,
  1226. item_from_flat_index,
  1227. )
  1228. if self.properties.is_property_set("space_type"):
  1229. space_type = self.space_type
  1230. else:
  1231. space_type = context.space_data.type
  1232. fn = item_from_flat_index if self.expand else item_from_index
  1233. item = fn(context, space_type, self.index)
  1234. if item is None:
  1235. # Don't report, since the number of tools may change.
  1236. return {'CANCELLED'}
  1237. # Same as: WM_OT_tool_set_by_id
  1238. fn = activate_by_id_or_cycle if self.cycle else activate_by_id
  1239. if fn(context, space_type, item.idname):
  1240. return {'FINISHED'}
  1241. else:
  1242. # Since we already have the tool, this can't happen.
  1243. raise Exception("Internal error setting tool")
  1244. class WM_OT_toolbar(Operator):
  1245. bl_idname = "wm.toolbar"
  1246. bl_label = "Toolbar"
  1247. @classmethod
  1248. def poll(cls, context):
  1249. return context.space_data is not None
  1250. if use_toolbar_release_hack:
  1251. _key_held = None
  1252. def invoke(self, context, event):
  1253. WM_OT_toolbar._key_held = event.type
  1254. return self.execute(context)
  1255. def execute(self, context):
  1256. from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
  1257. from bl_keymap_utils import keymap_from_toolbar
  1258. space_type = context.space_data.type
  1259. cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
  1260. if cls is None:
  1261. return {'CANCELLED'}
  1262. wm = context.window_manager
  1263. keymap = keymap_from_toolbar.generate(context, space_type)
  1264. def draw_menu(popover, context):
  1265. layout = popover.layout
  1266. layout.operator_context = 'INVOKE_REGION_WIN'
  1267. cls.draw_cls(layout, context, detect_layout=False, scale_y=1.0)
  1268. wm.popover(draw_menu, ui_units_x=8, keymap=keymap)
  1269. return {'FINISHED'}
  1270. class WM_MT_splash(Menu):
  1271. bl_label = "Splash"
  1272. def draw_setup(self, context):
  1273. wm = context.window_manager
  1274. # prefs = context.preferences
  1275. layout = self.layout
  1276. layout.operator_context = 'EXEC_DEFAULT'
  1277. layout.label(text="Quick Setup")
  1278. split = layout.split(factor=0.25)
  1279. split.label()
  1280. split = split.split(factor=2.0 / 3.0)
  1281. col = split.column()
  1282. col.label()
  1283. sub = col.split(factor=0.35)
  1284. row = sub.row()
  1285. row.alignment = 'RIGHT'
  1286. row.label(text="Shortcuts")
  1287. text = bpy.path.display_name(wm.keyconfigs.active.name)
  1288. if not text:
  1289. text = "Blender"
  1290. sub.menu("USERPREF_MT_keyconfigs", text=text)
  1291. kc = wm.keyconfigs.active
  1292. kc_prefs = kc.preferences
  1293. has_select_mouse = hasattr(kc_prefs, "select_mouse")
  1294. if has_select_mouse:
  1295. sub = col.split(factor=0.35)
  1296. row = sub.row()
  1297. row.alignment = 'RIGHT'
  1298. row.label(text="Select With")
  1299. sub.row().prop(kc_prefs, "select_mouse", expand=True)
  1300. has_select_mouse = True
  1301. has_spacebar_action = hasattr(kc_prefs, "spacebar_action")
  1302. if has_spacebar_action:
  1303. sub = col.split(factor=0.35)
  1304. row = sub.row()
  1305. row.alignment = 'RIGHT'
  1306. row.label(text="Spacebar")
  1307. sub.row().prop(kc_prefs, "spacebar_action", expand=True)
  1308. has_select_mouse = True
  1309. col.separator()
  1310. sub = col.split(factor=0.35)
  1311. row = sub.row()
  1312. row.alignment = 'RIGHT'
  1313. row.label(text="Theme")
  1314. label = bpy.types.USERPREF_MT_interface_theme_presets.bl_label
  1315. if label == "Presets":
  1316. label = "Blender Dark"
  1317. sub.menu("USERPREF_MT_interface_theme_presets", text=label)
  1318. # We need to make switching to a language easier first
  1319. #sub = col.split(factor=0.35)
  1320. #row = sub.row()
  1321. #row.alignment = 'RIGHT'
  1322. # row.label(text="Language:")
  1323. #prefs = context.preferences
  1324. #sub.prop(prefs.system, "language", text="")
  1325. # Keep height constant
  1326. if not has_select_mouse:
  1327. col.label()
  1328. if not has_spacebar_action:
  1329. col.label()
  1330. layout.label()
  1331. row = layout.row()
  1332. sub = row.row()
  1333. if bpy.types.PREFERENCES_OT_copy_prev.poll(context):
  1334. old_version = bpy.types.PREFERENCES_OT_copy_prev.previous_version()
  1335. sub.operator("preferences.copy_prev", text="Load %d.%d Settings" % old_version)
  1336. sub.operator("wm.save_userpref", text="Save New Settings")
  1337. else:
  1338. sub.label()
  1339. sub.label()
  1340. sub.operator("wm.save_userpref", text="Next")
  1341. layout.separator()
  1342. layout.separator()
  1343. def draw(self, context):
  1344. # Draw setup screen if no preferences have been saved yet.
  1345. import os
  1346. userconfig_path = bpy.utils.user_resource('CONFIG')
  1347. userdef_path = os.path.join(userconfig_path, "userpref.blend")
  1348. if not os.path.isfile(userdef_path):
  1349. self.draw_setup(context)
  1350. return
  1351. # Pass
  1352. layout = self.layout
  1353. layout.operator_context = 'EXEC_DEFAULT'
  1354. layout.emboss = 'PULLDOWN_MENU'
  1355. split = layout.split()
  1356. # Templates
  1357. col1 = split.column()
  1358. col1.label(text="New File")
  1359. bpy.types.TOPBAR_MT_file_new.draw_ex(col1, context, use_splash=True)
  1360. # Recent
  1361. col2 = split.column()
  1362. col2_title = col2.row()
  1363. found_recent = col2.template_recent_files()
  1364. if found_recent:
  1365. col2_title.label(text="Recent Files")
  1366. else:
  1367. if bpy.app.version_cycle in {'rc', 'release'}:
  1368. manual_version = '%d.%d' % bpy.app.version[:2]
  1369. else:
  1370. manual_version = 'dev'
  1371. # Links if no recent files
  1372. col2_title.label(text="Getting Started")
  1373. col2.operator(
  1374. "wm.url_open", text="Manual", icon='URL'
  1375. ).url = "https://docs.blender.org/manual/en/" + manual_version + "/"
  1376. col2.operator(
  1377. "wm.url_open", text="Release Notes", icon='URL',
  1378. ).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
  1379. col2.operator(
  1380. "wm.url_open", text="Blender Website", icon='URL',
  1381. ).url = "https://www.blender.org"
  1382. col2.operator(
  1383. "wm.url_open", text="Credits", icon='URL',
  1384. ).url = "https://www.blender.org/about/credits/"
  1385. layout.separator()
  1386. split = layout.split()
  1387. col1 = split.column()
  1388. sub = col1.row()
  1389. sub.operator_context = 'INVOKE_DEFAULT'
  1390. sub.operator("wm.open_mainfile", text="Open...", icon='FILE_FOLDER')
  1391. col1.operator("wm.recover_last_session", icon='RECOVER_LAST')
  1392. col2 = split.column()
  1393. col2.operator(
  1394. "wm.url_open", text="Release Notes", icon='URL',
  1395. ).url = "https://www.blender.org/download/releases/%d-%d/" % bpy.app.version[:2]
  1396. col2.operator(
  1397. "wm.url_open", text="Development Fund", icon='FUND'
  1398. ).url = "https://fund.blender.org"
  1399. layout.separator()
  1400. layout.separator()
  1401. class WM_OT_drop_blend_file(Operator):
  1402. bl_idname = "wm.drop_blend_file"
  1403. bl_label = "Handle dropped .blend file"
  1404. bl_options = {'INTERNAL'}
  1405. filepath: StringProperty()
  1406. def invoke(self, context, _event):
  1407. context.window_manager.popup_menu(self.draw_menu, title=bpy.path.basename(self.filepath), icon='QUESTION')
  1408. return {'FINISHED'}
  1409. def draw_menu(self, menu, _context):
  1410. layout = menu.layout
  1411. col = layout.column()
  1412. col.operator_context = 'INVOKE_DEFAULT'
  1413. props = col.operator("wm.open_mainfile", text="Open", icon='FILE_FOLDER')
  1414. props.filepath = self.filepath
  1415. props.display_file_selector = False
  1416. layout.separator()
  1417. col = layout.column()
  1418. col.operator_context = 'INVOKE_DEFAULT'
  1419. col.operator("wm.link", text="Link...", icon='LINK_BLEND').filepath = self.filepath
  1420. col.operator("wm.append", text="Append...", icon='APPEND_BLEND').filepath = self.filepath
  1421. classes = (
  1422. WM_OT_context_collection_boolean_set,
  1423. WM_OT_context_cycle_array,
  1424. WM_OT_context_cycle_enum,
  1425. WM_OT_context_cycle_int,
  1426. WM_OT_context_menu_enum,
  1427. WM_OT_context_modal_mouse,
  1428. WM_OT_context_pie_enum,
  1429. WM_OT_context_scale_float,
  1430. WM_OT_context_scale_int,
  1431. WM_OT_context_set_boolean,
  1432. WM_OT_context_set_enum,
  1433. WM_OT_context_set_float,
  1434. WM_OT_context_set_id,
  1435. WM_OT_context_set_int,
  1436. WM_OT_context_set_string,
  1437. WM_OT_context_set_value,
  1438. WM_OT_context_toggle,
  1439. WM_OT_context_toggle_enum,
  1440. WM_OT_doc_view,
  1441. WM_OT_doc_view_manual,
  1442. WM_OT_drop_blend_file,
  1443. WM_OT_operator_cheat_sheet,
  1444. WM_OT_operator_pie_enum,
  1445. WM_OT_path_open,
  1446. WM_OT_properties_add,
  1447. WM_OT_properties_context_change,
  1448. WM_OT_properties_edit,
  1449. WM_OT_properties_remove,
  1450. WM_OT_sysinfo,
  1451. WM_OT_owner_disable,
  1452. WM_OT_owner_enable,
  1453. WM_OT_url_open,
  1454. WM_OT_tool_set_by_id,
  1455. WM_OT_tool_set_by_index,
  1456. WM_OT_toolbar,
  1457. WM_MT_splash,
  1458. )