# Tattoo viewer: QuatSkin Lena with the chiefs-mark v3 right-arm sleeve baked in. # Saves verification shots to shots-lena/, then hands over to manual control: # left-drag orbits the camera around the model, scroll wheel zooms in/out. extends Node3D const GLB_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/lena_clean_uv.glb" const BAKED_PATH := "C:/Users/CAN/tinqs-ltd/tattoo-test/baked_lena_body.png" const SHOT_DIR := "C:/Users/CAN/tinqs-ltd/tattoo-test/shots-lena/" const TAKE_SHOTS := true var model: Node3D var skel: Skeleton3D var cam: Camera3D var interactive := false var dragging := false var orbit_yaw := 0.0 var orbit_pitch := 0.08 var orbit_dist := 3.4 var orbit_target := Vector3(0, 1.0, 0) func _ready() -> void: DirAccess.make_dir_recursive_absolute(SHOT_DIR) var env := Environment.new() env.background_mode = Environment.BG_COLOR env.background_color = Color(0.12, 0.13, 0.16) env.ambient_light_source = Environment.AMBIENT_SOURCE_COLOR env.ambient_light_color = Color(0.75, 0.78, 0.85) env.ambient_light_energy = 0.7 env.tonemap_mode = Environment.TONE_MAPPER_FILMIC var we := WorldEnvironment.new() we.environment = env add_child(we) var sun := DirectionalLight3D.new() sun.rotation_degrees = Vector3(-40, -35, 0) sun.light_energy = 1.5 add_child(sun) var fill := DirectionalLight3D.new() fill.rotation_degrees = Vector3(-15, 140, 0) fill.light_energy = 0.6 add_child(fill) var under := DirectionalLight3D.new() under.rotation_degrees = Vector3(35, 0, 0) under.light_energy = 0.35 add_child(under) var doc := GLTFDocument.new() var state := GLTFState.new() if doc.append_from_file(GLB_PATH, state) != OK: push_error("GLB load failed") return model = doc.generate_scene(state) add_child(model) var baked := ImageTexture.create_from_image(Image.load_from_file(BAKED_PATH)) var stack: Array = [model] while not stack.is_empty(): var n: Node = stack.pop_back() if n is Skeleton3D and skel == null: skel = n if n is MeshInstance3D and String(n.name).to_lower().contains("lena"): var mat := StandardMaterial3D.new() mat.roughness = 0.85 mat.albedo_texture = baked mat.cull_mode = BaseMaterial3D.CULL_BACK for s in n.mesh.get_surface_count(): n.set_surface_override_material(s, mat) for c in n.get_children(): stack.push_back(c) cam = Camera3D.new() cam.fov = 50 add_child(cam) cam.current = true if TAKE_SHOTS: await _take_shots() print("SHOTS_DONE") model.rotation.y = 0.0 interactive = true _update_cam() func _unhandled_input(event: InputEvent) -> void: if not interactive: return if event is InputEventMouseButton: if event.button_index == MOUSE_BUTTON_LEFT: dragging = event.pressed elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_UP: orbit_dist = clampf(orbit_dist * 0.90, 0.35, 8.0) _update_cam() elif event.pressed and event.button_index == MOUSE_BUTTON_WHEEL_DOWN: orbit_dist = clampf(orbit_dist / 0.90, 0.35, 8.0) _update_cam() elif event is InputEventMouseMotion and dragging: orbit_yaw -= event.relative.x * 0.008 orbit_pitch = clampf(orbit_pitch + event.relative.y * 0.008, -1.3, 1.3) _update_cam() func _update_cam() -> void: var dir := Vector3( sin(orbit_yaw) * cos(orbit_pitch), sin(orbit_pitch), cos(orbit_yaw) * cos(orbit_pitch)) cam.look_at_from_position(orbit_target + dir * orbit_dist, orbit_target) func _bone_point(bone: String) -> Vector3: return (skel.global_transform * skel.get_bone_global_rest(skel.find_bone(bone)).origin) func _limb_mid(a: String, b: String) -> Vector3: return (_bone_point(a) + _bone_point(b)) * 0.5 func _snap(shot_name: String) -> void: await get_tree().process_frame await get_tree().process_frame await RenderingServer.frame_post_draw var img := get_viewport().get_texture().get_image() img.save_png(SHOT_DIR + shot_name + ".png") print("shot saved: ", SHOT_DIR + shot_name + ".png") func _take_shots() -> void: # full body front / back model.rotation.y = 0.0 cam.look_at_from_position(Vector3(0, 1.1, 3.4), Vector3(0, 1.0, 0)) await _snap("01_full_front") model.rotation.y = PI await get_tree().process_frame await _snap("02_full_back") # right arm: front, outer side, back model.rotation.y = 0.0 await get_tree().process_frame var mid_r := _limb_mid("upperarm_r", "hand_r") cam.look_at_from_position(mid_r + Vector3(0, 0.12, 1.0), mid_r) await _snap("03_arm_r_front") cam.look_at_from_position(mid_r + Vector3(-1.0, 0.12, 0.15), mid_r) await _snap("04_arm_r_outer") model.rotation.y = PI await get_tree().process_frame var mid_r_back := _limb_mid("upperarm_r", "hand_r") mid_r_back.x = -mid_r_back.x mid_r_back.z = -mid_r_back.z cam.look_at_from_position(mid_r_back + Vector3(0, 0.12, 1.0), mid_r_back) await _snap("05_arm_r_back") # inner (underside) of the arm — where the elbow bend is concave model.rotation.y = 0.0 await get_tree().process_frame cam.look_at_from_position(mid_r + Vector3(0, -0.85, 0.5), mid_r) await _snap("06_arm_r_inner") # torso closeups cam.look_at_from_position(Vector3(0, 1.25, 0.9), Vector3(0, 1.25, 0)) await _snap("07_chest") model.rotation.y = PI await get_tree().process_frame cam.look_at_from_position(Vector3(0, 1.2, 0.9), Vector3(0, 1.2, 0)) await _snap("08_back") # legs front / outer / back model.rotation.y = 0.0 await get_tree().process_frame var leg_r := _limb_mid("thigh_r", "foot_r") cam.look_at_from_position(leg_r + Vector3(0, 0.05, 1.1), leg_r) await _snap("09_leg_r_front") cam.look_at_from_position(leg_r + Vector3(-1.0, 0.05, 0.3), leg_r) await _snap("10_leg_r_outer") model.rotation.y = PI await get_tree().process_frame var leg_b := Vector3(-leg_r.x, leg_r.y, -leg_r.z) cam.look_at_from_position(leg_b + Vector3(0, 0.05, 1.1), leg_b) await _snap("11_legs_back") # problem-area closeups: armpit, under-chin, cheek/ear model.rotation.y = 0.0 await get_tree().process_frame var pit_r := _bone_point("upperarm_r") + Vector3(-0.02, -0.05, 0) cam.look_at_from_position(pit_r + Vector3(-0.12, -0.28, 0.42), pit_r) await _snap("12_armpit_r") var head_p := _bone_point("Head") var chin := head_p + Vector3(0, -0.05, 0.06) cam.look_at_from_position(chin + Vector3(0, -0.22, 0.40), chin) await _snap("13_under_chin") var ear_r := head_p + Vector3(-0.06, 0.04, 0) cam.look_at_from_position(ear_r + Vector3(-0.38, 0.02, 0.16), ear_r) await _snap("14_ear_cheek_r") model.rotation.y = 0.0