Cleanup: trailing space
[blender-addons.git] / materials_library_vx / __init__.py
blobe8bc808e43307feee555fd5b204fa86686cd372e
1 # -*- coding:utf-8 -*-
3 # #####BEGIN GPL LICENSE BLOCK #####
5 # This program is free software; you can redistribute it and/or
6 # modify it under the terms of the GNU General Public License
7 # as published by the Free Software Foundation; either version 2
8 # of the License, or (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program; if not, write to the Free Software Foundation,
17 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19 # #####END GPL LICENSE BLOCK #####
21 bl_info = {
22 "name": "Material Library",
23 "author": "Mackraken",
24 "version": (0, 6, 0),
25 "blender": (2, 80, 0),
26 "location": "Properties > Material",
27 "description": "Material Library VX",
28 "warning": "",
29 "doc_url": "{BLENDER_MANUAL_URL}/addons/materials/material_library.html",
30 "tracker_url": "",
31 "category": "Material",
35 import bpy
36 import json
37 import os
38 from pathlib import Path
40 from bpy.app.handlers import persistent
41 from bpy.props import (
42 StringProperty, IntProperty, BoolProperty,
43 PointerProperty, CollectionProperty
45 from bpy.types import (
46 Panel, Menu, AddonPreferences, Operator,
47 PropertyGroup, UIList,
48 Scene
51 from rna_prop_ui import PropertyPanel
53 dev = False
55 user_path = Path(bpy.utils.resource_path('USER')).parent
56 matlib_path = os.path.join(user_path, "matlib")
58 if not os.path.exists(matlib_path):
59 import shutil
60 os.mkdir(matlib_path)
61 addon_path = os.path.dirname(__file__)
62 shutil.copy2(os.path.join(addon_path, "categories.txt"), matlib_path)
63 shutil.copy2(os.path.join(addon_path, "templates.blend"), matlib_path)
64 shutil.copy2(os.path.join(addon_path, "sample_materials.blend"), matlib_path)
66 ##debug print variables
67 def dd(*args, dodir=False):
68 if dev:
69 if dodir:
70 print(dir(*args))
71 print(*args)
73 #Regular Functions
74 def winpath(path):
75 return path.replace("\\", "\\\\")
77 def update_search_index(self, context):
78 search = self.search
79 for i, it in enumerate(self.materials):
80 if it.name==search:
81 self.mat_index = i
82 break
84 def check_path(path):
85 #isabs sometimes returns true on relpaths
86 if path and os.path.exists(path) and os.path.isfile(path) and os.path.isabs(path):
87 try:
88 if bpy.data.filepath and bpy.path.relpath(bpy.data.filepath) == bpy.path.relpath(path):
89 return False
90 except:
91 pass
92 #paths are on different drives. No problem then
93 return True
94 return False
96 def update_lib_index(self, context):
97 self.load_library()
99 def update_cat_index(self, context):
100 dd("cat index:", self.current_category, self.filter)
102 if self.filter:
103 self.filter = True
106 def update_filter(self, context):
108 dd("filter:", self.filter, self.cat_index, self.current_category)
109 # index = self.cat_index
111 # if self.filter:
112 # cat = self.current_category
113 # else:
114 # cat = ""
116 # self.current_library.filter = cat
117 self.update_list()
119 def check_index(collection, index):
120 count = len(collection)
121 return count>0 and index<count and index>=0
123 def send_command(cmd, output="sendmat.py"):
124 bin = winpath(bpy.app.binary_path)
125 scriptpath = winpath(os.path.join(bpy.app.tempdir, output))
127 with open(scriptpath, "w") as f:
128 f.write(cmd)
130 import subprocess
132 if output == "createlib.py":
133 code = subprocess.call([bin, "-b", "-P", scriptpath])
134 else:
135 libpath = winpath(bpy.context.scene.matlib.current_library.path)
136 code = subprocess.call([bin, "-b", libpath, "-P", scriptpath])
138 #code returns 0 if ok, 1 if not
139 return abs(code-1)
141 def list_materials(path, sort=False):
142 list = []
143 with bpy.data.libraries.load(path) as (data_from, data_to):
144 for mat in data_from.materials:
145 list.append(mat)
147 if sort: list = sorted(list)
148 return list
150 #category properties (none atm)
151 class EmptyGroup(PropertyGroup):
152 pass
153 # bpy.utils.register_class(EmptyGroup)
155 class matlibMaterials(PropertyGroup):
156 category: StringProperty()
157 # bpy.utils.register_class(matlibMaterials)
159 #bpy.types.Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
161 ### CATEGORIES
162 class Categories():
164 #cats = bpy.context.scene.matlib.categories
166 def __init__(self, cats):
167 self.cats = cats
169 def save(self):
170 scn = bpy.context.scene
171 cats = set([cat.name for cat in self.cats])
172 libpath = bpy.context.scene.matlib.current_library.path
174 cmd = """
175 print(30*"+")
176 import bpy
177 if not hasattr(bpy.context.scene, "matlib_categories"):
178 class EmptyProps(bpy.types.PropertyGroup):
179 pass
180 bpy.utils.register_class(EmptyProps)
181 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
182 cats = bpy.context.scene.matlib_categories
183 for cat in cats:
184 cats.remove(0)
186 for cat in cats:
187 cmd += """
188 cat = cats.add()
189 cat.name = "%s" """ % cat.capitalize()
190 cmd +='''
191 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(libpath)
193 return send_command(cmd, "save_categories.py")
195 def read(self, pull=True):
196 #mandar a imprimir el listado
197 catfile = winpath(os.path.join(matlib_path, "categories.txt"))
198 cmd = """
199 import bpy, json
200 class EmptyProps(bpy.types.PropertyGroup):
201 pass
202 bpy.utils.register_class(EmptyProps)
203 bpy.types.Scene.matlib_categories = bpy.props.CollectionProperty(type=EmptyProps)
204 cats = []
205 for cat in bpy.context.scene.matlib_categories:
206 materials = []
207 for mat in bpy.data.materials:
208 if "category" in mat.keys() and mat['category'] == cat.name:
209 materials.append(mat.name)
210 cats.append([cat.name, materials])
211 with open("%s", "w") as f:
212 f.write(json.dumps(cats, sort_keys=True, indent=4))
213 """ % catfile
214 if pull: send_command(cmd)
216 #leer el fichero
217 with open(catfile, "r") as f:
218 cats = json.loads(f.read())
220 dd(cats)
222 # #refrescar categorias
223 # for cat in self.cats:
224 # self.cats.remove(0)
226 # for cat in cats:
227 # item = self.cats.add()
228 # item.name = cat
230 return cats
232 def view(self):
233 for cat in self.cats:
234 dd(cat.name)
236 def add(self, name):
237 if name and name not in [item.name for item in self.cats]:
238 name = name.strip().capitalize()
239 item = self.cats.add()
240 item.name = name
241 if self.save():
242 dd(name, "added")
243 return True
244 else:
245 dd("duplicated?")
247 def remove(self, index):
248 self.cats.remove(index)
249 self.save()
251 class Library():
253 def __init__(self, matlib_path, name):
254 self.name = name
255 self.path = os.path.join(matlib_path, name)
256 # @property
257 # def default(self):
258 # return self.name == default_library
260 @property
261 def shortname(self):
262 # if self.default:
263 # return "Default Library"
264 return bpy.path.display_name(self.name).title()
267 def __repr__(self):
268 return str(type(self).__name__) + "('" + self.name + "')"
270 #bpy.utils.register_class(Library)
272 def get_libraries():
273 libs = [Library(matlib_path, f) for f in os.listdir(matlib_path) if f[-5::] == "blend"]
274 return sorted(libs, key=lambda x: bpy.path.display_name(x.name))
276 libraries = []
277 # get_libraries()
279 ### MATLIB CLASS
280 class matlibProperties(PropertyGroup):
282 #MATLIB PROPERTIES
284 #libraries are read from the xml
285 lib_index: IntProperty(min = -1, default = 2, update=update_lib_index)
286 all_materials: CollectionProperty(type = matlibMaterials)
287 materials: CollectionProperty(type = matlibMaterials)
288 mat_index: IntProperty(min = -1, default = -1)
289 categories: CollectionProperty(type = EmptyGroup)
290 cat_index: IntProperty(min = -1, default = -1, update=update_cat_index)
291 search: StringProperty(name="Search", description="Find By Name", update=update_search_index)
293 #MATLIB OPTIONS
294 #link: import material linked
295 #force import:
296 # if disable it wont import a material if its present in the scene,(avoid duplicates)
297 # instead it will apply the scene material rather than importing the same one from the library
298 #filter: enable or disable category filter
299 #last selected: store the last selected object to regain focus when apply a material.
300 #hide_search: Hides Search Field
301 link: BoolProperty(name = "Linked", description="Link the material", default = False)
302 force_import: BoolProperty(name = "Force Import", description="Use Scene Materials by default", default = False)
303 filter: BoolProperty(name = "Filter",description="Filter Categories", default = True, update=update_filter)
304 show_prefs: BoolProperty(name = "show_prefs", description="Preferences", default = False)
305 last_selected: StringProperty(name="Last Selected")
306 hide_search: BoolProperty(name="Hide Search", description="Use Blender Search Only")
307 #import_file = StringProperty("Import File", subtype="FILE_PATH")
308 #path = os.path.dirname(path)
309 #Development only
311 @property
312 def libraries(self):
313 global libraries
314 return libraries
316 @property
317 def current_library(self):
318 if check_index(libraries, self.lib_index):
319 return libraries[self.lib_index]
321 @property
322 def active_material(self):
323 if check_index(self.materials, self.mat_index):
324 return self.materials[self.mat_index]
326 def reload(self):
327 dd("loading libraries")
328 if self.current_library:
329 self.load_library()
330 elif self.lib_index == -1 and len(libraries):
331 self.lib_index = 0
334 def add_library(self, path, setEnabled = False):
335 #sanitize path
336 ext = os.path.extsep + "blend"
337 if not path.endswith(ext):
338 path += ext
340 if check_path(path):
341 # if path == default_library:
342 # return 'ERROR', "Cannot add default library."
343 #if path in [lib.path for lib in self.libraries]:
344 return 'ERROR', "Library already exists."
345 else:
346 dd("Can't find " + path)
347 #create file
348 cmd = '''
349 import bpy
350 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % winpath(path)
351 if not (send_command(cmd, "createlib.py")):
352 return 'ERROR', "There was an error creating the file. Make sure you run Blender with admin rights."
354 #self.libraries = sorted(self.libraries, key=lambda lib: sortlibs(lib))
355 dd("adding library", path)
356 global libraries
357 libraries = get_libraries()
358 return "INFO", "Library added"
360 def load_library(self):
361 self.empty_list(True)
362 if not self.current_library:
363 return 'ERROR', "Library not found!."
365 path = self.current_library.path
367 dd("loading library", self.lib_index, path)
369 if check_path(path):
370 self.filter = False
371 self.cat_index = -1
373 categories = Categories(self.categories)
374 self.cats = categories.read(True)
375 self.load_categories()
377 for mat in self.all_materials:
378 self.all_materials.remove(0)
380 for mat in list_materials(self.current_library.path, True):
381 item = self.all_materials.add()
382 item.name = mat
383 for cat in self.cats:
384 if mat in cat[1]:
385 item.category = cat[0]
386 break
388 self.update_list()
389 else:
390 return 'ERROR', "Library not found!."
392 def update_list(self):
393 ### THIS HAS TO SORT
394 self.empty_list()
395 if self.current_library:
396 current_category = self.current_category
397 #sorteditems = sorted(self.all_materials, key=lambda x: x.name)
398 for mat in self.all_materials:
399 #print(current_category, mat.category)
400 if not self.filter or (self.filter and mat.category == current_category) or current_category == "":
401 item = self.materials.add()
402 item.name = mat.name
403 item.category = mat.category
405 def empty_list(self, cats = False):
406 #self.mat_index = -1
407 for it in self.materials:
408 self.materials.remove(0)
410 if cats:
411 for c in self.categories:
412 self.categories.remove(0)
414 ### CATEGORIES
415 @property
416 def current_category(self):
417 #print(self.mat_index)
418 if check_index(self.categories, self.cat_index):
419 return self.categories[self.cat_index].name
420 return ""
422 def load_categories(self):
424 for c in self.categories:
425 self.categories.remove(0)
427 for c in self.cats:
428 cat = self.categories.add()
429 cat.name = c[0]
431 def add_category(self, name):
432 if name:
433 name = name.strip().title()
434 dd("add category", name)
435 categories = Categories(self.categories)
437 categories.add(name)
439 # if lib:
440 # cat = xml.find("category", name, lib, create = True)
441 # self.load_categories()
442 # else:
443 # return 'ERROR', "Library not found"
444 def remove_category(self):
445 dd("removing category", self.current_category)
446 categories = Categories(self.categories)
447 categories.remove(self.cat_index)
449 def set_category(self):
450 mat = self.active_material
451 #dd(lib, mat, self.current_category)
452 if mat:
453 #set mat to category
454 if self.cat_index>-1:
455 dd(self.current_category)
456 cat = self.current_category
457 if cat == self.all_materials[self.mat_index].category:
458 return
459 cmd = """
460 import bpy
461 try:
462 mat = bpy.data.materials['%s']
463 except:
464 mat = None
465 if mat:
466 mat['category'] = "%s"
467 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)
468 """ % (mat.name, cat, winpath(self.current_library.path))
469 if send_command(cmd):
470 self.all_materials[self.mat_index].category = cat
471 mat.category = cat
472 else:
473 return "WARNING", "There was an error."
475 # catnode = xml.find("category", self.current_category, lib, True)
476 # matnode = xml.find("material", mat.name, lib)
477 # if matnode:
478 # catnode.appendChild(matnode)
479 # else:
480 # matnode = xml.find("material", mat.name, catnode, True)
481 # xml.save()
482 # mat.category = cat
483 # self.current_library.materials[self.mat_index].category = cat
484 #remove mat from any category
485 else:
486 mat.category = ""
487 self.all_materials[self.mat_index].category = ""
488 else:
489 return "WARNING", "Select a material"
491 def get_material(self, name, link=False):
492 with bpy.data.libraries.load(self.current_library.path, link, False) as (data_from, data_to):
493 data_to.materials = [name]
494 if link:
495 print(name + " linked.")
496 else:
497 print(name + " appended.")
499 def apply(self, context, preview=False):
500 name = self.active_material.name
501 if not name: return "WARNING", "Select a material from the list."
503 linked = self.link or preview
504 force = self.force_import or linked
506 objects = []
507 active = context.object
508 dummy = self.get_dummy(context)
510 #setup objects
511 if preview:
512 if context.mode == "EDIT_MESH":
513 return "WARNING", "Can't preview on EDIT MODE"
514 if dummy!= active:
515 self.last_selected = context.object.name
516 context.view_layer.objects.active = dummy
517 objects.append(dummy)
518 #apply
519 else:
520 objects = [obj for obj in context.selected_objects if hasattr(obj.data, "materials")]
522 if not objects:
523 return "INFO", "Please select an object"
525 if dummy == context.object and not preview:
526 if (len(objects)==1 and dummy.select):
527 return "ERROR", "Apply is disabled for the Material Preview Object"
528 try:
529 last = context.scene.objects[self.last_selected]
530 if last in context.selected_objects:
531 context.view_layer.objects.active = last
532 else:
533 self.last_selected = ""
534 except:
535 context.view_layer.objects.active = None
536 dummy.select_set(False)
537 #objects = context.selected_objects
539 material = None
541 #mira si hay materiales linkados de la libreria actual
542 for mat in bpy.data.materials:
543 try:
544 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
545 except:
546 samelib = False
548 if mat.name == name and mat.library and samelib:
549 material = mat
550 dd("encontre linked", name, "no importo nada")
551 break
553 if not force:
554 #busca materiales no linkados
555 for mat in bpy.data.materials:
556 if mat.name == name and not mat.library:
557 material = mat
558 dd("encontre no linkado", name, "no importo nada")
559 break
561 if not material:
562 #go get it
563 dd("voy a buscarlo")
564 nmats = len(bpy.data.materials)
566 self.get_material(name, linked)
568 if not self.force_import:
569 try:
570 material = bpy.data.materials[name]
571 except:
572 pass
574 if not material:
575 if nmats == len(bpy.data.materials) and not linked:
576 return "ERROR", name + " doesn't exists at library " + str(linked)
577 else:
578 for mat in reversed(bpy.data.materials):
579 if mat.name[0:len(name)] == name:
580 #careful on how blender writes library paths
581 try:
582 samelib = bpy.path.relpath(mat.library.filepath) == bpy.path.relpath(self.current_library.path)
583 except:
584 samelib = False
586 if linked and mat.library and samelib:
587 material = mat
588 dd(name, "importado con link")
589 break
590 else:
591 if not mat.library:
592 dd(name, "importado sin link")
593 material = mat
594 break
595 if material:
596 material.use_fake_user = False
597 material.user_clear()
599 print ("Material", material, force)
601 #if material:
602 #maybe some test cases doesn't return a material, gotta take care of that
603 #i cannot think of any case like that right now
604 #maybe import linked when the database isnt sync
605 if context.mode == "EDIT_MESH":
606 obj = context.object
607 dd(material)
608 index = -1
609 for i, mat in enumerate(obj.data.materials):
610 if mat == material:
611 index = i
612 break
614 if index == -1:
615 obj.data.materials.append(material)
616 index = len(obj.data.materials)-1
617 dd(index)
618 import bmesh
619 bm = bmesh.from_edit_mesh(obj.data)
620 for f in bm.faces:
621 if f.select:
622 f.material_index = index
624 else:
625 for obj in objects:
626 index = obj.active_material_index
627 if index < len(obj.material_slots):
628 obj.material_slots[index].material = None
629 obj.material_slots[index].material = material
630 else:
631 obj.data.materials.append(material)
633 if not linked:
634 bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")
636 def add_material(self, mat):
638 if not mat:
639 return 'WARNING', "Select a material from the scene."
641 name = mat.name
642 thispath = winpath(bpy.data.filepath)
643 libpath = winpath(self.current_library.path)
645 if not thispath:
646 return 'WARNING', "Save this file before export."
648 if not libpath:
649 return 'WARNING', "Library not found!."
651 elif bpy.data.is_dirty:
652 bpy.ops.wm.save_mainfile(check_existing=True)
654 if mat.library:
655 return 'WARNING', 'Cannot export linked materials.'
657 dd("adding material", name, libpath)
659 overwrite = ""
660 if name in list_materials(libpath):
661 overwrite = '''
662 mat = bpy.data.materials["%s"]
663 mat.name = "tmp"
664 mat.use_fake_user = False
665 mat.user_clear()''' % name
667 cmd = '''
668 import bpy{0}
669 with bpy.data.libraries.load("{1}") as (data_from, data_to):
670 data_to.materials = ["{2}"]
671 mat = bpy.data.materials["{2}"]
672 mat.use_fake_user=True
673 bpy.ops.file.pack_all()
674 bpy.ops.wm.save_mainfile(filepath="{3}", check_existing=False, compress=True)
675 '''.format(overwrite, thispath, name, libpath)
677 if send_command(cmd):
678 #self.load_library()
679 if not overwrite:
680 item = self.all_materials.add()
681 item.name = name
682 if "category" in mat.keys():
683 item.category = mat['category']
684 #reorder all_materials
685 items = sorted([[item.name, item.category] for item in self.all_materials], key = lambda x: x[0])
687 self.all_materials.clear()
688 for it in items:
689 item = self.all_materials.add()
690 item.name = it[0]
691 item.category = it[1]
693 self.update_list()
695 return 'INFO', "Material added."
696 else:
697 print("Save Material Error: Run Blender with administrative privileges.")
698 return 'WARNING', "There was an error saving the material"
700 def remove_material(self):
701 name = self.active_material.name
702 libpath = winpath(self.current_library.path)
703 if name and libpath and name in list_materials(libpath):
704 cmd = '''import bpy
705 mat = bpy.data.materials["%s"]
706 mat.use_fake_user = False
707 mat.user_clear()
708 bpy.ops.wm.save_mainfile(filepath="%s", check_existing=False, compress=True)''' % (name , libpath)
709 if send_command(cmd, "removemat.py"):
710 self.all_materials.remove(self.mat_index)
711 self.update_list()
712 else:
713 return 'ERROR', "There was an error."
714 return "INFO", name + " removed."
716 def get_dummy(self, context):
717 dummy_name = "Material_Preview_Dummy"
718 dummy_mesh = "Material_Preview_Mesh"
719 scn = context.scene
720 try:
721 dummy = scn.objects[dummy_name]
722 except:
723 #create dummy
724 try:
725 me = bpy.data.meshes(dummy_mesh)
726 except:
727 me = bpy.data.meshes.new(dummy_mesh)
728 dummy = bpy.data.objects.new(dummy_name, me)
729 context.collection.objects.link(dummy)
731 dummy.hide_set(True)
732 dummy.hide_render = True
733 dummy.hide_select = True
734 return dummy
736 # bpy.utils.register_class(matlibProperties)
737 # Scene.matlib = PointerProperty(type = matlibProperties)
739 ### MENUS
740 class MATLIB_MT_LibsMenu(Menu):
741 bl_label = "Libraries Menu"
743 def draw(self, context):
744 layout = self.layout
745 libs = libraries
746 #layout.operator("matlib.operator", text="Default Library").cmd="lib-1"
747 for i, lib in enumerate(libs):
748 layout.operator("matlib.operator", text=lib.shortname).cmd="lib"+str(i)
750 class MATLIB_MT_CatsMenu(Menu):
751 bl_label = "Categories Menu"
753 def draw(self, context):
754 layout = self.layout
755 cats = context.scene.matlib.categories
756 layout.operator("matlib.operator", text="All").cmd="cat-1"
757 for i, cat in enumerate(cats):
758 layout.operator("matlib.operator", text=cat.name).cmd="cat"+str(i)
760 ### OPERATORS
761 #class MATLIB_OT_add(Operator):
762 # """Add Active Material"""
763 # bl_label = "Add"
764 # bl_idname = "matlib.add_material"
766 # @classmethod
767 # def poll(cls, context):
768 # return context.active_object is not None
770 # def exectute(self, context):
771 # print("executing")
772 # return {"FINISHED"}
776 class MATLIB_OT_add(Operator):
777 """Add active material to library"""
778 bl_idname = "matlib.add"
779 bl_label = "Add active material"
781 @classmethod
782 def poll(cls, context):
783 obj = context.active_object
784 return obj is not None and obj.active_material is not None
786 def execute(self, context):
787 matlib = context.scene.matlib
788 success = matlib.add_material(context.object.active_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 """Remove material from library"""
796 bl_idname = "matlib.remove"
797 bl_label = "Remove material from library"
799 @classmethod
800 def poll(cls, context):
801 matlib = context.scene.matlib
802 return check_index(matlib.materials, matlib.mat_index)
804 def execute(self, context):
805 matlib = context.scene.matlib
806 success = matlib.remove_material()
807 if type(success).__name__ == "tuple":
808 print(success)
809 self.report({success[0]}, success[1])
810 return {'FINISHED'}
812 class MATLIB_OT_remove(Operator):
813 """Reload library"""
814 bl_idname = "matlib.reload"
815 bl_label = "Reload library"
817 # @classmethod
818 # def poll(cls, context):
819 # matlib = context.scene.matlib
820 # index = matlib.mat_index
821 # l = len(matlib.materials)
822 # return l>0 and index >=0 and index < l
824 def execute(self, context):
825 matlib = context.scene.matlib
826 success = matlib.reload()
827 if type(success).__name__ == "tuple":
828 print(success)
829 self.report({success[0]}, success[1])
830 return {'FINISHED'}
833 class MATLIB_OT_apply(Operator):
834 """Apply selected material"""
835 bl_idname = "matlib.apply"
836 bl_label = "Apply material"
838 @classmethod
839 def poll(cls, context):
840 matlib = context.scene.matlib
841 index = matlib.mat_index
842 l = len(matlib.materials)
843 obj = context.active_object
844 return l>0 and index >=0 and index < l and obj is not None
846 def execute(self, context):
847 matlib = context.scene.matlib
848 success = matlib.apply(context, False)
849 if type(success).__name__ == "tuple":
850 print(success)
851 self.report({success[0]}, success[1])
852 return {'FINISHED'}
855 class MATLIB_OT_preview(Operator):
856 """Preview selected material"""
857 bl_idname = "matlib.preview"
858 bl_label = "Preview selected material"
860 @classmethod
861 def poll(cls, context):
862 matlib = context.scene.matlib
863 index = matlib.mat_index
864 l = len(matlib.materials)
865 obj = context.active_object
866 return l>0 and index >=0 and index < l
868 def execute(self, context):
869 matlib = context.scene.matlib
870 success = matlib.apply(context, True)
871 if type(success).__name__ == "tuple":
872 print(success)
873 self.report({success[0]}, success[1])
874 return {'FINISHED'}
877 class MATLIB_OT_flush(Operator):
878 """Flush unused materials"""
879 bl_idname = "matlib.flush"
880 bl_label = "Flush unused materials"
882 @classmethod
883 def poll(cls, context):
884 matlib = context.scene.matlib
885 index = matlib.mat_index
886 l = len(matlib.materials)
887 obj = context.active_object
888 return l>0 and index >=0 and index < l
890 def execute(self, context):
891 matlib = context.scene.matlib
892 dummy = matlib.get_dummy(context)
893 if dummy == context.object:
894 try:
895 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
896 except:
897 pass
899 for slot in dummy.material_slots:
900 slot.material = None
902 for mat in bpy.data.materials:
903 if mat.users==0:
904 i+=1
905 print (mat.name, "removed.")
906 bpy.data.materials.remove(mat)
908 plural = "" if i == 1 else "s"
909 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
911 return {'FINISHED'}
914 class MATLIB_OT_operator(Operator):
915 """Add, Remove, Reload, Apply, Preview, Clean Material"""
916 bl_label = "New"
917 bl_idname = "matlib.operator"
918 __doc__ = "Add, Remove, Reload, Apply, Preview, Clean Material"
920 category: StringProperty(name="Category")
921 filepath: StringProperty(options={'HIDDEN'})
922 cmd: bpy.props.StringProperty(name="Command", options={'HIDDEN'})
923 filter_glob: StringProperty(default="*.blend", options={'HIDDEN'})
924 @classmethod
925 def poll(cls, context):
926 return context.active_object is not None
928 def draw(self, context):
929 layout = self.layout
930 #cmd = LIBRARY_ADD
931 if self.cmd == "LIBRARY_ADD":
932 #layout.label(text="Select a blend file as library or")
933 #layout.label(text="Type a name to create a new library.")
934 layout.prop(self, "category", text="Library")
935 elif self.cmd == "FILTER_ADD":
936 layout.prop(self, "category")
938 def invoke(self, context, event):
940 cmd = self.cmd
941 print("invoke", cmd)
943 if cmd == "LIBRARY_ADD":
944 self.filepath = matlib_path + os.path.sep
945 dd("filepath", self.filepath, matlib_path)
946 #context.window_manager.fileselect_add(self)
947 context.window_manager.invoke_props_dialog(self)
948 return {'RUNNING_MODAL'}
949 elif cmd == "FILTER_ADD":
950 context.window_manager.invoke_props_dialog(self)
951 return {'RUNNING_MODAL'}
952 return self.execute(context)
954 ### TODO: execute doesn't trigger remove
955 def execute(self, context):
957 success = ""
958 matlib = context.scene.matlib
960 if self.cmd == "init":
961 print("initialize")
962 return {'FINISHED'}
964 #Library Commands
965 if self.cmd[0:3] == "lib":
966 index = int(self.cmd[3::])
967 matlib.lib_index = index
968 #success = matlib.load_library()
969 elif self.cmd == "LIBRARY_ADD":
970 dd("execute lib add")
971 libname = self.category
972 if libname[-6::] != ".blend": libname+= ".blend"
973 libname = os.path.join(matlib_path, libname)
974 print(libname)
976 success = matlib.add_library(libname, True)
977 for i, l in enumerate(libraries):
978 if l.name == self.category:
979 matlib.lib_index = i
980 break
982 elif self.cmd == "RELOAD":
983 success = matlib.reload()
985 if not matlib.current_library:
986 self.report({'ERROR'}, "Select a Library")
987 return {'CANCELLED'}
989 if self.cmd == "FILTER_ADD":
990 success = matlib.add_category(self.category)
991 for i, cat in enumerate(matlib.categories):
992 if cat.name == self.category:
993 matlib.cat_index = i
994 break
996 elif self.cmd == "FILTER_REMOVE":
997 matlib.remove_category()
999 elif self.cmd == "FILTER_SET":
1000 success = matlib.set_category()
1002 elif self.cmd[0:3] == "cat":
1003 index = int(self.cmd[3::])
1004 matlib.cat_index = index
1006 #Common Commands
1007 elif self.cmd == "ADD":
1008 success = matlib.add_material(context.object.active_material)
1010 elif self.cmd == "REMOVE":
1011 success = matlib.remove_material()
1014 elif self.cmd == "APPLY":
1015 success = matlib.apply(context)
1017 elif self.cmd == "PREVIEW":
1018 success = matlib.apply(context, True)
1020 elif self.cmd=="FLUSH":
1021 #release dummy materials
1022 dummy = matlib.get_dummy(context)
1023 if dummy == context.object:
1024 try:
1025 context.view_layer.objects.active = context.scene.objects[matlib.last_selected]
1026 except:
1027 pass
1029 for slot in dummy.material_slots:
1030 slot.material = None
1032 for mat in bpy.data.materials:
1033 if mat.users==0:
1034 i+=1
1035 print (mat.name, "removed.")
1036 bpy.data.materials.remove(mat)
1038 plural = "s"
1039 if i==1:
1040 plural = ""
1042 self.report({'INFO'}, str(i) + " material"+plural+" removed.")
1044 ### CONVERT
1045 elif self.cmd == "CONVERT":
1046 return {'FINISHED'}
1047 lib = matlib.current_library
1048 if lib:
1050 path = os.path.join(matlib_path, "www")
1051 if not os.path.exists(path):
1052 os.mkdir(path)
1053 path = os.path.join(path, lib.shortname)
1054 if not os.path.exists(path):
1055 os.mkdir(path)
1057 path = winpath(path)
1058 libpath = winpath(lib.name)
1060 print(path)
1061 print(libpath)
1063 #decirle a la libreria que cree un fichero blend por cada material que tenga.
1064 cmd = """
1065 print(30*"+")
1066 import bpy, os
1067 def list_materials():
1068 list = []
1069 with bpy.data.libraries.load("{0}") as (data_from, data_to):
1070 for mat in data_from.materials:
1071 list.append(mat)
1072 return sorted(list)
1074 def get_material(name, link=False):
1075 with bpy.data.libraries.load("{0}", link, False) as (data_from, data_to):
1076 data_to.materials = [name]
1077 if link:
1078 print(name + " linked.")
1079 else:
1080 print(name + " appended.")
1082 for scn in bpy.data.scenes:
1083 for obj in scn.objects:
1084 scn.objects.unlink(obj)
1085 obj.user_clear()
1086 bpy.data.objects.remove(obj)
1088 def clean_materials():
1089 for mat in bpy.data.materials:
1090 mat.user_clear()
1091 bpy.data.materials.remove(mat)
1093 bin = bpy.app.binary_path
1094 mats = list_materials()
1095 bpy.context.preferences.filepaths.save_version = 0
1096 for mat in mats:
1097 clean_materials()
1098 matpath = os.path.join("{1}", mat + ".blend")
1099 print(matpath)
1100 get_material(mat)
1101 material = bpy.data.materials[0]
1102 material.use_fake_user = True
1103 bpy.ops.wm.save_mainfile(filepath = matpath, compress=True, check_existing=False)
1104 """.format(libpath, path)
1105 print(cmd)
1106 send_command(cmd, "createlib.py")
1108 if type(success).__name__ == "tuple":
1109 print(success)
1110 self.report({success[0]}, success[1])
1112 return {'FINISHED'}
1115 class MATLIB_PT_vxPanel(Panel):
1116 bl_label = "Material Library VX"
1117 bl_space_type = "PROPERTIES"
1118 bl_region_type = "WINDOW"
1119 bl_context = "material"
1120 bl_options = {'DEFAULT_CLOSED'}
1122 @classmethod
1123 def poll(self, context):
1124 return context.active_object.active_material!=None
1126 def draw(self, context):
1127 layout = self.layout
1128 matlib = context.scene.matlib
1130 #hyper ugly trick but i dont know how to init classes at register time
1131 # if matlibProperties.init:
1132 # matlibProperties.init = False
1133 # matlib.__init__()
1135 #libraries
1136 col = layout.column(align=True)
1137 if matlib.current_library:
1138 text = matlib.current_library.shortname
1139 else:
1140 text = "Select a Library"
1141 split = col.split(factor=0.55, align=True)
1142 split.menu("MATLIB_MT_LibsMenu",text=text)
1143 split.operator("matlib.operator", icon="ADD", text="New Library").cmd = "LIBRARY_ADD"
1145 # #list
1146 row = layout.row()
1147 row.template_list("UI_UL_list", " ", matlib, "materials", matlib, "mat_index", rows=6)
1148 col = row.column()
1149 # row = layout.row()
1150 #operators
1151 col.operator("matlib.operator", icon="ADD", text="Add To Library").cmd="ADD"
1152 col.operator("matlib.operator", icon="MATERIAL", text="Apply To Selected").cmd="APPLY"
1153 col.operator("matlib.operator", icon="FILE_REFRESH", text="Reload Material").cmd="RELOAD"
1154 col.operator("matlib.operator", icon="COLOR", text="Preview Material").cmd="PREVIEW"
1155 col.operator("matlib.operator", icon="GHOST_DISABLED", text="Remove Preview").cmd="FLUSH"
1156 col.operator("matlib.operator", icon="REMOVE", text="Remove Material").cmd="REMOVE"
1157 col.prop(matlib, "show_prefs", icon="MODIFIER", text="Settings")
1159 # Search
1160 if not matlib.hide_search:
1161 row = layout.row(align=True)
1162 row.prop_search(matlib, "search", matlib, "materials", text="", icon="VIEWZOOM")
1164 #categories
1165 row = layout.row()
1166 if matlib.active_material:
1167 row.label(text="Category:")
1168 row.label(text = matlib.active_material.category)
1169 else:
1170 row.label(text="Category Tools:")
1171 row = layout.row(align=True)
1172 text = "All"
1173 if matlib.current_category: text = matlib.current_category
1174 row.menu("MATLIB_MT_CatsMenu",text=text)
1175 row = layout.row(align=True)
1176 row.prop(matlib, "filter", icon="FILTER", text="Filter")
1177 row.operator("matlib.operator", icon="FILE_PARENT", text="Set Type").cmd="FILTER_SET"
1178 row.operator("matlib.operator", icon="ADD", text="New").cmd="FILTER_ADD"
1179 row.operator("matlib.operator", icon="REMOVE", text="Remove").cmd="FILTER_REMOVE"
1181 #prefs
1182 if matlib.show_prefs:
1183 row = layout.row(align=True)
1184 row.prop(matlib, "force_import")
1185 row.prop(matlib, "link")
1186 row.prop(matlib, "hide_search")
1187 # row = layout.row(align=True)
1188 #row = layout.row()
1189 #row.operator("matlib.operator", icon="URL", text="Convert Library").cmd="CONVERT"
1191 # row = layout.row()
1192 # if (matlib.current_library):
1193 # row.label(matlib.current_library.name)
1194 # else:
1195 # row.label(text="Library not found!.")
1197 classes = [
1198 matlibMaterials,
1199 matlibProperties,
1200 EmptyGroup,
1201 MATLIB_PT_vxPanel,
1202 MATLIB_OT_operator,
1203 MATLIB_MT_LibsMenu,
1204 MATLIB_MT_CatsMenu
1206 #print(bpy.context.scene)
1209 @persistent
1210 def refresh_libs(dummy=None):
1211 global libraries
1212 global matlib_path
1213 default_path = bpy.context.preferences.addons[__name__].preferences.matlib_path
1214 if default_path is not None and default_path != '':
1215 matlib_path = default_path
1217 libraries = get_libraries()
1220 def reload_library(self, context):
1221 bpy.context.preferences.addons[__name__].preferences.matlib_path = bpy.path.abspath(bpy.context.preferences.addons[__name__].preferences.matlib_path)
1222 refresh_libs(self)
1225 class matlibvxPref(AddonPreferences):
1226 bl_idname = __name__
1227 matlib_path: StringProperty(
1228 name="Additional Path",
1229 description="User defined path to .blend libraries files",
1230 default="",
1231 subtype="DIR_PATH",
1232 update=reload_library
1235 def draw(self, context):
1236 layout = self.layout
1237 layout.prop(self, "matlib_path")
1239 classes = [
1240 matlibvxPref,
1241 matlibMaterials,
1242 EmptyGroup,
1243 MATLIB_PT_vxPanel,
1244 MATLIB_OT_operator,
1245 MATLIB_MT_LibsMenu,
1246 MATLIB_MT_CatsMenu,
1247 matlibProperties,
1251 classes = [
1252 matlibProperties,
1253 EmptyGroup,
1254 matlibMaterials,
1255 Categories,
1256 Library,
1257 MATLIB_MT_LibsMenu,
1258 MATLIB_MT_CatsMenu,
1259 MATLIB_OT_add,
1260 MATLIB_OT_remove,
1261 MATLIB_OT_remove,
1262 MATLIB_OT_apply,
1263 MATLIB_OT_preview,
1264 MATLIB_OT_flush,
1265 MATLIB_OT_operator,
1266 MATLIB_PT_vxPanel,
1267 matlibvxPref
1270 def register():
1271 global matlib_path
1273 for c in classes:
1274 bpy.utils.register_class(c)
1275 Scene.matlib_categories = CollectionProperty(type=EmptyGroup)
1276 Scene.matlib = PointerProperty(type = matlibProperties)
1277 bpy.app.handlers.load_post.append(refresh_libs)
1278 refresh_libs()
1281 def unregister():
1282 global libraries
1284 try:
1285 # raise ValueError list.remove(x): x not in list
1286 del Scene.matlib_categories
1287 except:
1288 pass
1289 del Scene.matlib
1290 libraries.clear()
1291 bpy.app.handlers.load_post.remove(refresh_libs)
1292 for c in classes:
1293 bpy.utils.unregister_class(c)
1296 if __name__ == "__main__":
1297 register()