25

Graphics3D[] objects created with BSpline functions will not export to 3DS format, which only supports the more basic primitives. Is there any straightforward way to get at an underlying polygon representation of the BSplineSurface[] graphics "primitive" (in quotes because its not very primitive)?

An example is the final 'pipe' example in the documentation ref/BSplineSurface. If you try Export["Pipe.3ds", %], you get an error.

In my particular case I'm creating arbitrary 'surface of revolution' objects as per the "Potter's Wheel" demonstration, where the cross section is determined by a BSpline with dynamic control points. That works fine, but then I need export the resulting objects to another program.

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
Gareth Russell
  • 303
  • 2
  • 7
  • Please include a sample code snippet -- you will get faster/better answers that way. – Ajasja Aug 09 '12 at 14:02
  • Good question. Would you mind providing an example of a surface you try to export? That would make playing around that much easier. – Yves Klett Aug 09 '12 at 14:02
  • 2
    Under most definitions of straightforward, I don't believe there is. You may have to write a program that converts it into graphics primitives. – Searke Aug 09 '12 at 14:02

1 Answers1

26

I don't know what you'll count as "straightforward", but...

(* data for B-spline surface, from example in docs *)
pts = {{{0.5, 0, -0.5}, {0, 0, -0.5}, {0, 1, -0.5}, {0.5, 1, -0.5}, {1, 1, -0.5},
        {1, 0, -0.5}, {0.5, 0, -0.5}}, 
       {{0.5, 0, 0.7}, {0, 0, 0.7}, {0, 1, 0.7}, {0.5, 1, 0.7}, {1, 1, 0.7},
        {1, 0, 0.7}, {0.5, 0, 0.7}}, 
   {{0.5, 0, 0.9}, {0, 0, 0.9}, {0, 1, 1.5}, {0.5, 1, 1.5}, {1, 1, 1.5},
    {1, 0, 0.9}, {0.5, 0, 0.9}}, 
   {{0.5, -0.1, 1}, {0, -0.1, 1}, {0, 0.5, 2}, {0.5, 0.5, 2}, {1, 0.5, 2},
    {1, -0.1, 1}, {0.5, -0.1, 1}}, 
   {{0.5, -0.3, 1}, {0, -0.3, 1}, {0, -0.3, 2}, {0.5, -0.3, 2},
    {1, -0.3, 2}, {1, -0.3, 1}, {0.5, -0.3, 1}}, 
   {{0.5, -1.5, 1}, {0, -1.5, 1}, {0, -1.5, 2}, {0.5, -1.5, 2},
    {1, -1.5, 2}, {1, -1.5, 1}, {0.5, -1.5, 1}}};
w = {{1, .5, .5, 1, .5, .5, 1}, {1, .5, .5, 1, .5, .5, 1},
     {1, .5, .5, 1, .5, .5, 1}, {1, .5, .5, 1, .5, .5, 1},
     {1, .5, .5, 1, .5, .5, 1}, {1, .5, .5, 1, .5, .5, 1}};
uk = {0, 0, 0, 1/4, 1/2, 3/4, 1, 1, 1};
vk = {0, 0, 0, 1/4, 1/2, 1/2, 3/4, 1, 1, 1};

Graphics3D[BSplineSurface[pts, SplineKnots -> {uk, vk}, SplineDegree -> 2, 
                          SplineWeights -> w, SplineClosed -> {False, True}]] /. 
           bs : BSplineSurface[pts_?ArrayQ, opts___] :> 
           Module[{bsf = BSplineFunction[pts, opts]}, 
                  Cases[Normal[Cases[ParametricPlot3D[bsf[u, v], {u, 0, 1}, {v, 0, 1}],
                        _GraphicsComplex, ∞]], _Polygon, ∞]]

polygons for the B-spline surface, adaptive sampling

You can check that the output is composed entirely of Polygon[] objects. If need be, you can tweak the options within ParametricPlot3D[].


From the comments, it was asked how one might do a version where the sampling is uniform and the polygons are quadrilaterals. The old version of ParametricPlot3D[] did something like that. Here's how I'd emulate it:

MakePolygons[vl_] /; ArrayQ[vl, 3] := Module[{dims = Most[Dimensions[vl]]}, 
  GraphicsComplex[Apply[Join, vl], Polygon[Flatten[Apply[Join[#1, Reverse[#2]] &, 
                  Partition[Partition[Range[Times @@ dims], Last[dims]], {2, 2}, {1, 1}],
                        {2}], 1]]]]

Graphics3D[BSplineSurface[pts, SplineKnots -> {uk, vk}, SplineDegree -> 2, 
                          SplineWeights -> w, SplineClosed -> {False, True}]] /. 
           bs : BSplineSurface[pts_?ArrayQ, opts___] :> 
           Module[{bsf = BSplineFunction[pts, opts], upts = 30, vpts = 18}, 
                  Cases[Normal[MakePolygons[
                        Table[bsf[u, v], {u, 0, 1, 1/(upts - 1)}, {v, 0, 1, 1/(vpts - 1)}]
                        ]], _Polygon, ∞]]

polygons for the B-spline surface, uniform sampling

J. M.'s missing motivation
  • 124,525
  • 11
  • 401
  • 574
  • Wicked! How´d you arrive at that one? – Yves Klett Aug 09 '12 at 14:44
  • 1
    Well, I know that BSplineSurface[] and BSplineCurve[] are always convertible in terms of BSplineFunction[]... then, it was a matter of extracting polygons from ParametricPlot3D[]. – J. M.'s missing motivation Aug 09 '12 at 14:48
  • My impression is the the whole BSpline surface stuff is still a bit standalone (not to say orphaned). – Yves Klett Aug 09 '12 at 15:08
  • 1
    Maybe we should ask Yu-Sung about that... – J. M.'s missing motivation Aug 09 '12 at 15:09
  • Definitely...he did show rather more advanced stuff some time ago which allegedly did not make the release... – Yves Klett Aug 09 '12 at 15:18
  • @J.M. Can we also tweak how the surface mesh is done ? Suppose if one wants to specify the minimum surface area the smallest triangle can have or if somebody wants a uniform quad mesh. That will be very helpful... – PlatoManiac Aug 09 '12 at 15:23
  • @Plato, "...if somebody wants a uniform quad mesh..." - that requires a bit more programming effort, but it's definitely doable (I'll edit this answer for that case if there's interest). For your first case, I don't know how to hijack the adaptive algorithm used by ParametricPlot3D[]. Sorry. – J. M.'s missing motivation Aug 09 '12 at 15:26
  • @J.M. Some more interesting tricks from your bag will be very much appreciated on this context. Please take your time to edit the answer for any further update. Uniform quad mesh for arbitrary 3D MMA BSpline will be a sure treat. I asked similar question once.. – PlatoManiac Aug 09 '12 at 15:30
  • @J.M. In the mean time should I ask a question titled "hijack the adaptive algorithm" used in 3D meshing....;) we are not getting many eye opening tricky questions in the forum recently... – PlatoManiac Aug 09 '12 at 15:33
  • Sounds like a neat plan, @Plato. :) – J. M.'s missing motivation Aug 09 '12 at 15:45
  • @J.M. here is some control over the adaptive alg. : ParametricPlot3D[bsf[u, v], {u, 0, 1}, {v, 0, 1}, Mesh -> All, Boxed -> False, Axes -> False, MaxRecursion -> 0, PlotPoints -> {20, 30}] Here MaxRecursion->0 switches it off and the PlotPoints can be a List. –  Aug 09 '12 at 16:34
  • @rueben, I know about that bit, but Plato was asking about controlling the size of the triangles, and I don't know which options to tweak for those. – J. M.'s missing motivation Aug 09 '12 at 16:36
  • @J.M. well the only way to do that is by using more plot points. To my knowledge there is no way to directly influence the triangle areas. - You could use TetGenLink` for that perhaps. –  Aug 09 '12 at 16:57
  • @J.M., perhaps it is possible to use the bounding box and the plot points to roughly estimate the resulting triangle size. Never tried that though. –  Aug 09 '12 at 17:02
  • @ruebenko How do you suggest to use TetGenLink to control the triangle area of the surface mesh if we take the above tube example? How to automatically define the required TetGen interpretable geometry input from the spline based triangular mesh of the tube (or any complex 3d geometry) that J.M creates elegantly..? – PlatoManiac Aug 09 '12 at 17:08
  • @PlatoManiac, sorry it took me a while to come back to this. You need to create a closed surface spline, extract the GraphicsComplex (not the Normal[] of it!) and those coords and incidents can be feed to TetGen. Now, I have tried this for this example and unfortunately there is issue with creating the tetrahedralization; TetGen reports open facets. If you want to I can send you the code. Write me an email at ruebenko AT wolfram.com –  Aug 09 '12 at 22:24
  • @ruebenko Thx! I sent you an email... – PlatoManiac Aug 09 '12 at 22:30
  • Well, thanks J.M., that was brilliant. I had no idea BSplineFunction produced such a different output to BSplineSurface under the hood. It's not related to this question, I think, but my my pipeline is to export to 3DS, import to SketchUp, and then immediately export to Collada. When I do that, the interior faces of the pipe are transparent, which creates a very odd-looking Collada file! This happens even if I specify something like FaceForm[Blue, Red] in Mathematica's code. The inside and outside are colored fine in Mathematica, but in SketchUp the interior has lost all texture. – Gareth Russell Aug 13 '12 at 11:28
  • Sounds like a different problem altogether, @Gareth. Does this oddness persist if you use the version with uniform sampling instead of the one with adaptive sampling? – J. M.'s missing motivation Aug 13 '12 at 11:31
  • Interesting. When I append FaceForm[Blue, Red] to the uniform sampling version, by putting it inside the Graphics3D and before the redefined BSplineSurface argument, it produces the opposite coloring: the inside of the pipe is blue and the outside red. When exported as 3DS, and then imported into SketchUp, the inside stays blue and the outside is now transparent. Which, when rendered, looks really strange. It's not the color per se -- it seems to be the 'front'/'back' attribute given to a polygon. – Gareth Russell Aug 15 '12 at 01:37
  • Very peculiar, @Gareth. It seems to be something screwy in the exporting... maybe ask a new question? – J. M.'s missing motivation Aug 15 '12 at 01:40
  • I'm trying to figure out why your custom BSplineSurface creates polygons whose 'front' surface ends up on the inside of the shape, but no luck yet. For my application, the simplicity of the uniform sampling will be much better, and if I can get the front/back thing sorted out, I'll be fine, because my shapes will be closed, so a transparent inside won't matter! – Gareth Russell Aug 15 '12 at 01:41
  • Ah, I now know why, @Gareth. From the docs for FaceForm[]: "The front face of a polygon is defined to be the one for which the corners as you specify them are in counterclockwise order (right-hand rule)." I modified the definition for MakePolygons[] to match this; try it now. – J. M.'s missing motivation Aug 15 '12 at 02:19