| 374 | } |
| 375 | |
| 376 | [[nodiscard]] std::optional<core::Tensor> renderAssetPreviewThreadSafe( |
| 377 | const std::string& path, |
| 378 | const int width, |
| 379 | const int height, |
| 380 | const float focal_length_mm, |
| 381 | const glm::mat3* rotation = nullptr, |
| 382 | const glm::vec3* translation = nullptr) { |
| 383 | auto invoke_render = [&]() -> std::optional<core::Tensor> { |
| 384 | return renderAssetPreviewOnViewerThread(path, width, height, focal_length_mm, rotation, translation); |
| 385 | }; |
| 386 | |
| 387 | auto* const viewer = get_visualizer(); |
| 388 | if (!viewer || viewer->isOnViewerThread()) { |
| 389 | return invoke_render(); |
| 390 | } |
| 391 | if (!viewer->acceptsPostedWork()) { |
| 392 | return std::nullopt; |
| 393 | } |
| 394 | |
| 395 | auto promise = std::make_shared<std::promise<std::optional<core::Tensor>>>(); |
| 396 | auto future = promise->get_future(); |
| 397 | auto completed = std::make_shared<std::atomic_bool>(false); |
| 398 | |
| 399 | auto finish = [promise, completed](std::optional<core::Tensor> result) mutable { |
| 400 | if (!completed->exchange(true)) { |
| 401 | promise->set_value(std::move(result)); |
| 402 | } |
| 403 | }; |
| 404 | |
| 405 | const bool posted = viewer->postWork(vis::Visualizer::WorkItem{ |
| 406 | .run = |
| 407 | [path, width, height, focal_length_mm, rotation, translation, finish]() mutable { |
| 408 | finish(renderAssetPreviewOnViewerThread(path, width, height, focal_length_mm, rotation, translation)); |
| 409 | }, |
| 410 | .cancel = |
| 411 | [finish]() mutable { |
| 412 | finish(std::nullopt); |
| 413 | }}); |
| 414 | if (!posted) { |
| 415 | return std::nullopt; |
| 416 | } |
| 417 | |
| 418 | nb::gil_scoped_release release; |
| 419 | return future.get(); |
| 420 | } |
| 421 | |
| 422 | [[nodiscard]] std::optional<glm::mat3> tensorToVisualizerRotation(const PyTensor& py_tensor) { |
| 423 | const auto& tensor = py_tensor.tensor(); |
no test coverage detected