|
|
import bpy, bmesh, json, sys, os, math |
|
|
|
|
|
|
|
|
argv = sys.argv |
|
|
argv = argv[argv.index("--")+1:] if "--" in argv else [] |
|
|
args = { argv[i].lstrip("-"): argv[i+1] for i in range(0, len(argv), 2) } |
|
|
spec_path = args.get("spec"); out_dir = args.get("out", os.getcwd()) |
|
|
|
|
|
with open(spec_path, "r") as f: |
|
|
spec = json.load(f) |
|
|
|
|
|
name = spec.get("name", "Book") |
|
|
sx, sy, sz = (spec["size_m"]["x"], spec["size_m"]["y"], spec["size_m"]["z"]) |
|
|
bevel = float(spec.get("bevel_m", 0.002)) |
|
|
corner_caps = bool(spec.get("corner_caps", True)) |
|
|
strap = spec.get("strap", {"enabled": False}) |
|
|
|
|
|
|
|
|
bpy.ops.wm.read_factory_settings(use_empty=True) |
|
|
|
|
|
|
|
|
mesh = bpy.data.meshes.new("book_lp") |
|
|
obj = bpy.data.objects.new(name, mesh) |
|
|
bpy.context.scene.collection.objects.link(obj) |
|
|
bm = bmesh.new() |
|
|
bmesh.ops.create_cube(bm, size=1.0) |
|
|
bm.to_mesh(mesh); bm.free() |
|
|
|
|
|
|
|
|
obj.scale = (sx/2, sy/2, sz/2) |
|
|
bpy.context.view_layer.objects.active = obj |
|
|
bpy.ops.object.transform_apply(location=False, rotation=False, scale=True) |
|
|
|
|
|
|
|
|
bev = obj.modifiers.new("Bevel","BEVEL") |
|
|
bev.width = bevel |
|
|
bev.segments = 2 |
|
|
bev.limit_method = 'ANGLE' |
|
|
bev.angle_limit = math.radians(60) |
|
|
|
|
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT') |
|
|
bpy.ops.mesh.loopcut_slide(MESH_OT_loopcut={"number_cuts":1}, TRANSFORM_OT_edge_slide={"value":0.0}) |
|
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
|
|
|
|
|
if strap.get("enabled", False): |
|
|
w = float(strap.get("width_m", 0.025)) |
|
|
bpy.ops.mesh.primitive_cube_add(size=1) |
|
|
s = bpy.context.active_object; s.name = "strap" |
|
|
s.scale = (sx*0.55, w/2, sz/2*1.05) |
|
|
s.location = (0, 0, 0) |
|
|
bool_mod = obj.modifiers.new("StrapUnion","BOOLEAN") |
|
|
bool_mod.operation = 'UNION'; bool_mod.object = s |
|
|
bpy.context.view_layer.objects.active = obj |
|
|
bpy.ops.object.modifier_apply(modifier=bool_mod.name) |
|
|
bpy.data.objects.remove(s, do_unlink=True) |
|
|
|
|
|
|
|
|
if corner_caps: |
|
|
cap_size = min(sx, sy, sz) * 0.12 |
|
|
for sxm in (-1, 1): |
|
|
for sym in (-1, 1): |
|
|
bpy.ops.mesh.primitive_cube_add(size=cap_size) |
|
|
c = bpy.context.active_object |
|
|
c.location = (sxm*(sx*0.5 - cap_size*0.35), |
|
|
sym*(sy*0.5 - cap_size*0.35), |
|
|
0) |
|
|
bool_mod = obj.modifiers.new("CapUnion","BOOLEAN") |
|
|
bool_mod.operation = 'UNION'; bool_mod.object = c |
|
|
bpy.context.view_layer.objects.active = obj |
|
|
bpy.ops.object.modifier_apply(modifier=bool_mod.name) |
|
|
bpy.data.objects.remove(c, do_unlink=True) |
|
|
|
|
|
|
|
|
bpy.context.view_layer.objects.active = obj |
|
|
bpy.ops.object.modifier_apply(modifier=bev.name) |
|
|
|
|
|
|
|
|
obj.data.use_auto_smooth = True |
|
|
bpy.ops.object.shade_smooth() |
|
|
|
|
|
|
|
|
bpy.ops.object.mode_set(mode='EDIT') |
|
|
bpy.ops.uv.smart_project(angle_limit=66, island_margin=0.02) |
|
|
bpy.ops.object.mode_set(mode='OBJECT') |
|
|
|
|
|
|
|
|
mat = bpy.data.materials.new("MAT_Book") |
|
|
mat.use_nodes = True |
|
|
nt = mat.node_tree |
|
|
bsdf = nt.nodes.get("Principled BSDF") |
|
|
base = nt.nodes.new("ShaderNodeRGB") |
|
|
leather = str(spec.get("style", {}).get("leather", "dark_brown")) |
|
|
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) |
|
|
nt.links.new(base.outputs["Color"], bsdf.inputs["Base Color"]) |
|
|
bsdf.inputs["Roughness"].default_value = 0.6 |
|
|
bsdf.inputs["Metallic"].default_value = 0.0 |
|
|
if len(obj.data.materials) == 0: |
|
|
obj.data.materials.append(mat) |
|
|
else: |
|
|
obj.data.materials[0] = mat |
|
|
|
|
|
|
|
|
os.makedirs(out_dir, exist_ok=True) |
|
|
export_path = os.path.join(out_dir, f"{name}.glb") |
|
|
bpy.ops.export_scene.gltf(filepath=export_path, use_selection=False, export_format='GLB') |
|
|
print("Exported:", export_path) |