3
$\begingroup$

I am trying to make DensityPlot3D from a model function a*x+(y*z) to be exported to .stl. The parameter a is unique for several materials to be considered, hence, it makes life easier for me to use Manipulate so that I can change the values of a for the materials (there are lot of them). This is my code:

 Manipulate[DensityPlot3D[a*x+(y*z),{x,0,2}, {y,0,2},{z,1,10}],{a, 1, 5}]

Unfortunately, even using Export["model.stl", model] failed. This is why I am considering using "Printout3D" possibly within the code. I don't know how this could be done and would love to have your inputs and suggestions. Thanks!

$\endgroup$
  • 4
    $\begingroup$ If you had taken the tour, as you should, you would have learned that is off-topic to ask questions with too many possible answers or that would require an extremely long answer. Your question may be put on-hold because it's not clear what you need. To avoid or revert the Hold you can edit your question to improve it and make it specific, well structured and easy to understand. Please don't be discouraged by that cleaning-up policy. Your questions are and will be most welcomed. Learn about good questions here. $\endgroup$ – rhermans Jul 15 '18 at 9:19
  • 3
    $\begingroup$ Since you have not included the code that generates your manipulation, it is impossible to give you a specific answer. A general but very vague answer is that you should add a Button expression that has an action (2nd agument) that calls Export to output whatever it is that you want to write to disk. The Button expression is likely to need the option Method -> Queued because Export can be slow. $\endgroup$ – m_goldberg Jul 15 '18 at 21:55
  • $\begingroup$ Thanks rhermans for the encouragement. I have made modifications to my question and I am sorry for the ambiguities in the earlier form of the question. $\endgroup$ – Dean Jul 16 '18 at 16:29
  • 2
    $\begingroup$ @Dean first off, you won't be able to export the Manipulate directly. What you can do is create a list of files, though, for different values of a. You're also going to run into trouble with the opacity. How do you want to handle that? My assumption is you'll need to use RegionPlot3D in the end. $\endgroup$ – b3m2a1 Jul 16 '18 at 18:03
  • 1
    $\begingroup$ @b3m2a1, done! Your contribution is still an excellent one. Thanks! $\endgroup$ – Dean Jul 22 '18 at 23:06
4
$\begingroup$

Using the cutoff idea from b3m2a1:

plot = With[{a = 1, cutoff = .5},
  DensityPlot3D[a*x + (y*z),
    {x, 0, 2}, {y, 0, 2}, {z, 1, 10},
    OpacityFunction -> Function[If[# < cutoff, 0, 1]]
  ]
];

We can turn this raster into an image, and then a mesh:

data = Reverse[plot[[1, 1]], {1, 2}];
ImageMesh[Image3D[data]]

enter image description here

We can smooth it with marching cubes:

ImageMesh[Image3D[data], Method -> "MarchingCubes"]

enter image description here

|improve this answer|||||
$\endgroup$
  • $\begingroup$ This is definitely the right way to go. I asked the OP to accept this so future visitors to the page see it. Very cool. And I'd never have thought to check ImageMesh for a Method so very cool on that as well. $\endgroup$ – b3m2a1 Jul 22 '18 at 21:42
5
$\begingroup$

As I mentioned in a comment, there are two issues with your current workflow.

For one, you won't be able to generate the STL directly from the Manipulate. Instead we'll need to make a list of objects to export.

For another, the Opacity in the DensityPlot3D means that it's not really a solid in any sense. My interpretation of your problem is that you want the hull of the thing generated, i.e. you effectively want the result from:

With[{a = 1, cutoff = .5},
 DensityPlot3D[a*x + (y*z),
  {x, 0, 2}, {y, 0, 2}, {z, 1, 10},
  OpacityFunction -> Function[If[# < cutoff, 0, 1]]
  ]
 ]

blah

The cleanest way to get this that I can think of in a general manner is to directly generate the ConvexHullMesh from this. For that reason I wrote a little wrapper function to generate that:

Options[regionHull] =
  Join[
   {
    "CutoffFunction" -> (# > .5 &),
    "CutoffScaling" -> True,
    "VoxelCounts" -> {15, 15, 15}
    },
   Options[ConvexHullMesh]
   ];
regionHull[
  function_,
  spec :
   {
    {xmin_, xmax_},
    {ymin_, ymax_},
    {zmin_, zmax_}
    },
  ops : OptionsPattern[]
  ] :=
 Catch@
  Module[
   {
    crds,
    vox,
    vals,
    cutFn = OptionValue["CutoffFunction"],
    scale = TrueQ@OptionValue["CutoffScaling"],
    cutSpec,
    res
    },
   vox =
    Replace[OptionValue["VoxelCounts"],
     {
      i_Integer :> ConstantArray[i, 3],
      i : {__Integer} :> Take[Flatten[ConstantArray[i, 3]], 3],
      v_ :>
       Throw@
        Failure["voxels",
         <|
           "MessageTemplate" -> "Voxel spec `` invalid",
          "MessageParameters" -> {v}
          |>
         ]
      }
     ];
   crds =
    Flatten[CoordinateBoundsArray[spec, Into /@ vox], 2];
   vals =
    Replace[Quiet[function@crds],
     Except[{__?NumericQ}] :>
      Replace[
       function @@@ crds,
       Except[{__?NumericQ}] :>
        Throw@
         Failure["unreal",
          <|

           "MessageTemplate" -> 
            "Function `` didn't return real values over the \
coordinate box ``",
           "MessageParameters" -> {function, spec}
           |>
          ]
       ]
     ];
   If[scale, vals = Rescale[vals]];
   cutSpec =
    Replace[Quiet[cutFn@vals],
     Except[{(True | False) ..}] :>
      Replace[Map[cutFn, vals],
       Except[{(True | False) ..}] :>
        Throw@
         Failure["untrue",
          <|

           "MessageTemplate" -> 
            "Cutoff function `` didn't return boolean values over the \
values",
           "MessageParameters" -> {cutFn, spec}
           |>
          ]
       ]
     ];
   crds =
    Pick[
     crds,
     cutSpec
     ];
   res =
    ConvexHullMesh[
     crds,
     FilterRules[{ops}, Options[ConvexHullMesh]]
     ];
   If[RegionQ[res],
    res,
    Failure[
     "irreg",
     <|

      "MessageTemplate" -> 
       "Couldn't generate valid mesh. Try increasing \"VoxelCounts\" \
by 1 or 2 (can crash the kernel if overly large)"
      |>
     ]
    ]
   ]

Now if you wanted a hull for your function you could generate it like:

hull =
 regionHull[
  With[{a = 1},
   {x, y, z} \[Function] a*x + (y*z)
   ],
  {
   {0, 2},
   {0, 2},
   {1, 10}
   },
  ImageSize -> 250,
  BoxRatios -> {1, 1, 1 }
  ]

enter image description here

And this can easily be exported to file:

Export["~/Desktop/test.stl", hull]

"~/Desktop/test.stl"

And then if you want it for many parameters of a we make a list of hulls:

hulls =
  Table[
   regionHull[
    With[{a = a},
     {x, y, z} \[Function] a*x + (y*z)
     ],
    {
     {0, 2},
     {0, 2},
     {1, 10}
     }
    ],
   {a, 1, 5, 1}
   ];

MapIndexed[
 Export[
   "~/Desktop/test``.stl"~TemplateApply~#2,
   #
   ] &,
 hulls
 ]

{"~/Desktop/test1.stl", "~/Desktop/test2.stl", "~/Desktop/test3.stl", \
"~/Desktop/test4.stl", "~/Desktop/test5.stl"}

One thing to be wary of is that the method is pretty sensitive to the "VoxelsCount" parameter. When that is too large ConvexHullMesh can also crash the kernel for unknown reasons.

|improve this answer|||||
$\endgroup$
  • 1
    $\begingroup$ Another way is to turn your DensityPlot3D into a mesh with ImageMesh. ImageMesh[Image3D[Reverse[plot[[1, 1]], {1, 2}]], Method -> "DualMarchingCubes"] $\endgroup$ – Chip Hurst Jul 17 '18 at 16:50
  • $\begingroup$ @ChipHurst Wow that's beautiful. That should be the answer. Post it. $\endgroup$ – b3m2a1 Jul 17 '18 at 16:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.