Fix #105009: AnimAll: Error when inserting key on string attribute
[blender-addons.git] / materials_library_vx / __init__.py
blobc0a605762e034ee2754233ad2ca960a9e074bbbd
1 # SPDX-FileCopyrightText: 2017-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 bl_info = {
6 "name": "Material Library",
7 "author": "Mackraken",
8 "version": (0, 6, 0),
9 "blender": (2, 80, 0),
10 "location": "Properties > Material",
11 "description": "Material Library VX",
12 "warning": "",
13 "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_library.html",
14 "tracker_url": "",
15 "category": "Material",
19 import bpy
20 import json
21 import os
22 from pathlib import Path
24 from bpy.app.handlers import persistent
25 from bpy.props import (
26 StringProperty, IntProperty, BoolProperty,
27 PointerProperty, CollectionProperty
29 from bpy.types import (
30 Panel, Menu, AddonPreferences, Operator,
31 PropertyGroup, UIList,
32 Scene
35 from rna_prop_ui import PropertyPanel
37 dev = False
39 user_path = Path(bpy.utils.resource_path('USER')).parent
40 matlib_path = os.path.join(user_path, "matlib")
42 if not os.path.exists(matlib_path):
43 import shutil
44 os.mkdir(matlib_path)
45 addon_path = os.path.dirname(__file__)
46 shutil.copy2(os.path.join(addon_path, "categories.txt"), matlib_path)
47 shutil.copy2(os.path.join(addon_path, "templates.blend"), matlib_path)
48 shutil.copy2(os.path.join(addon_path, "sample_materials.blend"), matlib_path)
50 ##debug print variables
51 def dd(*args, dodir=False):
52 if dev:
53 if dodir:
54 print(dir(*args))
55 print(*args)
57 #Regular Functions
58 def winpath(path):
59 return path.replace("\\", "\\\\")
61 def update_search_index(self, context):
62 search = self.search
63 for i, it in enumerate(self.materials):
64 if it.name==search:
65 self.mat_index = i
66 break
68 def check_path(path):
69 #isabs sometimes returns true on relpaths
70 if path and os.path.exists(path) and os.path.isfile(path) and os.path.isabs(path):
71 try:
72 if bpy.data.filepath and bpy.path.relpath(bpy.data.filepath) == bpy.path.relpath(path):
73 return False
74 except:
75 pass
76 #paths are on different drives. No problem then
77 return True
78 return False
80 def update_lib_index(self, context):
81 self.load_library()
83 def update_cat_index(self, context):
84 dd("cat index:", self.current_category, self.filter)
86 if self.filter:
87 self.filter = True
90 def update_filter(self, context):
92 dd("filter:", self.filter, self.cat_index, self.current_category)
93 # index = self.cat_index
95 # if self.filter:
96 # cat = self.current_category
97 # else:
98 # cat = ""
100 # self.current_library.filter = cat
101 self.update_list()
103 def check_index(collection, index):
104 count = len(collection)
105 return count>0 and index<count and index>=0
107 def send_command(cmd, output="sendmat.py"):
108 bin = winpath(bpy.app.binary_path)
109 scriptpath = winpath(os.path.join(bpy.app.tempdir, output))
111 with open(scriptpath, "w") as f:
112 f.write(cmd)
114 import subprocess
116 if output == "createlib.py":
117 code = subprocess.call([bin, "-b", "-P", scriptpath])
118 else:
119 libpath = winpath(bpy.context.scene.matlib.current_library.path)
120 code = subprocess.call([bin, "-b", libpath, "-P", scriptpath])
122 #code returns 0 if ok, 1 if not
123 return abs(code-1)
125 def list_materials(path, sort=False):
126 list = []
127 with bpy.data.libraries.load(path) as (data_from, data_to):
128 for mat in data_from.materials:
129 list.append(mat)
131 if sort: list = sorted(list)
132 return list
134 #category properties (none atm)
135 class EmptyGroup(PropertyGroup):
136 pass
137 # bpy.utils.register_class(EmptyGroup)
139 class matlibMaterials(PropertyGroup):
140 category: StringProperty()
141 # bpy.utils.register_class(matlibMaterials)
143 #bpy.types.Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
145 ### CATEGORIES
146 class Categories():
148 #cats = bpy.context.scene.matlib.categories
150 def __init__(self, cats):
151 self.cats = cats
153 def save(self):
154 scn = bpy.context.scene
155 cats = set([cat.name for cat in self.cats])
156 libpath = bpy.context.scene.matlib.current_library.path
158 cmd = """
159 print(30*"+")
160 import bpy
161 if not hasattr(bpy.context.scene, "matlib_categories"):
162 class EmptyProps(bpy.types.PropertyGroup):
163 pass
164 bpy.utils.register_class(EmptyProps)
165 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
166 cats = bpy.context.scene.matlib_categories
167 for cat in cats:
168 cats.remove(0)
170 for cat in cats:
171 cmd += """
172 cat = cats.add()
173 cat.name = "%s" """ % cat.capitalize()
174 cmd +='''
175 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(libpath)
177 return send_command(cmd, "save_categories.py")
179 def read(self, pull=True):
180 #mandar a imprimir el listado
181 catfile = winpath(os.path.join(matlib_path, "categories.txt"))
182 cmd = """
183 import bpy, json
184 class EmptyProps(bpy.types.PropertyGroup):
185 pass
186 bpy.utils.register_class(EmptyProps)
187 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
188 cats = []
189 for cat in bpy.context.scene.matlib_categories:
190 materials = []
191 for mat in bpy.data.materials:
192 if "category" in mat.keys() and mat['category'] == cat.name:
193 materials.append(mat.name)
194 cats.append([cat.name, materials])
195 with open("%s", "w") as f:
196 f.write(json.dumps(cats, sort_keys=True, indent=4))
197 """ % catfile
198 if pull: send_command(cmd)
200 #leer el fichero
201 with open(catfile, "r") as f:
202 cats = json.loads(f.read())
204 dd(cats)
206 # #refrescar categorias
207 # for cat in self.cats:
208 # self.cats.remove(0)
210 # for cat in cats:
211 # item = self.cats.add()
212 # item.name = cat
214 return cats
216 def view(self):
217 for cat in self.cats:
218 dd(cat.name)
220 def add(self, name):
221 if name and name not in [item.name for item in self.cats]:
222 name = name.strip().capitalize()
223 item = self.cats.add()
224 item.name = name
225 if self.save():
226 dd(name, "added")
227 return True
228 else:
229 dd("duplicated?")
231 def remove(self, index):
232 self.cats.remove(index)
233 self.save()
235 class Library():
237 def __init__(self, matlib_path, name):
238 self.name = name
239 self.path = os.path.join(matlib_path, name)
240 # @property
241 # def default(self):
242 # return self.name == default_library
244 @property
245 def shortname(self):
246 # if self.default:
247 # return "Default Library"
248 return bpy.path.display_name(self.name).title()
251 def __repr__(self):
252 return str(type(self).__name__) + "('" + self.name + "')"
254 #bpy.utils.register_class(Library)
256 def get_libraries():
257 libs = [Library(matlib_path, f) for f in os.listdir(matlib_path) if f[-5::] == "blend"]
258 return sorted(libs, key=lambda x: bpy.path.display_name(x.name))
260 libraries = []
261 # get_libraries()
263 ### MATLIB CLASS
264 class matlibProperties(PropertyGroup):
266 #MATLIB PROPERTIES
268 #libraries are read from the xml
269 lib_index: IntProperty(min = -1, default = 2, update=update_lib_index)
270 all_materials: CollectionProperty(type = matlibMaterials)
271 materials: CollectionProperty(type = matlibMaterials)
272 mat_index: IntProperty(min = -1, default = -1)
273 categories: CollectionProperty(type = EmptyGroup)
274 cat_index: IntProperty(min = -1, default = -1, update=update_cat_index)
275 search: StringProperty(name="Search", description="Find By Name", update=update_search_index)
277 #MATLIB OPTIONS
278 #link: import material linked
279 #force import:
280 # if disable it wont import a material if its present in the scene,(avoid duplicates)
281 # instead it will apply the scene material rather than importing the same one from the library
282 #filter: enable or disable category filter
283 #last selected: store the last selected object to regain focus when apply a material.
284 #hide_search: Hides Search Field
285 link: BoolProperty(name = "Linked", description="Link the material", default = False)
286 force_import: BoolProperty(name = "Force Import", description="Use Scene Materials by default", default = False)
287 filter: BoolProperty(name = "Filter",description="Filter Categories", default = True, update=update_filter)
288 show_prefs: BoolProperty(name = "show_prefs", description="Preferences", default = False)
289 last_selected: StringProperty(name="Last Selected")
290 hide_search: BoolProperty(name="Hide Search", description="Use Blender Search Only")
291 #import_file = StringProperty("Import File", subtype="FILE_PATH")
292 #path = os.path.dirname(path)
293 #Development only
295 @property
296 def libraries(self):
297 global libraries
298 return libraries
300 @property
301 def current_library(self):
302 if check_index(libraries, self.lib_index):
303 return libraries[self.lib_index]
305 @property
306 def active_material(self):
307 if check_index(self.materials, self.mat_index):
308 return self.materials[self.mat_index]
310 def reload(self):
311 dd("loading libraries")
312 if self.current_library:
313 self.load_library()
314 elif self.lib_index == -1 and len(libraries):
315 self.lib_index = 0
318 def add_library(self, path, setEnabled = False):
319 #sanitize path
320 ext = os.path.extsep + "blend"
321 if not path.endswith(ext):
322 path += ext
324 if check_path(path):
325 # if path == default_library:
326 # return 'ERROR', "Cannot add default library."
327 #if path in [lib.path for lib in self.libraries]:
328 return 'ERROR', "Library already exists."
329 else:
330 dd("Can't find " + path)
331 #create file
332 cmd = '''
333 import bpy
334 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(path)
335 if not (send_command(cmd, "createlib.py")):
336 return 'ERROR', "There was an error creating the file. Make sure you run Blender with admin rights."
338 #self.libraries = sorted(self.libraries, key=lambda lib: sortlibs(lib))
339 dd("adding library", path)
340 global libraries
341 libraries = get_libraries()
342 return "INFO", "Library added"
344 def load_library(self):
345 self.empty_list(True)
346 if not self.current_library:
347 return 'ERROR', "Library not found!."
349 path = self.current_library.path
351 dd("loading library", self.lib_index, path)
353 if check_path(path):
354 self.filter = False
355 self.cat_index = -1
357 categories = Categories(self.categories)
358 self.cats = categories.read(True)
359 self.load_categories()
361 for mat in self.all_materials:
362 self.all_materials.remove(0)
364 for mat in list_materials(self.current_library.path, True):
365 item = self.all_materials.add()
366 item.name = mat
367 for cat in self.cats:
368 if mat in cat[1]:
369 item.category = cat[0]
370 break
372 self.update_list()
373 else:
374 return 'ERROR', "Library not found!."
376 def update_list(self):
377 ### THIS HAS TO SORT
378 self.empty_list()
379 if self.current_library:
380 current_category = self.current_category
381 #sorteditems = sorted(self.all_materials, key=lambda x: x.name)
382 for mat in self.all_materials:
383 #print(current_category, mat.category)
384 if not self.filter or (self.filter and mat.category == current_category) or current_category == "":
385 item = self.materials.add()
386 item.name = mat.name
387 item.category = mat.category
389 def empty_list(self, cats = False):
390 #self.mat_index = -1
391 for it in self.materials:
392 self.materials.remove(0)
394 if cats:
395 for c in self.categories:
396 self.categories.remove(0)
398 ### CATEGORIES
399 @property
400 def current_category(self):
401 #print(self.mat_index)
402 if check_index(self.categories, self.cat_index):
403 return self.categories[self.cat_index].name
404 return ""
406 def load_categories(self):
408 for c in self.categories:
409 self.categories.remove(0)
411 for c in self.cats:
412 cat = self.categories.add()
413 cat.name = c[0]
415 def add_category(self, name):
416 if name:
417 name = name.strip().title()
418 dd("add category", name)
419 categories = Categories(self.categories)
421 categories.add(name)
423 # if lib:
424 # cat = xml.find("category", name, lib, create = True)
425 # self.load_categories()
426 # else:
427 # return 'ERROR', "Library not found"
428 def remove_category(self):
429 dd("removing category", self.current_category)
430 categories = Categories(self.categories)
431 categories.remove(self.cat_index)
433 def set_category(self):
434 mat = self.active_material
435 #dd(lib, mat, self.current_category)
436 if mat:
437 #set mat to category
438 if self.cat_index>-1:
439 dd(self.current_category)
440 cat = self.current_category
441 if cat == self.all_materials[self.mat_index].category:
442 return
443 cmd = """
444 import bpy
445 try:
446 mat = bpy.data.materials['%s']
447 except:
448 mat = None
449 if mat:
450 mat['category'] = "%s"
451 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)
452 """ % (mat.name, cat, winpath(self.current_library.path))
453 if send_command(cmd):
454 self.all_materials[self.mat_index].category = cat
455 mat.category = cat
456 else:
457 return "WARNING", "There was an error."
459 # catnode = xml.find("category", self.current_category, lib, True)
460 # matnode = xml.find("material", mat.name, lib)
461 # if matnode:
462 # catnode.appendChild(matnode)
463 # else:
464 # matnode = xml.find("material", mat.name, catnode, True)
465 # xml.save()
466 # mat.category = cat
467 # self.current_library.materials[self.mat_index].category = cat
468 #remove mat from any category
469 else:
470 mat.category = ""
471 self.all_materials[self.mat_index].category = ""
472 else:
473 return "WARNING", "Select a material"
475 def get_material(self, name, link=False):
476 with bpy.data.libraries.load(self.current_library.path, link=link, relative=False) as (data_from, data_to):
477 data_to.materials = [name]
478 if link:
479 print(name + " linked.")
480 else:
481 print(name + " appended.")
483 def apply(self, context, preview=False):
484 name = self.active_material.name
485 if not name: return "WARNING", "Select a material from the list."
487 linked = self.link or preview
488 force = self.force_import or linked
490 objects = []
491 active = context.object
492 dummy = self.get_dummy(context)
494 #setup objects
495 if preview:
496 if context.mode == "EDIT_MESH":
497 return "WARNING", "Can't preview on EDIT MODE"
498 if dummy!= active:
499 self.last_selected = context.object.name
500 context.view_layer.objects.active = dummy
501 objects.append(dummy)
502 #apply
503 else:
504 objects = [obj for obj in context.selected_objects if hasattr(obj.data, "materials")]
506 if not objects:
507 return "INFO", "Please select an object"
509 if dummy == context.object and not preview:
510 if (len(objects)==1 and dummy.select_get()):
511 return "ERROR", "Apply is disabled for the Material Preview Object"
512 try:
513 last = context.scene.objects[self.last_selected]
514 if last in context.selected_objects:
515 context.view_layer.objects.active = last
516 else:
517 self.last_selected = ""
518 except:
519 context.view_layer.objects.active = None
520 dummy.select_set(False)
521 #objects = context.selected_objects
523 material = None
525 #mira si hay materiales linkados de la libreria actual
526 for mat in bpy.data.materials:
527 try:
528 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
529 except:
530 samelib = False
532 if mat.name == name and mat.library and samelib:
533 material = mat
534 dd("encontre linked", name, "no importo nada")
535 break
537 if not force:
538 #busca materiales no linkados
539 for mat in bpy.data.materials:
540 if mat.name == name and not mat.library:
541 material = mat
542 dd("encontre no linkado", name, "no importo nada")
543 break
545 if not material:
546 #go get it
547 dd("voy a buscarlo")
548 nmats = len(bpy.data.materials)
550 self.get_material(name, linked)
552 if not self.force_import:
553 try:
554 material = bpy.data.materials[name]
555 except:
556 pass
558 if not material:
559 if nmats == len(bpy.data.materials) and not linked:
560 return "ERROR", name + " doesn't exists at library " + str(linked)
561 else:
562 for mat in reversed(bpy.data.materials):
563 if mat.name[0:len(name)] == name:
564 #careful on how blender writes library paths
565 try:
566 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
567 except:
568 samelib = False
570 if linked and mat.library and samelib:
571 material = mat
572 dd(name, "importado con link")
573 break
574 else:
575 if not mat.library:
576 dd(name, "importado sin link")
577 material = mat
578 break
579 if material:
580 material.use_fake_user = False
581 material.user_clear()
583 print ("Material", material, force)
585 #if material:
586 #maybe some test cases doesn't return a material, gotta take care of that
587 #i cannot think of any case like that right now
588 #maybe import linked when the database isn't sync
589 if context.mode == "EDIT_MESH":
590 obj = context.object
591 dd(material)
592 index = -1
593 for i, mat in enumerate(obj.data.materials):
594 if mat == material:
595 index = i
596 break
598 if index == -1:
599 obj.data.materials.append(material)
600 index = len(obj.data.materials)-1
601 dd(index)
602 import bmesh
603 bm = bmesh.from_edit_mesh(obj.data)
604 for f in bm.faces:
605 if f.select:
606 f.material_index = index
608 else:
609 for obj in objects:
610 index = obj.active_material_index
611 if index < len(obj.material_slots):
612 obj.material_slots[index].material = None
613 obj.material_slots[index].material = material
614 else:
615 obj.data.materials.append(material)
617 if not linked:
618 bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")
620 def add_material(self, mat):
622 if not mat:
623 return 'WARNING', "Select a material from the scene."
625 name = mat.name
626 thispath = winpath(bpy.data.filepath)
627 libpath = winpath(self.current_library.path)
629 if not thispath:
630 return 'WARNING', "Save this file before export."
632 if not libpath:
633 return 'WARNING', "Library not found!."
635 elif bpy.data.is_dirty:
636 bpy.ops.wm.save_mainfile(check_existing=True)
638 if mat.library:
639 return 'WARNING', 'Cannot export linked materials.'
641 dd("adding material", name, libpath)
643 overwrite = ""
644 if name in list_materials(libpath):
645 overwrite = '''
646 mat = bpy.data.materials["%s"]
647 mat.name = "tmp"
648 mat.use_fake_user = False
649 mat.user_clear()''' % name
651 cmd = '''
652 import bpy{0}
653 with bpy.data.libraries.load("{1}") as (data_from, data_to):
654 data_to.materials = ["{2}"]
655 mat = bpy.data.materials["{2}"]
656 mat.use_fake_user=True
657 bpy.ops.file.pack_all()
658 bpy.ops.wm.save_mainfile(filepath="{3}", check_existing=False, compress=True)
659 '''.format(overwrite, thispath, name, libpath)
661 if send_command(cmd):
662 #self.load_library()
663 if not overwrite:
664 item = self.all_materials.add()
665 item.name = name
666 if "category" in mat.keys():
667 item.category = mat['category']
668 #reorder all_materials
669 items = sorted([[item.name, item.category] for item in self.all_materials], key = lambda x: x[0])
671 self.all_materials.clear()
672 for it in items:
673 item = self.all_materials.add()
674 item.name = it[0]
675 item.category = it[1]
677 self.update_list()
679 return 'INFO', "Material added."
680 else:
681 print("Save Material Error: Run Blender with administrative privileges.")
682 return 'WARNING', "There was an error saving the material"
684 def remove_material(self):
685 name = self.active_material.name
686 libpath = winpath(self.current_library.path)
687 if name and libpath and name in list_materials(libpath):
688 cmd = '''import bpy
689 mat = bpy.data.materials["%s"]
690 mat.use_fake_user = False
691 mat.user_clear()
692 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % (name , libpath)
693 if send_command(cmd, "removemat.py"):
694 self.all_materials.remove(self.mat_index)
695 self.update_list()
696 else:
697 return 'ERROR', "There was an error."
698 return "INFO", name + " removed."
700 def get_dummy(self, context):
701 dummy_name = "Material_Preview_Dummy"
702 dummy_mesh = "Material_Preview_Mesh"
703 scn = context.scene
704 try:
705 dummy = scn.objects[dummy_name]
706 except:
707 #create dummy
708 try:
709 me = bpy.data.meshes(dummy_mesh)
710 except:
711 me = bpy.data.meshes.new(dummy_mesh)
712 dummy = bpy.data.objects.new(dummy_name, me)
713 context.collection.objects.link(dummy)
715 dummy.hide_set(True)
716 dummy.hide_render = True
717 dummy.hide_select = True
718 return dummy
720 # bpy.utils.register_class(matlibProperties)
721 # Scene.matlib = PointerProperty(type = matlibProperties)
723 ### MENUS
724 class MATLIB_MT_LibsMenu(Menu):
725 bl_label = "Libraries Menu"
727 def draw(self, context):
728 layout = self.layout
729 libs = libraries
730 #layout.operator("matlib.operator", text="Default Library").cmd="lib-1"
731 for i, lib in enumerate(libs):
732 layout.operator("matlib.operator", text=lib.shortname).cmd="lib"+str(i)
734 class MATLIB_MT_CatsMenu(Menu):
735 bl_label = "Categories Menu"
737 def draw(self, context):
738 layout = self.layout
739 cats = context.scene.matlib.categories
740 layout.operator("matlib.operator", text="All").cmd="cat-1"
741 for i, cat in enumerate(cats):
742 layout.operator("matlib.operator", text=cat.name).cmd="cat"+str(i)
744 ### OPERATORS
745 #class MATLIB_OT_add(Operator):
746 # """Add Active Material"""
747 # bl_label = "Add"
748 # bl_idname = "matlib.add_material"
750 # @classmethod
751 # def poll(cls, context):
752 # return context.active_object is not None
754 # def execute(self, context):
755 # print("executing")
756 # return {"FINISHED"}
760 class MATLIB_OT_add(Operator):
761 """Add active material to library"""
762 bl_idname = "matlib.add"
763 bl_label = "Add active material"
765 @classmethod
766 def poll(cls, context):
767 obj = context.active_object
768 return obj is not None and obj.active_material is not None
770 def execute(self, context):
771 matlib = context.scene.matlib
772 success = matlib.add_material(context.object.active_material)
773 if type(success).__name__ == "tuple":
774 print(success)
775 self.report({success[0]}, success[1])
776 return {'FINISHED'}
778 class MATLIB_OT_remove(Operator):
779 """Remove material from library"""
780 bl_idname = "matlib.remove"
781 bl_label = "Remove material from library"
783 @classmethod
784 def poll(cls, context):
785 matlib = context.scene.matlib
786 return check_index(matlib.materials, matlib.mat_index)
788 def execute(self, context):
789 matlib = context.scene.matlib
790 success = matlib.remove_material()
791 if type(success).__name__ == "tuple":
792 print(success)
793 self.report({success[0]}, success[1])
794 return {'FINISHED'}
796 class MATLIB_OT_remove(Operator):
797 """Reload library"""
798 bl_idname = "matlib.reload"
799 bl_label = "Reload library"
801 # @classmethod
802 # def poll(cls, context):
803 # matlib = context.scene.matlib
804 # index = matlib.mat_index
805 # l = len(matlib.materials)
806 # return l>0 and index >=0 and index < l
808 def execute(self, context):
809 matlib = context.scene.matlib
810 success = matlib.reload()
811 if type(success).__name__ == "tuple":
812 print(success)
813 self.report({success[0]}, success[1])
814 return {'FINISHED'}
817 class MATLIB_OT_apply(Operator):
818 """Apply selected material"""
819 bl_idname = "matlib.apply"
820 bl_label = "Apply material"
822 @classmethod
823 def poll(cls, context):
824 matlib = context.scene.matlib
825 index = matlib.mat_index
826 l = len(matlib.materials)
827 obj = context.active_object
828 return l>0 and index >=0 and index < l and obj is not None
830 def execute(self, context):
831 matlib = context.scene.matlib
832 success = matlib.apply(context, False)
833 if type(success).__name__ == "tuple":
834 print(success)
835 self.report({success[0]}, success[1])
836 return {'FINISHED'}
839 class MATLIB_OT_preview(Operator):
840 """Preview selected material"""
841 bl_idname = "matlib.preview"
842 bl_label = "Preview selected material"
844 @classmethod
845 def poll(cls, context):
846 matlib = context.scene.matlib
847 index = matlib.mat_index
848 l = len(matlib.materials)
849 obj = context.active_object
850 return l>0 and index >=0 and index < l
852 def execute(self, context):
853 matlib = context.scene.matlib
854 success = matlib.apply(context, True)
855 if type(success).__name__ == "tuple":
856 print(success)
857 self.report({success[0]}, success[1])
858 return {'FINISHED'}
861 class MATLIB_OT_flush(Operator):
862 """Flush unused materials"""
863 bl_idname = "matlib.flush"
864 bl_label = "Flush unused materials"
866 @classmethod
867 def poll(cls, context):
868 matlib = context.scene.matlib
869 index = matlib.mat_index
870 l = len(matlib.materials)
871 obj = context.active_object
872 return l>0 and index >=0 and index < l
874 def execute(self, context):
875 matlib = context.scene.matlib
876 dummy = matlib.get_dummy(context)
877 if dummy == context.object:
878 try:
879 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
880 except:
881 pass
883 for slot in dummy.material_slots:
884 slot.material = None
886 for mat in bpy.data.materials:
887 if mat.users==0:
888 i+=1
889 print (mat.name, "removed.")
890 bpy.data.materials.remove(mat)
892 plural = "" if i == 1 else "s"
893 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
895 return {'FINISHED'}
898 class MATLIB_OT_operator(Operator):
899 """Add, Remove, Reload, Apply, Preview, Clean Material"""
900 bl_label = "New"
901 bl_idname = "matlib.operator"
902 __doc__ = "Add, Remove, Reload, Apply, Preview, Clean Material"
904 category: StringProperty(name="Category")
905 filepath: StringProperty(options={'HIDDEN'})
906 cmd: bpy.props.StringProperty(name="Command", options={'HIDDEN'})
907 filter_glob: StringProperty(default="*.blend", options={'HIDDEN'})
908 @classmethod
909 def poll(cls, context):
910 return context.active_object is not None
912 def draw(self, context):
913 layout = self.layout
914 #cmd = LIBRARY_ADD
915 if self.cmd == "LIBRARY_ADD":
916 #layout.label(text="Select a blend file as library or")
917 #layout.label(text="Type a name to create a new library.")
918 layout.prop(self, "category", text="Library")
919 elif self.cmd == "FILTER_ADD":
920 layout.prop(self, "category")
922 def invoke(self, context, event):
924 cmd = self.cmd
925 print("invoke", cmd)
927 if cmd == "LIBRARY_ADD":
928 self.filepath = matlib_path + os.path.sep
929 dd("filepath", self.filepath, matlib_path)
930 #context.window_manager.fileselect_add(self)
931 context.window_manager.invoke_props_dialog(self)
932 return {'RUNNING_MODAL'}
933 elif cmd == "FILTER_ADD":
934 context.window_manager.invoke_props_dialog(self)
935 return {'RUNNING_MODAL'}
936 return self.execute(context)
938 ### TODO: execute doesn't trigger remove
939 def execute(self, context):
941 success = ""
942 matlib = context.scene.matlib
944 if self.cmd == "init":
945 print("initialize")
946 return {'FINISHED'}
948 #Library Commands
949 if self.cmd[0:3] == "lib":
950 index = int(self.cmd[3::])
951 matlib.lib_index = index
952 #success = matlib.load_library()
953 elif self.cmd == "LIBRARY_ADD":
954 dd("execute lib add")
955 libname = self.category
956 if libname[-6::] != ".blend": libname+= ".blend"
957 libname = os.path.join(matlib_path, libname)
958 print(libname)
960 success = matlib.add_library(libname, True)
961 for i, l in enumerate(libraries):
962 if l.name == self.category:
963 matlib.lib_index = i
964 break
966 elif self.cmd == "RELOAD":
967 success = matlib.reload()
969 if not matlib.current_library:
970 self.report({'ERROR'}, "Select a Library")
971 return {'CANCELLED'}
973 if self.cmd == "FILTER_ADD":
974 success = matlib.add_category(self.category)
975 for i, cat in enumerate(matlib.categories):
976 if cat.name == self.category:
977 matlib.cat_index = i
978 break
980 elif self.cmd == "FILTER_REMOVE":
981 matlib.remove_category()
983 elif self.cmd == "FILTER_SET":
984 success = matlib.set_category()
986 elif self.cmd[0:3] == "cat":
987 index = int(self.cmd[3::])
988 matlib.cat_index = index
990 #Common Commands
991 elif self.cmd == "ADD":
992 success = matlib.add_material(context.object.active_material)
994 elif self.cmd == "REMOVE":
995 success = matlib.remove_material()
998 elif self.cmd == "APPLY":
999 success = matlib.apply(context)
1001 elif self.cmd == "PREVIEW":
1002 success = matlib.apply(context, True)
1004 elif self.cmd=="FLUSH":
1005 #release dummy materials
1006 dummy = matlib.get_dummy(context)
1007 if dummy == context.object:
1008 try:
1009 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
1010 except:
1011 pass
1013 for slot in dummy.material_slots:
1014 slot.material = None
1016 for mat in bpy.data.materials:
1017 if mat.users==0:
1018 i+=1
1019 print (mat.name, "removed.")
1020 bpy.data.materials.remove(mat)
1022 plural = "s"
1023 if i==1:
1024 plural = ""
1026 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
1028 ### CONVERT
1029 elif self.cmd == "CONVERT":
1030 return {'FINISHED'}
1031 lib = matlib.current_library
1032 if lib:
1034 path = os.path.join(matlib_path, "www")
1035 if not os.path.exists(path):
1036 os.mkdir(path)
1037 path = os.path.join(path, lib.shortname)
1038 if not os.path.exists(path):
1039 os.mkdir(path)
1041 path = winpath(path)
1042 libpath = winpath(lib.name)
1044 print(path)
1045 print(libpath)
1047 #decirle a la libreria que cree un fichero blend por cada material que tenga.
1048 cmd = """
1049 print(30*"+")
1050 import bpy, os
1051 def list_materials():
1052 list = []
1053 with bpy.data.libraries.load("{0}") as (data_from, data_to):
1054 for mat in data_from.materials:
1055 list.append(mat)
1056 return sorted(list)
1058 def get_material(name, link=False):
1059 with bpy.data.libraries.load("{0}", link=link, relative=False) as (data_from, data_to):
1060 data_to.materials = [name]
1061 if link:
1062 print(name + " linked.")
1063 else:
1064 print(name + " appended.")
1066 for scn in bpy.data.scenes:
1067 for obj in scn.objects:
1068 scn.objects.unlink(obj)
1069 obj.user_clear()
1070 bpy.data.objects.remove(obj)
1072 def clean_materials():
1073 for mat in bpy.data.materials:
1074 mat.user_clear()
1075 bpy.data.materials.remove(mat)
1077 bin = bpy.app.binary_path
1078 mats = list_materials()
1079 bpy.context.preferences.filepaths.save_version = 0
1080 for mat in mats:
1081 clean_materials()
1082 matpath = os.path.join("{1}", mat + ".blend")
1083 print(matpath)
1084 get_material(mat)
1085 material = bpy.data.materials[0]
1086 material.use_fake_user = True
1087 bpy.ops.wm.save_mainfile(filepath = matpath, compress=True, check_existing=False)
1088 """.format(libpath, path)
1089 print(cmd)
1090 send_command(cmd, "createlib.py")
1092 if type(success).__name__ == "tuple":
1093 print(success)
1094 self.report({success[0]}, success[1])
1096 return {'FINISHED'}
1099 class MATLIB_PT_vxPanel(Panel):
1100 bl_label = "Material Library VX"
1101 bl_space_type = "PROPERTIES"
1102 bl_region_type = "WINDOW"
1103 bl_context = "material"
1104 bl_options = {'DEFAULT_CLOSED'}
1106 @classmethod
1107 def poll(self, context):
1108 return context.active_object.active_material!=None
1110 def draw(self, context):
1111 layout = self.layout
1112 matlib = context.scene.matlib
1114 #hyper ugly trick but i dont know how to init classes at register time
1115 # if matlibProperties.init:
1116 # matlibProperties.init = False
1117 # matlib.__init__()
1119 #libraries
1120 col = layout.column(align=True)
1121 if matlib.current_library:
1122 text = matlib.current_library.shortname
1123 else:
1124 text = "Select a Library"
1125 split = col.split(factor=0.55, align=True)
1126 split.menu("MATLIB_MT_LibsMenu",text=text)
1127 split.operator("matlib.operator", icon="ADD", text="New Library").cmd = "LIBRARY_ADD"
1129 # #list
1130 row = layout.row()
1131 row.template_list("UI_UL_list", " ", matlib, "materials", matlib, "mat_index", rows=6)
1132 col = row.column()
1133 # row = layout.row()
1134 #operators
1135 col.operator("matlib.operator", icon="ADD", text="Add To Library").cmd="ADD"
1136 col.operator("matlib.operator", icon="MATERIAL", text="Apply To Selected").cmd="APPLY"
1137 col.operator("matlib.operator", icon="FILE_REFRESH", text="Reload Material").cmd="RELOAD"
1138 col.operator("matlib.operator", icon="COLOR", text="Preview Material").cmd="PREVIEW"
1139 col.operator("matlib.operator", icon="GHOST_DISABLED", text="Remove Preview").cmd="FLUSH"
1140 col.operator("matlib.operator", icon="REMOVE", text="Remove Material").cmd="REMOVE"
1141 col.prop(matlib, "show_prefs", icon="MODIFIER", text="Settings")
1143 # Search
1144 if not matlib.hide_search:
1145 row = layout.row(align=True)
1146 row.prop_search(matlib, "search", matlib, "materials", text="", icon="VIEWZOOM")
1148 #categories
1149 row = layout.row()
1150 if matlib.active_material:
1151 row.label(text="Category:")
1152 row.label(text = matlib.active_material.category)
1153 else:
1154 row.label(text="Category Tools:")
1155 row = layout.row(align=True)
1156 text = "All"
1157 if matlib.current_category: text = matlib.current_category
1158 row.menu("MATLIB_MT_CatsMenu",text=text)
1159 row = layout.row(align=True)
1160 row.prop(matlib, "filter", icon="FILTER", text="Filter")
1161 row.operator("matlib.operator", icon="FILE_PARENT", text="Set Type").cmd="FILTER_SET"
1162 row.operator("matlib.operator", icon="ADD", text="New").cmd="FILTER_ADD"
1163 row.operator("matlib.operator", icon="REMOVE", text="Remove").cmd="FILTER_REMOVE"
1165 #prefs
1166 if matlib.show_prefs:
1167 row = layout.row(align=True)
1168 row.prop(matlib, "force_import")
1169 row.prop(matlib, "link")
1170 row.prop(matlib, "hide_search")
1171 # row = layout.row(align=True)
1172 #row = layout.row()
1173 #row.operator("matlib.operator", icon="URL", text="Convert Library").cmd="CONVERT"
1175 # row = layout.row()
1176 # if (matlib.current_library):
1177 # row.label(matlib.current_library.name)
1178 # else:
1179 # row.label(text="Library not found!.")
1181 classes = [
1182 matlibMaterials,
1183 matlibProperties,
1184 EmptyGroup,
1185 MATLIB_PT_vxPanel,
1186 MATLIB_OT_operator,
1187 MATLIB_MT_LibsMenu,
1188 MATLIB_MT_CatsMenu
1190 #print(bpy.context.scene)
1193 @persistent
1194 def refresh_libs(dummy=None):
1195 global libraries
1196 global matlib_path
1197 default_path = bpy.context.preferences.addons[__name__].preferences.matlib_path
1198 if default_path is not None and default_path != '':
1199 matlib_path = default_path
1201 libraries = get_libraries()
1204 def reload_library(self, context):
1205 bpy.context.preferences.addons[__name__].preferences.matlib_path = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.matlib_path)
1206 refresh_libs(self)
1209 class matlibvxPref(AddonPreferences):
1210 bl_idname = __name__
1211 matlib_path: StringProperty(
1212 name="Additional Path",
1213 description="User defined path to .blend libraries files",
1214 default="",
1215 subtype="DIR_PATH",
1216 update=reload_library
1219 def draw(self, context):
1220 layout = self.layout
1221 layout.prop(self, "matlib_path")
1223 classes = [
1224 matlibvxPref,
1225 matlibMaterials,
1226 EmptyGroup,
1227 MATLIB_PT_vxPanel,
1228 MATLIB_OT_operator,
1229 MATLIB_MT_LibsMenu,
1230 MATLIB_MT_CatsMenu,
1231 matlibProperties,
1235 classes = [
1236 matlibProperties,
1237 EmptyGroup,
1238 matlibMaterials,
1239 Categories,
1240 Library,
1241 MATLIB_MT_LibsMenu,
1242 MATLIB_MT_CatsMenu,
1243 MATLIB_OT_add,
1244 MATLIB_OT_remove,
1245 MATLIB_OT_remove,
1246 MATLIB_OT_apply,
1247 MATLIB_OT_preview,
1248 MATLIB_OT_flush,
1249 MATLIB_OT_operator,
1250 MATLIB_PT_vxPanel,
1251 matlibvxPref
1254 def register():
1255 global matlib_path
1257 for c in classes:
1258 bpy.utils.register_class(c)
1259 Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
1260 Scene.matlib = PointerProperty(type = matlibProperties)
1261 bpy.app.handlers.load_post.append(refresh_libs)
1262 refresh_libs()
1265 def unregister():
1266 global libraries
1268 try:
1269 # raise ValueError list.remove(x): x not in list
1270 del Scene.matlib_categories
1271 except:
1272 pass
1273 del Scene.matlib
1274 libraries.clear()
1275 bpy.app.handlers.load_post.remove(refresh_libs)
1276 for c in classes:
1277 bpy.utils.unregister_class(c)
1280 if __name__ == "__main__":
1281 register()