atproto Thingiverse but good
10

Configure Feed

Select the types of activity you want to include in your feed.

bugfixes to rewritten renderer

Orual (Jun 29, 2026, 7:06 PM EDT) b584b329 3b4dcacc

+93 -16
+17 -6
crates/polymodel-mesh/src/parser.rs
··· 26 26 F: FnMut(String) -> Fut, 27 27 Fut: Future<Output = Result<Vec<u8>, MeshLoadError>>, 28 28 { 29 - if format == MeshFormat::Threemf { 30 - return Err(MeshLoadError::UnsupportedFormat); 31 - } 32 - 33 29 let primary = fetch(primary_url.to_string()).await?; 34 30 let companions = if format == MeshFormat::Gltf { 35 31 let base = primary_url.rsplit_once('/').map(|(base, _)| base); ··· 266 262 .expect("3MF parses locally"); 267 263 assert_eq!(mesh.format, MeshFormat::Threemf); 268 264 assert_eq!(mesh.triangle_count(), 25692); 269 - assert_eq!(mesh.format, MeshFormat::Gltf); 270 - assert_eq!(mesh.triangle_count(), 12); 265 + } 266 + 267 + #[tokio::test] 268 + async fn load_mesh_with_companions_parses_threemf_locally() { 269 + let threemf = fixture("cube_gears.3mf"); 270 + let mesh = load_mesh_with_companions("cube_gears.3mf", MeshFormat::Threemf, |url| { 271 + let threemf = threemf.clone(); 272 + async move { 273 + assert_eq!(url, "cube_gears.3mf"); 274 + Ok(threemf) 275 + } 276 + }) 277 + .await 278 + .expect("3MF parses through the async worker helper"); 279 + 280 + assert_eq!(mesh.format, MeshFormat::Threemf); 281 + assert_eq!(mesh.triangle_count(), 25692); 271 282 } 272 283 }
+76 -10
crates/polymodel-renderer-worker/src/worker.rs
··· 99 99 } 100 100 101 101 fn init_renderer(&mut self, canvas: web_sys::OffscreenCanvas) { 102 + tracing::info!("renderer worker: initializing WebGL2 renderer"); 102 103 let opts = js_sys::Object::new(); 103 104 let _ = js_sys::Reflect::set(&opts, &"desynchronized".into(), &JsValue::from_bool(true)); 104 105 ··· 162 163 }); 163 164 self.post_event(&RendererEvent::ContextAcquired); 164 165 self.post_event(&RendererEvent::RendererReady); 166 + tracing::info!("renderer worker: renderer ready"); 165 167 } 166 168 } 167 169 ··· 184 186 > { 185 187 let fit = model.camera_fit(); 186 188 let cpu_model = model_with_default_materials(model); 189 + tracing::info!( 190 + format = ?model.format, 191 + parts = cpu_model.geometries.len(), 192 + materials = cpu_model.materials.len(), 193 + vertices = model.vertex_count(), 194 + triangles = model.triangle_count(), 195 + center = ?fit.center, 196 + radius = fit.radius, 197 + "renderer worker: building scene" 198 + ); 187 199 let render_model = three_d::Model::<three_d::PhysicalMaterial>::new(context, &cpu_model) 188 200 .map_err(|e| format!("renderer model: {e}"))?; 189 201 if render_model.iter().next().is_none() { ··· 336 348 three_d::DirectionalLight, 337 349 ) { 338 350 let ambient_light = 339 - three_d::AmbientLight::new(context, 0.45, three_d::Srgba::new_opaque(255, 255, 255)); 351 + three_d::AmbientLight::new(context, 1.15, three_d::Srgba::new_opaque(255, 255, 255)); 340 352 let key_light = three_d::DirectionalLight::new( 341 353 context, 342 - 2.6, 354 + 3.4, 343 355 three_d::Srgba::new_opaque(255, 255, 255), 344 356 three_d::vec3(-0.45, -0.55, -0.70), 345 357 ); 346 358 let fill_light = three_d::DirectionalLight::new( 347 359 context, 348 - 0.9, 349 - three_d::Srgba::new_opaque(170, 205, 255), 360 + 1.7, 361 + three_d::Srgba::new_opaque(215, 230, 255), 350 362 three_d::vec3(0.55, 0.35, -0.35), 351 363 ); 352 364 (ambient_light, key_light, fill_light) ··· 475 487 476 488 let screen = three_d::RenderTarget::screen(&renderer.context, st.pixel_width, st.pixel_height); 477 489 let screen = screen.clear(three_d::ClearState::color_and_depth( 478 - 0.035, 0.043, 0.067, 1.0, 1.0, 490 + 0.78, 0.80, 0.84, 1.0, 1.0, 479 491 )); 480 492 481 493 if let Some(model) = &renderer.model { ··· 502 514 expected_size: Option<i64>, 503 515 expected_digest: Option<Vec<u8>>, 504 516 ) { 517 + tracing::info!( 518 + ?format, 519 + primary_url, 520 + expected_size, 521 + has_expected_digest = expected_digest.is_some(), 522 + "renderer worker: starting mesh load" 523 + ); 505 524 let epoch = { 506 525 let mut st = state.borrow_mut(); 507 526 if st.disposed { ··· 514 533 let state_clone = state.clone(); 515 534 spawn_local(async move { 516 535 let result = load_mesh_async(&primary_url, format, expected_size, expected_digest).await; 536 + match &result { 537 + Ok(_) => tracing::info!( 538 + ?format, 539 + success = true, 540 + "renderer worker: mesh load future completed" 541 + ), 542 + Err(message) => tracing::error!( 543 + ?format, 544 + success = false, 545 + %message, 546 + "renderer worker: mesh load future completed" 547 + ), 548 + } 517 549 518 550 let mut st = match state_clone.try_borrow_mut() { 519 551 Ok(st) => st, ··· 563 595 expected_size: Option<i64>, 564 596 expected_digest: Option<Vec<u8>>, 565 597 ) -> Result<ModelMesh, String> { 566 - load_mesh_with_companions(primary_url, format, |url| { 598 + tracing::info!( 599 + ?format, 600 + primary_url, 601 + "renderer worker: load_mesh_async begin" 602 + ); 603 + let model = load_mesh_with_companions(primary_url, format, |url| { 567 604 let verify_primary = url == primary_url; 568 605 let expected_digest = expected_digest.clone(); 569 606 async move { 607 + tracing::debug!(%url, verify_primary, "renderer worker: fetching mesh resource"); 570 608 let bytes = fetch_bytes(&url).await.map_err(|e| { 571 609 polymodel_mesh::contract::MeshLoadError::Decode(format!("fetch {url}: {e}")) 572 610 })?; 611 + tracing::debug!( 612 + %url, 613 + verify_primary, 614 + bytes = bytes.len(), 615 + "renderer worker: fetched mesh resource" 616 + ); 573 617 if verify_primary && (expected_size.is_some() || expected_digest.is_some()) { 574 618 let size = expected_size.unwrap_or(bytes.len() as i64); 619 + tracing::debug!( 620 + %url, 621 + expected_size = size, 622 + bytes = bytes.len(), 623 + has_expected_digest = expected_digest.is_some(), 624 + "renderer worker: verifying primary part file" 625 + ); 575 626 polymodel_renderer_protocol::verify_part_file( 576 627 &bytes, 577 628 size, ··· 582 633 "part file verification failed: {e}" 583 634 )) 584 635 })?; 636 + tracing::debug!(%url, "renderer worker: primary part file verified"); 585 637 } 586 638 Ok(bytes) 587 639 } 588 640 }) 589 641 .await 590 - .map_err(|e| e.to_string()) 642 + .map_err(|e| e.to_string())?; 643 + tracing::info!( 644 + ?format, 645 + parts = model.part_count(), 646 + vertices = model.vertex_count(), 647 + triangles = model.triangle_count(), 648 + "renderer worker: mesh parsed" 649 + ); 650 + Ok(model) 591 651 } 592 652 593 653 // --------------------------------------------------------------------------- ··· 652 712 let screen = three_d::RenderTarget::screen(&context, width, height); 653 713 let _ = screen 654 714 .clear(three_d::ClearState::color_and_depth( 655 - 0.035, 0.043, 0.067, 1.0, 1.0, 715 + 0.78, 0.80, 0.84, 1.0, 1.0, 656 716 )) 657 717 .render( 658 718 &camera, ··· 836 896 837 897 pub fn entry() { 838 898 console_error_panic_hook::set_once(); 839 - tracing_wasm::set_as_global_default(); 899 + let mut wasm_log_config = tracing_wasm::WASMLayerConfigBuilder::new(); 900 + wasm_log_config.set_max_level(tracing::Level::DEBUG); 901 + tracing_wasm::set_as_global_default_with_config(wasm_log_config.build()); 902 + tracing::info!("renderer worker: tracing initialized"); 840 903 841 904 let scope: DedicatedWorkerGlobalScope = js_sys::global().unchecked_into(); 842 905 let state = Rc::new(RefCell::new(WorkerState::new(scope.clone()))); ··· 869 932 if let Ok(array) = data.dyn_into::<js_sys::Uint8Array>() { 870 933 let bytes = array.to_vec(); 871 934 match deserialize_command(&bytes) { 872 - Ok(cmd) => handle_command(&state_for_msg, cmd), 935 + Ok(cmd) => { 936 + tracing::debug!(command = ?cmd, "renderer worker: command received"); 937 + handle_command(&state_for_msg, cmd); 938 + } 873 939 Err(e) => { 874 940 tracing::error!("failed to deserialize command: {e}"); 875 941 }