IO: OBJ improvements for NURBS curves

* Bezier (NURBS) import, supporting both Blender and external variants.
* Cleaner Bezier export, with better results importing in Rhino.
* Internal support for exporting the new Curves type.
* Tests covering a broad number of related cases.

The current NURBS exporter writes curve data directly, without accounting
for internal padding or how knots are generated from modes. This adjusts the
export behavior to omit data that does not influence the geometry of the
curve. The result is a simplified output that is easier to parse during import,
both for the importer and when importing to other platforms (Rhino).
Visual explanation to the adjustment can be found in #139174.

Importer is also adjusted to support variations in knot patterns. Extending
it to support the data generated by the exporter and by other platforms (Rhino).
This should resolve some issues in relation to #138732.

Integrated tests are added to broadly validate the common cases generated
by the exporter, including variations of different modes and NURBS order.

Regression tests are added to validate that the NURBS generated in Rhino
are imported as expected.

More details in the PR.

Pull Request: https://projects.blender.org/blender/blender/pulls/139174
This commit is contained in:
Mattias Fredriksson
2025-08-01 06:37:28 +02:00
committed by Aras Pranckevicius
parent 2b97043379
commit e191d3d243
28 changed files with 1265 additions and 254 deletions

Binary file not shown.

BIN
tests/files/io_tests/obj/nurbs.obj (Stored with Git LFS)

Binary file not shown.

BIN
tests/files/io_tests/obj/nurbs_curves.obj (Stored with Git LFS)

Binary file not shown.

BIN
tests/files/io_tests/obj/nurbs_manual.obj (Stored with Git LFS)

Binary file not shown.

View File

@@ -57,58 +57,58 @@
==== Curves: 7
- Curve 'NurbsCircle' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:8x1 order:4x4 cyclic:True,False endp:False,False
- (12.463, 0.000, 1.000) w:1.000
- (12.463, 0.000, 0.000) w:1.000
- (12.463, 0.000, -1.000) w:1.000
...
- (10.463, 0.000, 0.000) w:1.000
- (10.463, 0.000, 1.000) w:1.000
- (11.463, 0.000, 1.000) w:1.000
- (10.463, 0.000, 1.000) w:1.000
- (10.463, 0.000, 0.000) w:1.000
...
- (12.463, 0.000, -1.000) w:1.000
- (12.463, 0.000, 0.000) w:1.000
- (12.463, 0.000, 1.000) w:1.000
- Curve 'NurbsCurve' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False
- (9.947, 0.000, 0.000) w:1.000
- (9.447, 0.000, -1.000) w:1.000
- (7.447, 0.000, -1.000) w:1.000
- (7.143, 0.000, 2.752) w:1.000
- (7.447, 0.000, -1.000) w:1.000
- (9.447, 0.000, -1.000) w:1.000
- (9.947, 0.000, 0.000) w:1.000
- Curve 'NurbsCurve2' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:7x1 order:4x4 cyclic:False,False endp:False,False
- (9.772, 0.000, 6.791) w:1.000
- (9.873, 0.000, 3.403) w:1.000
- (8.725, 0.000, 2.825) w:1.000
...
- (6.842, 0.000, 2.942) w:1.000
- (5.304, 0.000, 5.296) w:1.000
- (7.084, 0.000, 7.142) w:1.000
- (5.304, 0.000, 5.296) w:1.000
- (6.842, 0.000, 2.942) w:1.000
...
- (8.725, 0.000, 2.825) w:1.000
- (9.873, 0.000, 3.403) w:1.000
- (9.772, 0.000, 6.791) w:1.000
- Curve 'NurbsPathCurve' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:5x1 order:4x4 cyclic:False,False endp:True,False
- (17.691, 0.000, 0.000) w:1.000
- (16.632, 0.000, 1.854) w:1.000
- (15.691, 0.000, 0.000) w:1.000
- (14.671, 0.000, 1.288) w:1.000
- (13.691, 0.000, 0.000) w:1.000
- (14.671, 0.000, 1.288) w:1.000
- (15.691, 0.000, 0.000) w:1.000
- (16.632, 0.000, 1.854) w:1.000
- (17.691, 0.000, 0.000) w:1.000
- Curve 'NurbsPathCurve.001' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False
- (17.115, 0.000, 2.127) w:1.000
- (16.693, 0.000, -1.000) w:1.000
- (14.693, 0.000, -1.000) w:1.000
- (14.193, 0.000, 0.000) w:1.000
- (14.693, 0.000, -1.000) w:1.000
- (16.693, 0.000, -1.000) w:1.000
- (17.115, 0.000, 2.127) w:1.000
- Curve 'PolyCircle' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:2x2 cyclic:True,False endp:False,False
- (4.090, 0.000, -3.488) w:1.000
- (5.090, 0.000, -4.488) w:1.000
- (4.090, 0.000, -5.488) w:1.000
- spline type:NURBS pts:4x1 order:2x2 cyclic:True,False endp:True,False
- (3.090, 0.000, -4.488) w:1.000
- (4.090, 0.000, -5.488) w:1.000
- (5.090, 0.000, -4.488) w:1.000
- (4.090, 0.000, -3.488) w:1.000
- Curve 'PolyCurve' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:3x1 order:2x2 cyclic:False,False endp:False,False
- (1.000, 0.000, -4.488) w:1.000
- (0.371, 0.000, -5.659) w:1.000
- spline type:NURBS pts:3x1 order:2x2 cyclic:False,False endp:True,False
- (-1.000, 0.000, -4.488) w:1.000
- (0.371, 0.000, -5.659) w:1.000
- (1.000, 0.000, -4.488) w:1.000
==== Objects: 12
- Obj 'BezierCircle' MESH data:'BezierCircle'

View File

@@ -1,13 +1,13 @@
==== Curves: 1
- Curve 'nurbs' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:9x1 order:4x4 cyclic:True,False endp:False,False
- (0.260, -1.477, -0.866) w:1.000
- (-1.410, -0.513, 0.866) w:1.000
- (-1.500, 2.598, 0.000) w:1.000
...
- (-1.410, 0.513, -0.866) w:1.000
- (0.260, 1.477, 0.866) w:1.000
- (3.000, 0.000, 0.000) w:1.000
- (0.260, 1.477, 0.866) w:1.000
- (-1.410, 0.513, -0.866) w:1.000
...
- (-1.500, 2.598, 0.000) w:1.000
- (-1.410, -0.513, 0.866) w:1.000
- (0.260, -1.477, -0.866) w:1.000
==== Objects: 1
- Obj 'nurbs' CURVE data:'nurbs'

View File

@@ -1,38 +1,38 @@
==== Curves: 5
- Curve 'CurveDeg3' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:3x3 cyclic:False,False endp:False,False
- (10.000, -2.000, 0.000) w:1.000
- (10.000, 2.000, 0.000) w:1.000
- (6.000, 2.000, 0.000) w:1.000
- (6.000, -2.000, 0.000) w:1.000
- (6.000, 2.000, 0.000) w:1.000
- (10.000, 2.000, 0.000) w:1.000
- (10.000, -2.000, 0.000) w:1.000
- Curve 'nurbs_curves' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False
- (2.000, -2.000, 0.000) w:1.000
- (2.000, 2.000, 0.000) w:1.000
- (-2.000, 2.000, 0.000) w:1.000
- (-2.000, -2.000, 0.000) w:1.000
- (-2.000, 2.000, 0.000) w:1.000
- (2.000, 2.000, 0.000) w:1.000
- (2.000, -2.000, 0.000) w:1.000
- Curve 'NurbsCurveCyclic' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:True,False endp:False,False
- (-2.000, -2.000, 0.000) w:1.000
- (-2.000, 2.000, 0.000) w:1.000
- (-6.000, 2.000, 0.000) w:1.000
- (-6.000, -2.000, 0.000) w:1.000
- (-6.000, 2.000, 0.000) w:1.000
- (-2.000, 2.000, 0.000) w:1.000
- (-2.000, -2.000, 0.000) w:1.000
- Curve 'NurbsCurveDiffWeights' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:False,False
- (6.000, -2.000, 0.000) w:1.000
- (6.000, 2.000, 0.000) w:1.000
- (2.000, 2.000, 0.000) w:1.000
- (2.000, -2.000, 0.000) w:1.000
- (2.000, 2.000, 0.000) w:1.000
- (6.000, 2.000, 0.000) w:1.000
- (6.000, -2.000, 0.000) w:1.000
- Curve 'NurbsCurveEndpoint' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:4x1 order:4x4 cyclic:False,False endp:True,False
- (-6.000, -2.000, 0.000) w:1.000
- (-6.000, 2.000, 0.000) w:1.000
- (-10.000, 2.000, 0.000) w:1.000
- (-10.000, -2.000, 0.000) w:1.000
- (-10.000, 2.000, 0.000) w:1.000
- (-6.000, 2.000, 0.000) w:1.000
- (-6.000, -2.000, 0.000) w:1.000
==== Objects: 5
- Obj 'CurveDeg3' CURVE data:'CurveDeg3'

View File

@@ -0,0 +1,15 @@
==== Curves: 1
- Curve 'rhino_curve_arc_bezier_deg2' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:5x1 order:3x3 cyclic:False,False endp:True,False
- (6.819, 0.000, -19.510) w:1.000
- (3.112, 0.000, -30.504) w:0.897
- (-7.865, 0.000, -34.264) w:1.000
- (-18.842, 0.000, -38.024) w:0.897
- (-28.512, 0.000, -31.611) w:1.000
==== Objects: 1
- Obj 'rhino_curve_arc_bezier_deg2' CURVE data:'rhino_curve_arc_bezier_deg2'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

View File

@@ -0,0 +1,17 @@
==== Curves: 1
- Curve 'rhino_curve_bezier_deg3_cyclic' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:12x1 order:4x4 cyclic:True,False endp:True,False
- (-16.508, 0.000, -26.333) w:1.000
- (-19.459, 0.000, -23.609) w:1.000
- (-22.637, 0.000, -23.099) w:1.000
...
- (-11.571, 0.000, -32.462) w:1.000
- (-11.912, 0.000, -29.511) w:1.000
- (-14.662, 0.000, -28.037) w:1.000
==== Objects: 1
- Obj 'rhino_curve_bezier_deg3_cyclic' CURVE data:'rhino_curve_bezier_deg3_cyclic'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

View File

@@ -0,0 +1,17 @@
==== Curves: 1
- Curve 'rhino_curve_bezier_deg4_rat' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:9x1 order:5x5 cyclic:False,False endp:True,False
- (8.225, 0.000, 4.462) w:1.000
- (12.103, 0.000, 0.023) w:0.943
- (12.855, 0.000, -4.956) w:0.925
...
- (-2.991, 0.000, 1.965) w:1.000
- (-4.901, 0.000, -1.010) w:1.000
- (-5.653, 0.000, -8.056) w:1.000
==== Objects: 1
- Obj 'rhino_curve_bezier_deg4_rat' CURVE data:'rhino_curve_bezier_deg4_rat'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

View File

@@ -0,0 +1,17 @@
==== Curves: 1
- Curve 'rhino_curve_circle_bezier_deg2_cyclic' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:8x1 order:3x3 cyclic:True,False endp:True,False
- (-20.835, 0.000, -4.745) w:1.000
- (-22.398, 0.000, -8.096) w:0.707
- (-25.749, 0.000, -6.532) w:1.000
...
- (-25.973, 0.000, 1.733) w:0.707
- (-22.622, 0.000, 0.169) w:1.000
- (-19.271, 0.000, -1.394) w:0.707
==== Objects: 1
- Obj 'rhino_curve_circle_bezier_deg2_cyclic' CURVE data:'rhino_curve_circle_bezier_deg2_cyclic'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

View File

@@ -0,0 +1,17 @@
==== Curves: 1
- Curve 'rhino_curve_uniform_cyclic_deg3' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:12x1 order:4x4 cyclic:True,False endp:False,False
- (-8.471, 0.000, -8.620) w:1.000
- (-10.444, 0.000, -5.519) w:1.000
- (-12.981, 0.000, 4.721) w:1.000
...
- (-15.987, 0.000, 3.500) w:1.000
- (-10.162, 0.000, 5.003) w:1.000
- (-7.438, 0.000, -4.674) w:1.000
==== Objects: 1
- Obj 'rhino_curve_uniform_cyclic_deg3' CURVE data:'rhino_curve_uniform_cyclic_deg3'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

View File

@@ -0,0 +1,17 @@
==== Curves: 1
- Curve 'rhino_curve_uniform_cyclic_deg7' dim:3D resu:12 resv:12 splines:1
- spline type:NURBS pts:9x1 order:8x8 cyclic:True,False endp:False,False
- (19.014, 0.000, -57.653) w:1.000
- (-0.187, 0.000, -58.172) w:1.000
- (-12.880, 0.000, -60.533) w:1.000
...
- (3.446, 0.000, -52.464) w:1.000
- (18.496, 0.000, -53.761) w:1.000
- (38.216, 0.000, -45.717) w:1.000
==== Objects: 1
- Obj 'rhino_curve_uniform_cyclic_deg7' CURVE data:'rhino_curve_uniform_cyclic_deg7'
- pos 0.000, 0.000, 0.000
- rot 1.571, 0.000, 0.000 (XYZ)
- scl 1.000, 1.000, 1.000

BIN
tests/files/io_tests/obj/rhino_curve_arc_bezier_deg2.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/obj/rhino_curve_bezier_deg3_cyclic.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/obj/rhino_curve_bezier_deg4_rat.obj (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

BIN
tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg3.obj (Stored with Git LFS) Normal file

Binary file not shown.

BIN
tests/files/io_tests/obj/rhino_curve_uniform_cyclic_deg7.obj (Stored with Git LFS) Normal file

Binary file not shown.