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