Sun position: remove unused prop in HDRI mode
[blender-addons.git] / archipack / archipack_material.py
blob27aaefabf0303dda16dcc2808fe191734bfc2495
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 # <pep8 compliant>
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
28 import bpy
29 import os
30 # noinspection PyUnresolvedReferences
31 from bpy.types import (
32 Panel, PropertyGroup,
33 Object, Operator
35 from bpy.props import (
36 EnumProperty, CollectionProperty,
37 StringProperty
41 setman = None
42 libman = None
45 class MatLib():
46 """
47 A material library .blend file
48 Store material name
49 Apply material to objects
50 """
51 def __init__(self, matlib_path, name):
52 self.name = name
53 try:
54 self.path = os.path.join(matlib_path, name)
55 except:
56 pass
57 self.materials = []
59 def cleanup(self):
60 self.materials.clear()
62 def load_list(self, sort=False):
63 """
64 list material names
65 """
66 # print("MatLib.load_list(%s)" % (self.name))
67 self.materials.clear()
68 try:
69 with bpy.data.libraries.load(self.path) as (data_from, data_to):
70 for mat in data_from.materials:
71 self.materials.append(mat)
72 if sort:
73 self.materials = list(sorted(self.materials))
74 except:
75 pass
77 def has(self, name):
78 return name in self.materials
80 def load_mat(self, name, link):
81 """
82 Load a material from library
83 """
84 try:
85 # print("MatLib.load_mat(%s) linked:%s" % (name, link))
86 with bpy.data.libraries.load(self.path, link, False) as (data_from, data_to):
87 data_to.materials = [name]
88 except:
89 pass
91 def get_mat(self, name, link):
92 """
93 apply a material by name to active_object
94 into slot index
95 lazy load material list on demand
96 return material or None
97 """
99 # Lazy load material names
100 if len(self.materials) < 1:
101 self.load_list()
103 # material belongs to this libraray
104 if self.has(name):
106 # load material
107 self.load_mat(name, link)
109 return bpy.data.materials.get(name)
111 return None
114 class MatlibsManager():
116 Manage multiple library
117 Lazy load
119 def __init__(self):
120 self.matlibs = []
122 def cleanup(self):
123 for lib in self.matlibs:
124 lib.cleanup()
125 self.matlibs.clear()
127 def get_prefs(self, context):
129 let raise error if any
131 global __name__
132 prefs = None
133 # retrieve addon name from imports
134 addon_name = __name__.split('.')[0]
135 prefs = context.preferences.addons[addon_name].preferences
136 return prefs
138 @property
139 def loaded_path(self):
141 Loaded matlibs filenames
143 return [lib.path for lib in self.matlibs]
145 def from_data(self, name):
146 return bpy.data.materials.get(name)
148 def add_to_list(self, path):
150 Add material library to list
151 only store name of lib
152 reloading here doesn't make sense
154 loaded_path = self.loaded_path
156 if os.path.exists(path):
157 self.matlibs.extend(
159 MatLib(path, f) for f in os.listdir(path)
160 if f.endswith(".blend") and os.path.join(path, f) not in loaded_path
164 def load_list(self, context):
166 list available library path
168 # default library
169 dir_path = os.path.dirname(os.path.realpath(__file__))
170 mat_path = os.path.join(dir_path, "materials")
171 self.add_to_list(mat_path)
173 # user def library path from addon prefs
174 try:
175 prefs = self.get_prefs(context)
176 self.add_to_list(prefs.matlib_path)
177 except:
178 print("Archipack: Unable to load default material library, please check path in addon prefs")
179 pass
181 def apply(self, context, slot_index, name, link=False):
183 o = context.active_object
184 o.select_set(state=True)
186 # material with same name exist in scene
187 mat = self.from_data(name)
189 # mat not in scene: try to load from lib
190 if mat is None:
191 # print("mat %s not found in scene, loading" % (name))
192 # Lazy build matlibs list
193 if len(self.matlibs) < 1:
194 self.load_list(context)
196 for lib in self.matlibs:
197 mat = lib.get_mat(name, link)
198 if mat is not None:
199 break
201 # nothing found, build a default mat
202 if mat is None:
203 mat = bpy.data.materials.new(name)
205 if slot_index < len(o.material_slots):
206 o.material_slots[slot_index].material = None
207 o.material_slots[slot_index].material = mat
208 o.active_material_index = slot_index
210 if not link:
211 # break link
212 bpy.ops.object.make_local(type="SELECT_OBDATA_MATERIAL")
215 class MaterialSetManager():
217 Manage material sets for objects
218 Store material names for each set
219 Lazy load at enumerate time
221 def __init__(self):
223 Store sets for each object type
225 self.objects = {}
226 # hold reference of dynamic enumerator
227 self.enums = {}
228 self.default_enum = [('DEFAULT', 'Default', '', 0)]
231 def get_filename(self, object_type):
233 target_path = os.path.join("presets", "archipack_materials")
234 target_path = bpy.utils.user_resource('SCRIPTS',
235 target_path,
236 create=True)
237 return os.path.join(target_path, object_type) + '.txt'
239 def cleanup(self):
240 self.objects.clear()
241 self.enums.clear()
243 def register_set(self, object_type, set_name, materials_names):
245 if object_type not in self.objects.keys():
246 self.objects[object_type] = {}
248 self.objects[object_type][set_name.upper()] = materials_names
250 def load(self, object_type):
252 filename = self.get_filename(object_type)
254 # preset not found in user prefs, load from archipack's default
255 if not os.path.exists(filename):
256 rel_filepath = \
257 os.path.sep + "presets" + os.path.sep + \
258 "archipack_materials" + os.path.sep + object_type + '.txt'
260 filename = os.path.dirname(os.path.realpath(__file__)) + rel_filepath
262 # print("load filename %s" % filename)
264 material_sets = {}
266 # create file object, and set open mode
268 try:
269 with open(filename, 'r') as f:
270 lines = f.readlines()
272 for line in lines:
273 s_key, mat_name = line.split("##|##")
274 if str(s_key) not in material_sets.keys():
275 material_sets[s_key] = []
276 material_sets[s_key].append(mat_name.strip())
277 except:
278 print("Archipack: material preset for {} not found".format(object_type))
279 pass
281 s_keys = material_sets.keys()
282 for s_key in s_keys:
283 self.register_set(object_type, s_key, material_sets[s_key])
285 self.make_enum(object_type, s_keys)
287 def save(self, object_type):
288 # always save in user prefs
289 filename = self.get_filename(object_type)
290 # print("filename:%s" % filename)
291 o_dict = self.objects[object_type]
292 lines = []
293 s_keys = o_dict.keys()
294 for s_key in s_keys:
295 for mat in o_dict[s_key]:
296 lines.append("{}##|##{}\n".format(s_key, mat))
297 try:
298 f = open(filename, 'w')
299 f.writelines(lines)
300 except:
301 print("Archipack: An error occurred while saving {}".format(filename))
302 pass
303 finally:
304 f.close()
306 self.make_enum(object_type, s_keys)
308 def add(self, context, set_name):
309 o = context.active_object
310 if "archipack_material" in o:
311 object_type = o.archipack_material[0].category
312 materials_names = [slot.name for slot in o.material_slots if slot.name != '']
313 # print("%s " % materials_names)
314 self.register_set(object_type, set_name, materials_names)
315 self.save(object_type)
317 def remove(self, context):
318 o = context.active_object
319 if "archipack_material" in o:
320 d = o.archipack_material[0]
321 object_type = d.category
322 set_name = d.material
323 s_keys = self.objects[object_type].keys()
324 if set_name in s_keys:
325 self.objects[object_type].pop(set_name)
326 self.save(object_type)
327 self.make_enum(object_type, s_keys)
329 def get_materials(self, object_type, set_name):
330 if object_type not in self.objects.keys():
331 self.load(object_type)
332 if object_type not in self.objects.keys():
333 # print("Archipack: Unknown object type {}".format(object_type))
334 return None
335 if set_name not in self.objects[object_type].keys():
336 # print("Archipack: set {} not found".format(set_name))
337 return None
338 return self.objects[object_type][set_name]
340 def make_enum(self, object_type, s_keys):
341 if len(s_keys) > 0:
342 self.enums[object_type] = [(s.upper(), s.capitalize(), '', i) for i, s in enumerate(s_keys)]
344 def get_enum(self, object_type):
346 if object_type not in self.objects.keys():
347 self.load(object_type)
349 if object_type not in self.objects.keys():
350 self.objects[object_type] = {}
352 if object_type in self.enums:
353 return self.enums[object_type]
355 return self.default_enum
358 def material_enum(self, context):
359 global setman
360 if setman is None:
361 setman = MaterialSetManager()
362 return setman.get_enum(self.category)
365 def update(self, context):
366 self.update(context)
369 class archipack_material(PropertyGroup):
371 category : StringProperty(
372 name="Category",
373 description="Archipack object name",
374 default=""
376 material : EnumProperty(
377 name="Material",
378 description="Material Set name",
379 items=material_enum,
380 update=update
383 def apply_material(self, context, slot_index, name):
384 global libman
386 if libman is None:
387 libman = MatlibsManager()
389 libman.apply(context, slot_index, name, link=False)
391 def update(self, context):
392 global setman
394 if setman is None:
395 setman = MaterialSetManager()
397 o = context.active_object
398 sel = [
399 c for c in o.children
400 if 'archipack_material' in c and c.archipack_material[0].category == self.category]
402 # handle wall's holes
403 if o.data and "archipack_wall2" in o.data:
404 if o.parent is not None:
405 for child in o.parent.children:
406 if ('archipack_hybridhole' in child or
407 'archipack_robusthole' in child or
408 'archipack_hole' in child):
409 sel.append(child)
411 sel.append(o)
413 mats = setman.get_materials(self.category, self.material)
415 if mats is None or len(mats) < 1:
416 return False
418 for ob in sel:
419 context.view_layer.objects.active = ob
420 for slot_index, mat_name in enumerate(mats):
421 if slot_index >= len(ob.material_slots):
422 bpy.ops.object.material_slot_add()
423 self.apply_material(context, slot_index, mat_name)
425 context.view_layer.objects.active = o
427 return True
430 class ARCHIPACK_PT_material(Panel):
431 bl_idname = "ARCHIPACK_PT_material"
432 bl_label = "Archipack Material"
433 bl_space_type = 'VIEW_3D'
434 bl_region_type = 'UI'
435 bl_category = 'Archipack'
437 @classmethod
438 def poll(cls, context):
439 return context.active_object is not None and 'archipack_material' in context.active_object
441 def draw(self, context):
442 layout = self.layout
443 props = context.active_object.archipack_material[0]
444 row = layout.row(align=True)
445 row.prop(props, 'material', text="")
446 row.operator('archipack.material_add', icon="ADD", text="")
447 row.operator('archipack.material_remove', icon="REMOVE", text="")
450 class ARCHIPACK_OT_material(Operator):
451 bl_idname = "archipack.material"
452 bl_label = "Material"
453 bl_description = "Add archipack material"
454 bl_options = {'REGISTER', 'UNDO'}
456 category : StringProperty(
457 name="Category",
458 description="Archipack object name",
459 default=""
461 material : StringProperty(
462 name="Material",
463 description="Material Set name",
464 default=""
467 @classmethod
468 def poll(cls, context):
469 return context.active_object is not None
471 def execute(self, context):
473 o = context.active_object
475 if 'archipack_material' in o:
476 m = o.archipack_material[0]
477 else:
478 m = o.archipack_material.add()
480 m.category = self.category
481 try:
482 m.material = self.material
483 res = m.update(context)
484 except:
485 res = False
486 pass
488 if not res:
489 print("Archipack: unable to add material {} for {}".format(self.material, self.category))
490 # self.report({'WARNING'}, 'Material {} for {} not found'.format(self.material, self.category))
492 return {'FINISHED'}
495 class ARCHIPACK_OT_material_add(Operator):
496 bl_idname = "archipack.material_add"
497 bl_label = "Material"
498 bl_description = "Add a set of archipack material"
499 bl_options = {'REGISTER', 'UNDO'}
501 material : StringProperty(
502 name="Material",
503 description="Material Set name",
504 default=""
507 @classmethod
508 def poll(cls, context):
509 return context.active_object is not None
511 def invoke(self, context, event):
512 return context.window_manager.invoke_props_dialog(self)
514 def execute(self, context):
516 global setman
518 if setman is None:
519 setman = MaterialSetManager()
521 setman.add(context, self.material)
523 return {'FINISHED'}
526 class ARCHIPACK_OT_material_remove(Operator):
527 bl_idname = "archipack.material_remove"
528 bl_label = "Material"
529 bl_description = "Remove a set of archipack material"
530 bl_options = {'REGISTER', 'UNDO'}
532 @classmethod
533 def poll(cls, context):
534 return context.active_object is not None
536 def execute(self, context):
538 global setman
540 if setman is None:
541 setman = MaterialSetManager()
543 setman.remove(context)
545 return {'FINISHED'}
548 class ARCHIPACK_OT_material_library(Operator):
549 bl_idname = "archipack.material_library"
550 bl_label = "Material Library"
551 bl_description = "Add all archipack materials on a single object"
552 bl_options = {'REGISTER', 'UNDO'}
554 @classmethod
555 def poll(cls, context):
556 return context.active_object is not None
558 def execute(self, context):
560 global setman
562 if setman is None:
563 setman = MaterialSetManager()
565 o = context.active_object
567 if 'archipack_material' in o:
568 m = o.archipack_material[0]
569 else:
570 m = o.archipack_material.add()
571 o.data.materials.clear()
573 for category in setman.objects.keys():
574 prefix = category.capitalize() + "_"
575 for part in setman.objects[category]["DEFAULT"]:
576 name = prefix + part
577 mat = m.get_material(name)
578 o.data.materials.append(mat)
580 return {'FINISHED'}
583 def register():
584 bpy.utils.register_class(archipack_material)
585 Object.archipack_material = CollectionProperty(type=archipack_material)
586 bpy.utils.register_class(ARCHIPACK_OT_material)
587 bpy.utils.register_class(ARCHIPACK_OT_material_add)
588 bpy.utils.register_class(ARCHIPACK_OT_material_remove)
589 bpy.utils.register_class(ARCHIPACK_OT_material_library)
590 bpy.utils.register_class(ARCHIPACK_PT_material)
593 def unregister():
594 global libman
595 global setman
596 if libman is not None:
597 libman.cleanup()
598 if setman is not None:
599 setman.cleanup()
600 bpy.utils.unregister_class(ARCHIPACK_PT_material)
601 bpy.utils.unregister_class(ARCHIPACK_OT_material)
602 bpy.utils.unregister_class(ARCHIPACK_OT_material_add)
603 bpy.utils.unregister_class(ARCHIPACK_OT_material_remove)
604 bpy.utils.unregister_class(ARCHIPACK_OT_material_library)
605 del Object.archipack_material
606 bpy.utils.unregister_class(archipack_material)