From f359a39d11511721c5a201be6f6dcaa6d8780792 Mon Sep 17 00:00:00 2001 From: Michael Kowalski Date: Mon, 30 Jan 2023 17:22:26 -0500 Subject: [PATCH] Fix T100028: Convert USD camera properties to mm from USD units. Authored by Sonny Campbell. Currently when importing a USD file, some of the camera properties are ignored, or the units are not converted correctly from USD world units. On import we currently set the focal length, but not the camera sensor size (horizontal and vertical aperture), so the camera field of view is wrong. The sensor size information is in the USD file, but is ignored for perspective cameras. USD uses "tenth of a world unit" scale for some physical camera properties like focal length and aperture. https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits I have added the UsdStage's metersPerUnit parameter to the ImportSettings so the camera can do the required conversion on import. This will convert from the USD file's world units to millimeters for Blender's camera settings. Reviewed by: Sybren and makowalski. Differential Revision: https://developer.blender.org/D16019 --- .../blender/io/usd/intern/usd_capi_import.cc | 1 + .../io/usd/intern/usd_reader_camera.cc | 21 +++++++----- .../blender/io/usd/intern/usd_reader_prim.h | 7 +++- tests/python/bl_usd_import_test.py | 33 +++++++++++++++++++ 4 files changed, 53 insertions(+), 9 deletions(-) diff --git a/source/blender/io/usd/intern/usd_capi_import.cc b/source/blender/io/usd/intern/usd_capi_import.cc index fb870eb154c..f60bca22e45 100644 --- a/source/blender/io/usd/intern/usd_capi_import.cc +++ b/source/blender/io/usd/intern/usd_capi_import.cc @@ -212,6 +212,7 @@ static void import_startjob(void *customdata, bool *stop, bool *do_update, float } convert_to_z_up(stage, &data->settings); + data->settings.stage_meters_per_unit = UsdGeomGetStageMetersPerUnit(stage); /* Set up the stage for animated data. */ if (data->params.set_frame_range) { diff --git a/source/blender/io/usd/intern/usd_reader_camera.cc b/source/blender/io/usd/intern/usd_reader_camera.cc index da51787e437..db47e6373c9 100644 --- a/source/blender/io/usd/intern/usd_reader_camera.cc +++ b/source/blender/io/usd/intern/usd_reader_camera.cc @@ -54,14 +54,19 @@ void USDCameraReader::read_object_data(Main *bmain, const double motionSampleTim pxr::VtValue horAp; cam_prim.GetHorizontalApertureAttr().Get(&horAp, motionSampleTime); - bcam->lens = val.Get(); - /* TODO(@makowalski): support sensor size. */ -#if 0 - bcam->sensor_x = 0.0f; - bcam->sensor_y = 0.0f; -#endif - bcam->shiftx = verApOffset.Get(); - bcam->shifty = horApOffset.Get(); + /* + * For USD, these camera properties are in tenths of a world unit. + * https://graphics.pixar.com/usd/release/api/class_usd_geom_camera.html#UsdGeom_CameraUnits + * tenth_of_unit = stage_meters_per_unit / 10 + * val_in_meters = val.Get() * tenth_of_unit + * val_in_millimeters = val_in_meters * 1000 + */ + const double scale_to_mm = 100.0 * settings_->stage_meters_per_unit; + bcam->lens = val.Get() * scale_to_mm; + bcam->sensor_x = horAp.Get() * scale_to_mm; + bcam->sensor_y = verAp.Get() * scale_to_mm; + bcam->shiftx = verApOffset.Get() * scale_to_mm; + bcam->shifty = horApOffset.Get() * scale_to_mm; bcam->type = (projectionVal.Get().GetString() == "perspective") ? CAM_PERSP : CAM_ORTHO; diff --git a/source/blender/io/usd/intern/usd_reader_prim.h b/source/blender/io/usd/intern/usd_reader_prim.h index 377228929ff..c909f7c27e7 100644 --- a/source/blender/io/usd/intern/usd_reader_prim.h +++ b/source/blender/io/usd/intern/usd_reader_prim.h @@ -49,6 +49,10 @@ struct ImportSettings { * and is mutable similar to the map above. */ mutable std::map mat_name_to_mat; + /* We use the stage metersPerUnit to convert camera properties from USD scene units to the + * correct millimeter scale that Blender uses for camera parameters. */ + double stage_meters_per_unit; + ImportSettings() : do_convert_mat(false), from_up(0), @@ -60,7 +64,8 @@ struct ImportSettings { sequence_offset(0), read_flag(0), validate_meshes(false), - cache_file(NULL) + cache_file(NULL), + stage_meters_per_unit(1.0) { } }; diff --git a/tests/python/bl_usd_import_test.py b/tests/python/bl_usd_import_test.py index 70c1ac1c127..438a5c48d73 100644 --- a/tests/python/bl_usd_import_test.py +++ b/tests/python/bl_usd_import_test.py @@ -155,6 +155,39 @@ class USDImportTest(AbstractUSDTest): coords = list(filter(lambda x: x[0] > 1.0, coords)) self.assertGreater(len(coords), 16) + def test_import_camera_properties(self): + """Test importing camera to ensure properties set correctly.""" + + # This file has metersPerUnit = 1 + infile = str(self.testdir / "usd_camera_test_1.usda") + res = bpy.ops.wm.usd_import(filepath=infile) + self.assertEqual({'FINISHED'}, res) + + camera_object = bpy.data.objects["Test_Camera"] + test_cam = camera_object.data + + self.assertAlmostEqual(43.12, test_cam.lens, 2) + self.assertAlmostEqual(24.89, test_cam.sensor_width, 2) + self.assertAlmostEqual(14.00, test_cam.sensor_height, 2) + self.assertAlmostEqual(12.34, test_cam.shift_x, 2) + self.assertAlmostEqual(56.78, test_cam.shift_y, 2) + + bpy.ops.object.select_all(action='SELECT') + bpy.ops.object.delete() + + # This file has metersPerUnit = 0.1 + infile = str(self.testdir / "usd_camera_test_2.usda") + res = bpy.ops.wm.usd_import(filepath=infile) + self.assertEqual({'FINISHED'}, res) + + camera_object = bpy.data.objects["Test_Camera"] + test_cam = camera_object.data + + self.assertAlmostEqual(4.312, test_cam.lens, 3) + self.assertAlmostEqual(2.489, test_cam.sensor_width, 3) + self.assertAlmostEqual(1.400, test_cam.sensor_height, 3) + self.assertAlmostEqual(1.234, test_cam.shift_x, 3) + self.assertAlmostEqual(5.678, test_cam.shift_y, 3) def main(): global args