Percy3822 commited on
Commit
5cd3e6f
·
verified ·
1 Parent(s): cc5532b

Create generate_book.py

Browse files
Files changed (1) hide show
  1. generate_book.py +108 -0
generate_book.py ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import bpy, bmesh, json, sys, os, math
2
+
3
+ # -------- parse args: blender -b -P generate_book.py -- --spec spec.json --out out_dir
4
+ argv = sys.argv
5
+ argv = argv[argv.index("--")+1:] if "--" in argv else []
6
+ args = { argv[i].lstrip("-"): argv[i+1] for i in range(0, len(argv), 2) }
7
+ spec_path = args.get("spec"); out_dir = args.get("out", os.getcwd())
8
+
9
+ with open(spec_path, "r") as f:
10
+ spec = json.load(f)
11
+
12
+ name = spec.get("name","Book")
13
+ sx, sy, sz = (spec["size_m"]["x"], spec["size_m"]["y"], spec["size_m"]["z"])
14
+ bevel = spec.get("bevel_m", 0.002)
15
+ corner_caps = spec.get("corner_caps", True)
16
+ strap = spec.get("strap", {"enabled": False})
17
+
18
+ # -------- clean scene
19
+ bpy.ops.wm.read_factory_settings(use_empty=True)
20
+
21
+ # -------- build low poly book as a cube
22
+ mesh = bpy.data.meshes.new("book_lp")
23
+ obj = bpy.data.objects.new(name, mesh)
24
+ bpy.context.scene.collection.objects.link(obj)
25
+ bm = bmesh.new()
26
+ bmesh.ops.create_cube(bm, size=1.0) # 2m cube
27
+ bm.to_mesh(mesh); bm.free()
28
+
29
+ # scale to meters (cube size=2 → scale by half of desired dims)
30
+ obj.scale = (sx/2, sy/2, sz/2)
31
+ bpy.context.view_layer.objects.active = obj
32
+ bpy.ops.object.transform_apply(location=False, rotation=False, scale=True)
33
+
34
+ # bevel modifier for soft edges
35
+ bev = obj.modifiers.new("Bevel","BEVEL")
36
+ bev.width = bevel
37
+ bev.segments = 2
38
+ bev.limit_method = 'ANGLE'
39
+ bev.angle_limit = math.radians(60)
40
+
41
+ # simple spine loopcut (visual separation only; minimalistic)
42
+ bpy.ops.object.mode_set(mode='EDIT')
43
+ bpy.ops.mesh.loopcut_slide(MESH_OT_loopcut={"number_cuts":1}, TRANSFORM_OT_edge_slide={"value":0.0})
44
+ bpy.ops.object.mode_set(mode='OBJECT')
45
+
46
+ # optional strap
47
+ if strap.get("enabled", False):
48
+ w = strap.get("width_m", 0.025)
49
+ bpy.ops.mesh.primitive_cube_add(size=1)
50
+ s = bpy.context.active_object; s.name = "strap"
51
+ s.scale = (sx*0.55, w/2, sz/2*1.05)
52
+ s.location = (0, 0, 0)
53
+ bool_mod = obj.modifiers.new("StrapUnion","BOOLEAN")
54
+ bool_mod.operation = 'UNION'; bool_mod.object = s
55
+ bpy.context.view_layer.objects.active = obj
56
+ bpy.ops.object.modifier_apply(modifier=bool_mod.name)
57
+ bpy.data.objects.remove(s, do_unlink=True)
58
+
59
+ # corner caps (simple unions)
60
+ if corner_caps:
61
+ cap_size = min(sx, sy, sz) * 0.12
62
+ for sxm in (-1, 1):
63
+ for sym in (-1, 1):
64
+ bpy.ops.mesh.primitive_cube_add(size=cap_size)
65
+ c = bpy.context.active_object
66
+ c.location = (sxm*(sx*0.5 - cap_size*0.35),
67
+ sym*(sy*0.5 - cap_size*0.35),
68
+ 0)
69
+ bool_mod = obj.modifiers.new("CapUnion","BOOLEAN")
70
+ bool_mod.operation = 'UNION'; bool_mod.object = c
71
+ bpy.context.view_layer.objects.active = obj
72
+ bpy.ops.object.modifier_apply(modifier=bool_mod.name)
73
+ bpy.data.objects.remove(c, do_unlink=True)
74
+
75
+ # apply bevel last
76
+ bpy.context.view_layer.objects.active = obj
77
+ bpy.ops.object.modifier_apply(modifier=bev.name)
78
+
79
+ # shading + autosmooth
80
+ obj.data.use_auto_smooth = True
81
+ bpy.ops.object.shade_smooth()
82
+
83
+ # basic UV unwrap (smart project to keep it simple for now)
84
+ bpy.ops.object.mode_set(mode='EDIT')
85
+ bpy.ops.uv.smart_project(angle_limit=66, island_margin=0.02)
86
+ bpy.ops.object.mode_set(mode='OBJECT')
87
+
88
+ # simple PBR material (leather)
89
+ mat = bpy.data.materials.new("MAT_Book")
90
+ mat.use_nodes = True
91
+ nt = mat.node_tree
92
+ bsdf = nt.nodes.get("Principled BSDF")
93
+ base = nt.nodes.new("ShaderNodeRGB")
94
+ leather = spec.get("style", {}).get("leather", "dark_brown")
95
+ base.outputs[0].default_value = (0.12,0.06,0.03,1.0) if "brown" in leather else (0.2,0.2,0.2,1.0)
96
+ nt.links.new(base.outputs["Color"], bsdf.inputs["Base Color"])
97
+ bsdf.inputs["Roughness"].default_value = 0.6
98
+ bsdf.inputs["Metallic"].default_value = 0.0
99
+ if len(obj.data.materials) == 0:
100
+ obj.data.materials.append(mat)
101
+ else:
102
+ obj.data.materials[0] = mat
103
+
104
+ # export glTF
105
+ os.makedirs(out_dir, exist_ok=True)
106
+ export_path = os.path.join(out_dir, f"{name}.glb")
107
+ bpy.ops.export_scene.gltf(filepath=export_path, use_selection=False, export_format='GLB')
108
+ print("Exported:", export_path)