initial migration, not vetted
This commit is contained in:
parent
41a32b8f68
commit
09bc6a5304
29 changed files with 122147 additions and 0 deletions
309
blender/render_glb_variants.py
Normal file
309
blender/render_glb_variants.py
Normal file
|
|
@ -0,0 +1,309 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# 20260228 ChatGPT
|
||||||
|
# $Header$
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# SCRIPT_ID: render_glb_variants.py
|
||||||
|
# SCRIPT_VERSION: 6
|
||||||
|
#
|
||||||
|
# Goal:
|
||||||
|
# Headless-safe PNG rendering for GLB models in environments where EGL/OpenGL may crash.
|
||||||
|
# Uses Cycles CPU (no Eevee/Workbench).
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# blender -b -P blender/render_glb_variants.py -- /path/to/part.glb /path/to/out_prefix
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
SCRIPT_ID = "render_glb_variants.py"
|
||||||
|
SCRIPT_VERSION = 6
|
||||||
|
|
||||||
|
def log(msg):
|
||||||
|
print(f"{SCRIPT_ID} v{SCRIPT_VERSION}: {msg}")
|
||||||
|
|
||||||
|
def die(msg):
|
||||||
|
print(f"{SCRIPT_ID} v{SCRIPT_VERSION} ERROR: {msg}", file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
def argv_after_double_dash():
|
||||||
|
if "--" not in sys.argv:
|
||||||
|
return []
|
||||||
|
return sys.argv[sys.argv.index("--") + 1:]
|
||||||
|
|
||||||
|
def reset_scene():
|
||||||
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
|
scene = bpy.context.scene
|
||||||
|
|
||||||
|
scene.render.engine = 'CYCLES'
|
||||||
|
# Force CPU
|
||||||
|
scene.cycles.device = 'CPU'
|
||||||
|
# Fast-ish defaults for documentation renders
|
||||||
|
scene.cycles.samples = 32
|
||||||
|
scene.cycles.preview_samples = 16
|
||||||
|
scene.cycles.use_denoising = True
|
||||||
|
|
||||||
|
scene.render.resolution_x = 1200
|
||||||
|
scene.render.resolution_y = 1200
|
||||||
|
scene.render.image_settings.file_format = 'PNG'
|
||||||
|
scene.render.film_transparent = False
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
|
||||||
|
return scene
|
||||||
|
|
||||||
|
def set_world_background_white(scene, strength=1.0):
|
||||||
|
if scene.world is None:
|
||||||
|
scene.world = bpy.data.worlds.new("World")
|
||||||
|
world = scene.world
|
||||||
|
world.use_nodes = True
|
||||||
|
nt = world.node_tree
|
||||||
|
nt.nodes.clear()
|
||||||
|
out = nt.nodes.new("ShaderNodeOutputWorld")
|
||||||
|
bg = nt.nodes.new("ShaderNodeBackground")
|
||||||
|
bg.inputs["Color"].default_value = (1.0, 1.0, 1.0, 1.0)
|
||||||
|
bg.inputs["Strength"].default_value = strength
|
||||||
|
nt.links.new(bg.outputs["Background"], out.inputs["Surface"])
|
||||||
|
|
||||||
|
def import_glb(path):
|
||||||
|
before = set(bpy.data.objects)
|
||||||
|
bpy.ops.import_scene.gltf(filepath=path)
|
||||||
|
after = set(bpy.data.objects)
|
||||||
|
new_objs = [o for o in (after - before)]
|
||||||
|
meshes = [o for o in new_objs if o.type == 'MESH']
|
||||||
|
if not meshes:
|
||||||
|
meshes = [o for o in bpy.context.scene.objects if o.type == 'MESH']
|
||||||
|
if not meshes:
|
||||||
|
die(f"Failed to import GLB (no mesh objects found): {path}")
|
||||||
|
|
||||||
|
if len(meshes) > 1:
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
for o in meshes:
|
||||||
|
o.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = meshes[0]
|
||||||
|
bpy.ops.object.join()
|
||||||
|
obj = bpy.context.view_layer.objects.active
|
||||||
|
else:
|
||||||
|
obj = meshes[0]
|
||||||
|
|
||||||
|
obj.name = "PART"
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def center_origin_and_location(obj):
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
|
||||||
|
obj.location = (0, 0, 0)
|
||||||
|
|
||||||
|
def bounds_world(obj):
|
||||||
|
mw = obj.matrix_world
|
||||||
|
corners = [mw @ Vector(c) for c in obj.bound_box]
|
||||||
|
xs = [c.x for c in corners]; ys = [c.y for c in corners]; zs = [c.z for c in corners]
|
||||||
|
min_v = Vector((min(xs), min(ys), min(zs)))
|
||||||
|
max_v = Vector((max(xs), max(ys), max(zs)))
|
||||||
|
size = max_v - min_v
|
||||||
|
center = (min_v + max_v) / 2.0
|
||||||
|
return center, size
|
||||||
|
|
||||||
|
def ensure_camera(scene):
|
||||||
|
cam_data = bpy.data.cameras.new("OrthoCam")
|
||||||
|
cam_data.type = 'ORTHO'
|
||||||
|
cam = bpy.data.objects.new("OrthoCam", cam_data)
|
||||||
|
scene.collection.objects.link(cam)
|
||||||
|
scene.camera = cam
|
||||||
|
return cam
|
||||||
|
|
||||||
|
def ensure_light_rig(scene):
|
||||||
|
# Cycles likes real lights
|
||||||
|
def add_area(name, loc, power):
|
||||||
|
ld = bpy.data.lights.new(name, type='AREA')
|
||||||
|
ld.energy = power
|
||||||
|
lo = bpy.data.objects.new(name, ld)
|
||||||
|
lo.location = loc
|
||||||
|
scene.collection.objects.link(lo)
|
||||||
|
add_area("Key", (3, -3, 3), 800)
|
||||||
|
add_area("Fill", (-3, 3, 2), 400)
|
||||||
|
add_area("Top", (0, 0, 4), 300)
|
||||||
|
|
||||||
|
def set_isometric_camera(cam):
|
||||||
|
cam.location = (3.0, -3.0, 3.0)
|
||||||
|
cam.rotation_euler = (math.radians(55), 0, math.radians(45))
|
||||||
|
|
||||||
|
def fit_camera_to_object(cam, obj, margin=1.20):
|
||||||
|
_, size = bounds_world(obj)
|
||||||
|
max_dim = max(size.x, size.y, size.z)
|
||||||
|
cam.data.ortho_scale = max_dim * margin
|
||||||
|
|
||||||
|
def set_input_if_exists(node, socket_name, value):
|
||||||
|
if socket_name in node.inputs:
|
||||||
|
node.inputs[socket_name].default_value = value
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def make_material_clay(name="Clay", color=(0.83, 0.83, 0.83, 1.0)):
|
||||||
|
mat = bpy.data.materials.new(name)
|
||||||
|
mat.use_nodes = True
|
||||||
|
nt = mat.node_tree
|
||||||
|
nt.nodes.clear()
|
||||||
|
|
||||||
|
out = nt.nodes.new("ShaderNodeOutputMaterial")
|
||||||
|
bsdf = nt.nodes.new("ShaderNodeBsdfPrincipled")
|
||||||
|
|
||||||
|
set_input_if_exists(bsdf, "Base Color", color)
|
||||||
|
set_input_if_exists(bsdf, "Roughness", 1.0)
|
||||||
|
if not set_input_if_exists(bsdf, "Specular", 0.0):
|
||||||
|
set_input_if_exists(bsdf, "Specular IOR Level", 0.0)
|
||||||
|
|
||||||
|
nt.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
|
||||||
|
return mat
|
||||||
|
|
||||||
|
def make_material_black_emission(name="WireBlack"):
|
||||||
|
mat = bpy.data.materials.new(name)
|
||||||
|
mat.use_nodes = True
|
||||||
|
nt = mat.node_tree
|
||||||
|
nt.nodes.clear()
|
||||||
|
|
||||||
|
out = nt.nodes.new("ShaderNodeOutputMaterial")
|
||||||
|
em = nt.nodes.new("ShaderNodeEmission")
|
||||||
|
em.inputs["Color"].default_value = (0, 0, 0, 1)
|
||||||
|
em.inputs["Strength"].default_value = 1.0
|
||||||
|
nt.links.new(em.outputs["Emission"], out.inputs["Surface"])
|
||||||
|
return mat
|
||||||
|
|
||||||
|
def make_material_feature_ao(name="FeatureAO"):
|
||||||
|
"""
|
||||||
|
Cavity-like look using Ambient Occlusion node when available.
|
||||||
|
Falls back to normal-vector shading if AO node isn't available.
|
||||||
|
"""
|
||||||
|
mat = bpy.data.materials.new(name)
|
||||||
|
mat.use_nodes = True
|
||||||
|
nt = mat.node_tree
|
||||||
|
nt.nodes.clear()
|
||||||
|
|
||||||
|
out = nt.nodes.new("ShaderNodeOutputMaterial")
|
||||||
|
bsdf = nt.nodes.new("ShaderNodeBsdfPrincipled")
|
||||||
|
set_input_if_exists(bsdf, "Roughness", 1.0)
|
||||||
|
if not set_input_if_exists(bsdf, "Specular", 0.0):
|
||||||
|
set_input_if_exists(bsdf, "Specular IOR Level", 0.0)
|
||||||
|
|
||||||
|
# Try AO node
|
||||||
|
try:
|
||||||
|
ao = nt.nodes.new("ShaderNodeAmbientOcclusion")
|
||||||
|
ao.inputs["Distance"].default_value = 0.2
|
||||||
|
ramp = nt.nodes.new("ShaderNodeValToRGB")
|
||||||
|
# Slight contrast boost
|
||||||
|
ramp.color_ramp.elements[0].position = 0.35
|
||||||
|
ramp.color_ramp.elements[1].position = 0.85
|
||||||
|
nt.links.new(ao.outputs["AO"], ramp.inputs["Fac"])
|
||||||
|
nt.links.new(ramp.outputs["Color"], bsdf.inputs["Base Color"])
|
||||||
|
except Exception:
|
||||||
|
# Fallback: normals shading
|
||||||
|
geo = nt.nodes.new("ShaderNodeNewGeometry")
|
||||||
|
add = nt.nodes.new("ShaderNodeVectorMath"); add.operation = 'ADD'
|
||||||
|
add.inputs[1].default_value = (1.0, 1.0, 1.0)
|
||||||
|
mul = nt.nodes.new("ShaderNodeVectorMath"); mul.operation = 'MULTIPLY'
|
||||||
|
mul.inputs[1].default_value = (0.5, 0.5, 0.5)
|
||||||
|
nt.links.new(geo.outputs["Normal"], add.inputs[0])
|
||||||
|
nt.links.new(add.outputs["Vector"], mul.inputs[0])
|
||||||
|
nt.links.new(mul.outputs["Vector"], bsdf.inputs["Base Color"])
|
||||||
|
|
||||||
|
nt.links.new(bsdf.outputs["BSDF"], out.inputs["Surface"])
|
||||||
|
return mat
|
||||||
|
|
||||||
|
def assign_single_material(obj, mat):
|
||||||
|
obj.data.materials.clear()
|
||||||
|
obj.data.materials.append(mat)
|
||||||
|
|
||||||
|
def duplicate_object(obj, name):
|
||||||
|
dup = obj.copy()
|
||||||
|
dup.data = obj.data.copy()
|
||||||
|
dup.name = name
|
||||||
|
bpy.context.scene.collection.objects.link(dup)
|
||||||
|
return dup
|
||||||
|
|
||||||
|
def delete_object(obj):
|
||||||
|
bpy.ops.object.select_all(action='DESELECT')
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.delete()
|
||||||
|
|
||||||
|
def enable_freestyle(scene, thickness=1.0):
|
||||||
|
scene.render.use_freestyle = True
|
||||||
|
vl = scene.view_layers[0]
|
||||||
|
fs = vl.freestyle_settings
|
||||||
|
if not fs.linesets:
|
||||||
|
fs.linesets.new("LineSet")
|
||||||
|
ls = fs.linesets[0]
|
||||||
|
try:
|
||||||
|
ls.linestyle.thickness = thickness
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def render(scene, outpath):
|
||||||
|
scene.render.filepath = outpath
|
||||||
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
log("starting")
|
||||||
|
args = argv_after_double_dash()
|
||||||
|
if len(args) != 2:
|
||||||
|
die("Expected: <input.glb> <out_prefix>")
|
||||||
|
|
||||||
|
in_glb, out_prefix = args
|
||||||
|
scene = reset_scene()
|
||||||
|
set_world_background_white(scene)
|
||||||
|
|
||||||
|
obj = import_glb(in_glb)
|
||||||
|
center_origin_and_location(obj)
|
||||||
|
|
||||||
|
ensure_light_rig(scene)
|
||||||
|
cam = ensure_camera(scene)
|
||||||
|
set_isometric_camera(cam)
|
||||||
|
fit_camera_to_object(cam, obj, margin=1.20)
|
||||||
|
|
||||||
|
clay = make_material_clay()
|
||||||
|
wire_black = make_material_black_emission()
|
||||||
|
feature = make_material_feature_ao()
|
||||||
|
|
||||||
|
# 1) SOLID
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
assign_single_material(obj, clay)
|
||||||
|
render(scene, f"{out_prefix}_solid.png")
|
||||||
|
|
||||||
|
# 2) SOLID + EDGES
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
assign_single_material(obj, clay)
|
||||||
|
enable_freestyle(scene, thickness=1.0)
|
||||||
|
render(scene, f"{out_prefix}_solid_edges.png")
|
||||||
|
|
||||||
|
# 3) PURE WIRE
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
wire_obj = duplicate_object(obj, "PART_WIRE")
|
||||||
|
obj.hide_render = True
|
||||||
|
wire_obj.hide_render = False
|
||||||
|
assign_single_material(wire_obj, wire_black)
|
||||||
|
mod = wire_obj.modifiers.new("Wireframe", type='WIREFRAME')
|
||||||
|
mod.thickness = 0.0015
|
||||||
|
mod.use_replace = True
|
||||||
|
mod.use_boundary = True
|
||||||
|
render(scene, f"{out_prefix}_wire.png")
|
||||||
|
obj.hide_render = False
|
||||||
|
delete_object(wire_obj)
|
||||||
|
|
||||||
|
# 4) FEATURE POP (AO/cavity-ish)
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
assign_single_material(obj, feature)
|
||||||
|
render(scene, f"{out_prefix}_feature.png")
|
||||||
|
|
||||||
|
# 5) FREESTYLE ONLY
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
white = make_material_clay(name="WhiteClay", color=(1.0, 1.0, 1.0, 1.0))
|
||||||
|
assign_single_material(obj, white)
|
||||||
|
enable_freestyle(scene, thickness=1.2)
|
||||||
|
render(scene, f"{out_prefix}_freestyle.png")
|
||||||
|
|
||||||
|
log("done")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
222
blender/render_stl_variants.py
Normal file
222
blender/render_stl_variants.py
Normal file
|
|
@ -0,0 +1,222 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
# 20260228 ChatGPT
|
||||||
|
# $Header$
|
||||||
|
# $Id$
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# blender -b -P render_stl_variants.py -- /path/to/part.stl /path/to/out_prefix
|
||||||
|
#
|
||||||
|
# Output:
|
||||||
|
# <out_prefix>_solid.png
|
||||||
|
# <out_prefix>_solid_edges.png
|
||||||
|
# <out_prefix>_wire.png
|
||||||
|
# <out_prefix>_matcap.png
|
||||||
|
# <out_prefix>_freestyle.png
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# - Uses orthographic camera + fit-to-bounds framing (consistent prints).
|
||||||
|
# - Workbench engine for fast technical-looking renders.
|
||||||
|
# - Freestyle enabled only for the freestyle variant.
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
import sys
|
||||||
|
import math
|
||||||
|
from mathutils import Vector
|
||||||
|
|
||||||
|
def die(msg):
|
||||||
|
print(msg, file=sys.stderr)
|
||||||
|
raise SystemExit(1)
|
||||||
|
|
||||||
|
def argv_after_double_dash():
|
||||||
|
if "--" not in sys.argv:
|
||||||
|
return []
|
||||||
|
return sys.argv[sys.argv.index("--") + 1:]
|
||||||
|
|
||||||
|
def reset_scene():
|
||||||
|
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||||
|
scene = bpy.context.scene
|
||||||
|
scene.unit_settings.system = 'NONE'
|
||||||
|
scene.render.resolution_x = 1200
|
||||||
|
scene.render.resolution_y = 1200
|
||||||
|
scene.render.film_transparent = False
|
||||||
|
scene.render.image_settings.file_format = 'PNG'
|
||||||
|
return scene
|
||||||
|
|
||||||
|
def import_stl(path):
|
||||||
|
bpy.ops.import_mesh.stl(filepath=path)
|
||||||
|
objs = [o for o in bpy.context.selected_objects if o.type == 'MESH']
|
||||||
|
if not objs:
|
||||||
|
die(f"Failed to import STL: {path}")
|
||||||
|
# join into one object for consistent bounds
|
||||||
|
bpy.context.view_layer.objects.active = objs[0]
|
||||||
|
for o in objs:
|
||||||
|
o.select_set(True)
|
||||||
|
bpy.ops.object.join()
|
||||||
|
obj = bpy.context.view_layer.objects.active
|
||||||
|
obj.name = "PART"
|
||||||
|
return obj
|
||||||
|
|
||||||
|
def center_origin_and_location(obj):
|
||||||
|
bpy.context.view_layer.objects.active = obj
|
||||||
|
obj.select_set(True)
|
||||||
|
bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
|
||||||
|
obj.location = (0, 0, 0)
|
||||||
|
|
||||||
|
def bounds_world(obj):
|
||||||
|
# bounding box corners in world coordinates
|
||||||
|
mw = obj.matrix_world
|
||||||
|
corners = [mw @ Vector(c) for c in obj.bound_box]
|
||||||
|
xs = [c.x for c in corners]; ys = [c.y for c in corners]; zs = [c.z for c in corners]
|
||||||
|
min_v = Vector((min(xs), min(ys), min(zs)))
|
||||||
|
max_v = Vector((max(xs), max(ys), max(zs)))
|
||||||
|
size = max_v - min_v
|
||||||
|
center = (min_v + max_v) / 2.0
|
||||||
|
return center, size
|
||||||
|
|
||||||
|
def ensure_camera(scene):
|
||||||
|
cam_data = bpy.data.cameras.new("OrthoCam")
|
||||||
|
cam_data.type = 'ORTHO'
|
||||||
|
cam = bpy.data.objects.new("OrthoCam", cam_data)
|
||||||
|
scene.collection.objects.link(cam)
|
||||||
|
scene.camera = cam
|
||||||
|
return cam
|
||||||
|
|
||||||
|
def ensure_light_rig(scene):
|
||||||
|
# For workbench it barely matters, but keep a light for non-workbench fallback
|
||||||
|
ld = bpy.data.lights.new("Key", type='AREA')
|
||||||
|
lo = bpy.data.objects.new("Key", ld)
|
||||||
|
lo.location = (3, -3, 3)
|
||||||
|
scene.collection.objects.link(lo)
|
||||||
|
|
||||||
|
def set_isometric_camera(cam, target=(0,0,0), ortho_scale=2.0):
|
||||||
|
cam.data.ortho_scale = ortho_scale
|
||||||
|
cam.location = (3.0, -3.0, 3.0)
|
||||||
|
cam.rotation_euler = (math.radians(55), 0, math.radians(45))
|
||||||
|
|
||||||
|
def fit_camera_to_object(cam, obj, margin=1.15):
|
||||||
|
center, size = bounds_world(obj)
|
||||||
|
max_dim = max(size.x, size.y, size.z)
|
||||||
|
# ortho_scale roughly maps to view width; margin gives padding
|
||||||
|
cam.data.ortho_scale = max_dim * margin
|
||||||
|
|
||||||
|
def set_world_background(scene, gray=1.0):
|
||||||
|
# Simple white/gray background via World color
|
||||||
|
if scene.world is None:
|
||||||
|
scene.world = bpy.data.worlds.new("World")
|
||||||
|
scene.world.use_nodes = False
|
||||||
|
scene.world.color = (gray, gray, gray)
|
||||||
|
|
||||||
|
def workbench_common(scene):
|
||||||
|
scene.render.engine = 'BLENDER_WORKBENCH'
|
||||||
|
scene.display.shading.light = 'STUDIO'
|
||||||
|
scene.display.shading.color_type = 'SINGLE'
|
||||||
|
scene.display.shading.single_color = (0.82, 0.82, 0.82) # light gray “clay”
|
||||||
|
scene.display.shading.show_backface_culling = False
|
||||||
|
scene.display.shading.show_xray = False
|
||||||
|
|
||||||
|
def variant_solid(scene):
|
||||||
|
workbench_common(scene)
|
||||||
|
scene.display.shading.show_wireframes = False
|
||||||
|
|
||||||
|
def variant_wire(scene):
|
||||||
|
workbench_common(scene)
|
||||||
|
scene.display.shading.show_wireframes = True
|
||||||
|
scene.display.shading.wireframe_color_type = 'SINGLE'
|
||||||
|
scene.display.shading.wireframe_color = (0.0, 0.0, 0.0)
|
||||||
|
scene.display.shading.wireframe_opacity = 1.0
|
||||||
|
scene.display.shading.wireframe_thickness = 1
|
||||||
|
|
||||||
|
def variant_solid_edges(scene):
|
||||||
|
# Workbench wire overlay on top of solid
|
||||||
|
workbench_common(scene)
|
||||||
|
scene.display.shading.show_wireframes = True
|
||||||
|
scene.display.shading.wireframe_color_type = 'SINGLE'
|
||||||
|
scene.display.shading.wireframe_color = (0.0, 0.0, 0.0)
|
||||||
|
scene.display.shading.wireframe_opacity = 1.0
|
||||||
|
scene.display.shading.wireframe_thickness = 1
|
||||||
|
|
||||||
|
def variant_matcap(scene):
|
||||||
|
# Workbench “Matcap-like” studio + cavity (helps valleys/holes read on paper)
|
||||||
|
workbench_common(scene)
|
||||||
|
scene.display.shading.show_wireframes = False
|
||||||
|
scene.display.shading.show_cavity = True
|
||||||
|
scene.display.shading.cavity_type = 'WORLD'
|
||||||
|
scene.display.shading.cavity_ridge_factor = 1.0
|
||||||
|
scene.display.shading.cavity_valley_factor = 1.0
|
||||||
|
|
||||||
|
def variant_freestyle(scene):
|
||||||
|
# Freestyle line drawing + light solid base
|
||||||
|
# Works with Workbench in many cases, but Freestyle is historically tied to Eevee/Cycles.
|
||||||
|
# Here we switch to Eevee for reliability.
|
||||||
|
scene.render.engine = 'BLENDER_EEVEE'
|
||||||
|
scene.eevee.use_gtao = True
|
||||||
|
scene.render.film_transparent = False
|
||||||
|
|
||||||
|
# Setup simple material
|
||||||
|
mat = bpy.data.materials.new("Clay")
|
||||||
|
mat.use_nodes = True
|
||||||
|
bsdf = mat.node_tree.nodes.get("Principled BSDF")
|
||||||
|
bsdf.inputs["Base Color"].default_value = (0.85, 0.85, 0.85, 1.0)
|
||||||
|
bsdf.inputs["Roughness"].default_value = 1.0
|
||||||
|
|
||||||
|
obj = bpy.data.objects.get("PART")
|
||||||
|
if obj and obj.data.materials:
|
||||||
|
obj.data.materials[0] = mat
|
||||||
|
elif obj:
|
||||||
|
obj.data.materials.append(mat)
|
||||||
|
|
||||||
|
# Enable Freestyle
|
||||||
|
scene.render.use_freestyle = True
|
||||||
|
# Default freestyle line set is OK; keep lines black and modest thickness
|
||||||
|
if scene.view_layers:
|
||||||
|
vl = scene.view_layers[0]
|
||||||
|
vl.freestyle_settings.linesets.new("LineSet") if not vl.freestyle_settings.linesets else None
|
||||||
|
# Render Properties -> Freestyle settings are somewhat verbose to tune; default is acceptable for v1.
|
||||||
|
|
||||||
|
def render(scene, outpath):
|
||||||
|
scene.render.filepath = outpath
|
||||||
|
bpy.ops.render.render(write_still=True)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = argv_after_double_dash()
|
||||||
|
if len(args) != 2:
|
||||||
|
die("Expected: <input.stl> <out_prefix>")
|
||||||
|
|
||||||
|
in_stl, out_prefix = args
|
||||||
|
scene = reset_scene()
|
||||||
|
obj = import_stl(in_stl)
|
||||||
|
center_origin_and_location(obj)
|
||||||
|
ensure_light_rig(scene)
|
||||||
|
cam = ensure_camera(scene)
|
||||||
|
set_isometric_camera(cam)
|
||||||
|
fit_camera_to_object(cam, obj, margin=1.20)
|
||||||
|
set_world_background(scene, gray=1.0)
|
||||||
|
|
||||||
|
# SOLID
|
||||||
|
variant_solid(scene)
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
render(scene, f"{out_prefix}_solid.png")
|
||||||
|
|
||||||
|
# SOLID + EDGES
|
||||||
|
variant_solid_edges(scene)
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
render(scene, f"{out_prefix}_solid_edges.png")
|
||||||
|
|
||||||
|
# WIRE
|
||||||
|
variant_wire(scene)
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
render(scene, f"{out_prefix}_wire.png")
|
||||||
|
|
||||||
|
# MATCAP-ish (cavity)
|
||||||
|
variant_matcap(scene)
|
||||||
|
scene.render.use_freestyle = False
|
||||||
|
render(scene, f"{out_prefix}_matcap.png")
|
||||||
|
|
||||||
|
# FREESTYLE
|
||||||
|
variant_freestyle(scene)
|
||||||
|
render(scene, f"{out_prefix}_freestyle.png")
|
||||||
|
|
||||||
|
print("Done.")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
104
lab.html
Normal file
104
lab.html
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>RenderLab v1</title>
|
||||||
|
<style>
|
||||||
|
body { margin:0; background:white; }
|
||||||
|
canvas { display:block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from './threejs/three.module.js';
|
||||||
|
import { GLTFLoader } from './threejs/GLTFLoader.js';
|
||||||
|
import { OrbitControls } from './threejs/OrbitControls.js';
|
||||||
|
import GUI from './threejs/lil-gui.module.min.js';
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0xffffff);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000);
|
||||||
|
camera.position.set(3,3,3);
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias:true });
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
light.position.set(5,5,5);
|
||||||
|
scene.add(light);
|
||||||
|
|
||||||
|
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
|
||||||
|
scene.add(ambient);
|
||||||
|
|
||||||
|
let mesh, edgeLines;
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load('./glb/part.glb', gltf => {
|
||||||
|
|
||||||
|
mesh = gltf.scene.children[0];
|
||||||
|
|
||||||
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xdddddd,
|
||||||
|
roughness: 0.9,
|
||||||
|
metalness: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(mesh);
|
||||||
|
addEdges(30);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function addEdges(angle) {
|
||||||
|
if (!mesh) return;
|
||||||
|
if (edgeLines) scene.remove(edgeLines);
|
||||||
|
|
||||||
|
const edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||||
|
edgeLines = new THREE.LineSegments(
|
||||||
|
edges,
|
||||||
|
new THREE.LineBasicMaterial({ color: 0x000000 })
|
||||||
|
);
|
||||||
|
|
||||||
|
scene.add(edgeLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
wireframe: false,
|
||||||
|
edgeAngle: 30,
|
||||||
|
lightIntensity: 1.5,
|
||||||
|
savePNG: () => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = 'render.png';
|
||||||
|
link.href = renderer.domElement.toDataURL("image/png");
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const gui = new GUI();
|
||||||
|
gui.add(params, 'wireframe').onChange(v => {
|
||||||
|
if (mesh) mesh.material.wireframe = v;
|
||||||
|
});
|
||||||
|
gui.add(params, 'edgeAngle', 1, 90).onChange(v => addEdges(v));
|
||||||
|
gui.add(params, 'lightIntensity', 0, 3).onChange(v => light.intensity = v);
|
||||||
|
gui.add(params, 'savePNG');
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
camera.aspect = innerWidth/innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
176
web/batch_glb_png/batch_render.js
Normal file
176
web/batch_glb_png/batch_render.js
Normal file
|
|
@ -0,0 +1,176 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* batch_render.js
|
||||||
|
* 20260228 ChatGPT
|
||||||
|
* $Header$
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* node batch_render.js job.json
|
||||||
|
*/
|
||||||
|
|
||||||
|
import fs from 'node:fs';
|
||||||
|
import path from 'node:path';
|
||||||
|
import process from 'node:process';
|
||||||
|
import http from 'node:http';
|
||||||
|
import { pathToFileURL, fileURLToPath } from 'node:url';
|
||||||
|
import puppeteer from 'puppeteer';
|
||||||
|
|
||||||
|
function die (msg) { console.error(msg); process.exit(1); }
|
||||||
|
function ensureDir(p) { fs.mkdirSync(p, { recursive: true }); }
|
||||||
|
|
||||||
|
function listFiles(dir, suffix) {
|
||||||
|
const out = [];
|
||||||
|
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||||
|
const full = path.join(dir, ent.name);
|
||||||
|
if (ent.isDirectory()) out.push(...listFiles(full, suffix));
|
||||||
|
else if (!suffix || ent.name.toLowerCase().endsWith(suffix)) out.push(full);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
function sanitizeBasename(p) {
|
||||||
|
const b = path.basename(p);
|
||||||
|
return b.replace(/[^\w.\-]+/g, '_');
|
||||||
|
}
|
||||||
|
|
||||||
|
function mimeTypeFor(p) {
|
||||||
|
const ext = path.extname(p).toLowerCase();
|
||||||
|
if (ext === '.html') return 'text/html; charset=utf-8';
|
||||||
|
if (ext === '.js') return 'text/javascript; charset=utf-8';
|
||||||
|
if (ext === '.json') return 'application/json; charset=utf-8';
|
||||||
|
if (ext === '.glb') return 'model/gltf-binary';
|
||||||
|
if (ext === '.gltf') return 'model/gltf+json; charset=utf-8';
|
||||||
|
if (ext === '.bin') return 'application/octet-stream';
|
||||||
|
if (ext === '.png') return 'image/png';
|
||||||
|
if (ext === '.jpg' || ext === '.jpeg') return 'image/jpeg';
|
||||||
|
if (ext === '.css') return 'text/css; charset=utf-8';
|
||||||
|
return 'application/octet-stream';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serve static files from rootDir
|
||||||
|
function startStaticServer(rootDir) {
|
||||||
|
const server = http.createServer((req, res) => {
|
||||||
|
try {
|
||||||
|
const urlPath = decodeURIComponent((req.url || '/').split('?')[0]);
|
||||||
|
const safePath = path.normalize(urlPath).replace(/^(\.\.(\/|\\|$))+/, '');
|
||||||
|
const filePath = path.join(rootDir, safePath);
|
||||||
|
|
||||||
|
// Default route
|
||||||
|
const finalPath = fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()
|
||||||
|
? path.join(filePath, 'index.html')
|
||||||
|
: filePath;
|
||||||
|
|
||||||
|
if (!fs.existsSync(finalPath) || !fs.statSync(finalPath).isFile()) {
|
||||||
|
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end('404\n');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = fs.readFileSync(finalPath);
|
||||||
|
res.writeHead(200, { 'Content-Type': mimeTypeFor(finalPath) });
|
||||||
|
res.end(data);
|
||||||
|
} catch (e) {
|
||||||
|
res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' });
|
||||||
|
res.end(`500\n${String(e)}\n`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
server.listen(0, '127.0.0.1', () => {
|
||||||
|
const addr = server.address();
|
||||||
|
resolve({ server, port: addr.port });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const jobPath = process.argv[2];
|
||||||
|
if (!jobPath) die("Usage: node batch_render.js job.json");
|
||||||
|
|
||||||
|
const jobAbs = path.resolve(jobPath);
|
||||||
|
const jobDir = path.dirname(jobAbs);
|
||||||
|
const job = JSON.parse(fs.readFileSync(jobAbs, 'utf8'));
|
||||||
|
|
||||||
|
const profileAbs = path.resolve(jobDir, job.profile ?? die("job.profile missing"));
|
||||||
|
const inputDirAbs = path.resolve(jobDir, job.input_dir ?? die("job.input_dir missing"));
|
||||||
|
const outputDirAbs = path.resolve(jobDir, job.output_dir ?? die("job.output_dir missing"));
|
||||||
|
const pattern = (job.pattern ?? ".glb").toLowerCase();
|
||||||
|
|
||||||
|
ensureDir(outputDirAbs);
|
||||||
|
|
||||||
|
const glbs = listFiles(inputDirAbs, pattern);
|
||||||
|
if (glbs.length === 0) die(`No files ending with '${pattern}' found under ${inputDirAbs}`);
|
||||||
|
|
||||||
|
const renderHtmlAbs = path.resolve(jobDir, 'render.html');
|
||||||
|
if (!fs.existsSync(renderHtmlAbs)) die(`Missing render.html at ${renderHtmlAbs}`);
|
||||||
|
|
||||||
|
// Start local server rooted at jobDir so render.html, libs/, glbs/, profile json are reachable
|
||||||
|
const { server, port } = await startStaticServer(jobDir);
|
||||||
|
const base = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
|
// Load profile for viewport sizing
|
||||||
|
const profile = JSON.parse(fs.readFileSync(profileAbs, 'utf8'));
|
||||||
|
const vw = profile.output?.width ?? 1024;
|
||||||
|
const vh = profile.output?.height ?? 768;
|
||||||
|
const dpr = profile.output?.pixelRatio ?? 1;
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: 'new',
|
||||||
|
args: [
|
||||||
|
'--no-sandbox',
|
||||||
|
'--disable-dev-shm-usage',
|
||||||
|
'--disable-gpu',
|
||||||
|
'--use-gl=swiftshader',
|
||||||
|
'--enable-unsafe-swiftshader'
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await browser.newPage();
|
||||||
|
|
||||||
|
// Log page-side failures clearly
|
||||||
|
page.on('console', (msg) => console.log('PAGE console:', msg.type(), msg.text()));
|
||||||
|
page.on('pageerror', (err) => console.log('PAGE error:', err));
|
||||||
|
page.on('requestfailed', (req) => console.log('REQ failed:', req.url(), req.failure()?.errorText));
|
||||||
|
|
||||||
|
await page.setViewport({ width: vw, height: vh, deviceScaleFactor: dpr });
|
||||||
|
|
||||||
|
// Make URLs relative to server root
|
||||||
|
const renderUrl = `${base}/render.html`;
|
||||||
|
const profileUrl = `${base}/${encodeURIComponent(path.relative(jobDir, profileAbs)).replace(/%2F/g,'/')}`;
|
||||||
|
|
||||||
|
for (const glbAbs of glbs) {
|
||||||
|
const rel = path.relative(jobDir, glbAbs);
|
||||||
|
const glbUrl = `${base}/${encodeURIComponent(rel).replace(/%2F/g,'/')}`;
|
||||||
|
|
||||||
|
const outName = sanitizeBasename(glbAbs).replace(/\.glb$/i, '.png');
|
||||||
|
const outAbs = path.join(outputDirAbs, outName);
|
||||||
|
|
||||||
|
const url = `${renderUrl}?glb=${encodeURIComponent(glbUrl)}&profile=${encodeURIComponent(profileUrl)}`;
|
||||||
|
|
||||||
|
// Don’t use networkidle0 here; module imports + GLB load can keep network “busy”
|
||||||
|
await page.goto(url, { waitUntil: 'load' });
|
||||||
|
|
||||||
|
await page.waitForFunction(() => {
|
||||||
|
return window.__RENDER_STATUS__ && window.__RENDER_STATUS__.ready === true;
|
||||||
|
}, { timeout: 120000 }); // 2 minutes, GLBs can be chunky
|
||||||
|
|
||||||
|
const status = await page.evaluate(() => window.__RENDER_STATUS__);
|
||||||
|
if (!status || !status.pngDataUrl) {
|
||||||
|
throw new Error(`No pngDataUrl produced for ${glbAbs} (status=${JSON.stringify(status)})`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const b64 = status.pngDataUrl.replace(/^data:image\/png;base64,/, '');
|
||||||
|
fs.writeFileSync(outAbs, Buffer.from(b64, 'base64'));
|
||||||
|
console.log(`WROTE\t${outAbs}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
await page.close();
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((e) => { console.error("ERROR:", e); process.exit(2); });
|
||||||
6
web/batch_glb_png/job.json
Normal file
6
web/batch_glb_png/job.json
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
{
|
||||||
|
"profile": "./three_profile_20260228_111725.json",
|
||||||
|
"input_dir": "./glbs",
|
||||||
|
"output_dir": "./pngs",
|
||||||
|
"pattern": ".glb"
|
||||||
|
}
|
||||||
1
web/batch_glb_png/libs/GLTFLoader.js
Symbolic link
1
web/batch_glb_png/libs/GLTFLoader.js
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../vendor/three/addons/loaders/GLTFLoader.js
|
||||||
1
web/batch_glb_png/libs/three.module.js
Symbolic link
1
web/batch_glb_png/libs/three.module.js
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../vendor/three/build/three.module.js
|
||||||
91
web/batch_glb_png/link_glbs.sh
Executable file
91
web/batch_glb_png/link_glbs.sh
Executable file
|
|
@ -0,0 +1,91 @@
|
||||||
|
#!/bin/sh
|
||||||
|
# 20260228 ChatGPT
|
||||||
|
# $Header$
|
||||||
|
#
|
||||||
|
# Copy/paste:
|
||||||
|
# cd ~/work/Voron/renderlab/web/batch_glb_png
|
||||||
|
# chmod +x link_glbs.sh
|
||||||
|
# ./link_glbs.sh /home/jlpoole/work/Voron/test1
|
||||||
|
#
|
||||||
|
# This creates symlinks under ./glbs/ pointing to every *.glb under ROOT.
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
ROOT="${1:-}"
|
||||||
|
[ -n "$ROOT" ] || { echo "Usage: $0 /path/to/root" >&2; exit 2; }
|
||||||
|
[ -d "$ROOT" ] || { echo "ERROR: not a directory: $ROOT" >&2; exit 2; }
|
||||||
|
|
||||||
|
OUTDIR="./glbs"
|
||||||
|
mkdir -p "$OUTDIR"
|
||||||
|
|
||||||
|
ts="$(date +%Y%m%d_%H%M%S)"
|
||||||
|
log="link_glbs_${ts}.log"
|
||||||
|
|
||||||
|
# Make ROOT absolute and strip any trailing slash for consistent relpath math.
|
||||||
|
ROOT_ABS="$(cd "$ROOT" && pwd)"
|
||||||
|
ROOT_ABS="${ROOT_ABS%/}"
|
||||||
|
|
||||||
|
echo "ROOT: $ROOT_ABS" | tee "$log"
|
||||||
|
echo "OUTDIR: $(cd "$OUTDIR" && pwd)" | tee -a "$log"
|
||||||
|
echo "LOG: $log" | tee -a "$log"
|
||||||
|
echo "" | tee -a "$log"
|
||||||
|
|
||||||
|
# Sanitize: turn a path into a filesystem-safe basename.
|
||||||
|
# Example:
|
||||||
|
# Klicky-Probe/Printers/Voron/.../Dock_mount_fixed_v2.stl.glb
|
||||||
|
# becomes:
|
||||||
|
# Klicky-Probe__Printers__Voron__...__Dock_mount_fixed_v2.stl.glb
|
||||||
|
sanitize() {
|
||||||
|
# shellcheck disable=SC2001
|
||||||
|
echo "$1" \
|
||||||
|
| sed 's|^/||' \
|
||||||
|
| sed 's|/|__|g' \
|
||||||
|
| sed 's|[^A-Za-z0-9._-]|_|g'
|
||||||
|
}
|
||||||
|
|
||||||
|
count=0
|
||||||
|
skipped=0
|
||||||
|
collisions=0
|
||||||
|
|
||||||
|
# Use -print0 to handle spaces safely.
|
||||||
|
find "$ROOT_ABS" -type f -name '*.glb' -print0 \
|
||||||
|
| while IFS= read -r -d '' f; do
|
||||||
|
# Compute relative path under ROOT_ABS
|
||||||
|
rel="${f#$ROOT_ABS/}"
|
||||||
|
|
||||||
|
name="$(sanitize "$rel")"
|
||||||
|
linkpath="$OUTDIR/$name"
|
||||||
|
|
||||||
|
# If the link exists and points to the same target, skip quietly.
|
||||||
|
if [ -L "$linkpath" ]; then
|
||||||
|
target="$(readlink "$linkpath" || true)"
|
||||||
|
if [ "$target" = "$f" ]; then
|
||||||
|
echo "SKIP (already linked): $linkpath -> $f" >> "$log"
|
||||||
|
skipped=$((skipped+1))
|
||||||
|
continue
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If the name already exists but points elsewhere, disambiguate.
|
||||||
|
if [ -e "$linkpath" ]; then
|
||||||
|
i=1
|
||||||
|
base="$linkpath"
|
||||||
|
while [ -e "$linkpath" ]; do
|
||||||
|
linkpath="${base%.glb}_$i.glb"
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
collisions=$((collisions+1))
|
||||||
|
fi
|
||||||
|
|
||||||
|
ln -s "$f" "$linkpath"
|
||||||
|
echo "LINK: $linkpath -> $f" >> "$log"
|
||||||
|
count=$((count+1))
|
||||||
|
done
|
||||||
|
|
||||||
|
# The while loop runs in a subshell in many /bin/sh implementations,
|
||||||
|
# so counters above may not update. Summarize by counting log lines instead.
|
||||||
|
linked_lines="$(grep -c '^LINK: ' "$log" 2>/dev/null || echo 0)"
|
||||||
|
skipped_lines="$(grep -c '^SKIP ' "$log" 2>/dev/null || echo 0)"
|
||||||
|
|
||||||
|
echo "" | tee -a "$log"
|
||||||
|
echo "DONE. linked=$linked_lines skipped=$skipped_lines (details in $log)" | tee -a "$log"
|
||||||
135
web/batch_glb_png/link_glbs_20260228_152056.log
Normal file
135
web/batch_glb_png/link_glbs_20260228_152056.log
Normal file
|
|
@ -0,0 +1,135 @@
|
||||||
|
ROOT: /home/jlpoole/work/Voron/test1
|
||||||
|
OUTDIR: /home/jlpoole/work/Voron/renderlab/web/batch_glb_png/glbs
|
||||||
|
LOG: link_glbs_20260228_152056.log
|
||||||
|
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Dock_mount_fixed_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Dock_mount_fixed_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Dock_sidemount_fixed_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Dock_sidemount_fixed_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Dock_sidemount_left_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Dock_sidemount_left_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Dock_sidemount_right_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Dock_sidemount_right_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__KlickyProbe_AB_mount_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/KlickyProbe_AB_mount_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Mount_magnet_holder.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Mount_magnet_holder.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Mount_magnet_pressfit_helper.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Mount_magnet_pressfit_helper.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Printers__Voron__v1.8_v2.4_Legacy_Trident__v1.8_v2.4_Legacy_Trident_STL__Mount_pressfit_holder_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Printers/Voron/v1.8_v2.4_Legacy_Trident/v1.8_v2.4_Legacy_Trident_STL/Mount_pressfit_holder_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__1mm_Spacer.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/1mm_Spacer.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__KlickyProbe_v2.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/KlickyProbe_v2.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__Probe_Dock_v2.1.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/Probe_Dock_v2.1.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__Probe_magnet_holder.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/Probe_magnet_holder.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__Probe_magnet_pressfit_helper.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/Probe_magnet_pressfit_helper.stl.glb
|
||||||
|
LINK: ./glbs/Klicky-Probe__Probes__KlickyProbe__STL__Probe_pressfit_holder.stl.glb -> /home/jlpoole/work/Voron/test1/Klicky-Probe/Probes/KlickyProbe/STL/Probe_pressfit_holder.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__COB_Light_Strip__cob_light_strip_mount_100mm.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/COB Light Strip/cob_light_strip_mount_100mm.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__COB_Light_Strip__cob_light_strip_mount_150mm.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/COB Light Strip/cob_light_strip_mount_150mm.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__COB_Light_Strip__cob_light_strip_mount_50mm.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/COB Light Strip/cob_light_strip_mount_50mm.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__LDO_Door__handle_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/LDO Door/handle_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__LDO_Door__handle_b.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/LDO Door/handle_b.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__LDO_Door__handle_b_nameplate.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/LDO Door/handle_b_nameplate.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__LDO_Door__screw_hinge_x3.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/LDO Door/screw_hinge_x3.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__exhaust_cover.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/exhaust_cover.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__handlebar_spacer_x4.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/handlebar_spacer_x4.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__ldo_bestagon_insert.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/ldo_bestagon_insert.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__led_fan_pcb_spacer_x2.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/led_fan_pcb_spacer_x2.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__nozzle_probe_ldo.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/nozzle_probe_ldo.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__z_belt_cover_a_led.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/z_belt_cover_a_led.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoron2__STLs__z_rail_stop_x4.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoron2/STLs/z_rail_stop_x4.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoronTrident__STLs__BTT_Pi_TFT4.3_Mount___a__faceplate.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoronTrident/STLs/BTT Pi TFT4.3 Mount/[a]_faceplate.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoronTrident__STLs__BTT_Pi_TFT4.3_Mount__mount.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoronTrident/STLs/BTT Pi TFT4.3 Mount/mount.stl.glb
|
||||||
|
LINK: ./glbs/LDOVoronTrident__STLs__bed_wago_mount.stl.glb -> /home/jlpoole/work/Voron/test1/LDOVoronTrident/STLs/bed_wago_mount.stl.glb
|
||||||
|
LINK: ./glbs/Leviathan__STLs__Leviathan_bracket_set.stl.glb -> /home/jlpoole/work/Voron/test1/Leviathan/STLs/Leviathan_bracket_set.stl.glb
|
||||||
|
LINK: ./glbs/Nitehawk-SB__STLs__cw2_captive_pcb_cover.stl.glb -> /home/jlpoole/work/Voron/test1/Nitehawk-SB/STLs/cw2_captive_pcb_cover.stl.glb
|
||||||
|
LINK: ./glbs/Nitehawk-SB__STLs__usb_adapter_mount.stl.glb -> /home/jlpoole/work/Voron/test1/Nitehawk-SB/STLs/usb_adapter_mount.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Electronics_Bay__lrs_200_psu_bracket_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Electronics_Bay/lrs_200_psu_bracket_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Electronics_Bay__pcb_din_clip_x3.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Electronics_Bay/pcb_din_clip_x3.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Electronics_Bay__wago_221-415_mount_3by5.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Electronics_Bay/wago_221-415_mount_3by5.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Exhaust_Filter__exhaust_filter_grill.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Exhaust_Filter/exhaust_filter_grill.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units___a__cable_cover.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/[a]_cable_cover.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units___a__z_chain_retainer_bracket_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/[a]_z_chain_retainer_bracket_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units__a_drive_frame_lower.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/a_drive_frame_lower.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units__a_drive_frame_upper.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/a_drive_frame_upper.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units__b_drive_frame_lower.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/b_drive_frame_lower.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__AB_Drive_Units__b_drive_frame_upper.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/AB_Drive_Units/b_drive_frame_upper.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers___a__tensioner_left.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/[a]_tensioner_left.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers___a__tensioner_right.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/[a]_tensioner_right.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers__front_idler_left_lower.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/front_idler_left_lower.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers__front_idler_left_upper.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/front_idler_left_upper.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers__front_idler_right_lower.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/front_idler_right_lower.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Front_Idlers__front_idler_right_upper.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Front_Idlers/front_idler_right_upper.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints___a__endstop_pod_D2F_switch.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/[a]_endstop_pod_D2F_switch.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints___a__xy_joint_cable_bridge_2hole.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/[a]_xy_joint_cable_bridge_2hole.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints__xy_joint_left_lower_MGN12.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/xy_joint_left_lower_MGN12.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints__xy_joint_left_upper_MGN12.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/xy_joint_left_upper_MGN12.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints__xy_joint_right_lower_MGN12.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/xy_joint_right_lower_MGN12.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__XY_Joints__xy_joint_right_upper_MGN12.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/XY_Joints/xy_joint_right_upper_MGN12.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__X_Carriage__probe_retainer_bracket.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/X_Carriage/probe_retainer_bracket.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__X_Carriage__x_frame_V2TR_MGN12_left.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/X_Carriage/x_frame_V2TR_MGN12_left.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__X_Axis__X_Carriage__x_frame_V2TR_MGN12_right.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/X_Axis/X_Carriage/x_frame_V2TR_MGN12_right.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Z_Joints__z_joint_lower_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Z_Joints/z_joint_lower_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__Z_Joints__z_joint_upper_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/Z_Joints/z_joint_upper_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry___a__z_belt_clip_lower_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/[a]_z_belt_clip_lower_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry___a__z_belt_clip_upper_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/[a]_z_belt_clip_upper_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__z_chain_bottom_anchor.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/z_chain_bottom_anchor.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Gantry__z_chain_guide.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Gantry/z_chain_guide.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__Front_Doors__latch_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/Front_Doors/latch_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__bottom_panel_clip_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/bottom_panel_clip_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__bottom_panel_hinge_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/bottom_panel_hinge_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__corner_panel_clip_4mm_x8.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/corner_panel_clip_4mm_x8.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__corner_panel_clip_6mm_x8.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/corner_panel_clip_6mm_x8.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__deck_support_4mm_x8.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/deck_support_4mm_x8.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__midspan_panel_clip_4mm_x7.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/midspan_panel_clip_4mm_x7.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__midspan_panel_clip_6mm_x8.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/midspan_panel_clip_6mm_x8.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__z_belt_cover_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/z_belt_cover_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Panel_Mounting__z_belt_cover_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Panel_Mounting/z_belt_cover_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__300__front_skirt_a_300.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/300/front_skirt_a_300.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__300__front_skirt_b_300.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/300/front_skirt_b_300.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__300__rear_center_skirt_300.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/300/rear_center_skirt_300.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__300__side_skirt_a_300_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/300/side_skirt_a_300_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__300__side_skirt_b_300_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/300/side_skirt_b_300_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__350__front_skirt_a_350.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/350/front_skirt_a_350.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__350__front_skirt_b_350.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/350/front_skirt_b_350.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__350__rear_center_skirt_350.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/350/rear_center_skirt_350.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__350__side_skirt_a_350_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/350/side_skirt_a_350_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__350__side_skirt_b_350_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/350/side_skirt_b_350_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__belt_guard_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_belt_guard_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__belt_guard_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_belt_guard_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__fan_grill_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_fan_grill_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__fan_grill_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_fan_grill_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__fan_grill_open_optional_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_fan_grill_open_optional_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__fan_grill_retainer_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_fan_grill_retainer_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts___a__keystone_blank_insert.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/[a]_keystone_blank_insert.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__keystone_panel.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/keystone_panel.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__power_inlet_IECGS_1mm.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/power_inlet_IECGS_1mm.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Skirts__side_fan_support_x2.STL.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Skirts/side_fan_support_x2.STL.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Spool_Management__bowden_retainer.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Spool_Management/bowden_retainer.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Spool_Management__spool_holder.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Spool_Management/spool_holder.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Test_Prints__Heatset_Practice.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Test_Prints/Heatset_Practice.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Tools__MGN12_rail_guide_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Tools/MGN12_rail_guide_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Tools__MGN9_rail_guide_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Tools/MGN9_rail_guide_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Tools__pulley_jig.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Tools/pulley_jig.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive___a__belt_tensioner_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/[a]_belt_tensioner_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive___a__belt_tensioner_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/[a]_belt_tensioner_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive___a__z_drive_baseplate_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/[a]_z_drive_baseplate_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive___a__z_drive_baseplate_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/[a]_z_drive_baseplate_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_drive_main_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_drive_main_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_drive_main_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_drive_main_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_drive_retainer_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_drive_retainer_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_drive_retainer_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_drive_retainer_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_motor_mount_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_motor_mount_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Drive__z_motor_mount_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Drive/z_motor_mount_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Idlers___a__z_tensioner_9mm_x4.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Idlers/[a]_z_tensioner_9mm_x4.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Idlers__z_tensioner_bracket_a_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Idlers/z_tensioner_bracket_a_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-2__STLs__Z_Idlers__z_tensioner_bracket_b_x2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-2/STLs/Z_Idlers/z_tensioner_bracket_b_x2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive___a__guidler_a.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/[a]_guidler_a.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive___a__guidler_b.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/[a]_guidler_b.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive___a__latch.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/[a]_latch.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive___a__latch_shuttle.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/[a]_latch_shuttle.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive__main_body.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/main_body.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__Direct_Drive__motor_plate.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/Direct_Drive/motor_plate.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2___a__pcb_spacer.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/[a]_pcb_spacer.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Clockwork2__chain_anchor_2hole.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Clockwork2/chain_anchor_2hole.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner__Printheads__revo_voron__stealthburner_printhead_revo_voron_front.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/Printheads/revo_voron/stealthburner_printhead_revo_voron_front.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner__Printheads__revo_voron__stealthburner_printhead_revo_voron_rear_cw2.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/Printheads/revo_voron/stealthburner_printhead_revo_voron_rear_cw2.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner___a__stealthburner_main_body.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/[a]_stealthburner_main_body.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner___c__stealthburner_LED_diffuser.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/[c]_stealthburner_LED_diffuser.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner___o__stealthburner_LED_carrier.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/[o]_stealthburner_LED_carrier.stl.glb
|
||||||
|
LINK: ./glbs/Voron-Stealthburner__STLs__Stealthburner___o__stealthburner_LED_diffuser_mask.stl.glb -> /home/jlpoole/work/Voron/test1/Voron-Stealthburner/STLs/Stealthburner/[o]_stealthburner_LED_diffuser_mask.stl.glb
|
||||||
|
|
||||||
|
DONE. linked=128 skipped=0
|
||||||
|
0 (details in link_glbs_20260228_152056.log)
|
||||||
5
web/batch_glb_png/node_modules/.gitignore
generated
vendored
Normal file
5
web/batch_glb_png/node_modules/.gitignore
generated
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
.bin
|
||||||
|
.modules.yaml
|
||||||
|
.pnpm
|
||||||
|
.pnpm-workspace-state-v1.json
|
||||||
|
puppeteer
|
||||||
7
web/batch_glb_png/package.json
Normal file
7
web/batch_glb_png/package.json
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"name": "batch_glb_png",
|
||||||
|
"type": "module",
|
||||||
|
"dependencies": {
|
||||||
|
"puppeteer": "^24.37.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
868
web/batch_glb_png/pnpm-lock.yaml
generated
Normal file
868
web/batch_glb_png/pnpm-lock.yaml
generated
Normal file
|
|
@ -0,0 +1,868 @@
|
||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
puppeteer:
|
||||||
|
specifier: ^24.37.5
|
||||||
|
version: 24.37.5
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@babel/code-frame@7.29.0':
|
||||||
|
resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.28.5':
|
||||||
|
resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@puppeteer/browsers@2.13.0':
|
||||||
|
resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@tootallnate/quickjs-emscripten@0.23.0':
|
||||||
|
resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==}
|
||||||
|
|
||||||
|
'@types/node@25.3.2':
|
||||||
|
resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==}
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
|
||||||
|
|
||||||
|
agent-base@7.1.4:
|
||||||
|
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
ansi-regex@5.0.1:
|
||||||
|
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
ansi-styles@4.3.0:
|
||||||
|
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
argparse@2.0.1:
|
||||||
|
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
|
||||||
|
|
||||||
|
ast-types@0.13.4:
|
||||||
|
resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
b4a@1.8.0:
|
||||||
|
resolution: {integrity: sha512-qRuSmNSkGQaHwNbM7J78Wwy+ghLEYF1zNrSeMxj4Kgw6y33O3mXcQ6Ie9fRvfU/YnxWkOchPXbaLb73TkIsfdg==}
|
||||||
|
peerDependencies:
|
||||||
|
react-native-b4a: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
react-native-b4a:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-events@2.8.2:
|
||||||
|
resolution: {integrity: sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==}
|
||||||
|
peerDependencies:
|
||||||
|
bare-abort-controller: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bare-abort-controller:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-fs@4.5.5:
|
||||||
|
resolution: {integrity: sha512-XvwYM6VZqKoqDll8BmSww5luA5eflDzY0uEFfBJtFKe4PAAtxBjU3YIxzIBzhyaEQBy1VXEQBto4cpN5RZJw+w==}
|
||||||
|
engines: {bare: '>=1.16.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bare-buffer: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bare-buffer:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-os@3.7.0:
|
||||||
|
resolution: {integrity: sha512-64Rcwj8qlnTZU8Ps6JJEdSmxBEUGgI7g8l+lMtsJLl4IsfTcHMTfJ188u2iGV6P6YPRZrtv72B2kjn+hp+Yv3g==}
|
||||||
|
engines: {bare: '>=1.14.0'}
|
||||||
|
|
||||||
|
bare-path@3.0.0:
|
||||||
|
resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==}
|
||||||
|
|
||||||
|
bare-stream@2.8.0:
|
||||||
|
resolution: {integrity: sha512-reUN0M2sHRqCdG4lUK3Fw8w98eeUIZHL5c3H7Mbhk2yVBL+oofgaIp0ieLfD5QXwPCypBpmEEKU2WZKzbAk8GA==}
|
||||||
|
peerDependencies:
|
||||||
|
bare-buffer: '*'
|
||||||
|
bare-events: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bare-buffer:
|
||||||
|
optional: true
|
||||||
|
bare-events:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
bare-url@2.3.2:
|
||||||
|
resolution: {integrity: sha512-ZMq4gd9ngV5aTMa5p9+UfY0b3skwhHELaDkhEHetMdX0LRkW9kzaym4oo/Eh+Ghm0CCDuMTsRIGM/ytUc1ZYmw==}
|
||||||
|
|
||||||
|
basic-ftp@5.2.0:
|
||||||
|
resolution: {integrity: sha512-VoMINM2rqJwJgfdHq6RiUudKt2BV+FY5ZFezP/ypmwayk68+NzzAQy4XXLlqsGD4MCzq3DrmNFD/uUmBJuGoXw==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13:
|
||||||
|
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
|
||||||
|
|
||||||
|
callsites@3.1.0:
|
||||||
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
chromium-bidi@14.0.0:
|
||||||
|
resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==}
|
||||||
|
peerDependencies:
|
||||||
|
devtools-protocol: '*'
|
||||||
|
|
||||||
|
cliui@8.0.1:
|
||||||
|
resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
color-convert@2.0.1:
|
||||||
|
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
|
||||||
|
engines: {node: '>=7.0.0'}
|
||||||
|
|
||||||
|
color-name@1.1.4:
|
||||||
|
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
|
||||||
|
|
||||||
|
cosmiconfig@9.0.0:
|
||||||
|
resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.9.5'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
data-uri-to-buffer@6.0.2:
|
||||||
|
resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
degenerator@5.0.1:
|
||||||
|
resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
devtools-protocol@0.0.1566079:
|
||||||
|
resolution: {integrity: sha512-MJfAEA1UfVhSs7fbSQOG4czavUp1ajfg6prlAN0+cmfa2zNjaIbvq8VneP7do1WAQQIvgNJWSMeP6UyI90gIlQ==}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0:
|
||||||
|
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
|
||||||
|
|
||||||
|
env-paths@2.2.1:
|
||||||
|
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
error-ex@1.3.4:
|
||||||
|
resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==}
|
||||||
|
|
||||||
|
escalade@3.2.0:
|
||||||
|
resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
escodegen@2.1.0:
|
||||||
|
resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
esprima@4.0.1:
|
||||||
|
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
estraverse@5.3.0:
|
||||||
|
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
|
||||||
|
esutils@2.0.3:
|
||||||
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
events-universal@1.0.1:
|
||||||
|
resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==}
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
|
||||||
|
engines: {node: '>= 10.17.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
fast-fifo@1.3.2:
|
||||||
|
resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==}
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
|
||||||
|
|
||||||
|
get-caller-file@2.0.5:
|
||||||
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
get-uri@6.0.5:
|
||||||
|
resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
import-fresh@3.3.1:
|
||||||
|
resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
ip-address@10.1.0:
|
||||||
|
resolution: {integrity: sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==}
|
||||||
|
engines: {node: '>= 12'}
|
||||||
|
|
||||||
|
is-arrayish@0.2.1:
|
||||||
|
resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0:
|
||||||
|
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
js-tokens@4.0.0:
|
||||||
|
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||||
|
|
||||||
|
js-yaml@4.1.1:
|
||||||
|
resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
json-parse-even-better-errors@2.3.1:
|
||||||
|
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
|
||||||
|
|
||||||
|
lines-and-columns@1.2.4:
|
||||||
|
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||||
|
|
||||||
|
lru-cache@7.18.3:
|
||||||
|
resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
netmask@2.0.2:
|
||||||
|
resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==}
|
||||||
|
engines: {node: '>= 0.4.0'}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
|
||||||
|
|
||||||
|
pac-proxy-agent@7.2.0:
|
||||||
|
resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
pac-resolver@7.0.1:
|
||||||
|
resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
parent-module@1.0.1:
|
||||||
|
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
parse-json@5.2.0:
|
||||||
|
resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
pend@1.2.0:
|
||||||
|
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
progress@2.0.3:
|
||||||
|
resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
proxy-agent@6.5.0:
|
||||||
|
resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0:
|
||||||
|
resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
|
||||||
|
|
||||||
|
puppeteer-core@24.37.5:
|
||||||
|
resolution: {integrity: sha512-ybL7iE78YPN4T6J+sPLO7r0lSByp/0NN6PvfBEql219cOnttoTFzCWKiBOjstXSqi/OKpwae623DWAsL7cn2MQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
puppeteer@24.37.5:
|
||||||
|
resolution: {integrity: sha512-3PAOIQLceyEmn1Fi76GkGO2EVxztv5OtdlB1m8hMUZL3f8KDHnlvXbvCXv+Ls7KzF1R0KdKBqLuT/Hhrok12hQ==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
require-directory@2.1.1:
|
||||||
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
resolve-from@4.0.0:
|
||||||
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
|
||||||
|
semver@7.7.4:
|
||||||
|
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
smart-buffer@4.2.0:
|
||||||
|
resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
|
||||||
|
engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
|
||||||
|
|
||||||
|
socks-proxy-agent@8.0.5:
|
||||||
|
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
|
||||||
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
|
socks@2.8.7:
|
||||||
|
resolution: {integrity: sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==}
|
||||||
|
engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
|
||||||
|
|
||||||
|
source-map@0.6.1:
|
||||||
|
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
streamx@2.23.0:
|
||||||
|
resolution: {integrity: sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==}
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
tar-fs@3.1.1:
|
||||||
|
resolution: {integrity: sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==}
|
||||||
|
|
||||||
|
tar-stream@3.1.8:
|
||||||
|
resolution: {integrity: sha512-U6QpVRyCGHva435KoNWy9PRoi2IFYCgtEhq9nmrPPpbRacPs9IH4aJ3gbrFC8dPcXvdSZ4XXfXT5Fshbp2MtlQ==}
|
||||||
|
|
||||||
|
teex@1.0.1:
|
||||||
|
resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==}
|
||||||
|
|
||||||
|
text-decoder@1.2.7:
|
||||||
|
resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==}
|
||||||
|
|
||||||
|
tslib@2.8.1:
|
||||||
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
typed-query-selector@2.12.1:
|
||||||
|
resolution: {integrity: sha512-uzR+FzI8qrUEIu96oaeBJmd9E7CFEiQ3goA5qCVgc4s5llSubcfGHq9yUstZx/k4s9dXHVKsE35YWoFyvEqEHA==}
|
||||||
|
|
||||||
|
undici-types@7.18.2:
|
||||||
|
resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==}
|
||||||
|
|
||||||
|
webdriver-bidi-protocol@0.4.1:
|
||||||
|
resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==}
|
||||||
|
|
||||||
|
wrap-ansi@7.0.0:
|
||||||
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
wrappy@1.0.2:
|
||||||
|
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
|
||||||
|
|
||||||
|
ws@8.19.0:
|
||||||
|
resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==}
|
||||||
|
engines: {node: '>=10.0.0'}
|
||||||
|
peerDependencies:
|
||||||
|
bufferutil: ^4.0.1
|
||||||
|
utf-8-validate: '>=5.0.2'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
bufferutil:
|
||||||
|
optional: true
|
||||||
|
utf-8-validate:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
y18n@5.0.8:
|
||||||
|
resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
yargs-parser@21.1.1:
|
||||||
|
resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yargs@17.7.2:
|
||||||
|
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
|
||||||
|
|
||||||
|
zod@3.25.76:
|
||||||
|
resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==}
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@babel/code-frame@7.29.0':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-validator-identifier': 7.28.5
|
||||||
|
js-tokens: 4.0.0
|
||||||
|
picocolors: 1.1.1
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.28.5': {}
|
||||||
|
|
||||||
|
'@puppeteer/browsers@2.13.0':
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
extract-zip: 2.0.1
|
||||||
|
progress: 2.0.3
|
||||||
|
proxy-agent: 6.5.0
|
||||||
|
semver: 7.7.4
|
||||||
|
tar-fs: 3.1.1
|
||||||
|
yargs: 17.7.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- react-native-b4a
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
'@tootallnate/quickjs-emscripten@0.23.0': {}
|
||||||
|
|
||||||
|
'@types/node@25.3.2':
|
||||||
|
dependencies:
|
||||||
|
undici-types: 7.18.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
'@types/yauzl@2.10.3':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 25.3.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
agent-base@7.1.4: {}
|
||||||
|
|
||||||
|
ansi-regex@5.0.1: {}
|
||||||
|
|
||||||
|
ansi-styles@4.3.0:
|
||||||
|
dependencies:
|
||||||
|
color-convert: 2.0.1
|
||||||
|
|
||||||
|
argparse@2.0.1: {}
|
||||||
|
|
||||||
|
ast-types@0.13.4:
|
||||||
|
dependencies:
|
||||||
|
tslib: 2.8.1
|
||||||
|
|
||||||
|
b4a@1.8.0: {}
|
||||||
|
|
||||||
|
bare-events@2.8.2: {}
|
||||||
|
|
||||||
|
bare-fs@4.5.5:
|
||||||
|
dependencies:
|
||||||
|
bare-events: 2.8.2
|
||||||
|
bare-path: 3.0.0
|
||||||
|
bare-stream: 2.8.0(bare-events@2.8.2)
|
||||||
|
bare-url: 2.3.2
|
||||||
|
fast-fifo: 1.3.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
bare-os@3.7.0: {}
|
||||||
|
|
||||||
|
bare-path@3.0.0:
|
||||||
|
dependencies:
|
||||||
|
bare-os: 3.7.0
|
||||||
|
|
||||||
|
bare-stream@2.8.0(bare-events@2.8.2):
|
||||||
|
dependencies:
|
||||||
|
streamx: 2.23.0
|
||||||
|
teex: 1.0.1
|
||||||
|
optionalDependencies:
|
||||||
|
bare-events: 2.8.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
bare-url@2.3.2:
|
||||||
|
dependencies:
|
||||||
|
bare-path: 3.0.0
|
||||||
|
|
||||||
|
basic-ftp@5.2.0: {}
|
||||||
|
|
||||||
|
buffer-crc32@0.2.13: {}
|
||||||
|
|
||||||
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
|
chromium-bidi@14.0.0(devtools-protocol@0.0.1566079):
|
||||||
|
dependencies:
|
||||||
|
devtools-protocol: 0.0.1566079
|
||||||
|
mitt: 3.0.1
|
||||||
|
zod: 3.25.76
|
||||||
|
|
||||||
|
cliui@8.0.1:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 7.0.0
|
||||||
|
|
||||||
|
color-convert@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
color-name: 1.1.4
|
||||||
|
|
||||||
|
color-name@1.1.4: {}
|
||||||
|
|
||||||
|
cosmiconfig@9.0.0:
|
||||||
|
dependencies:
|
||||||
|
env-paths: 2.2.1
|
||||||
|
import-fresh: 3.3.1
|
||||||
|
js-yaml: 4.1.1
|
||||||
|
parse-json: 5.2.0
|
||||||
|
|
||||||
|
data-uri-to-buffer@6.0.2: {}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
degenerator@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
ast-types: 0.13.4
|
||||||
|
escodegen: 2.1.0
|
||||||
|
esprima: 4.0.1
|
||||||
|
|
||||||
|
devtools-protocol@0.0.1566079: {}
|
||||||
|
|
||||||
|
emoji-regex@8.0.0: {}
|
||||||
|
|
||||||
|
end-of-stream@1.4.5:
|
||||||
|
dependencies:
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
|
env-paths@2.2.1: {}
|
||||||
|
|
||||||
|
error-ex@1.3.4:
|
||||||
|
dependencies:
|
||||||
|
is-arrayish: 0.2.1
|
||||||
|
|
||||||
|
escalade@3.2.0: {}
|
||||||
|
|
||||||
|
escodegen@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
esprima: 4.0.1
|
||||||
|
estraverse: 5.3.0
|
||||||
|
esutils: 2.0.3
|
||||||
|
optionalDependencies:
|
||||||
|
source-map: 0.6.1
|
||||||
|
|
||||||
|
esprima@4.0.1: {}
|
||||||
|
|
||||||
|
estraverse@5.3.0: {}
|
||||||
|
|
||||||
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
|
events-universal@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
bare-events: 2.8.2
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
|
||||||
|
extract-zip@2.0.1:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
get-stream: 5.2.0
|
||||||
|
yauzl: 2.10.0
|
||||||
|
optionalDependencies:
|
||||||
|
'@types/yauzl': 2.10.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
fast-fifo@1.3.2: {}
|
||||||
|
|
||||||
|
fd-slicer@1.1.0:
|
||||||
|
dependencies:
|
||||||
|
pend: 1.2.0
|
||||||
|
|
||||||
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
|
get-stream@5.2.0:
|
||||||
|
dependencies:
|
||||||
|
pump: 3.0.4
|
||||||
|
|
||||||
|
get-uri@6.0.5:
|
||||||
|
dependencies:
|
||||||
|
basic-ftp: 5.2.0
|
||||||
|
data-uri-to-buffer: 6.0.2
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
http-proxy-agent@7.0.2:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
https-proxy-agent@7.0.6:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
import-fresh@3.3.1:
|
||||||
|
dependencies:
|
||||||
|
parent-module: 1.0.1
|
||||||
|
resolve-from: 4.0.0
|
||||||
|
|
||||||
|
ip-address@10.1.0: {}
|
||||||
|
|
||||||
|
is-arrayish@0.2.1: {}
|
||||||
|
|
||||||
|
is-fullwidth-code-point@3.0.0: {}
|
||||||
|
|
||||||
|
js-tokens@4.0.0: {}
|
||||||
|
|
||||||
|
js-yaml@4.1.1:
|
||||||
|
dependencies:
|
||||||
|
argparse: 2.0.1
|
||||||
|
|
||||||
|
json-parse-even-better-errors@2.3.1: {}
|
||||||
|
|
||||||
|
lines-and-columns@1.2.4: {}
|
||||||
|
|
||||||
|
lru-cache@7.18.3: {}
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
netmask@2.0.2: {}
|
||||||
|
|
||||||
|
once@1.4.0:
|
||||||
|
dependencies:
|
||||||
|
wrappy: 1.0.2
|
||||||
|
|
||||||
|
pac-proxy-agent@7.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@tootallnate/quickjs-emscripten': 0.23.0
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
get-uri: 6.0.5
|
||||||
|
http-proxy-agent: 7.0.2
|
||||||
|
https-proxy-agent: 7.0.6
|
||||||
|
pac-resolver: 7.0.1
|
||||||
|
socks-proxy-agent: 8.0.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
pac-resolver@7.0.1:
|
||||||
|
dependencies:
|
||||||
|
degenerator: 5.0.1
|
||||||
|
netmask: 2.0.2
|
||||||
|
|
||||||
|
parent-module@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
callsites: 3.1.0
|
||||||
|
|
||||||
|
parse-json@5.2.0:
|
||||||
|
dependencies:
|
||||||
|
'@babel/code-frame': 7.29.0
|
||||||
|
error-ex: 1.3.4
|
||||||
|
json-parse-even-better-errors: 2.3.1
|
||||||
|
lines-and-columns: 1.2.4
|
||||||
|
|
||||||
|
pend@1.2.0: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
progress@2.0.3: {}
|
||||||
|
|
||||||
|
proxy-agent@6.5.0:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
http-proxy-agent: 7.0.2
|
||||||
|
https-proxy-agent: 7.0.6
|
||||||
|
lru-cache: 7.18.3
|
||||||
|
pac-proxy-agent: 7.2.0
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
socks-proxy-agent: 8.0.5
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
proxy-from-env@1.1.0: {}
|
||||||
|
|
||||||
|
pump@3.0.4:
|
||||||
|
dependencies:
|
||||||
|
end-of-stream: 1.4.5
|
||||||
|
once: 1.4.0
|
||||||
|
|
||||||
|
puppeteer-core@24.37.5:
|
||||||
|
dependencies:
|
||||||
|
'@puppeteer/browsers': 2.13.0
|
||||||
|
chromium-bidi: 14.0.0(devtools-protocol@0.0.1566079)
|
||||||
|
debug: 4.4.3
|
||||||
|
devtools-protocol: 0.0.1566079
|
||||||
|
typed-query-selector: 2.12.1
|
||||||
|
webdriver-bidi-protocol: 0.4.1
|
||||||
|
ws: 8.19.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- bufferutil
|
||||||
|
- react-native-b4a
|
||||||
|
- supports-color
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
puppeteer@24.37.5:
|
||||||
|
dependencies:
|
||||||
|
'@puppeteer/browsers': 2.13.0
|
||||||
|
chromium-bidi: 14.0.0(devtools-protocol@0.0.1566079)
|
||||||
|
cosmiconfig: 9.0.0
|
||||||
|
devtools-protocol: 0.0.1566079
|
||||||
|
puppeteer-core: 24.37.5
|
||||||
|
typed-query-selector: 2.12.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- bufferutil
|
||||||
|
- react-native-b4a
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
- utf-8-validate
|
||||||
|
|
||||||
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
|
semver@7.7.4: {}
|
||||||
|
|
||||||
|
smart-buffer@4.2.0: {}
|
||||||
|
|
||||||
|
socks-proxy-agent@8.0.5:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 7.1.4
|
||||||
|
debug: 4.4.3
|
||||||
|
socks: 2.8.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
socks@2.8.7:
|
||||||
|
dependencies:
|
||||||
|
ip-address: 10.1.0
|
||||||
|
smart-buffer: 4.2.0
|
||||||
|
|
||||||
|
source-map@0.6.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
streamx@2.23.0:
|
||||||
|
dependencies:
|
||||||
|
events-universal: 1.0.1
|
||||||
|
fast-fifo: 1.3.2
|
||||||
|
text-decoder: 1.2.7
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
string-width@4.2.3:
|
||||||
|
dependencies:
|
||||||
|
emoji-regex: 8.0.0
|
||||||
|
is-fullwidth-code-point: 3.0.0
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
strip-ansi@6.0.1:
|
||||||
|
dependencies:
|
||||||
|
ansi-regex: 5.0.1
|
||||||
|
|
||||||
|
tar-fs@3.1.1:
|
||||||
|
dependencies:
|
||||||
|
pump: 3.0.4
|
||||||
|
tar-stream: 3.1.8
|
||||||
|
optionalDependencies:
|
||||||
|
bare-fs: 4.5.5
|
||||||
|
bare-path: 3.0.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
tar-stream@3.1.8:
|
||||||
|
dependencies:
|
||||||
|
b4a: 1.8.0
|
||||||
|
bare-fs: 4.5.5
|
||||||
|
fast-fifo: 1.3.2
|
||||||
|
streamx: 2.23.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- bare-buffer
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
teex@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
streamx: 2.23.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bare-abort-controller
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
text-decoder@1.2.7:
|
||||||
|
dependencies:
|
||||||
|
b4a: 1.8.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- react-native-b4a
|
||||||
|
|
||||||
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
typed-query-selector@2.12.1: {}
|
||||||
|
|
||||||
|
undici-types@7.18.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
webdriver-bidi-protocol@0.4.1: {}
|
||||||
|
|
||||||
|
wrap-ansi@7.0.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 4.3.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
|
wrappy@1.0.2: {}
|
||||||
|
|
||||||
|
ws@8.19.0: {}
|
||||||
|
|
||||||
|
y18n@5.0.8: {}
|
||||||
|
|
||||||
|
yargs-parser@21.1.1: {}
|
||||||
|
|
||||||
|
yargs@17.7.2:
|
||||||
|
dependencies:
|
||||||
|
cliui: 8.0.1
|
||||||
|
escalade: 3.2.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
string-width: 4.2.3
|
||||||
|
y18n: 5.0.8
|
||||||
|
yargs-parser: 21.1.1
|
||||||
|
|
||||||
|
yauzl@2.10.0:
|
||||||
|
dependencies:
|
||||||
|
buffer-crc32: 0.2.13
|
||||||
|
fd-slicer: 1.1.0
|
||||||
|
|
||||||
|
zod@3.25.76: {}
|
||||||
2
web/batch_glb_png/pnpm-workspace.yaml
Normal file
2
web/batch_glb_png/pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
onlyBuiltDependencies:
|
||||||
|
- puppeteer
|
||||||
153
web/batch_glb_png/render.html
Normal file
153
web/batch_glb_png/render.html
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Headless GLB Renderer</title>
|
||||||
|
<style>
|
||||||
|
html, body { margin:0; padding:0; background:#fff; overflow:hidden; }
|
||||||
|
canvas { display:block; }
|
||||||
|
</style>
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "./libs/three.module.js",
|
||||||
|
"three/addons/": "./vendor/three/addons/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||||
|
|
||||||
|
function qs (k) { return new URLSearchParams(location.search).get(k); }
|
||||||
|
|
||||||
|
const glbUrl = qs('glb');
|
||||||
|
const profileUrl = qs('profile');
|
||||||
|
|
||||||
|
window.__RENDER_STATUS__ = { ready:false, error:null, pngDataUrl:null };
|
||||||
|
|
||||||
|
if (!glbUrl || !profileUrl) {
|
||||||
|
window.__RENDER_STATUS__.error = "Missing ?glb=... or ?profile=...";
|
||||||
|
throw new Error(window.__RENDER_STATUS__.error);
|
||||||
|
}
|
||||||
|
|
||||||
|
const profile = await (await fetch(profileUrl)).json();
|
||||||
|
|
||||||
|
// Renderer
|
||||||
|
const renderer = new THREE.WebGLRenderer({
|
||||||
|
antialias: true,
|
||||||
|
preserveDrawingBuffer: true,
|
||||||
|
alpha: false
|
||||||
|
});
|
||||||
|
renderer.setPixelRatio(profile.output?.pixelRatio ?? 1);
|
||||||
|
renderer.setSize(profile.output?.width ?? 1024, profile.output?.height ?? 768, false);
|
||||||
|
|
||||||
|
// Three r152+ color management (safe-guard)
|
||||||
|
if (THREE.SRGBColorSpace) renderer.outputColorSpace = THREE.SRGBColorSpace;
|
||||||
|
|
||||||
|
const bg = profile.scene?.background ?? 0xffffff;
|
||||||
|
renderer.setClearColor(bg, 1);
|
||||||
|
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
// Scene
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
|
||||||
|
// Camera
|
||||||
|
const cam = new THREE.PerspectiveCamera(
|
||||||
|
profile.camera?.fov ?? 50,
|
||||||
|
(profile.output?.width ?? 1024) / (profile.output?.height ?? 768),
|
||||||
|
profile.camera?.near ?? 0.1,
|
||||||
|
profile.camera?.far ?? 1000
|
||||||
|
);
|
||||||
|
cam.position.fromArray(profile.camera?.position ?? [0,0,10]);
|
||||||
|
cam.up.fromArray(profile.camera?.up ?? [0,1,0]);
|
||||||
|
|
||||||
|
// We won’t use OrbitControls in headless; we just mimic its target by looking at it.
|
||||||
|
const target = new THREE.Vector3().fromArray(profile.controls?.target ?? [0,0,0]);
|
||||||
|
cam.lookAt(target);
|
||||||
|
cam.updateProjectionMatrix();
|
||||||
|
|
||||||
|
// Lights
|
||||||
|
const dir = new THREE.DirectionalLight(0xffffff, profile.lights?.directional?.intensity ?? 2.0);
|
||||||
|
dir.position.fromArray(profile.lights?.directional?.position ?? [5,5,5]);
|
||||||
|
scene.add(dir);
|
||||||
|
|
||||||
|
const amb = new THREE.AmbientLight(0xffffff, profile.lights?.ambient?.intensity ?? 0.5);
|
||||||
|
scene.add(amb);
|
||||||
|
|
||||||
|
// Load GLB
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
const gltf = await loader.loadAsync(glbUrl);
|
||||||
|
const root = gltf.scene;
|
||||||
|
scene.add(root);
|
||||||
|
|
||||||
|
// --- Fit-to-frame while keeping the profile's viewing direction ---
|
||||||
|
const box = new THREE.Box3().setFromObject(root);
|
||||||
|
const size = box.getSize(new THREE.Vector3());
|
||||||
|
const center = box.getCenter(new THREE.Vector3());
|
||||||
|
|
||||||
|
// Use profile target if present, but still re-center model so target makes sense
|
||||||
|
const profTarget = new THREE.Vector3().fromArray(profile.controls?.target ?? [0, 0, 0]);
|
||||||
|
|
||||||
|
// Shift the model so its center sits at the profile target (usually [0,0,0] or your saved target)
|
||||||
|
root.position.sub(center).add(profTarget);
|
||||||
|
|
||||||
|
// Recompute bounds after shifting
|
||||||
|
const box2 = new THREE.Box3().setFromObject(root);
|
||||||
|
const size2 = box2.getSize(new THREE.Vector3());
|
||||||
|
const center2 = box2.getCenter(new THREE.Vector3());
|
||||||
|
|
||||||
|
// Keep the same view direction as the profile camera->target vector
|
||||||
|
const profCamPos = new THREE.Vector3().fromArray(profile.camera?.position ?? [0, 0, 10]);
|
||||||
|
const viewDir = profCamPos.clone().sub(profTarget).normalize();
|
||||||
|
|
||||||
|
// Compute distance so the whole object fits the camera FOV
|
||||||
|
|
||||||
|
const maxDim = Math.max(size2.x, size2.y, size2.z, 1e-6);
|
||||||
|
const fov = cam.fov * (Math.PI / 180);
|
||||||
|
const fitPadding = profile.camera?.fitPadding ?? 1.15; // you can add this to JSON later
|
||||||
|
const distance = (maxDim / 2) / Math.tan(fov / 2) * fitPadding;
|
||||||
|
|
||||||
|
// Position camera and aim
|
||||||
|
cam.position.copy(center2).add(viewDir.multiplyScalar(distance));
|
||||||
|
cam.lookAt(center2);
|
||||||
|
cam.updateProjectionMatrix();
|
||||||
|
|
||||||
|
// Adjust near/far safely
|
||||||
|
cam.near = Math.max(0.01, distance / 100);
|
||||||
|
cam.far = distance * 100;
|
||||||
|
cam.updateProjectionMatrix();
|
||||||
|
|
||||||
|
// Apply renderParams
|
||||||
|
const wireframe = !!profile.renderParams?.wireframe;
|
||||||
|
root.traverse((obj) => {
|
||||||
|
if (obj.isMesh && obj.material) {
|
||||||
|
// If multi-material
|
||||||
|
if (Array.isArray(obj.material)) {
|
||||||
|
obj.material.forEach(m => { m.wireframe = wireframe; m.needsUpdate = true; });
|
||||||
|
} else {
|
||||||
|
obj.material.wireframe = wireframe;
|
||||||
|
obj.material.needsUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Optional: edges overlay if you later add it (kept off unless you implement lines).
|
||||||
|
// profile.renderParams.edgeAngle is available; current lab.html uses it to build edges.
|
||||||
|
|
||||||
|
renderer.render(scene, cam);
|
||||||
|
|
||||||
|
// Give the GPU a beat (helps on some headless setups)
|
||||||
|
await new Promise(r => setTimeout(r, 50));
|
||||||
|
|
||||||
|
window.__RENDER_STATUS__.pngDataUrl = renderer.domElement.toDataURL('image/png');
|
||||||
|
window.__RENDER_STATUS__.ready = true;
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
52
web/batch_glb_png/three_profile_20260228_111725.json
Normal file
52
web/batch_glb_png/three_profile_20260228_111725.json
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"provenance": "lab.html exportProfile Sat Feb 28 2026 11:17:25 GMT-0800 (Pacific Standard Time)",
|
||||||
|
"output": {
|
||||||
|
"width": 1227,
|
||||||
|
"height": 994,
|
||||||
|
"pixelRatio": 1
|
||||||
|
},
|
||||||
|
"scene": {
|
||||||
|
"background": 16777215
|
||||||
|
},
|
||||||
|
"camera": {
|
||||||
|
"type": "PerspectiveCamera",
|
||||||
|
"fov": 50,
|
||||||
|
"near": 0.1,
|
||||||
|
"far": 1000,
|
||||||
|
"position": [
|
||||||
|
-12.932153617745264,
|
||||||
|
18.85116776875012,
|
||||||
|
19.470685732446455
|
||||||
|
],
|
||||||
|
"up": [
|
||||||
|
0,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"controls": {
|
||||||
|
"target": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"renderParams": {
|
||||||
|
"wireframe": false,
|
||||||
|
"edgeAngle": 30,
|
||||||
|
"lightIntensity": 3
|
||||||
|
},
|
||||||
|
"lights": {
|
||||||
|
"directional": {
|
||||||
|
"position": [
|
||||||
|
5,
|
||||||
|
5,
|
||||||
|
5
|
||||||
|
],
|
||||||
|
"intensity": 3
|
||||||
|
},
|
||||||
|
"ambient": {
|
||||||
|
"intensity": 0.6
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
web/batch_glb_png/vendor
Symbolic link
1
web/batch_glb_png/vendor
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../vendor
|
||||||
1
web/glb
Symbolic link
1
web/glb
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../glb/
|
||||||
192
web/lab.html
Normal file
192
web/lab.html
Normal file
|
|
@ -0,0 +1,192 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>RenderLab v1</title>
|
||||||
|
<style>
|
||||||
|
body { margin:0; background:white; }
|
||||||
|
canvas { display:block; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script type="importmap">
|
||||||
|
{
|
||||||
|
"imports": {
|
||||||
|
"three": "./vendor/three/build/three.module.js",
|
||||||
|
"three/addons/": "./vendor/three/addons/"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from 'three';
|
||||||
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
||||||
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
||||||
|
import GUI from 'three/addons/libs/lil-gui.module.min.js';
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0xffffff);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000);
|
||||||
|
camera.position.set(3,3,3);
|
||||||
|
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({
|
||||||
|
antialias: true,
|
||||||
|
preserveDrawingBuffer: true, // <-- critical for toDataURL on many setups
|
||||||
|
alpha: false // ensure opaque canvas
|
||||||
|
});
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
renderer.setClearColor(0xffffff, 1); // opaque white
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
light.position.set(5,5,5);
|
||||||
|
scene.add(light);
|
||||||
|
|
||||||
|
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
|
||||||
|
scene.add(ambient);
|
||||||
|
|
||||||
|
let mesh, edgeLines;
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load('./glb/part.glb', gltf => {
|
||||||
|
|
||||||
|
mesh = gltf.scene.children[0];
|
||||||
|
|
||||||
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xdddddd,
|
||||||
|
roughness: 0.9,
|
||||||
|
metalness: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(mesh);
|
||||||
|
addEdges(30);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function ts_yyyymmdd_hhmiss () {
|
||||||
|
const d = new Date();
|
||||||
|
const pad = n => String(n).padStart(2,'0');
|
||||||
|
return (
|
||||||
|
d.getFullYear() +
|
||||||
|
pad(d.getMonth()+1) +
|
||||||
|
pad(d.getDate()) + '_' +
|
||||||
|
pad(d.getHours()) +
|
||||||
|
pad(d.getMinutes()) +
|
||||||
|
pad(d.getSeconds())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadTextFile (filename, text) {
|
||||||
|
const blob = new Blob([text], { type: 'application/json' });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = url;
|
||||||
|
a.download = filename;
|
||||||
|
a.click();
|
||||||
|
URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addEdges(angle) {
|
||||||
|
if (!mesh) return;
|
||||||
|
if (edgeLines) scene.remove(edgeLines);
|
||||||
|
|
||||||
|
const edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||||
|
edgeLines = new THREE.LineSegments(
|
||||||
|
edges,
|
||||||
|
new THREE.LineBasicMaterial({ color: 0x000000 })
|
||||||
|
);
|
||||||
|
|
||||||
|
scene.add(edgeLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
wireframe: false,
|
||||||
|
edgeAngle: 30,
|
||||||
|
lightIntensity: 1.5,
|
||||||
|
|
||||||
|
savePNG: () => {
|
||||||
|
renderer.render(scene, camera); // recommended before capture
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = 'render.png';
|
||||||
|
link.href = renderer.domElement.toDataURL("image/png");
|
||||||
|
link.click();
|
||||||
|
},
|
||||||
|
|
||||||
|
exportProfile: () => {
|
||||||
|
controls.update(); // ensure target is current
|
||||||
|
|
||||||
|
const profile = {
|
||||||
|
provenance: `lab.html exportProfile ${new Date().toString()}`,
|
||||||
|
|
||||||
|
output: {
|
||||||
|
width: renderer.domElement.width,
|
||||||
|
height: renderer.domElement.height,
|
||||||
|
pixelRatio: renderer.getPixelRatio()
|
||||||
|
},
|
||||||
|
|
||||||
|
scene: {
|
||||||
|
background: scene.background?.getHex?.() ?? null
|
||||||
|
},
|
||||||
|
|
||||||
|
camera: {
|
||||||
|
type: 'PerspectiveCamera',
|
||||||
|
fov: camera.fov,
|
||||||
|
near: camera.near,
|
||||||
|
far: camera.far,
|
||||||
|
position: camera.position.toArray(),
|
||||||
|
up: camera.up.toArray()
|
||||||
|
},
|
||||||
|
|
||||||
|
controls: {
|
||||||
|
target: controls.target.toArray()
|
||||||
|
},
|
||||||
|
|
||||||
|
renderParams: {
|
||||||
|
wireframe: params.wireframe,
|
||||||
|
edgeAngle: params.edgeAngle,
|
||||||
|
lightIntensity: params.lightIntensity
|
||||||
|
},
|
||||||
|
|
||||||
|
lights: {
|
||||||
|
directional: { position: light.position.toArray(), intensity: light.intensity },
|
||||||
|
ambient: { intensity: ambient.intensity }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fn = `three_profile_${ts_yyyymmdd_hhmiss()}.json`;
|
||||||
|
downloadTextFile(fn, JSON.stringify(profile, null, 2));
|
||||||
|
console.log('Exported profile:', profile);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const gui = new GUI();
|
||||||
|
gui.add(params, 'wireframe').onChange(v => {
|
||||||
|
if (mesh) mesh.material.wireframe = v;
|
||||||
|
});
|
||||||
|
gui.add(params, 'edgeAngle', 1, 90).onChange(v => addEdges(v));
|
||||||
|
gui.add(params, 'lightIntensity', 0, 3).onChange(v => light.intensity = v);
|
||||||
|
gui.add(params, 'savePNG');
|
||||||
|
gui.add(params, 'exportProfile');
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
camera.aspect = innerWidth/innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
104
web/lab.html~
Normal file
104
web/lab.html~
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>RenderLab v1</title>
|
||||||
|
<style>
|
||||||
|
body { margin:0; background:white; }
|
||||||
|
canvas { display:block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from './threejs/three.module.js';
|
||||||
|
import { GLTFLoader } from './threejs/GLTFLoader.js';
|
||||||
|
import { OrbitControls } from './threejs/OrbitControls.js';
|
||||||
|
import GUI from './threejs/lil-gui.module.min.js';
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0xffffff);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000);
|
||||||
|
camera.position.set(3,3,3);
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias:true });
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
renderer.setPixelRatio(window.devicePixelRatio);
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
light.position.set(5,5,5);
|
||||||
|
scene.add(light);
|
||||||
|
|
||||||
|
const ambient = new THREE.AmbientLight(0xffffff, 0.6);
|
||||||
|
scene.add(ambient);
|
||||||
|
|
||||||
|
let mesh, edgeLines;
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load('./glb/part.glb', gltf => {
|
||||||
|
|
||||||
|
mesh = gltf.scene.children[0];
|
||||||
|
|
||||||
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xdddddd,
|
||||||
|
roughness: 0.9,
|
||||||
|
metalness: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(mesh);
|
||||||
|
addEdges(30);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function addEdges(angle) {
|
||||||
|
if (!mesh) return;
|
||||||
|
if (edgeLines) scene.remove(edgeLines);
|
||||||
|
|
||||||
|
const edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||||
|
edgeLines = new THREE.LineSegments(
|
||||||
|
edges,
|
||||||
|
new THREE.LineBasicMaterial({ color: 0x000000 })
|
||||||
|
);
|
||||||
|
|
||||||
|
scene.add(edgeLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
wireframe: false,
|
||||||
|
edgeAngle: 30,
|
||||||
|
lightIntensity: 1.5,
|
||||||
|
savePNG: () => {
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.download = 'render.png';
|
||||||
|
link.href = renderer.domElement.toDataURL("image/png");
|
||||||
|
link.click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const gui = new GUI();
|
||||||
|
gui.add(params, 'wireframe').onChange(v => {
|
||||||
|
if (mesh) mesh.material.wireframe = v;
|
||||||
|
});
|
||||||
|
gui.add(params, 'edgeAngle', 1, 90).onChange(v => addEdges(v));
|
||||||
|
gui.add(params, 'lightIntensity', 0, 3).onChange(v => light.intensity = v);
|
||||||
|
gui.add(params, 'savePNG');
|
||||||
|
|
||||||
|
window.addEventListener('resize', () => {
|
||||||
|
camera.aspect = innerWidth/innerHeight;
|
||||||
|
camera.updateProjectionMatrix();
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
4663
web/threejs/GLTFLoader.js
Normal file
4663
web/threejs/GLTFLoader.js
Normal file
File diff suppressed because it is too large
Load diff
1417
web/threejs/OrbitControls.js
Normal file
1417
web/threejs/OrbitControls.js
Normal file
File diff suppressed because it is too large
Load diff
8
web/threejs/lil-gui.module.min.js
vendored
Normal file
8
web/threejs/lil-gui.module.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
53044
web/threejs/three.module.js
Normal file
53044
web/threejs/three.module.js
Normal file
File diff suppressed because one or more lines are too long
1417
web/vendor/three/addons/controls/OrbitControls.js
vendored
Normal file
1417
web/vendor/three/addons/controls/OrbitControls.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
8
web/vendor/three/addons/libs/lil-gui.module.min.js
vendored
Normal file
8
web/vendor/three/addons/libs/lil-gui.module.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
4663
web/vendor/three/addons/loaders/GLTFLoader.js
vendored
Normal file
4663
web/vendor/three/addons/loaders/GLTFLoader.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
1375
web/vendor/three/addons/utils/BufferGeometryUtils.js
vendored
Normal file
1375
web/vendor/three/addons/utils/BufferGeometryUtils.js
vendored
Normal file
File diff suppressed because it is too large
Load diff
53044
web/vendor/three/build/three.module.js
vendored
Normal file
53044
web/vendor/three/build/three.module.js
vendored
Normal file
File diff suppressed because one or more lines are too long
78
web/wireframe_viewer.html
Normal file
78
web/wireframe_viewer.html
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>GLB Wireframe Viewer</title>
|
||||||
|
<style>
|
||||||
|
body { margin:0; background:white; }
|
||||||
|
canvas { display:block; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script type="module">
|
||||||
|
|
||||||
|
import * as THREE from './three.module.js';
|
||||||
|
import { GLTFLoader } from './GLTFLoader.js';
|
||||||
|
import { OrbitControls } from './OrbitControls.js';
|
||||||
|
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0xffffff);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(50, innerWidth/innerHeight, 0.1, 1000);
|
||||||
|
camera.position.set(3,3,3);
|
||||||
|
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias:true });
|
||||||
|
renderer.setSize(innerWidth, innerHeight);
|
||||||
|
document.body.appendChild(renderer.domElement);
|
||||||
|
|
||||||
|
const controls = new OrbitControls(camera, renderer.domElement);
|
||||||
|
|
||||||
|
const light = new THREE.DirectionalLight(0xffffff, 1.5);
|
||||||
|
light.position.set(5,5,5);
|
||||||
|
scene.add(light);
|
||||||
|
|
||||||
|
let mesh, edgeLines;
|
||||||
|
|
||||||
|
const loader = new GLTFLoader();
|
||||||
|
loader.load('part.glb', gltf => {
|
||||||
|
|
||||||
|
mesh = gltf.scene.children[0];
|
||||||
|
|
||||||
|
mesh.material = new THREE.MeshStandardMaterial({
|
||||||
|
color: 0xdddddd,
|
||||||
|
roughness: 0.9,
|
||||||
|
metalness: 0
|
||||||
|
});
|
||||||
|
|
||||||
|
scene.add(mesh);
|
||||||
|
|
||||||
|
addEdges(30);
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
function addEdges(angle) {
|
||||||
|
if (edgeLines) scene.remove(edgeLines);
|
||||||
|
|
||||||
|
const edges = new THREE.EdgesGeometry(mesh.geometry, angle);
|
||||||
|
edgeLines = new THREE.LineSegments(
|
||||||
|
edges,
|
||||||
|
new THREE.LineBasicMaterial({ color: 0x000000 })
|
||||||
|
);
|
||||||
|
|
||||||
|
scene.add(edgeLines);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('keydown', e => {
|
||||||
|
if (e.key === 'w') mesh.material.wireframe = !mesh.material.wireframe;
|
||||||
|
if (e.key === 'e') addEdges(25);
|
||||||
|
});
|
||||||
|
|
||||||
|
function animate() {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
}
|
||||||
|
animate();
|
||||||
|
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue