Fix #105009: AnimAll: Error when inserting key on string attribute
[blender-addons.git] / archimesh / achm_books_maker.py
blob9199fbdf4f731cfcaa6586d575f129677611ab81
1 # SPDX-FileCopyrightText: 2016-2022 Blender Foundation
3 # SPDX-License-Identifier: GPL-2.0-or-later
5 # ----------------------------------------------------------
6 # Automatic generation of books
7 # Author: Antonio Vazquez (antonioya)
9 # ----------------------------------------------------------
10 # noinspection PyUnresolvedReferences
11 import bpy
12 from math import cos, sin, radians
13 from random import randint
14 from copy import copy
15 from colorsys import rgb_to_hsv, hsv_to_rgb
16 from bpy.types import Operator
17 from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty
18 from .achm_tools import *
21 # ------------------------------------------------------------------
22 # Define UI class
23 # Books
24 # ------------------------------------------------------------------
25 class ARCHIMESH_OT_Books(Operator):
26 bl_idname = "mesh.archimesh_books"
27 bl_label = "Books"
28 bl_description = "Books Generator"
29 bl_category = 'View'
30 bl_options = {'REGISTER', 'UNDO'}
32 width: FloatProperty(
33 name='Width', min=0.001, max=1, default=0.045, precision=3,
34 description='Bounding book width',
36 depth: FloatProperty(
37 name='Depth', min=0.001, max=1, default=0.22, precision=3,
38 description='Bounding book depth',
40 height: FloatProperty(
41 name='Height', min=0.001, max=1, default=0.30, precision=3,
42 description='Bounding book height',
44 num: IntProperty(
45 name='Number of books', min=1, max=100, default=5,
46 description='Number total of books',
49 rX: FloatProperty(
50 name='X', min=0.000, max=0.999, default=0, precision=3,
51 description='Randomness for X axis',
53 rY: FloatProperty(
54 name='Y', min=0.000, max=0.999, default=0, precision=3,
55 description='Randomness for Y axis',
57 rZ: FloatProperty(
58 name='Z', min=0.000, max=0.999, default=0, precision=3,
59 description='Randomness for Z axis',
62 rot: FloatProperty(
63 name='Rotation', min=0.000, max=1, default=0, precision=3,
64 description='Randomness for vertical position (0-> All straight)',
66 afn: IntProperty(
67 name='Affinity', min=0, max=10, default=5,
68 description='Number of books with same rotation angle',
71 # Materials
72 crt_mat: BoolProperty(
73 name="Create default Cycles materials",
74 description="Create default materials for Cycles render",
75 default=True,
77 objcol: FloatVectorProperty(
78 name="Color",
79 description="Color for material",
80 default=(1.0, 1.0, 1.0, 1.0),
81 min=0.1, max=1,
82 subtype='COLOR',
83 size=4,
85 rC: FloatProperty(
86 name='Randomness',
87 min=0.000, max=1, default=0, precision=3,
88 description='Randomness for color ',
91 # -----------------------------------------------------
92 # Draw (create UI interface)
93 # -----------------------------------------------------
94 # noinspection PyUnusedLocal
95 def draw(self, context):
96 layout = self.layout
97 space = bpy.context.space_data
98 if not space.local_view:
99 # Imperial units warning
100 if bpy.context.scene.unit_settings.system == "IMPERIAL":
101 row = layout.row()
102 row.label(text="Warning: Imperial units not supported", icon='COLOR_RED')
104 box = layout.box()
105 box.label(text="Book size")
106 row = box.row()
107 row.prop(self, 'width')
108 row.prop(self, 'depth')
109 row.prop(self, 'height')
110 row = box.row()
111 row.prop(self, 'num', slider=True)
113 box = layout.box()
114 box.label(text="Randomness")
115 row = box.row()
116 row.prop(self, 'rX', slider=True)
117 row.prop(self, 'rY', slider=True)
118 row.prop(self, 'rZ', slider=True)
119 row = box.row()
120 row.prop(self, 'rot', slider=True)
121 row.prop(self, 'afn', slider=True)
123 box = layout.box()
124 if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
125 box.enabled = False
126 box.prop(self, 'crt_mat')
127 if self.crt_mat:
128 row = box.row()
129 row.prop(self, 'objcol')
130 row = box.row()
131 row.prop(self, 'rC', slider=True)
132 else:
133 row = layout.row()
134 row.label(text="Warning: Operator does not work in local view mode", icon='ERROR')
136 # -----------------------------------------------------
137 # Execute
138 # -----------------------------------------------------
139 # noinspection PyUnusedLocal
140 def execute(self, context):
141 if bpy.context.mode == "OBJECT":
142 # Create shelves
143 create_book_mesh(self)
144 return {'FINISHED'}
145 else:
146 self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
147 return {'CANCELLED'}
150 # ------------------------------------------------------------------------------
151 # Generate mesh data
152 # All custom values are passed using self container (self.myvariable)
153 # ------------------------------------------------------------------------------
154 def create_book_mesh(self):
155 # deactivate others
156 for o in bpy.data.objects:
157 if o.select_get() is True:
158 o.select_set(False)
159 bpy.ops.object.select_all(action='DESELECT')
160 generate_books(self)
162 return
165 # ------------------------------------------------------------------------------
166 # Generate books
167 # All custom values are passed using self container (self.myvariable)
168 # ------------------------------------------------------------------------------
169 def generate_books(self):
170 boxes = []
171 location = bpy.context.scene.cursor.location
172 myloc = copy(location) # copy location to keep 3D cursor position
174 # Create
175 lastx = myloc.x
176 ox = 0
177 oy = 0
178 oz = 0
179 ot = 0
180 i = 0
181 for x in range(self.num):
182 # reset rotation
183 if i >= self.afn:
184 i = 0
185 ot = -1
187 mydata = create_book("Book" + str(x),
188 self.width, self.depth, self.height,
189 lastx, myloc.y, myloc.z,
190 self.crt_mat if bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'} else False,
191 self.rX, self.rY, self.rZ, self.rot, ox, oy, oz, ot,
192 self.objcol, self.rC)
193 boxes.extend([mydata[0]])
194 bookdata = mydata[1]
196 # calculate rotation using previous book
197 ot = bookdata[3]
198 i += 1
199 oz = 0
201 # calculate x size after rotation
202 if i < self.afn:
203 size = 0.0002
204 else:
205 size = 0.0003 + cos(radians(90 - bookdata[3])) * bookdata[2] # the height is the radius
206 oz = bookdata[2]
208 lastx = lastx + bookdata[0] + size
210 # refine units
211 for box in boxes:
212 remove_doubles(box)
213 set_normals(box)
215 # deactivate others
216 for o in bpy.data.objects:
217 if o.select_get() is True:
218 o.select_set(False)
220 boxes[0].select_set(True)
221 bpy.context.view_layer.objects.active = boxes[0]
223 return
226 # ------------------------------------------------------------------------------
227 # Create books unit
229 # objName: Name for the new object
230 # thickness: wood thickness (sides)
231 # sX: Size in X axis
232 # sY: Size in Y axis
233 # sZ: Size in Z axis
234 # pX: position X axis
235 # pY: position Y axis
236 # pZ: position Z axis
237 # mat: Flag for creating materials
238 # frX: Random factor X
239 # frY: Random factor Y
240 # frZ: Random factor Z
241 # frR: Random factor Rotation
242 # oX: override x size
243 # oY: override y size
244 # oZ: override z size
245 # oR: override rotation
246 # objcol: color
247 # frC: color randomness factor
248 # ------------------------------------------------------------------------------
249 def create_book(objname, sx, sy, sz, px, py, pz, mat, frx,
250 fry, frz, frr, ox, oy, oz, ot, objcol, frc):
251 # gap Randomness
252 ri = randint(10, 150)
253 gap = ri / 100000
254 # Randomness X
255 if ox == 0:
256 ri = randint(0, int(frx * 1000))
257 factor = ri / 1000
258 sx -= sx * factor
259 if sx < (gap * 3):
260 sx = gap * 3
261 else:
262 sx = ox
264 # Randomness Y
265 if oy == 0:
266 ri = randint(0, int(fry * 1000))
267 factor = ri / 1000
268 sy -= sy * factor
269 if sy < (gap * 3):
270 sy = gap * 3
271 else:
272 sy = oy
274 # Randomness Z
275 if oz == 0:
276 ri = randint(0, int(frz * 1000))
277 factor = ri / 1000
278 sz -= sz * factor
279 if sz < (gap * 3):
280 sz = gap * 3
281 else:
282 sz = oz
284 # Randomness rotation
285 rot = 0
286 if frr > 0 and ot != -1:
287 if ot == 0:
288 ri = randint(0, int(frr * 1000))
289 factor = ri / 1000
290 rot = 30 * factor
291 else:
292 rot = ot
294 # Randomness color (only hue)
295 hsv = rgb_to_hsv(objcol[0], objcol[1], objcol[2])
296 hue = hsv[0]
297 if frc > 0:
298 rc1 = randint(0, int(hue * 1000)) # 0 to hue
299 rc2 = randint(int(hue * 1000), 1000) # hue to maximum
300 rc3 = randint(0, 1000) # sign
302 if rc3 >= hue * 1000:
303 hue += (rc2 * frc) / 1000
304 else:
305 hue -= (rc1 * frc) / 1000
306 # Convert random color
307 objcol = hsv_to_rgb(hue, hsv[1], hsv[2])
309 myvertex = []
310 myfaces = []
311 x = 0
312 # Left side
313 myvertex.extend([(x, -sy, 0), (0, 0, 0), (x, 0, sz), (x, -sy, sz)])
314 myfaces.extend([(0, 1, 2, 3)])
316 myvertex.extend([(x + gap, -sy + gap, 0), (x + gap, 0, 0), (x + gap, 0, sz),
317 (x + gap, -sy + gap, sz)])
318 myfaces.extend([(4, 5, 6, 7)])
320 # Right side
321 x = sx - gap
322 myvertex.extend([(x, -sy + gap, 0), (x, 0, 0), (x, 0, sz), (x, -sy + gap, sz)])
323 myfaces.extend([(8, 9, 10, 11)])
325 myvertex.extend([(x + gap, -sy, 0), (x + gap, 0, 0), (x + gap, 0, sz), (x + gap, -sy, sz)])
326 myfaces.extend([(12, 13, 14, 15)])
328 myfaces.extend(
329 [(0, 12, 15, 3), (4, 8, 11, 7), (3, 15, 11, 7), (0, 12, 8, 4), (0, 1, 5, 4),
330 (8, 9, 13, 12), (3, 2, 6, 7),
331 (11, 10, 14, 15), (1, 2, 6, 5), (9, 10, 14, 13)])
333 # Top inside
334 myvertex.extend([(gap, -sy + gap, sz - gap), (gap, -gap, sz - gap), (sx - gap, -gap, sz - gap),
335 (sx - gap, -sy + gap, sz - gap)])
336 myfaces.extend([(16, 17, 18, 19)])
338 # bottom inside and front face
339 myvertex.extend([(gap, -sy + gap, gap), (gap, -gap, gap), (sx - gap, -gap, gap), (sx - gap, -sy + gap, gap)])
340 myfaces.extend([(20, 21, 22, 23), (17, 18, 22, 21)])
342 mymesh = bpy.data.meshes.new(objname)
343 mybook = bpy.data.objects.new(objname, mymesh)
345 mybook.location[0] = px
346 mybook.location[1] = py
347 mybook.location[2] = pz + sin(radians(rot)) * sx
348 bpy.context.collection.objects.link(mybook)
350 mymesh.from_pydata(myvertex, [], myfaces)
351 mymesh.update(calc_edges=True)
353 # ---------------------------------
354 # Materials and UV Maps
355 # ---------------------------------
356 if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
357 rgb = objcol
358 # External
359 mat = create_diffuse_material(objname + "_material", True,
360 rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2], 0.05)
361 set_material(mybook, mat)
362 # UV unwrap external
363 select_faces(mybook, 0, True)
364 select_faces(mybook, 3, False)
365 select_faces(mybook, 4, False)
366 unwrap_mesh(mybook, False)
367 # Add Internal
368 mat = create_diffuse_material(objname + "_side_material", True, 0.5, 0.5, 0.5, 0.5, 0.5, 0.3, 0.03)
369 mybook.data.materials.append(mat)
370 select_faces(mybook, 14, True)
371 select_faces(mybook, 15, False)
372 select_faces(mybook, 16, False)
373 set_material_faces(mybook, 1)
374 # UV unwrap
375 bpy.ops.object.mode_set(mode='EDIT', toggle=False)
376 bpy.ops.mesh.select_all(action='DESELECT')
377 bpy.ops.object.mode_set(mode='OBJECT')
378 select_faces(mybook, 14, True)
379 select_faces(mybook, 15, False)
380 select_faces(mybook, 16, False)
381 unwrap_mesh(mybook, False)
383 # ---------------------------------
384 # Rotation on Y axis
385 # ---------------------------------
386 mybook.rotation_euler = (0.0, radians(rot), 0.0) # radians
388 # add some gap to the size between books
389 return mybook, (sx, sy, sz, rot)