Skinify: fix shape generation
[blender-addons.git] / materials_library_vx / __init__.py
blobc19eab876f09dc374000a1aab7a1dae48631c799
1 # SPDX-License-Identifier: GPL-2.0-or-later
3 bl_info = {
4 "name": "Material Library",
5 "author": "Mackraken",
6 "version": (0, 6, 0),
7 "blender": (2, 80, 0),
8 "location": "Properties > Material",
9 "description": "Material Library VX",
10 "warning": "",
11 "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_library.html",
12 "tracker_url": "",
13 "category": "Material",
17 import bpy
18 import json
19 import os
20 from pathlib import Path
22 from bpy.app.handlers import persistent
23 from bpy.props import (
24 StringProperty, IntProperty, BoolProperty,
25 PointerProperty, CollectionProperty
27 from bpy.types import (
28 Panel, Menu, AddonPreferences, Operator,
29 PropertyGroup, UIList,
30 Scene
33 from rna_prop_ui import PropertyPanel
35 dev = False
37 user_path = Path(bpy.utils.resource_path('USER')).parent
38 matlib_path = os.path.join(user_path, "matlib")
40 if not os.path.exists(matlib_path):
41 import shutil
42 os.mkdir(matlib_path)
43 addon_path = os.path.dirname(__file__)
44 shutil.copy2(os.path.join(addon_path, "categories.txt"), matlib_path)
45 shutil.copy2(os.path.join(addon_path, "templates.blend"), matlib_path)
46 shutil.copy2(os.path.join(addon_path, "sample_materials.blend"), matlib_path)
48 ##debug print variables
49 def dd(*args, dodir=False):
50 if dev:
51 if dodir:
52 print(dir(*args))
53 print(*args)
55 #Regular Functions
56 def winpath(path):
57 return path.replace("\\", "\\\\")
59 def update_search_index(self, context):
60 search = self.search
61 for i, it in enumerate(self.materials):
62 if it.name==search:
63 self.mat_index = i
64 break
66 def check_path(path):
67 #isabs sometimes returns true on relpaths
68 if path and os.path.exists(path) and os.path.isfile(path) and os.path.isabs(path):
69 try:
70 if bpy.data.filepath and bpy.path.relpath(bpy.data.filepath) == bpy.path.relpath(path):
71 return False
72 except:
73 pass
74 #paths are on different drives. No problem then
75 return True
76 return False
78 def update_lib_index(self, context):
79 self.load_library()
81 def update_cat_index(self, context):
82 dd("cat index:", self.current_category, self.filter)
84 if self.filter:
85 self.filter = True
88 def update_filter(self, context):
90 dd("filter:", self.filter, self.cat_index, self.current_category)
91 # index = self.cat_index
93 # if self.filter:
94 # cat = self.current_category
95 # else:
96 # cat = ""
98 # self.current_library.filter = cat
99 self.update_list()
101 def check_index(collection, index):
102 count = len(collection)
103 return count>0 and index<count and index>=0
105 def send_command(cmd, output="sendmat.py"):
106 bin = winpath(bpy.app.binary_path)
107 scriptpath = winpath(os.path.join(bpy.app.tempdir, output))
109 with open(scriptpath, "w") as f:
110 f.write(cmd)
112 import subprocess
114 if output == "createlib.py":
115 code = subprocess.call([bin, "-b", "-P", scriptpath])
116 else:
117 libpath = winpath(bpy.context.scene.matlib.current_library.path)
118 code = subprocess.call([bin, "-b", libpath, "-P", scriptpath])
120 #code returns 0 if ok, 1 if not
121 return abs(code-1)
123 def list_materials(path, sort=False):
124 list = []
125 with bpy.data.libraries.load(path) as (data_from, data_to):
126 for mat in data_from.materials:
127 list.append(mat)
129 if sort: list = sorted(list)
130 return list
132 #category properties (none atm)
133 class EmptyGroup(PropertyGroup):
134 pass
135 # bpy.utils.register_class(EmptyGroup)
137 class matlibMaterials(PropertyGroup):
138 category: StringProperty()
139 # bpy.utils.register_class(matlibMaterials)
141 #bpy.types.Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
143 ### CATEGORIES
144 class Categories():
146 #cats = bpy.context.scene.matlib.categories
148 def __init__(self, cats):
149 self.cats = cats
151 def save(self):
152 scn = bpy.context.scene
153 cats = set([cat.name for cat in self.cats])
154 libpath = bpy.context.scene.matlib.current_library.path
156 cmd = """
157 print(30*"+")
158 import bpy
159 if not hasattr(bpy.context.scene, "matlib_categories"):
160 class EmptyProps(bpy.types.PropertyGroup):
161 pass
162 bpy.utils.register_class(EmptyProps)
163 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
164 cats = bpy.context.scene.matlib_categories
165 for cat in cats:
166 cats.remove(0)
168 for cat in cats:
169 cmd += """
170 cat = cats.add()
171 cat.name = "%s" """ % cat.capitalize()
172 cmd +='''
173 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(libpath)
175 return send_command(cmd, "save_categories.py")
177 def read(self, pull=True):
178 #mandar a imprimir el listado
179 catfile = winpath(os.path.join(matlib_path, "categories.txt"))
180 cmd = """
181 import bpy, json
182 class EmptyProps(bpy.types.PropertyGroup):
183 pass
184 bpy.utils.register_class(EmptyProps)
185 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
186 cats = []
187 for cat in bpy.context.scene.matlib_categories:
188 materials = []
189 for mat in bpy.data.materials:
190 if "category" in mat.keys() and mat['category'] == cat.name:
191 materials.append(mat.name)
192 cats.append([cat.name, materials])
193 with open("%s", "w") as f:
194 f.write(json.dumps(cats, sort_keys=True, indent=4))
195 """ % catfile
196 if pull: send_command(cmd)
198 #leer el fichero
199 with open(catfile, "r") as f:
200 cats = json.loads(f.read())
202 dd(cats)
204 # #refrescar categorias
205 # for cat in self.cats:
206 # self.cats.remove(0)
208 # for cat in cats:
209 # item = self.cats.add()
210 # item.name = cat
212 return cats
214 def view(self):
215 for cat in self.cats:
216 dd(cat.name)
218 def add(self, name):
219 if name and name not in [item.name for item in self.cats]:
220 name = name.strip().capitalize()
221 item = self.cats.add()
222 item.name = name
223 if self.save():
224 dd(name, "added")
225 return True
226 else:
227 dd("duplicated?")
229 def remove(self, index):
230 self.cats.remove(index)
231 self.save()
233 class Library():
235 def __init__(self, matlib_path, name):
236 self.name = name
237 self.path = os.path.join(matlib_path, name)
238 # @property
239 # def default(self):
240 # return self.name == default_library
242 @property
243 def shortname(self):
244 # if self.default:
245 # return "Default Library"
246 return bpy.path.display_name(self.name).title()
249 def __repr__(self):
250 return str(type(self).__name__) + "('" + self.name + "')"
252 #bpy.utils.register_class(Library)
254 def get_libraries():
255 libs = [Library(matlib_path, f) for f in os.listdir(matlib_path) if f[-5::] == "blend"]
256 return sorted(libs, key=lambda x: bpy.path.display_name(x.name))
258 libraries = []
259 # get_libraries()
261 ### MATLIB CLASS
262 class matlibProperties(PropertyGroup):
264 #MATLIB PROPERTIES
266 #libraries are read from the xml
267 lib_index: IntProperty(min = -1, default = 2, update=update_lib_index)
268 all_materials: CollectionProperty(type = matlibMaterials)
269 materials: CollectionProperty(type = matlibMaterials)
270 mat_index: IntProperty(min = -1, default = -1)
271 categories: CollectionProperty(type = EmptyGroup)
272 cat_index: IntProperty(min = -1, default = -1, update=update_cat_index)
273 search: StringProperty(name="Search", description="Find By Name", update=update_search_index)
275 #MATLIB OPTIONS
276 #link: import material linked
277 #force import:
278 # if disable it wont import a material if its present in the scene,(avoid duplicates)
279 # instead it will apply the scene material rather than importing the same one from the library
280 #filter: enable or disable category filter
281 #last selected: store the last selected object to regain focus when apply a material.
282 #hide_search: Hides Search Field
283 link: BoolProperty(name = "Linked", description="Link the material", default = False)
284 force_import: BoolProperty(name = "Force Import", description="Use Scene Materials by default", default = False)
285 filter: BoolProperty(name = "Filter",description="Filter Categories", default = True, update=update_filter)
286 show_prefs: BoolProperty(name = "show_prefs", description="Preferences", default = False)
287 last_selected: StringProperty(name="Last Selected")
288 hide_search: BoolProperty(name="Hide Search", description="Use Blender Search Only")
289 #import_file = StringProperty("Import File", subtype="FILE_PATH")
290 #path = os.path.dirname(path)
291 #Development only
293 @property
294 def libraries(self):
295 global libraries
296 return libraries
298 @property
299 def current_library(self):
300 if check_index(libraries, self.lib_index):
301 return libraries[self.lib_index]
303 @property
304 def active_material(self):
305 if check_index(self.materials, self.mat_index):
306 return self.materials[self.mat_index]
308 def reload(self):
309 dd("loading libraries")
310 if self.current_library:
311 self.load_library()
312 elif self.lib_index == -1 and len(libraries):
313 self.lib_index = 0
316 def add_library(self, path, setEnabled = False):
317 #sanitize path
318 ext = os.path.extsep + "blend"
319 if not path.endswith(ext):
320 path += ext
322 if check_path(path):
323 # if path == default_library:
324 # return 'ERROR', "Cannot add default library."
325 #if path in [lib.path for lib in self.libraries]:
326 return 'ERROR', "Library already exists."
327 else:
328 dd("Can't find " + path)
329 #create file
330 cmd = '''
331 import bpy
332 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(path)
333 if not (send_command(cmd, "createlib.py")):
334 return 'ERROR', "There was an error creating the file. Make sure you run Blender with admin rights."
336 #self.libraries = sorted(self.libraries, key=lambda lib: sortlibs(lib))
337 dd("adding library", path)
338 global libraries
339 libraries = get_libraries()
340 return "INFO", "Library added"
342 def load_library(self):
343 self.empty_list(True)
344 if not self.current_library:
345 return 'ERROR', "Library not found!."
347 path = self.current_library.path
349 dd("loading library", self.lib_index, path)
351 if check_path(path):
352 self.filter = False
353 self.cat_index = -1
355 categories = Categories(self.categories)
356 self.cats = categories.read(True)
357 self.load_categories()
359 for mat in self.all_materials:
360 self.all_materials.remove(0)
362 for mat in list_materials(self.current_library.path, True):
363 item = self.all_materials.add()
364 item.name = mat
365 for cat in self.cats:
366 if mat in cat[1]:
367 item.category = cat[0]
368 break
370 self.update_list()
371 else:
372 return 'ERROR', "Library not found!."
374 def update_list(self):
375 ### THIS HAS TO SORT
376 self.empty_list()
377 if self.current_library:
378 current_category = self.current_category
379 #sorteditems = sorted(self.all_materials, key=lambda x: x.name)
380 for mat in self.all_materials:
381 #print(current_category, mat.category)
382 if not self.filter or (self.filter and mat.category == current_category) or current_category == "":
383 item = self.materials.add()
384 item.name = mat.name
385 item.category = mat.category
387 def empty_list(self, cats = False):
388 #self.mat_index = -1
389 for it in self.materials:
390 self.materials.remove(0)
392 if cats:
393 for c in self.categories:
394 self.categories.remove(0)
396 ### CATEGORIES
397 @property
398 def current_category(self):
399 #print(self.mat_index)
400 if check_index(self.categories, self.cat_index):
401 return self.categories[self.cat_index].name
402 return ""
404 def load_categories(self):
406 for c in self.categories:
407 self.categories.remove(0)
409 for c in self.cats:
410 cat = self.categories.add()
411 cat.name = c[0]
413 def add_category(self, name):
414 if name:
415 name = name.strip().title()
416 dd("add category", name)
417 categories = Categories(self.categories)
419 categories.add(name)
421 # if lib:
422 # cat = xml.find("category", name, lib, create = True)
423 # self.load_categories()
424 # else:
425 # return 'ERROR', "Library not found"
426 def remove_category(self):
427 dd("removing category", self.current_category)
428 categories = Categories(self.categories)
429 categories.remove(self.cat_index)
431 def set_category(self):
432 mat = self.active_material
433 #dd(lib, mat, self.current_category)
434 if mat:
435 #set mat to category
436 if self.cat_index>-1:
437 dd(self.current_category)
438 cat = self.current_category
439 if cat == self.all_materials[self.mat_index].category:
440 return
441 cmd = """
442 import bpy
443 try:
444 mat = bpy.data.materials['%s']
445 except:
446 mat = None
447 if mat:
448 mat['category'] = "%s"
449 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)
450 """ % (mat.name, cat, winpath(self.current_library.path))
451 if send_command(cmd):
452 self.all_materials[self.mat_index].category = cat
453 mat.category = cat
454 else:
455 return "WARNING", "There was an error."
457 # catnode = xml.find("category", self.current_category, lib, True)
458 # matnode = xml.find("material", mat.name, lib)
459 # if matnode:
460 # catnode.appendChild(matnode)
461 # else:
462 # matnode = xml.find("material", mat.name, catnode, True)
463 # xml.save()
464 # mat.category = cat
465 # self.current_library.materials[self.mat_index].category = cat
466 #remove mat from any category
467 else:
468 mat.category = ""
469 self.all_materials[self.mat_index].category = ""
470 else:
471 return "WARNING", "Select a material"
473 def get_material(self, name, link=False):
474 with bpy.data.libraries.load(self.current_library.path, link=link, relative=False) as (data_from, data_to):
475 data_to.materials = [name]
476 if link:
477 print(name + " linked.")
478 else:
479 print(name + " appended.")
481 def apply(self, context, preview=False):
482 name = self.active_material.name
483 if not name: return "WARNING", "Select a material from the list."
485 linked = self.link or preview
486 force = self.force_import or linked
488 objects = []
489 active = context.object
490 dummy = self.get_dummy(context)
492 #setup objects
493 if preview:
494 if context.mode == "EDIT_MESH":
495 return "WARNING", "Can't preview on EDIT MODE"
496 if dummy!= active:
497 self.last_selected = context.object.name
498 context.view_layer.objects.active = dummy
499 objects.append(dummy)
500 #apply
501 else:
502 objects = [obj for obj in context.selected_objects if hasattr(obj.data, "materials")]
504 if not objects:
505 return "INFO", "Please select an object"
507 if dummy == context.object and not preview:
508 if (len(objects)==1 and dummy.select_get()):
509 return "ERROR", "Apply is disabled for the Material Preview Object"
510 try:
511 last = context.scene.objects[self.last_selected]
512 if last in context.selected_objects:
513 context.view_layer.objects.active = last
514 else:
515 self.last_selected = ""
516 except:
517 context.view_layer.objects.active = None
518 dummy.select_set(False)
519 #objects = context.selected_objects
521 material = None
523 #mira si hay materiales linkados de la libreria actual
524 for mat in bpy.data.materials:
525 try:
526 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
527 except:
528 samelib = False
530 if mat.name == name and mat.library and samelib:
531 material = mat
532 dd("encontre linked", name, "no importo nada")
533 break
535 if not force:
536 #busca materiales no linkados
537 for mat in bpy.data.materials:
538 if mat.name == name and not mat.library:
539 material = mat
540 dd("encontre no linkado", name, "no importo nada")
541 break
543 if not material:
544 #go get it
545 dd("voy a buscarlo")
546 nmats = len(bpy.data.materials)
548 self.get_material(name, linked)
550 if not self.force_import:
551 try:
552 material = bpy.data.materials[name]
553 except:
554 pass
556 if not material:
557 if nmats == len(bpy.data.materials) and not linked:
558 return "ERROR", name + " doesn't exists at library " + str(linked)
559 else:
560 for mat in reversed(bpy.data.materials):
561 if mat.name[0:len(name)] == name:
562 #careful on how blender writes library paths
563 try:
564 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
565 except:
566 samelib = False
568 if linked and mat.library and samelib:
569 material = mat
570 dd(name, "importado con link")
571 break
572 else:
573 if not mat.library:
574 dd(name, "importado sin link")
575 material = mat
576 break
577 if material:
578 material.use_fake_user = False
579 material.user_clear()
581 print ("Material", material, force)
583 #if material:
584 #maybe some test cases doesn't return a material, gotta take care of that
585 #i cannot think of any case like that right now
586 #maybe import linked when the database isn't sync
587 if context.mode == "EDIT_MESH":
588 obj = context.object
589 dd(material)
590 index = -1
591 for i, mat in enumerate(obj.data.materials):
592 if mat == material:
593 index = i
594 break
596 if index == -1:
597 obj.data.materials.append(material)
598 index = len(obj.data.materials)-1
599 dd(index)
600 import bmesh
601 bm = bmesh.from_edit_mesh(obj.data)
602 for f in bm.faces:
603 if f.select:
604 f.material_index = index
606 else:
607 for obj in objects:
608 index = obj.active_material_index
609 if index < len(obj.material_slots):
610 obj.material_slots[index].material = None
611 obj.material_slots[index].material = material
612 else:
613 obj.data.materials.append(material)
615 if not linked:
616 bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")
618 def add_material(self, mat):
620 if not mat:
621 return 'WARNING', "Select a material from the scene."
623 name = mat.name
624 thispath = winpath(bpy.data.filepath)
625 libpath = winpath(self.current_library.path)
627 if not thispath:
628 return 'WARNING', "Save this file before export."
630 if not libpath:
631 return 'WARNING', "Library not found!."
633 elif bpy.data.is_dirty:
634 bpy.ops.wm.save_mainfile(check_existing=True)
636 if mat.library:
637 return 'WARNING', 'Cannot export linked materials.'
639 dd("adding material", name, libpath)
641 overwrite = ""
642 if name in list_materials(libpath):
643 overwrite = '''
644 mat = bpy.data.materials["%s"]
645 mat.name = "tmp"
646 mat.use_fake_user = False
647 mat.user_clear()''' % name
649 cmd = '''
650 import bpy{0}
651 with bpy.data.libraries.load("{1}") as (data_from, data_to):
652 data_to.materials = ["{2}"]
653 mat = bpy.data.materials["{2}"]
654 mat.use_fake_user=True
655 bpy.ops.file.pack_all()
656 bpy.ops.wm.save_mainfile(filepath="{3}", check_existing=False, compress=True)
657 '''.format(overwrite, thispath, name, libpath)
659 if send_command(cmd):
660 #self.load_library()
661 if not overwrite:
662 item = self.all_materials.add()
663 item.name = name
664 if "category" in mat.keys():
665 item.category = mat['category']
666 #reorder all_materials
667 items = sorted([[item.name, item.category] for item in self.all_materials], key = lambda x: x[0])
669 self.all_materials.clear()
670 for it in items:
671 item = self.all_materials.add()
672 item.name = it[0]
673 item.category = it[1]
675 self.update_list()
677 return 'INFO', "Material added."
678 else:
679 print("Save Material Error: Run Blender with administrative privileges.")
680 return 'WARNING', "There was an error saving the material"
682 def remove_material(self):
683 name = self.active_material.name
684 libpath = winpath(self.current_library.path)
685 if name and libpath and name in list_materials(libpath):
686 cmd = '''import bpy
687 mat = bpy.data.materials["%s"]
688 mat.use_fake_user = False
689 mat.user_clear()
690 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % (name , libpath)
691 if send_command(cmd, "removemat.py"):
692 self.all_materials.remove(self.mat_index)
693 self.update_list()
694 else:
695 return 'ERROR', "There was an error."
696 return "INFO", name + " removed."
698 def get_dummy(self, context):
699 dummy_name = "Material_Preview_Dummy"
700 dummy_mesh = "Material_Preview_Mesh"
701 scn = context.scene
702 try:
703 dummy = scn.objects[dummy_name]
704 except:
705 #create dummy
706 try:
707 me = bpy.data.meshes(dummy_mesh)
708 except:
709 me = bpy.data.meshes.new(dummy_mesh)
710 dummy = bpy.data.objects.new(dummy_name, me)
711 context.collection.objects.link(dummy)
713 dummy.hide_set(True)
714 dummy.hide_render = True
715 dummy.hide_select = True
716 return dummy
718 # bpy.utils.register_class(matlibProperties)
719 # Scene.matlib = PointerProperty(type = matlibProperties)
721 ### MENUS
722 class MATLIB_MT_LibsMenu(Menu):
723 bl_label = "Libraries Menu"
725 def draw(self, context):
726 layout = self.layout
727 libs = libraries
728 #layout.operator("matlib.operator", text="Default Library").cmd="lib-1"
729 for i, lib in enumerate(libs):
730 layout.operator("matlib.operator", text=lib.shortname).cmd="lib"+str(i)
732 class MATLIB_MT_CatsMenu(Menu):
733 bl_label = "Categories Menu"
735 def draw(self, context):
736 layout = self.layout
737 cats = context.scene.matlib.categories
738 layout.operator("matlib.operator", text="All").cmd="cat-1"
739 for i, cat in enumerate(cats):
740 layout.operator("matlib.operator", text=cat.name).cmd="cat"+str(i)
742 ### OPERATORS
743 #class MATLIB_OT_add(Operator):
744 # """Add Active Material"""
745 # bl_label = "Add"
746 # bl_idname = "matlib.add_material"
748 # @classmethod
749 # def poll(cls, context):
750 # return context.active_object is not None
752 # def execute(self, context):
753 # print("executing")
754 # return {"FINISHED"}
758 class MATLIB_OT_add(Operator):
759 """Add active material to library"""
760 bl_idname = "matlib.add"
761 bl_label = "Add active material"
763 @classmethod
764 def poll(cls, context):
765 obj = context.active_object
766 return obj is not None and obj.active_material is not None
768 def execute(self, context):
769 matlib = context.scene.matlib
770 success = matlib.add_material(context.object.active_material)
771 if type(success).__name__ == "tuple":
772 print(success)
773 self.report({success[0]}, success[1])
774 return {'FINISHED'}
776 class MATLIB_OT_remove(Operator):
777 """Remove material from library"""
778 bl_idname = "matlib.remove"
779 bl_label = "Remove material from library"
781 @classmethod
782 def poll(cls, context):
783 matlib = context.scene.matlib
784 return check_index(matlib.materials, matlib.mat_index)
786 def execute(self, context):
787 matlib = context.scene.matlib
788 success = matlib.remove_material()
789 if type(success).__name__ == "tuple":
790 print(success)
791 self.report({success[0]}, success[1])
792 return {'FINISHED'}
794 class MATLIB_OT_remove(Operator):
795 """Reload library"""
796 bl_idname = "matlib.reload"
797 bl_label = "Reload library"
799 # @classmethod
800 # def poll(cls, context):
801 # matlib = context.scene.matlib
802 # index = matlib.mat_index
803 # l = len(matlib.materials)
804 # return l>0 and index >=0 and index < l
806 def execute(self, context):
807 matlib = context.scene.matlib
808 success = matlib.reload()
809 if type(success).__name__ == "tuple":
810 print(success)
811 self.report({success[0]}, success[1])
812 return {'FINISHED'}
815 class MATLIB_OT_apply(Operator):
816 """Apply selected material"""
817 bl_idname = "matlib.apply"
818 bl_label = "Apply material"
820 @classmethod
821 def poll(cls, context):
822 matlib = context.scene.matlib
823 index = matlib.mat_index
824 l = len(matlib.materials)
825 obj = context.active_object
826 return l>0 and index >=0 and index < l and obj is not None
828 def execute(self, context):
829 matlib = context.scene.matlib
830 success = matlib.apply(context, False)
831 if type(success).__name__ == "tuple":
832 print(success)
833 self.report({success[0]}, success[1])
834 return {'FINISHED'}
837 class MATLIB_OT_preview(Operator):
838 """Preview selected material"""
839 bl_idname = "matlib.preview"
840 bl_label = "Preview selected material"
842 @classmethod
843 def poll(cls, context):
844 matlib = context.scene.matlib
845 index = matlib.mat_index
846 l = len(matlib.materials)
847 obj = context.active_object
848 return l>0 and index >=0 and index < l
850 def execute(self, context):
851 matlib = context.scene.matlib
852 success = matlib.apply(context, True)
853 if type(success).__name__ == "tuple":
854 print(success)
855 self.report({success[0]}, success[1])
856 return {'FINISHED'}
859 class MATLIB_OT_flush(Operator):
860 """Flush unused materials"""
861 bl_idname = "matlib.flush"
862 bl_label = "Flush unused materials"
864 @classmethod
865 def poll(cls, context):
866 matlib = context.scene.matlib
867 index = matlib.mat_index
868 l = len(matlib.materials)
869 obj = context.active_object
870 return l>0 and index >=0 and index < l
872 def execute(self, context):
873 matlib = context.scene.matlib
874 dummy = matlib.get_dummy(context)
875 if dummy == context.object:
876 try:
877 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
878 except:
879 pass
881 for slot in dummy.material_slots:
882 slot.material = None
884 for mat in bpy.data.materials:
885 if mat.users==0:
886 i+=1
887 print (mat.name, "removed.")
888 bpy.data.materials.remove(mat)
890 plural = "" if i == 1 else "s"
891 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
893 return {'FINISHED'}
896 class MATLIB_OT_operator(Operator):
897 """Add, Remove, Reload, Apply, Preview, Clean Material"""
898 bl_label = "New"
899 bl_idname = "matlib.operator"
900 __doc__ = "Add, Remove, Reload, Apply, Preview, Clean Material"
902 category: StringProperty(name="Category")
903 filepath: StringProperty(options={'HIDDEN'})
904 cmd: bpy.props.StringProperty(name="Command", options={'HIDDEN'})
905 filter_glob: StringProperty(default="*.blend", options={'HIDDEN'})
906 @classmethod
907 def poll(cls, context):
908 return context.active_object is not None
910 def draw(self, context):
911 layout = self.layout
912 #cmd = LIBRARY_ADD
913 if self.cmd == "LIBRARY_ADD":
914 #layout.label(text="Select a blend file as library or")
915 #layout.label(text="Type a name to create a new library.")
916 layout.prop(self, "category", text="Library")
917 elif self.cmd == "FILTER_ADD":
918 layout.prop(self, "category")
920 def invoke(self, context, event):
922 cmd = self.cmd
923 print("invoke", cmd)
925 if cmd == "LIBRARY_ADD":
926 self.filepath = matlib_path + os.path.sep
927 dd("filepath", self.filepath, matlib_path)
928 #context.window_manager.fileselect_add(self)
929 context.window_manager.invoke_props_dialog(self)
930 return {'RUNNING_MODAL'}
931 elif cmd == "FILTER_ADD":
932 context.window_manager.invoke_props_dialog(self)
933 return {'RUNNING_MODAL'}
934 return self.execute(context)
936 ### TODO: execute doesn't trigger remove
937 def execute(self, context):
939 success = ""
940 matlib = context.scene.matlib
942 if self.cmd == "init":
943 print("initialize")
944 return {'FINISHED'}
946 #Library Commands
947 if self.cmd[0:3] == "lib":
948 index = int(self.cmd[3::])
949 matlib.lib_index = index
950 #success = matlib.load_library()
951 elif self.cmd == "LIBRARY_ADD":
952 dd("execute lib add")
953 libname = self.category
954 if libname[-6::] != ".blend": libname+= ".blend"
955 libname = os.path.join(matlib_path, libname)
956 print(libname)
958 success = matlib.add_library(libname, True)
959 for i, l in enumerate(libraries):
960 if l.name == self.category:
961 matlib.lib_index = i
962 break
964 elif self.cmd == "RELOAD":
965 success = matlib.reload()
967 if not matlib.current_library:
968 self.report({'ERROR'}, "Select a Library")
969 return {'CANCELLED'}
971 if self.cmd == "FILTER_ADD":
972 success = matlib.add_category(self.category)
973 for i, cat in enumerate(matlib.categories):
974 if cat.name == self.category:
975 matlib.cat_index = i
976 break
978 elif self.cmd == "FILTER_REMOVE":
979 matlib.remove_category()
981 elif self.cmd == "FILTER_SET":
982 success = matlib.set_category()
984 elif self.cmd[0:3] == "cat":
985 index = int(self.cmd[3::])
986 matlib.cat_index = index
988 #Common Commands
989 elif self.cmd == "ADD":
990 success = matlib.add_material(context.object.active_material)
992 elif self.cmd == "REMOVE":
993 success = matlib.remove_material()
996 elif self.cmd == "APPLY":
997 success = matlib.apply(context)
999 elif self.cmd == "PREVIEW":
1000 success = matlib.apply(context, True)
1002 elif self.cmd=="FLUSH":
1003 #release dummy materials
1004 dummy = matlib.get_dummy(context)
1005 if dummy == context.object:
1006 try:
1007 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
1008 except:
1009 pass
1011 for slot in dummy.material_slots:
1012 slot.material = None
1014 for mat in bpy.data.materials:
1015 if mat.users==0:
1016 i+=1
1017 print (mat.name, "removed.")
1018 bpy.data.materials.remove(mat)
1020 plural = "s"
1021 if i==1:
1022 plural = ""
1024 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
1026 ### CONVERT
1027 elif self.cmd == "CONVERT":
1028 return {'FINISHED'}
1029 lib = matlib.current_library
1030 if lib:
1032 path = os.path.join(matlib_path, "www")
1033 if not os.path.exists(path):
1034 os.mkdir(path)
1035 path = os.path.join(path, lib.shortname)
1036 if not os.path.exists(path):
1037 os.mkdir(path)
1039 path = winpath(path)
1040 libpath = winpath(lib.name)
1042 print(path)
1043 print(libpath)
1045 #decirle a la libreria que cree un fichero blend por cada material que tenga.
1046 cmd = """
1047 print(30*"+")
1048 import bpy, os
1049 def list_materials():
1050 list = []
1051 with bpy.data.libraries.load("{0}") as (data_from, data_to):
1052 for mat in data_from.materials:
1053 list.append(mat)
1054 return sorted(list)
1056 def get_material(name, link=False):
1057 with bpy.data.libraries.load("{0}", link=link, relative=False) as (data_from, data_to):
1058 data_to.materials = [name]
1059 if link:
1060 print(name + " linked.")
1061 else:
1062 print(name + " appended.")
1064 for scn in bpy.data.scenes:
1065 for obj in scn.objects:
1066 scn.objects.unlink(obj)
1067 obj.user_clear()
1068 bpy.data.objects.remove(obj)
1070 def clean_materials():
1071 for mat in bpy.data.materials:
1072 mat.user_clear()
1073 bpy.data.materials.remove(mat)
1075 bin = bpy.app.binary_path
1076 mats = list_materials()
1077 bpy.context.preferences.filepaths.save_version = 0
1078 for mat in mats:
1079 clean_materials()
1080 matpath = os.path.join("{1}", mat + ".blend")
1081 print(matpath)
1082 get_material(mat)
1083 material = bpy.data.materials[0]
1084 material.use_fake_user = True
1085 bpy.ops.wm.save_mainfile(filepath = matpath, compress=True, check_existing=False)
1086 """.format(libpath, path)
1087 print(cmd)
1088 send_command(cmd, "createlib.py")
1090 if type(success).__name__ == "tuple":
1091 print(success)
1092 self.report({success[0]}, success[1])
1094 return {'FINISHED'}
1097 class MATLIB_PT_vxPanel(Panel):
1098 bl_label = "Material Library VX"
1099 bl_space_type = "PROPERTIES"
1100 bl_region_type = "WINDOW"
1101 bl_context = "material"
1102 bl_options = {'DEFAULT_CLOSED'}
1104 @classmethod
1105 def poll(self, context):
1106 return context.active_object.active_material!=None
1108 def draw(self, context):
1109 layout = self.layout
1110 matlib = context.scene.matlib
1112 #hyper ugly trick but i dont know how to init classes at register time
1113 # if matlibProperties.init:
1114 # matlibProperties.init = False
1115 # matlib.__init__()
1117 #libraries
1118 col = layout.column(align=True)
1119 if matlib.current_library:
1120 text = matlib.current_library.shortname
1121 else:
1122 text = "Select a Library"
1123 split = col.split(factor=0.55, align=True)
1124 split.menu("MATLIB_MT_LibsMenu",text=text)
1125 split.operator("matlib.operator", icon="ADD", text="New Library").cmd = "LIBRARY_ADD"
1127 # #list
1128 row = layout.row()
1129 row.template_list("UI_UL_list", " ", matlib, "materials", matlib, "mat_index", rows=6)
1130 col = row.column()
1131 # row = layout.row()
1132 #operators
1133 col.operator("matlib.operator", icon="ADD", text="Add To Library").cmd="ADD"
1134 col.operator("matlib.operator", icon="MATERIAL", text="Apply To Selected").cmd="APPLY"
1135 col.operator("matlib.operator", icon="FILE_REFRESH", text="Reload Material").cmd="RELOAD"
1136 col.operator("matlib.operator", icon="COLOR", text="Preview Material").cmd="PREVIEW"
1137 col.operator("matlib.operator", icon="GHOST_DISABLED", text="Remove Preview").cmd="FLUSH"
1138 col.operator("matlib.operator", icon="REMOVE", text="Remove Material").cmd="REMOVE"
1139 col.prop(matlib, "show_prefs", icon="MODIFIER", text="Settings")
1141 # Search
1142 if not matlib.hide_search:
1143 row = layout.row(align=True)
1144 row.prop_search(matlib, "search", matlib, "materials", text="", icon="VIEWZOOM")
1146 #categories
1147 row = layout.row()
1148 if matlib.active_material:
1149 row.label(text="Category:")
1150 row.label(text = matlib.active_material.category)
1151 else:
1152 row.label(text="Category Tools:")
1153 row = layout.row(align=True)
1154 text = "All"
1155 if matlib.current_category: text = matlib.current_category
1156 row.menu("MATLIB_MT_CatsMenu",text=text)
1157 row = layout.row(align=True)
1158 row.prop(matlib, "filter", icon="FILTER", text="Filter")
1159 row.operator("matlib.operator", icon="FILE_PARENT", text="Set Type").cmd="FILTER_SET"
1160 row.operator("matlib.operator", icon="ADD", text="New").cmd="FILTER_ADD"
1161 row.operator("matlib.operator", icon="REMOVE", text="Remove").cmd="FILTER_REMOVE"
1163 #prefs
1164 if matlib.show_prefs:
1165 row = layout.row(align=True)
1166 row.prop(matlib, "force_import")
1167 row.prop(matlib, "link")
1168 row.prop(matlib, "hide_search")
1169 # row = layout.row(align=True)
1170 #row = layout.row()
1171 #row.operator("matlib.operator", icon="URL", text="Convert Library").cmd="CONVERT"
1173 # row = layout.row()
1174 # if (matlib.current_library):
1175 # row.label(matlib.current_library.name)
1176 # else:
1177 # row.label(text="Library not found!.")
1179 classes = [
1180 matlibMaterials,
1181 matlibProperties,
1182 EmptyGroup,
1183 MATLIB_PT_vxPanel,
1184 MATLIB_OT_operator,
1185 MATLIB_MT_LibsMenu,
1186 MATLIB_MT_CatsMenu
1188 #print(bpy.context.scene)
1191 @persistent
1192 def refresh_libs(dummy=None):
1193 global libraries
1194 global matlib_path
1195 default_path = bpy.context.preferences.addons[__name__].preferences.matlib_path
1196 if default_path is not None and default_path != '':
1197 matlib_path = default_path
1199 libraries = get_libraries()
1202 def reload_library(self, context):
1203 bpy.context.preferences.addons[__name__].preferences.matlib_path = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.matlib_path)
1204 refresh_libs(self)
1207 class matlibvxPref(AddonPreferences):
1208 bl_idname = __name__
1209 matlib_path: StringProperty(
1210 name="Additional Path",
1211 description="User defined path to .blend libraries files",
1212 default="",
1213 subtype="DIR_PATH",
1214 update=reload_library
1217 def draw(self, context):
1218 layout = self.layout
1219 layout.prop(self, "matlib_path")
1221 classes = [
1222 matlibvxPref,
1223 matlibMaterials,
1224 EmptyGroup,
1225 MATLIB_PT_vxPanel,
1226 MATLIB_OT_operator,
1227 MATLIB_MT_LibsMenu,
1228 MATLIB_MT_CatsMenu,
1229 matlibProperties,
1233 classes = [
1234 matlibProperties,
1235 EmptyGroup,
1236 matlibMaterials,
1237 Categories,
1238 Library,
1239 MATLIB_MT_LibsMenu,
1240 MATLIB_MT_CatsMenu,
1241 MATLIB_OT_add,
1242 MATLIB_OT_remove,
1243 MATLIB_OT_remove,
1244 MATLIB_OT_apply,
1245 MATLIB_OT_preview,
1246 MATLIB_OT_flush,
1247 MATLIB_OT_operator,
1248 MATLIB_PT_vxPanel,
1249 matlibvxPref
1252 def register():
1253 global matlib_path
1255 for c in classes:
1256 bpy.utils.register_class(c)
1257 Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
1258 Scene.matlib = PointerProperty(type = matlibProperties)
1259 bpy.app.handlers.load_post.append(refresh_libs)
1260 refresh_libs()
1263 def unregister():
1264 global libraries
1266 try:
1267 # raise ValueError list.remove(x): x not in list
1268 del Scene.matlib_categories
1269 except:
1270 pass
1271 del Scene.matlib
1272 libraries.clear()
1273 bpy.app.handlers.load_post.remove(refresh_libs)
1274 for c in classes:
1275 bpy.utils.unregister_class(c)
1278 if __name__ == "__main__":
1279 register()