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 #####
23 # ----------------------------------------------------------
24 # Author: Stephen Leger (s-leger)
26 # ----------------------------------------------------------
27 # noinspection PyUnresolvedReferences
30 # noinspection PyUnresolvedReferences
31 from bpy
.types
import (
35 from bpy
.props
import (
36 EnumProperty
, CollectionProperty
,
47 A material library .blend file
49 Apply material to objects
51 def __init__(self
, matlib_path
, name
):
54 self
.path
= os
.path
.join(matlib_path
, name
)
60 self
.materials
.clear()
62 def load_list(self
, sort
=False):
66 # print("MatLib.load_list(%s)" % (self.name))
67 self
.materials
.clear()
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
)
73 self
.materials
= list(sorted(self
.materials
))
78 return name
in self
.materials
80 def load_mat(self
, name
, link
):
82 Load a material from library
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
]
91 def get_mat(self
, name
, link
):
93 apply a material by name to active_object
95 lazy load material list on demand
96 return material or None
99 # Lazy load material names
100 if len(self
.materials
) < 1:
103 # material belongs to this libraray
107 self
.load_mat(name
, link
)
109 return bpy
.data
.materials
.get(name
)
114 class MatlibsManager():
116 Manage multiple library
123 for lib
in self
.matlibs
:
127 def get_prefs(self
, context
):
129 let raise error if any
133 # retrieve addon name from imports
134 addon_name
= __name__
.split('.')[0]
135 prefs
= context
.preferences
.addons
[addon_name
].preferences
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
):
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
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
175 prefs
= self
.get_prefs(context
)
176 self
.add_to_list(prefs
.matlib_path
)
178 print("Archipack: Unable to load default material library, please check path in addon prefs")
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
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
)
201 # nothing found, build a default mat
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
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
223 Store sets for each object type
226 # hold reference of dynamic enumerator
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',
237 return os
.path
.join(target_path
, object_type
) + '.txt'
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
):
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)
266 # create file object, and set open mode
269 with
open(filename
, 'r') as f
:
270 lines
= f
.readlines()
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())
278 print("Archipack: material preset for {} not found".format(object_type
))
281 s_keys
= material_sets
.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
]
293 s_keys
= o_dict
.keys()
295 for mat
in o_dict
[s_key
]:
296 lines
.append("{}##|##{}\n".format(s_key
, mat
))
298 f
= open(filename
, 'w')
301 print("Archipack: An error occurred while saving {}".format(filename
))
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))
335 if set_name
not in self
.objects
[object_type
].keys():
336 # print("Archipack: set {} not found".format(set_name))
338 return self
.objects
[object_type
][set_name
]
340 def make_enum(self
, object_type
, s_keys
):
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
):
361 setman
= MaterialSetManager()
362 return setman
.get_enum(self
.category
)
365 def update(self
, context
):
369 class archipack_material(PropertyGroup
):
371 category
: StringProperty(
373 description
="Archipack object name",
376 material
: EnumProperty(
378 description
="Material Set name",
383 def apply_material(self
, context
, slot_index
, name
):
387 libman
= MatlibsManager()
389 libman
.apply(context
, slot_index
, name
, link
=False)
391 def update(self
, context
):
395 setman
= MaterialSetManager()
397 o
= context
.active_object
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
):
413 mats
= setman
.get_materials(self
.category
, self
.material
)
415 if mats
is None or len(mats
) < 1:
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
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'
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
):
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(
458 description
="Archipack object name",
461 material
: StringProperty(
463 description
="Material Set name",
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]
478 m
= o
.archipack_material
.add()
480 m
.category
= self
.category
482 m
.material
= self
.material
483 res
= m
.update(context
)
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))
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(
503 description
="Material Set name",
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
):
519 setman
= MaterialSetManager()
521 setman
.add(context
, self
.material
)
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'}
533 def poll(cls
, context
):
534 return context
.active_object
is not None
536 def execute(self
, context
):
541 setman
= MaterialSetManager()
543 setman
.remove(context
)
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'}
555 def poll(cls
, context
):
556 return context
.active_object
is not None
558 def execute(self
, context
):
563 setman
= MaterialSetManager()
565 o
= context
.active_object
567 if 'archipack_material' in o
:
568 m
= o
.archipack_material
[0]
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"]:
577 mat
= m
.get_material(name
)
578 o
.data
.materials
.append(mat
)
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
)
596 if libman
is not None:
598 if setman
is not None:
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
)