safety
71
create_glb.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
#!/usr/bin/env python3
|
||||
# create_glb.py — Blender headless STL -> GLB
|
||||
#
|
||||
# Example:
|
||||
# blender-bin-5.0.0 --background --python create_glb.py -- input.stl [output.glb]
|
||||
|
||||
import bpy
|
||||
import sys
|
||||
import os
|
||||
from mathutils import Vector
|
||||
|
||||
def die(msg, rc=2):
|
||||
print(f"ERROR: {msg}")
|
||||
raise SystemExit(rc)
|
||||
|
||||
# args after "--"
|
||||
argv = sys.argv
|
||||
argv = argv[argv.index("--") + 1:] if "--" in argv else []
|
||||
|
||||
if len(argv) == 1:
|
||||
inp = argv[0]
|
||||
base, _ = os.path.splitext(inp)
|
||||
outp = base + ".glb"
|
||||
elif len(argv) >= 2:
|
||||
inp, outp = argv[0], argv[1]
|
||||
else:
|
||||
die("USAGE: blender --background --python create_glb.py -- input.stl [output.glb]")
|
||||
|
||||
if not os.path.exists(inp):
|
||||
die(f"Input not found: {inp}")
|
||||
|
||||
# Empty scene
|
||||
bpy.ops.wm.read_factory_settings(use_empty=True)
|
||||
|
||||
# Import STL (Blender 4/5 operator)
|
||||
res = bpy.ops.wm.stl_import(filepath=inp)
|
||||
if 'FINISHED' not in res:
|
||||
die(f"STL import failed for: {inp}")
|
||||
|
||||
# Gather imported mesh objects
|
||||
objs = [o for o in bpy.context.scene.objects if o.type == 'MESH']
|
||||
if not objs:
|
||||
die("No mesh objects after import (unexpected)")
|
||||
|
||||
# Compute combined bounding box center in world space
|
||||
min_v = Vector(( 1e30, 1e30, 1e30))
|
||||
max_v = Vector((-1e30, -1e30, -1e30))
|
||||
|
||||
for o in objs:
|
||||
# object bound_box is in local coords; transform to world
|
||||
for corner in o.bound_box:
|
||||
v = o.matrix_world @ Vector(corner)
|
||||
min_v.x = min(min_v.x, v.x); min_v.y = min(min_v.y, v.y); min_v.z = min(min_v.z, v.z)
|
||||
max_v.x = max(max_v.x, v.x); max_v.y = max(max_v.y, v.y); max_v.z = max(max_v.z, v.z)
|
||||
|
||||
center = (min_v + max_v) * 0.5
|
||||
|
||||
# Translate all meshes so center is at origin
|
||||
for o in objs:
|
||||
o.location -= center
|
||||
|
||||
# Export GLB
|
||||
res = bpy.ops.export_scene.gltf(
|
||||
filepath=outp,
|
||||
export_format='GLB',
|
||||
export_apply=True,
|
||||
)
|
||||
if 'FINISHED' not in res:
|
||||
die(f"GLB export failed: {outp}")
|
||||
|
||||
print(f"Wrote: {outp}")
|
||||
14
creating_glbs.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
|
||||
|
||||
|
||||
Here is a command to create a single glb:
|
||||
|
||||
blender-bin-5.0.0 --background --python create_glb.py -- \
|
||||
/usr/local/src/Voron-Stealthburner/STLs/Stealthburner/'[o]_stealthburner_LED_carrier.stl' \
|
||||
/tmp/out.glb
|
||||
|
||||
The git repository for Voron-Stealthburner was staged under /usr/local/src. The above command
|
||||
selects a specific STL and then places the STL under /tmp.
|
||||
|
||||
In a production mode, we want to be able to point to a directory tree of STLs and then
|
||||
generate glb equivalents in the similar tree structure.
|
||||
0
exercises/create_png_from_glb/README.md
Normal file
BIN
exercises/create_png_profile/20260304_101254_Wed.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
exercises/create_png_profile/20260304_101754_Wed.png
Normal file
|
After Width: | Height: | Size: 79 KiB |
BIN
exercises/create_png_profile/20260304_102737_Wed.png
Normal file
|
After Width: | Height: | Size: 39 KiB |
BIN
exercises/create_png_profile/20260304_102919_Wed.png
Normal file
|
After Width: | Height: | Size: 82 KiB |
260
exercises/create_png_profile/README.md
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# Goal
|
||||
Create a "profile", i.e. a JSON file, to be used by the script that mass converts *.glb to *.png
|
||||
|
||||
## Introduction
|
||||
This exercise only requires that you launch a small HTTP server in a console. Otherwise, everything involved is handled through the HTML page lab.html. You will interact with lab.html's 3D rendering of a glb file that is included with this project.
|
||||
|
||||
## Steps
|
||||
Open a browser or a new window of the browser (Ctrl-n in Firefox) and resize the browser to a small rectangle. You are reducing the size so as to mimic what the PNG cell in the manifest table will look like. For example, this reduced window
|
||||
is 521 × 432 pixels.
|
||||

|
||||
|
||||
In a console:
|
||||
|
||||
cd ~/work/Voron/voronstl/web
|
||||
python3 -m http.server 8001
|
||||
|
||||
You should have a console that looks like this:
|
||||
|
||||

|
||||
|
||||
It is necessary to start the web server within the "web" directory as that directory
|
||||
will be servers "root".
|
||||
|
||||
|
||||
Visit:
|
||||
|
||||
http://localhost:8001/lab.html
|
||||
|
||||
|
||||
You will see a zoomed-in image:
|
||||

|
||||
|
||||
Zoom out until the entire part fits within the window.
|
||||
|
||||
Click the Controls bar to collapse the sub menus.
|
||||

|
||||
Move the object to center it in the window: Shift + left mouse button. You want to have the entire part fit within the view and be cenetered.
|
||||
|
||||
Click Controls bar to open the sub menus. Adjust the lighintensity to a high value, if not the maximum values. This will cause the image to go lighter allowing for contrast with shadows that help discern the part.
|
||||

|
||||
Optional: Save the PNG for your own reference.
|
||||
|
||||
Click "Export Profile" and save your current settings.
|
||||
|
||||

|
||||
|
||||
You now have a specification on sizing and angles which may work well for all of the other parts. Note: I took mine and applied the specifications saved above for a mass PNG creation and all the others looked very good.
|
||||
|
||||
### Additional Information:
|
||||
Here's what a JSON file looks like:
|
||||
|
||||
<table style="width:100%; table-layout:fixed;">
|
||||
<tr>
|
||||
<th>Image</th>
|
||||
<th>Description</th>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<td style="width:50%; vertical-align:top;">
|
||||
<img src="20260304_102919_Wed.png" >
|
||||
</td>
|
||||
|
||||
<td style="width:50%; vertical-align:top;"><pre style="white-space:pre-wrap; overflow:auto; max-width:100%;">jlpoole@jp ~/work/Voron/voronstl/web $ jq . out/three_profile_20260304_102657.json
|
||||
{
|
||||
"provenance": "lab.html exportProfile Wed Mar 04 2026 10:26:57 GMT-0800 (Pacific Standard Time)",
|
||||
"output": {
|
||||
"width": 500,
|
||||
"height": 315,
|
||||
"pixelRatio": 1
|
||||
},
|
||||
"scene": {
|
||||
"background": 16777215
|
||||
},
|
||||
"camera": {
|
||||
"type": "PerspectiveCamera",
|
||||
"fov": 50,
|
||||
"near": 0.1,
|
||||
"far": 1000,
|
||||
"position": [
|
||||
11.93432933230491,
|
||||
11.71721921868296,
|
||||
13.265804443912849
|
||||
],
|
||||
"up": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
]
|
||||
},
|
||||
"controls": {
|
||||
"target": [
|
||||
2.075876663103527,
|
||||
-2.147814989328729,
|
||||
0.7122034073683343
|
||||
]
|
||||
},
|
||||
"renderParams": {
|
||||
"wireframe": false,
|
||||
"edgeAngle": 30,
|
||||
"lightIntensity": 1.5
|
||||
},
|
||||
"lights": {
|
||||
"directional": {
|
||||
"position": [
|
||||
5,
|
||||
5,
|
||||
5
|
||||
],
|
||||
"intensity": 1.5
|
||||
},
|
||||
"ambient": {
|
||||
"intensity": 0.6
|
||||
}
|
||||
}
|
||||
}
|
||||
jlpoole@jp ~/work/Voron/voronstl/web $
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
## ChatGPT's analysis of what is important for consistency
|
||||
|
||||
Here is a response from ChatGPT that summarizes what variables are captured and saved so that they can be used to repeat the same type of transformation of *.glb to PNG.
|
||||
|
||||
## Response
|
||||
|
||||
Good — now you’re at the point where it’s worth making the capture **repeatable** across a whole directory of GLBs.
|
||||
|
||||
In practice, you want to “freeze” *everything that affects the pixels* and treat the GLB as the only variable.
|
||||
|
||||
## What to save (so every PNG is consistent)
|
||||
|
||||
### 1) Camera pose and projection
|
||||
|
||||
Save:
|
||||
|
||||
* `camera.position` (x,y,z)
|
||||
* `camera.up` (x,y,z)
|
||||
* `controls.target` (x,y,z) if you’re using OrbitControls
|
||||
* `camera.fov`, `camera.near`, `camera.far`
|
||||
* (optional) whether you used perspective vs orthographic
|
||||
|
||||
These define “where the viewer is” and are the biggest determinant of repeatability.
|
||||
|
||||
### 2) Output framing rules (per-model normalization)
|
||||
|
||||
You need a deterministic rule for “how big the part appears in frame”, otherwise parts with different sizes will look random.
|
||||
|
||||
Two common strategies:
|
||||
|
||||
**A. Fit-to-frame (recommended for mass renders)**
|
||||
For each GLB:
|
||||
|
||||
* compute `Box3` bounds
|
||||
* compute `center` and `size`
|
||||
* set `controls.target = center`
|
||||
* set camera distance so the whole bounding box fits (using FOV + max dimension)
|
||||
* optionally lock azimuth/elevation (so every part is viewed from the same angle)
|
||||
|
||||
This yields consistent “fill” and eliminates manual tuning.
|
||||
|
||||
**B. Fixed camera (only if all parts are same scale and you want exact same viewpoint)**
|
||||
Works if your GLBs are all in the same coordinate system/scale and similarly sized.
|
||||
|
||||
### 3) Lighting rig
|
||||
|
||||
Save:
|
||||
|
||||
* light types (Directional/Hemisphere/Ambient)
|
||||
* each light’s `position`, `intensity`, `color`
|
||||
* shadow settings if used
|
||||
|
||||
Your current screenshot shows a simple shaded render; keep lighting stable or you’ll get different highlight/shadow shapes across parts.
|
||||
|
||||
### 4) Renderer and capture settings
|
||||
|
||||
Save:
|
||||
|
||||
* `width`, `height`
|
||||
* `devicePixelRatio` you used (often set explicitly for determinism)
|
||||
* `renderer.toneMapping`, `outputColorSpace` (Three.js r152+ uses `renderer.outputColorSpace = THREE.SRGBColorSpace`)
|
||||
* `renderer.setClearColor()` (white) and alpha behavior
|
||||
* `preserveDrawingBuffer: true` (for browser capture)
|
||||
* whether you use `physicallyCorrectLights` (if you do, keep it constant)
|
||||
|
||||
### 5) Material overrides / wireframe settings
|
||||
|
||||
If you’re overriding materials for “clay render” or wireframe, save:
|
||||
|
||||
* your override material parameters (`MeshStandardMaterial` vs `MeshPhongMaterial`, roughness/metalness)
|
||||
* edge/wire settings (`EdgesGeometry` threshold angle, line material linewidth/color)
|
||||
|
||||
### 6) Background / staging
|
||||
|
||||
Save:
|
||||
|
||||
* background color (white)
|
||||
* whether you add a ground plane, axes, environment map, etc.
|
||||
|
||||
Even “invisible” helpers can affect auto-framing if they’re included in bounds—so exclude them from bounds computation.
|
||||
|
||||
---
|
||||
|
||||
## The best thing to save: a small “render profile” JSON
|
||||
|
||||
Put this in a file like `render_profile.json`:
|
||||
|
||||
* canvas size
|
||||
* camera angle (azimuth/elevation) OR explicit camera position
|
||||
* fit-to-frame padding factor (e.g., 1.15)
|
||||
* light positions/intensities
|
||||
* material mode (`source`, `clay`, `wireframe`, `edges`)
|
||||
* background color
|
||||
|
||||
Then your batch renderer (browser or headless) loads profile + GLB and produces PNG.
|
||||
|
||||
Example shape (conceptually):
|
||||
|
||||
```json
|
||||
{
|
||||
"output": { "w": 1600, "h": 1200, "pixelRatio": 1 },
|
||||
"background": "#ffffff",
|
||||
"camera": { "fov": 35, "near": 0.01, "far": 1000, "azimuth": -35, "elevation": 20, "fitPadding": 1.15 },
|
||||
"lights": [
|
||||
{ "type": "dir", "pos": [3,5,2], "intensity": 2.8 },
|
||||
{ "type": "hem", "sky": "#ffffff", "ground": "#666666", "intensity": 0.6 }
|
||||
],
|
||||
"materialMode": "clay"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## How to mass-create PNGs (two viable routes)
|
||||
|
||||
### Route 1: Headless batch (best)
|
||||
|
||||
Use **node + headless WebGL** (or Puppeteer driving Chrome) to render each GLB with the profile and write PNGs. This is what you want if you’re doing hundreds/thousands.
|
||||
|
||||
### Route 2: Browser batch page (simpler)
|
||||
|
||||
A page that:
|
||||
|
||||
* loads a list of GLB URLs
|
||||
* applies the profile
|
||||
* renders each, downloads PNG automatically (or zips them)
|
||||
|
||||
This is simpler but less pleasant at scale.
|
||||
|
||||
---
|
||||
|
||||
## One key question that determines the “right” profile
|
||||
|
||||
Do you want your PNGs to be:
|
||||
|
||||
* **same camera angle + fit-to-frame** (parts all fill the image similarly), or
|
||||
* **same absolute camera** (so relative size differences are visible)?
|
||||
|
||||
If you tell me which you want, I’ll give you a concrete “profile JSON + camera fit math + batch driver” that matches it.
|
||||
|
After Width: | Height: | Size: 31 KiB |
|
After Width: | Height: | Size: 34 KiB |
|
After Width: | Height: | Size: 35 KiB |
104
lab.html
|
|
@ -1,104 +0,0 @@
|
|||
<!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>
|
||||
38
mv_themes.js
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// mv_theme.js
|
||||
// Apply consistent colors for manifest viewing.
|
||||
//
|
||||
// Usage:
|
||||
// <model-viewer class="mv" data-theme="primary" ...></model-viewer>
|
||||
// <model-viewer class="mv" data-theme="accent" ...></model-viewer>
|
||||
|
||||
const THEMES = {
|
||||
primary: { rgba: [0.10, 0.45, 1.00, 1.0] }, // diagnostic blue
|
||||
accent: { rgba: [0.78, 0.33, 0.10, 1.0] }, // burnt orange-ish
|
||||
};
|
||||
|
||||
function applyTheme(mv) {
|
||||
const key = (mv.dataset.theme || "primary").toLowerCase();
|
||||
const theme = THEMES[key] || THEMES.primary;
|
||||
|
||||
// Lighting-related material choices that improve face contrast
|
||||
const metallic = 0.0;
|
||||
const roughness = 0.35;
|
||||
|
||||
for (const mat of mv.model.materials) {
|
||||
mat.pbrMetallicRoughness.setBaseColorFactor(theme.rgba);
|
||||
mat.pbrMetallicRoughness.setMetallicFactor(metallic);
|
||||
mat.pbrMetallicRoughness.setRoughnessFactor(roughness);
|
||||
}
|
||||
}
|
||||
|
||||
function hook(mv) {
|
||||
if (mv.model) {
|
||||
applyTheme(mv);
|
||||
} else {
|
||||
mv.addEventListener("load", () => applyTheme(mv), { once: true });
|
||||
}
|
||||
}
|
||||
|
||||
for (const mv of document.querySelectorAll("model-viewer.mv")) {
|
||||
hook(mv);
|
||||
}
|
||||
6
simple_server.md
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
Use this command to run a simple little server for the HTML:
|
||||
|
||||
python3 -m http.server 8000
|
||||
|
||||
The directory the command above is run in is the root directory. Then
|
||||
http://127.0.0.1:8000/[FILE NAME]
|
||||
BIN
web/[o]_stealthburner_LED_carrier.stl.glb
Normal file
73
web/assets/beaker.svg
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
||||
|
||||
<svg
|
||||
width="800px"
|
||||
height="800px"
|
||||
viewBox="0 -0.08 45 45"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="beaker.svg"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1" />
|
||||
<sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="1.09625"
|
||||
inkscape:cx="399.5439"
|
||||
inkscape:cy="400"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1128"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" />
|
||||
<path
|
||||
id="Path_74"
|
||||
data-name="Path 74"
|
||||
d="m 15.693226,14.498953 h 16.333 v 6.166 l 4.907,8.793 6.139,11 -5.379,1.71 H 3.6932258 l 3.5,-7 7.2280002,-13.417 z"
|
||||
fill="#d1d3d4"
|
||||
style="fill:#ff413f;fill-opacity:1" />
|
||||
<path
|
||||
id="Path_75"
|
||||
data-name="Path 75"
|
||||
d="M 43,42.832 31,21.332 V 2 H 14 v 19.335 l -12,21.5 z"
|
||||
fill="none"
|
||||
stroke="#231f20"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="4" />
|
||||
<line
|
||||
id="Line_37"
|
||||
data-name="Line 37"
|
||||
x2="35.332001"
|
||||
fill="#ffffff"
|
||||
stroke="#231f20"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="4"
|
||||
x1="9.665"
|
||||
y1="2"
|
||||
y2="2" />
|
||||
<path
|
||||
id="Path_76"
|
||||
data-name="Path 76"
|
||||
d="M 18.5,34.16 A 2.333,2.333 0 1 1 16.166,31.827 2.334,2.334 0 0 1 18.5,34.16 Z"
|
||||
fill="#ffffff" />
|
||||
<path
|
||||
id="Path_77"
|
||||
data-name="Path 77"
|
||||
d="m 29.907,29.701 a 3.7,3.7 0 1 1 -3.7,-3.7 3.7,3.7 0 0 1 3.7,3.7 z"
|
||||
fill="#ffffff" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
BIN
web/assets/favicon-128.png
Normal file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
web/assets/favicon-16.png
Normal file
|
After Width: | Height: | Size: 567 B |
BIN
web/assets/favicon-256.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
web/assets/favicon-32.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
BIN
web/assets/favicon-48.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
web/assets/favicon-64.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
web/assets/favicon.ico
Normal file
|
After Width: | Height: | Size: 105 KiB |
3
web/batch_glb_png/glbs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!.gitignore
|
||||
!README.md
|
||||
3
web/batch_glb_png/pngs/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
*
|
||||
!README.md
|
||||
!.gitignore
|
||||
BIN
web/favicon.ico
Normal file
|
After Width: | Height: | Size: 105 KiB |
14
web/lab.html
|
|
@ -1,8 +1,12 @@
|
|||
<!DOCTYPE html>
|
||||
<!-- To run, start a local server:
|
||||
python3 -m http.server 8001
|
||||
Then open http://localhost:8001/lab.html in a modern browser.
|
||||
-->
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>RenderLab v1</title>
|
||||
<title>RenderLab: PNG Profile Exporter</title>
|
||||
<style>
|
||||
body { margin:0; background:white; }
|
||||
canvas { display:block; }
|
||||
|
|
@ -54,7 +58,13 @@ scene.add(ambient);
|
|||
let mesh, edgeLines;
|
||||
|
||||
const loader = new GLTFLoader();
|
||||
loader.load('./glb/part.glb', gltf => {
|
||||
|
||||
//
|
||||
// This part is particularly tricky to view, so it makes an excellent test case.
|
||||
//
|
||||
// ./Voron-Stealthburner/STLs/Stealthburner/[o]_stealthburner_LED_carrier.stl.glb
|
||||
//
|
||||
loader.load('[o]_stealthburner_LED_carrier.stl.glb', gltf => {
|
||||
|
||||
mesh = gltf.scene.children[0];
|
||||
|
||||
|
|
|
|||
2
web/out/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||