mirror of
https://github.com/blender/blender-addons.git
synced 2025-07-25 16:05:20 +00:00
388 lines
12 KiB
Python
388 lines
12 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
# ----------------------------------------------------------
|
|
# Automatic generation of books
|
|
# Author: Antonio Vazquez (antonioya)
|
|
#
|
|
# ----------------------------------------------------------
|
|
# noinspection PyUnresolvedReferences
|
|
import bpy
|
|
from math import cos, sin, radians
|
|
from random import randint
|
|
from copy import copy
|
|
from colorsys import rgb_to_hsv, hsv_to_rgb
|
|
from bpy.types import Operator
|
|
from bpy.props import BoolProperty, IntProperty, FloatProperty, FloatVectorProperty
|
|
from .achm_tools import *
|
|
|
|
|
|
# ------------------------------------------------------------------
|
|
# Define UI class
|
|
# Books
|
|
# ------------------------------------------------------------------
|
|
class ARCHIMESH_OT_Books(Operator):
|
|
bl_idname = "mesh.archimesh_books"
|
|
bl_label = "Books"
|
|
bl_description = "Books Generator"
|
|
bl_category = 'View'
|
|
bl_options = {'REGISTER', 'UNDO'}
|
|
|
|
width: FloatProperty(
|
|
name='Width', min=0.001, max=1, default=0.045, precision=3,
|
|
description='Bounding book width',
|
|
)
|
|
depth: FloatProperty(
|
|
name='Depth', min=0.001, max=1, default=0.22, precision=3,
|
|
description='Bounding book depth',
|
|
)
|
|
height: FloatProperty(
|
|
name='Height', min=0.001, max=1, default=0.30, precision=3,
|
|
description='Bounding book height',
|
|
)
|
|
num: IntProperty(
|
|
name='Number of books', min=1, max=100, default=5,
|
|
description='Number total of books',
|
|
)
|
|
|
|
rX: FloatProperty(
|
|
name='X', min=0.000, max=0.999, default=0, precision=3,
|
|
description='Randomness for X axis',
|
|
)
|
|
rY: FloatProperty(
|
|
name='Y', min=0.000, max=0.999, default=0, precision=3,
|
|
description='Randomness for Y axis',
|
|
)
|
|
rZ: FloatProperty(
|
|
name='Z', min=0.000, max=0.999, default=0, precision=3,
|
|
description='Randomness for Z axis',
|
|
)
|
|
|
|
rot: FloatProperty(
|
|
name='Rotation', min=0.000, max=1, default=0, precision=3,
|
|
description='Randomness for vertical position (0-> All straight)',
|
|
)
|
|
afn: IntProperty(
|
|
name='Affinity', min=0, max=10, default=5,
|
|
description='Number of books with same rotation angle',
|
|
)
|
|
|
|
# Materials
|
|
crt_mat: BoolProperty(
|
|
name="Create default Cycles materials",
|
|
description="Create default materials for Cycles render",
|
|
default=True,
|
|
)
|
|
objcol: FloatVectorProperty(
|
|
name="Color",
|
|
description="Color for material",
|
|
default=(1.0, 1.0, 1.0, 1.0),
|
|
min=0.1, max=1,
|
|
subtype='COLOR',
|
|
size=4,
|
|
)
|
|
rC: FloatProperty(
|
|
name='Randomness',
|
|
min=0.000, max=1, default=0, precision=3,
|
|
description='Randomness for color ',
|
|
)
|
|
|
|
# -----------------------------------------------------
|
|
# Draw (create UI interface)
|
|
# -----------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def draw(self, context):
|
|
layout = self.layout
|
|
space = bpy.context.space_data
|
|
if not space.local_view:
|
|
# Imperial units warning
|
|
if bpy.context.scene.unit_settings.system == "IMPERIAL":
|
|
row = layout.row()
|
|
row.label(text="Warning: Imperial units not supported", icon='COLOR_RED')
|
|
|
|
box = layout.box()
|
|
box.label(text="Book size")
|
|
row = box.row()
|
|
row.prop(self, 'width')
|
|
row.prop(self, 'depth')
|
|
row.prop(self, 'height')
|
|
row = box.row()
|
|
row.prop(self, 'num', slider=True)
|
|
|
|
box = layout.box()
|
|
box.label(text="Randomness")
|
|
row = box.row()
|
|
row.prop(self, 'rX', slider=True)
|
|
row.prop(self, 'rY', slider=True)
|
|
row.prop(self, 'rZ', slider=True)
|
|
row = box.row()
|
|
row.prop(self, 'rot', slider=True)
|
|
row.prop(self, 'afn', slider=True)
|
|
|
|
box = layout.box()
|
|
if not context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
|
|
box.enabled = False
|
|
box.prop(self, 'crt_mat')
|
|
if self.crt_mat:
|
|
row = box.row()
|
|
row.prop(self, 'objcol')
|
|
row = box.row()
|
|
row.prop(self, 'rC', slider=True)
|
|
else:
|
|
row = layout.row()
|
|
row.label(text="Warning: Operator does not work in local view mode", icon='ERROR')
|
|
|
|
# -----------------------------------------------------
|
|
# Execute
|
|
# -----------------------------------------------------
|
|
# noinspection PyUnusedLocal
|
|
def execute(self, context):
|
|
if bpy.context.mode == "OBJECT":
|
|
# Create shelves
|
|
create_book_mesh(self)
|
|
return {'FINISHED'}
|
|
else:
|
|
self.report({'WARNING'}, "Archimesh: Option only valid in Object mode")
|
|
return {'CANCELLED'}
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Generate mesh data
|
|
# All custom values are passed using self container (self.myvariable)
|
|
# ------------------------------------------------------------------------------
|
|
def create_book_mesh(self):
|
|
# deactivate others
|
|
for o in bpy.data.objects:
|
|
if o.select_get() is True:
|
|
o.select_set(False)
|
|
bpy.ops.object.select_all(action='DESELECT')
|
|
generate_books(self)
|
|
|
|
return
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Generate books
|
|
# All custom values are passed using self container (self.myvariable)
|
|
# ------------------------------------------------------------------------------
|
|
def generate_books(self):
|
|
boxes = []
|
|
location = bpy.context.scene.cursor.location
|
|
myloc = copy(location) # copy location to keep 3D cursor position
|
|
|
|
# Create
|
|
lastx = myloc.x
|
|
ox = 0
|
|
oy = 0
|
|
oz = 0
|
|
ot = 0
|
|
i = 0
|
|
for x in range(self.num):
|
|
# reset rotation
|
|
if i >= self.afn:
|
|
i = 0
|
|
ot = -1
|
|
|
|
mydata = create_book("Book" + str(x),
|
|
self.width, self.depth, self.height,
|
|
lastx, myloc.y, myloc.z,
|
|
self.crt_mat if bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'} else False,
|
|
self.rX, self.rY, self.rZ, self.rot, ox, oy, oz, ot,
|
|
self.objcol, self.rC)
|
|
boxes.extend([mydata[0]])
|
|
bookdata = mydata[1]
|
|
|
|
# calculate rotation using previous book
|
|
ot = bookdata[3]
|
|
i += 1
|
|
oz = 0
|
|
|
|
# calculate x size after rotation
|
|
if i < self.afn:
|
|
size = 0.0002
|
|
else:
|
|
size = 0.0003 + cos(radians(90 - bookdata[3])) * bookdata[2] # the height is the radius
|
|
oz = bookdata[2]
|
|
|
|
lastx = lastx + bookdata[0] + size
|
|
|
|
# refine units
|
|
for box in boxes:
|
|
remove_doubles(box)
|
|
set_normals(box)
|
|
|
|
# deactivate others
|
|
for o in bpy.data.objects:
|
|
if o.select_get() is True:
|
|
o.select_set(False)
|
|
|
|
boxes[0].select_set(True)
|
|
bpy.context.view_layer.objects.active = boxes[0]
|
|
|
|
return
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Create books unit
|
|
#
|
|
# objName: Name for the new object
|
|
# thickness: wood thickness (sides)
|
|
# sX: Size in X axis
|
|
# sY: Size in Y axis
|
|
# sZ: Size in Z axis
|
|
# pX: position X axis
|
|
# pY: position Y axis
|
|
# pZ: position Z axis
|
|
# mat: Flag for creating materials
|
|
# frX: Random factor X
|
|
# frY: Random factor Y
|
|
# frZ: Random factor Z
|
|
# frR: Random factor Rotation
|
|
# oX: override x size
|
|
# oY: override y size
|
|
# oZ: override z size
|
|
# oR: override rotation
|
|
# objcol: color
|
|
# frC: color randomness factor
|
|
# ------------------------------------------------------------------------------
|
|
def create_book(objname, sx, sy, sz, px, py, pz, mat, frx,
|
|
fry, frz, frr, ox, oy, oz, ot, objcol, frc):
|
|
# gap Randomness
|
|
ri = randint(10, 150)
|
|
gap = ri / 100000
|
|
# Randomness X
|
|
if ox == 0:
|
|
ri = randint(0, int(frx * 1000))
|
|
factor = ri / 1000
|
|
sx -= sx * factor
|
|
if sx < (gap * 3):
|
|
sx = gap * 3
|
|
else:
|
|
sx = ox
|
|
|
|
# Randomness Y
|
|
if oy == 0:
|
|
ri = randint(0, int(fry * 1000))
|
|
factor = ri / 1000
|
|
sy -= sy * factor
|
|
if sy < (gap * 3):
|
|
sy = gap * 3
|
|
else:
|
|
sy = oy
|
|
|
|
# Randomness Z
|
|
if oz == 0:
|
|
ri = randint(0, int(frz * 1000))
|
|
factor = ri / 1000
|
|
sz -= sz * factor
|
|
if sz < (gap * 3):
|
|
sz = gap * 3
|
|
else:
|
|
sz = oz
|
|
|
|
# Randomness rotation
|
|
rot = 0
|
|
if frr > 0 and ot != -1:
|
|
if ot == 0:
|
|
ri = randint(0, int(frr * 1000))
|
|
factor = ri / 1000
|
|
rot = 30 * factor
|
|
else:
|
|
rot = ot
|
|
|
|
# Randomness color (only hue)
|
|
hsv = rgb_to_hsv(objcol[0], objcol[1], objcol[2])
|
|
hue = hsv[0]
|
|
if frc > 0:
|
|
rc1 = randint(0, int(hue * 1000)) # 0 to hue
|
|
rc2 = randint(int(hue * 1000), 1000) # hue to maximum
|
|
rc3 = randint(0, 1000) # sign
|
|
|
|
if rc3 >= hue * 1000:
|
|
hue += (rc2 * frc) / 1000
|
|
else:
|
|
hue -= (rc1 * frc) / 1000
|
|
# Convert random color
|
|
objcol = hsv_to_rgb(hue, hsv[1], hsv[2])
|
|
|
|
myvertex = []
|
|
myfaces = []
|
|
x = 0
|
|
# Left side
|
|
myvertex.extend([(x, -sy, 0), (0, 0, 0), (x, 0, sz), (x, -sy, sz)])
|
|
myfaces.extend([(0, 1, 2, 3)])
|
|
|
|
myvertex.extend([(x + gap, -sy + gap, 0), (x + gap, 0, 0), (x + gap, 0, sz),
|
|
(x + gap, -sy + gap, sz)])
|
|
myfaces.extend([(4, 5, 6, 7)])
|
|
|
|
# Right side
|
|
x = sx - gap
|
|
myvertex.extend([(x, -sy + gap, 0), (x, 0, 0), (x, 0, sz), (x, -sy + gap, sz)])
|
|
myfaces.extend([(8, 9, 10, 11)])
|
|
|
|
myvertex.extend([(x + gap, -sy, 0), (x + gap, 0, 0), (x + gap, 0, sz), (x + gap, -sy, sz)])
|
|
myfaces.extend([(12, 13, 14, 15)])
|
|
|
|
myfaces.extend(
|
|
[(0, 12, 15, 3), (4, 8, 11, 7), (3, 15, 11, 7), (0, 12, 8, 4), (0, 1, 5, 4),
|
|
(8, 9, 13, 12), (3, 2, 6, 7),
|
|
(11, 10, 14, 15), (1, 2, 6, 5), (9, 10, 14, 13)])
|
|
|
|
# Top inside
|
|
myvertex.extend([(gap, -sy + gap, sz - gap), (gap, -gap, sz - gap), (sx - gap, -gap, sz - gap),
|
|
(sx - gap, -sy + gap, sz - gap)])
|
|
myfaces.extend([(16, 17, 18, 19)])
|
|
|
|
# bottom inside and front face
|
|
myvertex.extend([(gap, -sy + gap, gap), (gap, -gap, gap), (sx - gap, -gap, gap), (sx - gap, -sy + gap, gap)])
|
|
myfaces.extend([(20, 21, 22, 23), (17, 18, 22, 21)])
|
|
|
|
mymesh = bpy.data.meshes.new(objname)
|
|
mybook = bpy.data.objects.new(objname, mymesh)
|
|
|
|
mybook.location[0] = px
|
|
mybook.location[1] = py
|
|
mybook.location[2] = pz + sin(radians(rot)) * sx
|
|
bpy.context.collection.objects.link(mybook)
|
|
|
|
mymesh.from_pydata(myvertex, [], myfaces)
|
|
mymesh.update(calc_edges=True)
|
|
|
|
# ---------------------------------
|
|
# Materials and UV Maps
|
|
# ---------------------------------
|
|
if mat and bpy.context.scene.render.engine in {'CYCLES', 'BLENDER_EEVEE'}:
|
|
rgb = objcol
|
|
# External
|
|
mat = create_diffuse_material(objname + "_material", True,
|
|
rgb[0], rgb[1], rgb[2], rgb[0], rgb[1], rgb[2], 0.05)
|
|
set_material(mybook, mat)
|
|
# UV unwrap external
|
|
select_faces(mybook, 0, True)
|
|
select_faces(mybook, 3, False)
|
|
select_faces(mybook, 4, False)
|
|
unwrap_mesh(mybook, False)
|
|
# Add Internal
|
|
mat = create_diffuse_material(objname + "_side_material", True, 0.5, 0.5, 0.5, 0.5, 0.5, 0.3, 0.03)
|
|
mybook.data.materials.append(mat)
|
|
select_faces(mybook, 14, True)
|
|
select_faces(mybook, 15, False)
|
|
select_faces(mybook, 16, False)
|
|
set_material_faces(mybook, 1)
|
|
# UV unwrap
|
|
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
|
bpy.ops.mesh.select_all(action='DESELECT')
|
|
bpy.ops.object.mode_set(mode='OBJECT')
|
|
select_faces(mybook, 14, True)
|
|
select_faces(mybook, 15, False)
|
|
select_faces(mybook, 16, False)
|
|
unwrap_mesh(mybook, False)
|
|
|
|
# ---------------------------------
|
|
# Rotation on Y axis
|
|
# ---------------------------------
|
|
mybook.rotation_euler = (0.0, radians(rot), 0.0) # radians
|
|
|
|
# add some gap to the size between books
|
|
return mybook, (sx, sy, sz, rot)
|