5
$\begingroup$

Below is a code fragment demonstrating a problem that has been vexing me for days. The history is in my question #298513 (thanks again to @cvgmt and @user21), but knowledge of that question or its answers is not necessary for this question.

My goal. I wish to produce, as an STL-exportable region, a marker (a simple hex prism, cylinder, what have you) with a single digit "engraved into" or "scooped out of" the top surface.

My attempts. My simplistic approach follows below; note that RegionDifference fails with a complaint about a number of lines that apparently have one endpoint not connected to anything (BoundaryMeshRegion::bsuncl). I find that surprising as the RegionProduct that created regCharOriginal should not be terribly difficult to compute; besides, FindMeshDefects found nothing to complain about.

Following that I tried RepairMesh which did improve the situation; the RegionDifference now just remains unevaluated but no longer issues an error message. I presume that is related to the original volume being composed of triangular Prisms while the repaired mesh consists of a Polygon list (only triangles, apparently).

My question. To me this looks like a number of Mathematica bugs; on the other hand, I would find it difficult to exaggerate the degree of my ignorance regarding meshes and the like. So:

Should the code below work, or am I committing some glaring error?
Why does it work for character="1" and others that only have an external contour?
Why do some characters with "islands" ("6", "8", "9") work in some fonts?

Other things. Working with ElementMesh seems to lack the possibility of boolean operations on two such meshes. Am I overlooking something?

Looking into OpenCascadeLink, I find that everything I need should be doable, but how do I get from a 2D-surface (or mesh) for a character to a 3D-prism without individually turning each mesh triangle into a prism and then stitching them together in OpenCascade?

Thank you for reading this far. Any pointers, help etc. I will most gratefully receive.

$Version

(* "14.0.0 for Microsoft Windows (64-bit) (December 13, 2023)" *)

regBase=RegionProduct[
  CanonicalizePolygon[RegularPolygon[{11/Sqrt[3.],Pi/6},6]],
  Line[{{0.},{3.}}]
];

(* Output suppressed, would be 3D-image of a hexagonal prism *)

character="0"; (* with "1", the failure does not occur *)
regText2D=BoundaryDiscretizeGraphics[
  Text[Style[character,Bold,FontSize->8,FontFamily->"Arial"]],_Text];

(* Output suppressed *)

regCharOriginal=RegionProduct[regText2D,Line[{{3.},{2.}}]];

(* Output suppressed, would be a 3D- Region depicting "0" at coords that place it centered into the hex prism and right under the prism's top plane *)

FindMeshDefects[regCharOriginal]

(* No output, apparently the mesh has no discernible defects *)

regAll=RegionDifference[regBase,regCharOriginal]

(* Expected: hex prism with a "0" scooped out of the top *)
(* Actual output: error message, unevaluated RegionDifference *)
(* BoundaryMeshRegion::bsuncl: The boundary surface is not closed because the edges Line[{{2184,2181},{2175,574},{2227,2215},{652,653},{2552,2553},{2168,2409},{2284,2293},{728,729},{2545,2542},{2230,2180},<<139>>}] only come from a single face. *)
(* BooleanRegion[#1&&!#2&,{«image of hex prism»,«image of "0"»}] *)

regCharRepaired=RepairMesh[regCharOriginal]

(* No output *)

regAll=RegionDifference[regBase,regCharRepaired]

(* Expected: hex prism with a "0" scooped out of the top *)
(* Actual output: unevaluated RegionDifference *)
(* BooleanRegion[#1&&!#2&,{«image of hex prism»,«image of "0"»}] *)
$\endgroup$
1
  • $\begingroup$ I think it would be good to report this to support AT wolfram.com $\endgroup$
    – user21
    Feb 26 at 6:02

1 Answer 1

8
$\begingroup$
  • OpenCascadeLink work for such difference computation.
Clear["Global`*"];
Needs["OpenCascadeLink`"];
Needs["NDSolve`FEM`"];
poly = RegularPolygon[{11/Sqrt[3.], Pi/6}, 6];
regBase = 
  RegionProduct[
   CanonicalizePolygon[RegularPolygon[{11/Sqrt[3.], Pi/6}, 6]], 
   Line[{{0.}, {3.}}]];
character = "0"; 
regText2D = 
  BoundaryDiscretizeGraphics[
   Text[Style[character, Bold, FontSize -> 8, 
     FontFamily -> "Arial"]], _Text];
regCharOriginal = RegionProduct[regText2D, Line[{{3.}, {2.}}]];
bm1 = ToBoundaryMesh[regCharOriginal];
bm2 = ToBoundaryMesh[regBase];
shape1 = OpenCascadeShape[bm1];
shape2 = OpenCascadeShape[bm2];
shape = OpenCascadeShapeDifference[shape2, shape1];
OpenCascadeShapeSurfaceMeshToBoundaryMesh[
  shape] // BoundaryMeshRegion

enter image description here

  • Add the code by @user21 in the comment.( Needs the new version of `OpenCascadeLink``, for example, Mathematica 14.1.0 )
ocPoly = OpenCascadeShape[poly];
 extrudePoly = 
 OpenCascadeShapeLinearSweep[ocPoly, {{0, 0, 0}, {0, 0, 3}}]; ocChar =
  OpenCascadeShape[regText2D]; ocChar = 
 OpenCascadeShapeTransformation[ocChar, 
  TranslationTransform[{0, 0, 2}]]; extrudeChar = 
 OpenCascadeShapeLinearSweep[ocChar, {{0, 0, 2}, {0, 0, 3}}]; shape = 
 OpenCascadeShapeDifference[extrudePoly, 
  extrudeChar]; (bmesh = 
   OpenCascadeShapeSurfaceMeshToBoundaryMesh[shape])["Wireframe"]

enter image description here

$\endgroup$
6
  • 1
    $\begingroup$ A different approach would to use line = RegionBoundary[poly] /. {x_?NumericQ, y_} :> {x, y, 0.}; ocline = OpenCascadeShape[line]; extrudeLine = OpenCascadeShapeLinearSweep[ocline, {{0, 0, 0}, {0, 0, 3}}];. In the next version it will be much easier to convert 2D graphics primitives to OpenCascade shapes. Then we no longer need the {x_?NumericQ, y_} :> {x, y, 0.} $\endgroup$
    – user21
    Feb 26 at 6:18
  • $\begingroup$ @user21 Good news! $\endgroup$
    – cvgmt
    Feb 26 at 6:20
  • 1
    $\begingroup$ if you have other use cases that you think would be useful, please let me know. Also, the new code is already available if you want to play with it. github.com/WolframResearch/OpenCascadeLink $\endgroup$
    – user21
    Feb 26 at 6:23
  • $\begingroup$ @cvgmt user21 – Once again, I cannot thank you enough! $\endgroup$ Feb 26 at 9:01
  • $\begingroup$ In the next version (or if you get OpenCascadeLink from github) you will be able to directly use the 2D: ocPoly = OpenCascadeShape[poly]; extrudePoly = OpenCascadeShapeLinearSweep[ocPoly, {{0, 0, 0}, {0, 0, 3}}]; ocChar = OpenCascadeShape[regText2D]; ocChar = OpenCascadeShapeTransformation[ocChar, TranslationTransform[{0, 0, 2}]]; extrudeChar = OpenCascadeShapeLinearSweep[ocChar, {{0, 0, 2}, {0, 0, 3}}]; shape = OpenCascadeShapeDifference[extrudePoly, extrudeChar];(bmesh = OpenCascadeShapeSurfaceMeshToBoundaryMesh[shape])["Wireframe"] $\endgroup$
    – user21
    Feb 29 at 7:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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