From 66507c6159d99be04e3f0539c20b7208b1f7356e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 26 Aug 2026 23:11:30 +0000 Subject: [PATCH 1/3] Add region mesh style defined by a closed STL surface Ports the STL mesh support behind the LAMMPS "create_atoms mesh" command to SPARTA. Placing particles on the triangles themselves, as LAMMPS does for granular spheres, has no DSMC analogue (SPARTA particles have no radius, and gas seeded exactly on a surface is a problem once those triangles are also read as surfs), so the geometry is exposed the way it is useful here instead: as a region. region ID mesh file.stl [origin/trans/scale/rotate ...] [side in|out] The region is the volume enclosed by the surface, so it composes with everything that already takes a region, including create_particles, region union, and region intersect, and it describes shapes none of the analytic styles can. src/stl_reader.{h,cpp} is a port of the LAMMPS STLReader class that create_atoms mesh uses, keeping its structure, its text vs binary detection by file size, its ASCII grammar checks, its error messages, and its chunked broadcast for large meshes. The LAMMPS TextFileReader and ValueTokenizer classes have no SPARTA equivalent, so the ASCII parser reads and splits the lines itself. SPARTA already ships STL meshes in data/, which can now be read directly rather than only converted by tools/stl2surf.py. RegMesh::inside() casts a ray in +x and counts triangle crossings, with the triangles binned by their y-z extent so a query only tests the few that can be hit. A ray that grazes an edge or vertex is detected and recast with a small offset. The mesh is checked for closure, since the crossing parity is only meaningful for a watertight surface. There is no mesh/kk style, and none is needed for the reason the Restrictions section now gives: create_particles/kk tests points against the region on the host, so create_particles/kk works with a mesh region as is. The KOKKOS commands that test regions on the device already error out cleanly on a region style they do not support. Verified against the analytic styles: a mesh region and a region block of the same bounds create byte-identical particle sets at 1, 2, 3, and 4 MPI ranks, for side in and side out; binary and ASCII STL of the same sphere agree exactly; and create_particles/kk matches the non-Kokkos result at 1, 2, and 4 threads. Co-Authored-By: Stan Moore Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ACVHM1qF2xS53wZi4kirAH --- cmake/common/set/sparta_cmake_defaults.cmake | 1 + data/README | 6 +- doc/region.html | 65 +- doc/region.txt | 63 +- examples/region_mesh/air.species | 24 + examples/region_mesh/air.vss | 22 + examples/region_mesh/data.torus.stl | 4034 +++++++++++++++++ examples/region_mesh/in.region_mesh | 44 + .../region_mesh/log.26Aug26.mpi_1.region_mesh | 122 + .../region_mesh/log.26Aug26.mpi_4.region_mesh | 123 + src/region_mesh.cpp | 433 ++ src/region_mesh.h | 82 + src/stl_reader.cpp | 365 ++ src/stl_reader.h | 118 + 14 files changed, 5489 insertions(+), 13 deletions(-) create mode 100644 examples/region_mesh/air.species create mode 100644 examples/region_mesh/air.vss create mode 100644 examples/region_mesh/data.torus.stl create mode 100644 examples/region_mesh/in.region_mesh create mode 100644 examples/region_mesh/log.26Aug26.mpi_1.region_mesh create mode 100644 examples/region_mesh/log.26Aug26.mpi_4.region_mesh create mode 100644 src/region_mesh.cpp create mode 100644 src/region_mesh.h create mode 100644 src/stl_reader.cpp create mode 100644 src/stl_reader.h diff --git a/cmake/common/set/sparta_cmake_defaults.cmake b/cmake/common/set/sparta_cmake_defaults.cmake index 1774ccc6c..691e085dc 100644 --- a/cmake/common/set/sparta_cmake_defaults.cmake +++ b/cmake/common/set/sparta_cmake_defaults.cmake @@ -52,6 +52,7 @@ if(SPARTA_ENABLE_TESTING) "explicit2implicit" "mfp_mct" "optmove" + "region_mesh" "torque") set(SPARTA_DISABLED_TESTS diff --git a/data/README b/data/README index 8c432a681..d04586f15 100644 --- a/data/README +++ b/data/README @@ -23,7 +23,9 @@ are read by the read_surf command. Files that end in ".stl" are STL-formatted surface files which can be converted to SPARTA formatted surface files via the stl2surf.py tool -in the tools directory. +in the tools directory. They can also be read directly by the region +command with its mesh style, which defines the region as the volume +enclosed by the surface. Files that end in ".surf" list reactions for surface chemistry and are read by the surf_react command. @@ -36,5 +38,5 @@ species.vib = doc/species.html vss/vhs/hs = doc/collide.html tce = doc/react.html sdata = doc/read_surf.html -stl = doc/Section_tools.html#stl2surf +stl = doc/Section_tools.html#stl2surf and doc/region.html surf = doc/surf_react.html diff --git a/doc/region.html b/doc/region.html index ac9212aaa..2a847f7ff 100644 --- a/doc/region.html +++ b/doc/region.html @@ -17,7 +17,7 @@

region command
  • ID = user-assigned name for the region -
  • style = block or cylinder or plane or sphere or union or intersect or block/kk or cylinder/kk or plane/kk or sphere/kk +
  • style = block or cylinder or mesh or plane or sphere or union or intersect or block/kk or cylinder/kk or plane/kk or sphere/kk
      block or block/kk args = xlo xhi ylo yhi zlo zhi
         xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units)
    @@ -26,6 +26,8 @@ 

    region command c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) radius = cylinder radius (distance units) lo,hi = bounds of cylinder in dim (distance units) + mesh args = file + file = name of file with a closed triangulated surface in STL format plane or plane/kk args = px py pz nx ny nz px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) @@ -41,11 +43,20 @@

    region command

  • zero or more keyword/value pairs may be appended -
  • keyword = side +
  • keyword = side or origin or trans or scale or rotate
      side value = in or out
         in = the region is inside the specified geometry
    -    out = the region is outside the specified geometry 
    +    out = the region is outside the specified geometry
    +  origin values = Ox Oy Oz (only for style mesh)
    +    Ox,Oy,Oz = point that scale and rotate operate around (distance units)
    +  trans values = Dx Dy Dz (only for style mesh)
    +    Dx,Dy,Dz = distance to translate the mesh by (distance units)
    +  scale values = Sx Sy Sz (only for style mesh)
    +    Sx,Sy,Sz = scale factor to apply to the mesh in each dimension
    +  rotate values = theta Rx Ry Rz (only for style mesh)
    +    theta = angle to rotate the mesh by (degrees)
    +    Rx,Ry,Rz = axis of rotation 
     
@@ -54,7 +65,9 @@

region command
region 1 block -3.0 5.0 INF 10.0 INF INF
 region 2 sphere 0.0 0.0 0.0 5 side out
 region void cylinder y 2 3 5 -5.0 INF
-region outside union 4 side1 side2 side3 side4 
+region outside union 4 side1 side2 side3 side4
+region flask mesh flask.stl
+region duct mesh duct.stl scale 0.001 0.001 0.001 side out 
 

Description:

@@ -82,6 +95,37 @@

region command y-direction located at x = 2.0 and z = 3.0, with a radius of 5.0, and extending in the y-direction from -5.0 to infinity.

+

The mesh style defines the region as the volume enclosed by a closed +triangulated surface, which is read from a file in STL +(stereolithography) format. Both variants of that format are supported, +plain text ("ASCII") and binary. Which one a file is, is detected from +its contents, not from its name. The direction of the triangle normals +stored in the file is ignored. Every processor reads and stores the +entire mesh, so this style is best suited to small and medium sized +meshes. +

+

A point is tested against the mesh by casting a ray from the point in +the +x direction and counting how many triangles it crosses. An odd +count means the point is inside the surface. This requires the surface +to be closed (watertight). SPARTA prints a warning if the triangles in +the file do not enclose a volume, in which case the inside/outside test +gives arbitrary answers. As for the other styles, a point that lies +exactly on the surface is considered to be inside it, and the side +keyword selects whether the region is the volume inside or outside the +mesh. +

+

The origin, trans, scale, and rotate keywords can only be used +with the mesh style. They transform the mesh after it is read, which +is useful when the STL file uses different units or is positioned +differently than the simulation box. They are applied in the order they +appear in the command, and follow the same conventions as the +read_surf keywords of the same name. Trans +translates the mesh by the specified distance. Scale multiplies the +distance of every vertex from the origin point by the specified +factors. Rotate rotates the mesh by theta degrees around the axis +Rx,Ry,Rz which passes through the origin point. The origin point is +0,0,0 by default and is moved along by any preceding trans. +

The union style creates a region consisting of the volume of all the listed regions combined. The intersect style creates a region consisting of the volume that is common to all the listed regions. @@ -127,11 +171,20 @@

region command


-

Restrictions: none +

Restrictions: +

+

There is no mesh/kk style. The +create_particles command tests points against a +region on the host, so a mesh region can be used with +create_particles/kk. Commands that test regions on the device, such as +fix emit/face and fix ave/histo, +will generate an error if they are given a mesh region when running +with the KOKKOS package.

Related commands:

-

dump_modify +

create_particles, read_surf, +dump_modify

Default:

diff --git a/doc/region.txt b/doc/region.txt index 7ad8b3150..22e0ae09f 100644 --- a/doc/region.txt +++ b/doc/region.txt @@ -13,7 +13,7 @@ region command :h3 region ID style args keyword value ... :pre ID = user-assigned name for the region :ulb,l -style = {block} or {cylinder} or {plane} or {sphere} or {union} or {intersect} or {block/kk} or {cylinder/kk} or {plane/kk} or {sphere/kk} :l +style = {block} or {cylinder} or {mesh} or {plane} or {sphere} or {union} or {intersect} or {block/kk} or {cylinder/kk} or {plane/kk} or {sphere/kk} :l {block} or {block/kk} args = xlo xhi ylo yhi zlo zhi xlo,xhi,ylo,yhi,zlo,zhi = bounds of block in all dimensions (distance units) {cylinder} or {cylinder/kk} args = dim c1 c2 radius lo hi @@ -21,6 +21,8 @@ style = {block} or {cylinder} or {plane} or {sphere} or {union} or {intersect} o c1,c2 = coords of cylinder axis in other 2 dimensions (distance units) radius = cylinder radius (distance units) lo,hi = bounds of cylinder in dim (distance units) + {mesh} args = file + file = name of file with a closed triangulated surface in STL format {plane} or {plane/kk} args = px py pz nx ny nz px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) @@ -34,10 +36,19 @@ style = {block} or {cylinder} or {plane} or {sphere} or {union} or {intersect} o N = # of regions to follow, must be 2 or greater reg-ID1,reg-ID2, ... = IDs of regions to intersect :pre zero or more keyword/value pairs may be appended :l -keyword = {side} :l +keyword = {side} or {origin} or {trans} or {scale} or {rotate} :l {side} value = {in} or {out} {in} = the region is inside the specified geometry - {out} = the region is outside the specified geometry :pre + {out} = the region is outside the specified geometry + {origin} values = Ox Oy Oz (only for style {mesh}) + Ox,Oy,Oz = point that {scale} and {rotate} operate around (distance units) + {trans} values = Dx Dy Dz (only for style {mesh}) + Dx,Dy,Dz = distance to translate the mesh by (distance units) + {scale} values = Sx Sy Sz (only for style {mesh}) + Sx,Sy,Sz = scale factor to apply to the mesh in each dimension + {rotate} values = theta Rx Ry Rz (only for style {mesh}) + theta = angle to rotate the mesh by (degrees) + Rx,Ry,Rz = axis of rotation :pre :ule [Examples:] @@ -45,7 +56,9 @@ keyword = {side} :l region 1 block -3.0 5.0 INF 10.0 INF INF region 2 sphere 0.0 0.0 0.0 5 side out region void cylinder y 2 3 5 -5.0 INF -region outside union 4 side1 side2 side3 side4 :pre +region outside union 4 side1 side2 side3 side4 +region flask mesh flask.stl +region duct mesh duct.stl scale 0.001 0.001 0.001 side out :pre [Description:] @@ -73,6 +86,37 @@ third example above specifies a cylinder with its axis in the y-direction located at x = 2.0 and z = 3.0, with a radius of 5.0, and extending in the y-direction from -5.0 to infinity. +The {mesh} style defines the region as the volume enclosed by a closed +triangulated surface, which is read from a file in STL +(stereolithography) format. Both variants of that format are supported, +plain text ("ASCII") and binary. Which one a file is, is detected from +its contents, not from its name. The direction of the triangle normals +stored in the file is ignored. Every processor reads and stores the +entire mesh, so this style is best suited to small and medium sized +meshes. + +A point is tested against the mesh by casting a ray from the point in +the +x direction and counting how many triangles it crosses. An odd +count means the point is inside the surface. This requires the surface +to be closed (watertight). SPARTA prints a warning if the triangles in +the file do not enclose a volume, in which case the inside/outside test +gives arbitrary answers. As for the other styles, a point that lies +exactly on the surface is considered to be inside it, and the {side} +keyword selects whether the region is the volume inside or outside the +mesh. + +The {origin}, {trans}, {scale}, and {rotate} keywords can only be used +with the {mesh} style. They transform the mesh after it is read, which +is useful when the STL file uses different units or is positioned +differently than the simulation box. They are applied in the order they +appear in the command, and follow the same conventions as the +"read_surf"_read_surf.html keywords of the same name. {Trans} +translates the mesh by the specified distance. {Scale} multiplies the +distance of every vertex from the {origin} point by the specified +factors. {Rotate} rotates the mesh by theta degrees around the axis +Rx,Ry,Rz which passes through the {origin} point. The {origin} point is +0,0,0 by default and is moved along by any preceding {trans}. + The {union} style creates a region consisting of the volume of all the listed regions combined. The {intersect} style creates a region consisting of the volume that is common to all the listed regions. @@ -118,10 +162,19 @@ effectively. :line -[Restrictions:] none +[Restrictions:] + +There is no {mesh/kk} style. The +"create_particles"_create_particles.html command tests points against a +region on the host, so a {mesh} region can be used with +create_particles/kk. Commands that test regions on the device, such as +"fix emit/face"_fix_emit_face.html and "fix ave/histo"_fix_ave_histo.html, +will generate an error if they are given a {mesh} region when running +with the KOKKOS package. [Related commands:] +"create_particles"_create_particles.html, "read_surf"_read_surf.html, "dump_modify"_dump_modify.html [Default:] diff --git a/examples/region_mesh/air.species b/examples/region_mesh/air.species new file mode 100644 index 000000000..d102d9070 --- /dev/null +++ b/examples/region_mesh/air.species @@ -0,0 +1,24 @@ +# Species data + +# ID +# Molwt (amu) +# Molmass (kg) +# Rotational dof +# RotRel +# Vibrational dof +# VibRel +# VibTemp (K) +# species wt +# charge + +O2 32.00 5.31E-26 2 0.2 2 5.58659E-5 2256.0 1.0 0.0 +N2 28.016 4.65E-26 2 0.2 2 1.90114E-5 3371.0 1.0 0.0 +O 16.00 2.65E-26 0 0.0 0 0.0 0.0 1.0 0.0 +N 14.008 2.325E-26 0 0.0 0 0.0 0.0 1.0 0.0 +NO 30.008 4.98E-26 2 0.2 2 7.14285E-4 2719.0 1.0 0.0 +O2+ 32.00 5.31E-26 2 0.2 2 5.58659E-5 2256.0 1.0 1.0 +N2+ 28.016 4.65E-26 2 0.2 2 1.90114E-5 3371.0 1.0 1.0 +O+ 16.00 2.65E-26 0 0.0 0 0.0 0.0 1.0 1.0 +N+ 14.008 2.325E-26 0 0.0 0 0.0 0.0 1.0 1.0 +NO+ 30.008 4.98E-26 2 0.2 2 7.14285E-4 2719.0 1.0 1.0 +e 0.001 9.10938188E-31 0 0.0 0 0.0 0.0 1.0 -1.0 diff --git a/examples/region_mesh/air.vss b/examples/region_mesh/air.vss new file mode 100644 index 000000000..9c7ecbe5d --- /dev/null +++ b/examples/region_mesh/air.vss @@ -0,0 +1,22 @@ +# VSS collision model parameters for each species + +# diameter (mass) +# omega (unitless) +# tref (temperature) +# alpha (unitless) +# Zrotinf (unitless) +# T* (temperature) +# C1 (temperature) +# C2 (temperature^(1/3)) + +O2 3.96E-10 0.77 273.15 1.4 16.5 113.5 56.5 153.5 +N2 4.07E-10 0.74 273.15 1.6 18.1 91.5 9.1 220.0 +O 3.0E-10 0.80 273.15 1.0 0.0 0.0 0.0 0.0 +N 3.0E-10 0.80 273.15 1.0 0.0 0.0 0.0 0.0 +NO 4.0E-10 0.80 273.15 1.0 7.5 119.0 9.10 220.00 +O2+ 3.96E-10 0.77 273.15 1.4 16.5 113.5 56.5 153.5 +N2+ 4.07E-10 0.74 273.15 1.6 18.1 91.5 9.1 220.0 +O+ 3.0E-10 0.80 273.15 1.0 0.0 0.0 0.0 0.0 +N+ 3.0E-10 0.80 273.15 1.0 0.0 0.0 0.0 0.0 +NO+ 4.0E-10 0.80 273.15 1.0 7.5 119.0 9.10 220.00 +e 7.0E-13 0.50 273.15 1.0 0.0 0.0 0.0 0.0 diff --git a/examples/region_mesh/data.torus.stl b/examples/region_mesh/data.torus.stl new file mode 100644 index 000000000..5266fd9b9 --- /dev/null +++ b/examples/region_mesh/data.torus.stl @@ -0,0 +1,4034 @@ +solid torus + facet normal 0.958209 0.126151 0.256751 + outer loop + vertex 4 0 0 + vertex 3.8637 1.03528 0 + vertex 3.73429 1.0006 0.5 + endloop + endfacet + facet normal 0.958209 0.126151 0.256751 + outer loop + vertex 4 0 0 + vertex 3.73429 1.0006 0.5 + vertex 3.86603 0 0.5 + endloop + endfacet + facet normal 0.704063 0.0926916 0.704063 + outer loop + vertex 3.86603 0 0.5 + vertex 3.73429 1.0006 0.5 + vertex 3.38074 0.905867 0.866025 + endloop + endfacet + facet normal 0.704063 0.0926916 0.704063 + outer loop + vertex 3.86603 0 0.5 + vertex 3.38074 0.905867 0.866025 + vertex 3.5 0 0.866025 + endloop + endfacet + facet normal 0.258669 0.0340544 0.965366 + outer loop + vertex 3.5 0 0.866025 + vertex 3.38074 0.905867 0.866025 + vertex 2.89778 0.776457 1 + endloop + endfacet + facet normal 0.258669 0.0340544 0.965366 + outer loop + vertex 3.5 0 0.866025 + vertex 2.89778 0.776457 1 + vertex 3 0 1 + endloop + endfacet + facet normal -0.258669 -0.0340544 0.965366 + outer loop + vertex 3 0 1 + vertex 2.89778 0.776457 1 + vertex 2.41481 0.647048 0.866025 + endloop + endfacet + facet normal -0.258669 -0.0340544 0.965366 + outer loop + vertex 3 0 1 + vertex 2.41481 0.647048 0.866025 + vertex 2.5 0 0.866025 + endloop + endfacet + facet normal -0.704063 -0.0926916 0.704063 + outer loop + vertex 2.5 0 0.866025 + vertex 2.41481 0.647048 0.866025 + vertex 2.06126 0.552313 0.5 + endloop + endfacet + facet normal -0.704063 -0.0926916 0.704063 + outer loop + vertex 2.5 0 0.866025 + vertex 2.06126 0.552313 0.5 + vertex 2.13397 0 0.5 + endloop + endfacet + facet normal -0.958209 -0.126151 0.256751 + outer loop + vertex 2.13397 0 0.5 + vertex 2.06126 0.552313 0.5 + vertex 1.93185 0.517638 1.22465e-16 + endloop + endfacet + facet normal -0.958209 -0.126151 0.256751 + outer loop + vertex 2.13397 0 0.5 + vertex 1.93185 0.517638 1.22465e-16 + vertex 2 0 1.22465e-16 + endloop + endfacet + facet normal -0.958209 -0.126151 -0.256751 + outer loop + vertex 2 0 1.22465e-16 + vertex 1.93185 0.517638 1.22465e-16 + vertex 2.06126 0.552313 -0.5 + endloop + endfacet + facet normal -0.958209 -0.126151 -0.256751 + outer loop + vertex 2 0 1.22465e-16 + vertex 2.06126 0.552313 -0.5 + vertex 2.13397 0 -0.5 + endloop + endfacet + facet normal -0.704063 -0.0926916 -0.704063 + outer loop + vertex 2.13397 0 -0.5 + vertex 2.06126 0.552313 -0.5 + vertex 2.41481 0.647048 -0.866025 + endloop + endfacet + facet normal -0.704063 -0.0926916 -0.704063 + outer loop + vertex 2.13397 0 -0.5 + vertex 2.41481 0.647048 -0.866025 + vertex 2.5 0 -0.866025 + endloop + endfacet + facet normal -0.258669 -0.0340544 -0.965366 + outer loop + vertex 2.5 0 -0.866025 + vertex 2.41481 0.647048 -0.866025 + vertex 2.89778 0.776457 -1 + endloop + endfacet + facet normal -0.258669 -0.0340544 -0.965366 + outer loop + vertex 2.5 0 -0.866025 + vertex 2.89778 0.776457 -1 + vertex 3 0 -1 + endloop + endfacet + facet normal 0.258669 0.0340544 -0.965366 + outer loop + vertex 3 0 -1 + vertex 2.89778 0.776457 -1 + vertex 3.38074 0.905867 -0.866025 + endloop + endfacet + facet normal 0.258669 0.0340544 -0.965366 + outer loop + vertex 3 0 -1 + vertex 3.38074 0.905867 -0.866025 + vertex 3.5 0 -0.866025 + endloop + endfacet + facet normal 0.704063 0.0926916 -0.704063 + outer loop + vertex 3.5 0 -0.866025 + vertex 3.38074 0.905867 -0.866025 + vertex 3.73429 1.0006 -0.5 + endloop + endfacet + facet normal 0.704063 0.0926916 -0.704063 + outer loop + vertex 3.5 0 -0.866025 + vertex 3.73429 1.0006 -0.5 + vertex 3.86603 0 -0.5 + endloop + endfacet + facet normal 0.958209 0.126151 -0.256751 + outer loop + vertex 3.86603 0 -0.5 + vertex 3.73429 1.0006 -0.5 + vertex 3.8637 1.03528 -2.44929e-16 + endloop + endfacet + facet normal 0.958209 0.126151 -0.256751 + outer loop + vertex 3.86603 0 -0.5 + vertex 3.8637 1.03528 -2.44929e-16 + vertex 4 0 -2.44929e-16 + endloop + endfacet + facet normal 0.892909 0.369855 0.256751 + outer loop + vertex 3.8637 1.03528 0 + vertex 3.4641 2 0 + vertex 3.34808 1.93301 0.5 + endloop + endfacet + facet normal 0.892909 0.369855 0.256751 + outer loop + vertex 3.8637 1.03528 0 + vertex 3.34808 1.93301 0.5 + vertex 3.73429 1.0006 0.5 + endloop + endfacet + facet normal 0.656082 0.271758 0.704063 + outer loop + vertex 3.73429 1.0006 0.5 + vertex 3.34808 1.93301 0.5 + vertex 3.03109 1.75 0.866025 + endloop + endfacet + facet normal 0.656082 0.271758 0.704063 + outer loop + vertex 3.73429 1.0006 0.5 + vertex 3.03109 1.75 0.866025 + vertex 3.38074 0.905867 0.866025 + endloop + endfacet + facet normal 0.241041 0.0998425 0.965366 + outer loop + vertex 3.38074 0.905867 0.866025 + vertex 3.03109 1.75 0.866025 + vertex 2.59808 1.5 1 + endloop + endfacet + facet normal 0.241041 0.0998425 0.965366 + outer loop + vertex 3.38074 0.905867 0.866025 + vertex 2.59808 1.5 1 + vertex 2.89778 0.776457 1 + endloop + endfacet + facet normal -0.241041 -0.0998425 0.965366 + outer loop + vertex 2.89778 0.776457 1 + vertex 2.59808 1.5 1 + vertex 2.16506 1.25 0.866025 + endloop + endfacet + facet normal -0.241041 -0.0998425 0.965366 + outer loop + vertex 2.89778 0.776457 1 + vertex 2.16506 1.25 0.866025 + vertex 2.41481 0.647048 0.866025 + endloop + endfacet + facet normal -0.656082 -0.271758 0.704063 + outer loop + vertex 2.41481 0.647048 0.866025 + vertex 2.16506 1.25 0.866025 + vertex 1.84808 1.06699 0.5 + endloop + endfacet + facet normal -0.656082 -0.271758 0.704063 + outer loop + vertex 2.41481 0.647048 0.866025 + vertex 1.84808 1.06699 0.5 + vertex 2.06126 0.552313 0.5 + endloop + endfacet + facet normal -0.892909 -0.369855 0.256751 + outer loop + vertex 2.06126 0.552313 0.5 + vertex 1.84808 1.06699 0.5 + vertex 1.73205 1 1.22465e-16 + endloop + endfacet + facet normal -0.892909 -0.369855 0.256751 + outer loop + vertex 2.06126 0.552313 0.5 + vertex 1.73205 1 1.22465e-16 + vertex 1.93185 0.517638 1.22465e-16 + endloop + endfacet + facet normal -0.892909 -0.369855 -0.256751 + outer loop + vertex 1.93185 0.517638 1.22465e-16 + vertex 1.73205 1 1.22465e-16 + vertex 1.84808 1.06699 -0.5 + endloop + endfacet + facet normal -0.892909 -0.369855 -0.256751 + outer loop + vertex 1.93185 0.517638 1.22465e-16 + vertex 1.84808 1.06699 -0.5 + vertex 2.06126 0.552313 -0.5 + endloop + endfacet + facet normal -0.656082 -0.271758 -0.704063 + outer loop + vertex 2.06126 0.552313 -0.5 + vertex 1.84808 1.06699 -0.5 + vertex 2.16506 1.25 -0.866025 + endloop + endfacet + facet normal -0.656082 -0.271758 -0.704063 + outer loop + vertex 2.06126 0.552313 -0.5 + vertex 2.16506 1.25 -0.866025 + vertex 2.41481 0.647048 -0.866025 + endloop + endfacet + facet normal -0.241041 -0.0998425 -0.965366 + outer loop + vertex 2.41481 0.647048 -0.866025 + vertex 2.16506 1.25 -0.866025 + vertex 2.59808 1.5 -1 + endloop + endfacet + facet normal -0.241041 -0.0998425 -0.965366 + outer loop + vertex 2.41481 0.647048 -0.866025 + vertex 2.59808 1.5 -1 + vertex 2.89778 0.776457 -1 + endloop + endfacet + facet normal 0.241041 0.0998425 -0.965366 + outer loop + vertex 2.89778 0.776457 -1 + vertex 2.59808 1.5 -1 + vertex 3.03109 1.75 -0.866025 + endloop + endfacet + facet normal 0.241041 0.0998425 -0.965366 + outer loop + vertex 2.89778 0.776457 -1 + vertex 3.03109 1.75 -0.866025 + vertex 3.38074 0.905867 -0.866025 + endloop + endfacet + facet normal 0.656082 0.271758 -0.704063 + outer loop + vertex 3.38074 0.905867 -0.866025 + vertex 3.03109 1.75 -0.866025 + vertex 3.34808 1.93301 -0.5 + endloop + endfacet + facet normal 0.656082 0.271758 -0.704063 + outer loop + vertex 3.38074 0.905867 -0.866025 + vertex 3.34808 1.93301 -0.5 + vertex 3.73429 1.0006 -0.5 + endloop + endfacet + facet normal 0.892909 0.369855 -0.256751 + outer loop + vertex 3.73429 1.0006 -0.5 + vertex 3.34808 1.93301 -0.5 + vertex 3.4641 2 -2.44929e-16 + endloop + endfacet + facet normal 0.892909 0.369855 -0.256751 + outer loop + vertex 3.73429 1.0006 -0.5 + vertex 3.4641 2 -2.44929e-16 + vertex 3.8637 1.03528 -2.44929e-16 + endloop + endfacet + facet normal 0.766758 0.588354 0.256751 + outer loop + vertex 3.4641 2 0 + vertex 2.82843 2.82843 0 + vertex 2.73369 2.73369 0.5 + endloop + endfacet + facet normal 0.766758 0.588354 0.256751 + outer loop + vertex 3.4641 2 0 + vertex 2.73369 2.73369 0.5 + vertex 3.34808 1.93301 0.5 + endloop + endfacet + facet normal 0.56339 0.432305 0.704063 + outer loop + vertex 3.34808 1.93301 0.5 + vertex 2.73369 2.73369 0.5 + vertex 2.47487 2.47487 0.866025 + endloop + endfacet + facet normal 0.56339 0.432305 0.704063 + outer loop + vertex 3.34808 1.93301 0.5 + vertex 2.47487 2.47487 0.866025 + vertex 3.03109 1.75 0.866025 + endloop + endfacet + facet normal 0.206987 0.158826 0.965366 + outer loop + vertex 3.03109 1.75 0.866025 + vertex 2.47487 2.47487 0.866025 + vertex 2.12132 2.12132 1 + endloop + endfacet + facet normal 0.206987 0.158826 0.965366 + outer loop + vertex 3.03109 1.75 0.866025 + vertex 2.12132 2.12132 1 + vertex 2.59808 1.5 1 + endloop + endfacet + facet normal -0.206987 -0.158826 0.965366 + outer loop + vertex 2.59808 1.5 1 + vertex 2.12132 2.12132 1 + vertex 1.76777 1.76777 0.866025 + endloop + endfacet + facet normal -0.206987 -0.158826 0.965366 + outer loop + vertex 2.59808 1.5 1 + vertex 1.76777 1.76777 0.866025 + vertex 2.16506 1.25 0.866025 + endloop + endfacet + facet normal -0.56339 -0.432305 0.704063 + outer loop + vertex 2.16506 1.25 0.866025 + vertex 1.76777 1.76777 0.866025 + vertex 1.50895 1.50895 0.5 + endloop + endfacet + facet normal -0.56339 -0.432305 0.704063 + outer loop + vertex 2.16506 1.25 0.866025 + vertex 1.50895 1.50895 0.5 + vertex 1.84808 1.06699 0.5 + endloop + endfacet + facet normal -0.766758 -0.588354 0.256751 + outer loop + vertex 1.84808 1.06699 0.5 + vertex 1.50895 1.50895 0.5 + vertex 1.41421 1.41421 1.22465e-16 + endloop + endfacet + facet normal -0.766758 -0.588354 0.256751 + outer loop + vertex 1.84808 1.06699 0.5 + vertex 1.41421 1.41421 1.22465e-16 + vertex 1.73205 1 1.22465e-16 + endloop + endfacet + facet normal -0.766758 -0.588354 -0.256751 + outer loop + vertex 1.73205 1 1.22465e-16 + vertex 1.41421 1.41421 1.22465e-16 + vertex 1.50895 1.50895 -0.5 + endloop + endfacet + facet normal -0.766758 -0.588354 -0.256751 + outer loop + vertex 1.73205 1 1.22465e-16 + vertex 1.50895 1.50895 -0.5 + vertex 1.84808 1.06699 -0.5 + endloop + endfacet + facet normal -0.56339 -0.432305 -0.704063 + outer loop + vertex 1.84808 1.06699 -0.5 + vertex 1.50895 1.50895 -0.5 + vertex 1.76777 1.76777 -0.866025 + endloop + endfacet + facet normal -0.56339 -0.432305 -0.704063 + outer loop + vertex 1.84808 1.06699 -0.5 + vertex 1.76777 1.76777 -0.866025 + vertex 2.16506 1.25 -0.866025 + endloop + endfacet + facet normal -0.206987 -0.158826 -0.965366 + outer loop + vertex 2.16506 1.25 -0.866025 + vertex 1.76777 1.76777 -0.866025 + vertex 2.12132 2.12132 -1 + endloop + endfacet + facet normal -0.206987 -0.158826 -0.965366 + outer loop + vertex 2.16506 1.25 -0.866025 + vertex 2.12132 2.12132 -1 + vertex 2.59808 1.5 -1 + endloop + endfacet + facet normal 0.206987 0.158826 -0.965366 + outer loop + vertex 2.59808 1.5 -1 + vertex 2.12132 2.12132 -1 + vertex 2.47487 2.47487 -0.866025 + endloop + endfacet + facet normal 0.206987 0.158826 -0.965366 + outer loop + vertex 2.59808 1.5 -1 + vertex 2.47487 2.47487 -0.866025 + vertex 3.03109 1.75 -0.866025 + endloop + endfacet + facet normal 0.56339 0.432305 -0.704063 + outer loop + vertex 3.03109 1.75 -0.866025 + vertex 2.47487 2.47487 -0.866025 + vertex 2.73369 2.73369 -0.5 + endloop + endfacet + facet normal 0.56339 0.432305 -0.704063 + outer loop + vertex 3.03109 1.75 -0.866025 + vertex 2.73369 2.73369 -0.5 + vertex 3.34808 1.93301 -0.5 + endloop + endfacet + facet normal 0.766758 0.588354 -0.256751 + outer loop + vertex 3.34808 1.93301 -0.5 + vertex 2.73369 2.73369 -0.5 + vertex 2.82843 2.82843 -2.44929e-16 + endloop + endfacet + facet normal 0.766758 0.588354 -0.256751 + outer loop + vertex 3.34808 1.93301 -0.5 + vertex 2.82843 2.82843 -2.44929e-16 + vertex 3.4641 2 -2.44929e-16 + endloop + endfacet + facet normal 0.588354 0.766758 0.256751 + outer loop + vertex 2.82843 2.82843 0 + vertex 2 3.4641 0 + vertex 1.93301 3.34808 0.5 + endloop + endfacet + facet normal 0.588354 0.766758 0.256751 + outer loop + vertex 2.82843 2.82843 0 + vertex 1.93301 3.34808 0.5 + vertex 2.73369 2.73369 0.5 + endloop + endfacet + facet normal 0.432305 0.56339 0.704063 + outer loop + vertex 2.73369 2.73369 0.5 + vertex 1.93301 3.34808 0.5 + vertex 1.75 3.03109 0.866025 + endloop + endfacet + facet normal 0.432305 0.56339 0.704063 + outer loop + vertex 2.73369 2.73369 0.5 + vertex 1.75 3.03109 0.866025 + vertex 2.47487 2.47487 0.866025 + endloop + endfacet + facet normal 0.158826 0.206987 0.965366 + outer loop + vertex 2.47487 2.47487 0.866025 + vertex 1.75 3.03109 0.866025 + vertex 1.5 2.59808 1 + endloop + endfacet + facet normal 0.158826 0.206987 0.965366 + outer loop + vertex 2.47487 2.47487 0.866025 + vertex 1.5 2.59808 1 + vertex 2.12132 2.12132 1 + endloop + endfacet + facet normal -0.158826 -0.206987 0.965366 + outer loop + vertex 2.12132 2.12132 1 + vertex 1.5 2.59808 1 + vertex 1.25 2.16506 0.866025 + endloop + endfacet + facet normal -0.158826 -0.206987 0.965366 + outer loop + vertex 2.12132 2.12132 1 + vertex 1.25 2.16506 0.866025 + vertex 1.76777 1.76777 0.866025 + endloop + endfacet + facet normal -0.432305 -0.56339 0.704063 + outer loop + vertex 1.76777 1.76777 0.866025 + vertex 1.25 2.16506 0.866025 + vertex 1.06699 1.84808 0.5 + endloop + endfacet + facet normal -0.432305 -0.56339 0.704063 + outer loop + vertex 1.76777 1.76777 0.866025 + vertex 1.06699 1.84808 0.5 + vertex 1.50895 1.50895 0.5 + endloop + endfacet + facet normal -0.588354 -0.766758 0.256751 + outer loop + vertex 1.50895 1.50895 0.5 + vertex 1.06699 1.84808 0.5 + vertex 1 1.73205 1.22465e-16 + endloop + endfacet + facet normal -0.588354 -0.766758 0.256751 + outer loop + vertex 1.50895 1.50895 0.5 + vertex 1 1.73205 1.22465e-16 + vertex 1.41421 1.41421 1.22465e-16 + endloop + endfacet + facet normal -0.588354 -0.766758 -0.256751 + outer loop + vertex 1.41421 1.41421 1.22465e-16 + vertex 1 1.73205 1.22465e-16 + vertex 1.06699 1.84808 -0.5 + endloop + endfacet + facet normal -0.588354 -0.766758 -0.256751 + outer loop + vertex 1.41421 1.41421 1.22465e-16 + vertex 1.06699 1.84808 -0.5 + vertex 1.50895 1.50895 -0.5 + endloop + endfacet + facet normal -0.432305 -0.56339 -0.704063 + outer loop + vertex 1.50895 1.50895 -0.5 + vertex 1.06699 1.84808 -0.5 + vertex 1.25 2.16506 -0.866025 + endloop + endfacet + facet normal -0.432305 -0.56339 -0.704063 + outer loop + vertex 1.50895 1.50895 -0.5 + vertex 1.25 2.16506 -0.866025 + vertex 1.76777 1.76777 -0.866025 + endloop + endfacet + facet normal -0.158826 -0.206987 -0.965366 + outer loop + vertex 1.76777 1.76777 -0.866025 + vertex 1.25 2.16506 -0.866025 + vertex 1.5 2.59808 -1 + endloop + endfacet + facet normal -0.158826 -0.206987 -0.965366 + outer loop + vertex 1.76777 1.76777 -0.866025 + vertex 1.5 2.59808 -1 + vertex 2.12132 2.12132 -1 + endloop + endfacet + facet normal 0.158826 0.206987 -0.965366 + outer loop + vertex 2.12132 2.12132 -1 + vertex 1.5 2.59808 -1 + vertex 1.75 3.03109 -0.866025 + endloop + endfacet + facet normal 0.158826 0.206987 -0.965366 + outer loop + vertex 2.12132 2.12132 -1 + vertex 1.75 3.03109 -0.866025 + vertex 2.47487 2.47487 -0.866025 + endloop + endfacet + facet normal 0.432305 0.56339 -0.704063 + outer loop + vertex 2.47487 2.47487 -0.866025 + vertex 1.75 3.03109 -0.866025 + vertex 1.93301 3.34808 -0.5 + endloop + endfacet + facet normal 0.432305 0.56339 -0.704063 + outer loop + vertex 2.47487 2.47487 -0.866025 + vertex 1.93301 3.34808 -0.5 + vertex 2.73369 2.73369 -0.5 + endloop + endfacet + facet normal 0.588354 0.766758 -0.256751 + outer loop + vertex 2.73369 2.73369 -0.5 + vertex 1.93301 3.34808 -0.5 + vertex 2 3.4641 -2.44929e-16 + endloop + endfacet + facet normal 0.588354 0.766758 -0.256751 + outer loop + vertex 2.73369 2.73369 -0.5 + vertex 2 3.4641 -2.44929e-16 + vertex 2.82843 2.82843 -2.44929e-16 + endloop + endfacet + facet normal 0.369855 0.892909 0.256751 + outer loop + vertex 2 3.4641 0 + vertex 1.03528 3.8637 0 + vertex 1.0006 3.73429 0.5 + endloop + endfacet + facet normal 0.369855 0.892909 0.256751 + outer loop + vertex 2 3.4641 0 + vertex 1.0006 3.73429 0.5 + vertex 1.93301 3.34808 0.5 + endloop + endfacet + facet normal 0.271758 0.656082 0.704063 + outer loop + vertex 1.93301 3.34808 0.5 + vertex 1.0006 3.73429 0.5 + vertex 0.905867 3.38074 0.866025 + endloop + endfacet + facet normal 0.271758 0.656082 0.704063 + outer loop + vertex 1.93301 3.34808 0.5 + vertex 0.905867 3.38074 0.866025 + vertex 1.75 3.03109 0.866025 + endloop + endfacet + facet normal 0.0998425 0.241041 0.965366 + outer loop + vertex 1.75 3.03109 0.866025 + vertex 0.905867 3.38074 0.866025 + vertex 0.776457 2.89778 1 + endloop + endfacet + facet normal 0.0998425 0.241041 0.965366 + outer loop + vertex 1.75 3.03109 0.866025 + vertex 0.776457 2.89778 1 + vertex 1.5 2.59808 1 + endloop + endfacet + facet normal -0.0998425 -0.241041 0.965366 + outer loop + vertex 1.5 2.59808 1 + vertex 0.776457 2.89778 1 + vertex 0.647048 2.41481 0.866025 + endloop + endfacet + facet normal -0.0998425 -0.241041 0.965366 + outer loop + vertex 1.5 2.59808 1 + vertex 0.647048 2.41481 0.866025 + vertex 1.25 2.16506 0.866025 + endloop + endfacet + facet normal -0.271758 -0.656082 0.704063 + outer loop + vertex 1.25 2.16506 0.866025 + vertex 0.647048 2.41481 0.866025 + vertex 0.552313 2.06126 0.5 + endloop + endfacet + facet normal -0.271758 -0.656082 0.704063 + outer loop + vertex 1.25 2.16506 0.866025 + vertex 0.552313 2.06126 0.5 + vertex 1.06699 1.84808 0.5 + endloop + endfacet + facet normal -0.369855 -0.892909 0.256751 + outer loop + vertex 1.06699 1.84808 0.5 + vertex 0.552313 2.06126 0.5 + vertex 0.517638 1.93185 1.22465e-16 + endloop + endfacet + facet normal -0.369855 -0.892909 0.256751 + outer loop + vertex 1.06699 1.84808 0.5 + vertex 0.517638 1.93185 1.22465e-16 + vertex 1 1.73205 1.22465e-16 + endloop + endfacet + facet normal -0.369855 -0.892909 -0.256751 + outer loop + vertex 1 1.73205 1.22465e-16 + vertex 0.517638 1.93185 1.22465e-16 + vertex 0.552313 2.06126 -0.5 + endloop + endfacet + facet normal -0.369855 -0.892909 -0.256751 + outer loop + vertex 1 1.73205 1.22465e-16 + vertex 0.552313 2.06126 -0.5 + vertex 1.06699 1.84808 -0.5 + endloop + endfacet + facet normal -0.271758 -0.656082 -0.704063 + outer loop + vertex 1.06699 1.84808 -0.5 + vertex 0.552313 2.06126 -0.5 + vertex 0.647048 2.41481 -0.866025 + endloop + endfacet + facet normal -0.271758 -0.656082 -0.704063 + outer loop + vertex 1.06699 1.84808 -0.5 + vertex 0.647048 2.41481 -0.866025 + vertex 1.25 2.16506 -0.866025 + endloop + endfacet + facet normal -0.0998425 -0.241041 -0.965366 + outer loop + vertex 1.25 2.16506 -0.866025 + vertex 0.647048 2.41481 -0.866025 + vertex 0.776457 2.89778 -1 + endloop + endfacet + facet normal -0.0998425 -0.241041 -0.965366 + outer loop + vertex 1.25 2.16506 -0.866025 + vertex 0.776457 2.89778 -1 + vertex 1.5 2.59808 -1 + endloop + endfacet + facet normal 0.0998425 0.241041 -0.965366 + outer loop + vertex 1.5 2.59808 -1 + vertex 0.776457 2.89778 -1 + vertex 0.905867 3.38074 -0.866025 + endloop + endfacet + facet normal 0.0998425 0.241041 -0.965366 + outer loop + vertex 1.5 2.59808 -1 + vertex 0.905867 3.38074 -0.866025 + vertex 1.75 3.03109 -0.866025 + endloop + endfacet + facet normal 0.271758 0.656082 -0.704063 + outer loop + vertex 1.75 3.03109 -0.866025 + vertex 0.905867 3.38074 -0.866025 + vertex 1.0006 3.73429 -0.5 + endloop + endfacet + facet normal 0.271758 0.656082 -0.704063 + outer loop + vertex 1.75 3.03109 -0.866025 + vertex 1.0006 3.73429 -0.5 + vertex 1.93301 3.34808 -0.5 + endloop + endfacet + facet normal 0.369855 0.892909 -0.256751 + outer loop + vertex 1.93301 3.34808 -0.5 + vertex 1.0006 3.73429 -0.5 + vertex 1.03528 3.8637 -2.44929e-16 + endloop + endfacet + facet normal 0.369855 0.892909 -0.256751 + outer loop + vertex 1.93301 3.34808 -0.5 + vertex 1.03528 3.8637 -2.44929e-16 + vertex 2 3.4641 -2.44929e-16 + endloop + endfacet + facet normal 0.126151 0.958209 0.256751 + outer loop + vertex 1.03528 3.8637 0 + vertex 2.44929e-16 4 0 + vertex 2.36726e-16 3.86603 0.5 + endloop + endfacet + facet normal 0.126151 0.958209 0.256751 + outer loop + vertex 1.03528 3.8637 0 + vertex 2.36726e-16 3.86603 0.5 + vertex 1.0006 3.73429 0.5 + endloop + endfacet + facet normal 0.0926916 0.704063 0.704063 + outer loop + vertex 1.0006 3.73429 0.5 + vertex 2.36726e-16 3.86603 0.5 + vertex 2.14313e-16 3.5 0.866025 + endloop + endfacet + facet normal 0.0926916 0.704063 0.704063 + outer loop + vertex 1.0006 3.73429 0.5 + vertex 2.14313e-16 3.5 0.866025 + vertex 0.905867 3.38074 0.866025 + endloop + endfacet + facet normal 0.0340544 0.258669 0.965366 + outer loop + vertex 0.905867 3.38074 0.866025 + vertex 2.14313e-16 3.5 0.866025 + vertex 1.83697e-16 3 1 + endloop + endfacet + facet normal 0.0340544 0.258669 0.965366 + outer loop + vertex 0.905867 3.38074 0.866025 + vertex 1.83697e-16 3 1 + vertex 0.776457 2.89778 1 + endloop + endfacet + facet normal -0.0340544 -0.258669 0.965366 + outer loop + vertex 0.776457 2.89778 1 + vertex 1.83697e-16 3 1 + vertex 1.53081e-16 2.5 0.866025 + endloop + endfacet + facet normal -0.0340544 -0.258669 0.965366 + outer loop + vertex 0.776457 2.89778 1 + vertex 1.53081e-16 2.5 0.866025 + vertex 0.647048 2.41481 0.866025 + endloop + endfacet + facet normal -0.0926916 -0.704063 0.704063 + outer loop + vertex 0.647048 2.41481 0.866025 + vertex 1.53081e-16 2.5 0.866025 + vertex 1.30668e-16 2.13397 0.5 + endloop + endfacet + facet normal -0.0926916 -0.704063 0.704063 + outer loop + vertex 0.647048 2.41481 0.866025 + vertex 1.30668e-16 2.13397 0.5 + vertex 0.552313 2.06126 0.5 + endloop + endfacet + facet normal -0.126151 -0.958209 0.256751 + outer loop + vertex 0.552313 2.06126 0.5 + vertex 1.30668e-16 2.13397 0.5 + vertex 1.22465e-16 2 1.22465e-16 + endloop + endfacet + facet normal -0.126151 -0.958209 0.256751 + outer loop + vertex 0.552313 2.06126 0.5 + vertex 1.22465e-16 2 1.22465e-16 + vertex 0.517638 1.93185 1.22465e-16 + endloop + endfacet + facet normal -0.126151 -0.958209 -0.256751 + outer loop + vertex 0.517638 1.93185 1.22465e-16 + vertex 1.22465e-16 2 1.22465e-16 + vertex 1.30668e-16 2.13397 -0.5 + endloop + endfacet + facet normal -0.126151 -0.958209 -0.256751 + outer loop + vertex 0.517638 1.93185 1.22465e-16 + vertex 1.30668e-16 2.13397 -0.5 + vertex 0.552313 2.06126 -0.5 + endloop + endfacet + facet normal -0.0926916 -0.704063 -0.704063 + outer loop + vertex 0.552313 2.06126 -0.5 + vertex 1.30668e-16 2.13397 -0.5 + vertex 1.53081e-16 2.5 -0.866025 + endloop + endfacet + facet normal -0.0926916 -0.704063 -0.704063 + outer loop + vertex 0.552313 2.06126 -0.5 + vertex 1.53081e-16 2.5 -0.866025 + vertex 0.647048 2.41481 -0.866025 + endloop + endfacet + facet normal -0.0340544 -0.258669 -0.965366 + outer loop + vertex 0.647048 2.41481 -0.866025 + vertex 1.53081e-16 2.5 -0.866025 + vertex 1.83697e-16 3 -1 + endloop + endfacet + facet normal -0.0340544 -0.258669 -0.965366 + outer loop + vertex 0.647048 2.41481 -0.866025 + vertex 1.83697e-16 3 -1 + vertex 0.776457 2.89778 -1 + endloop + endfacet + facet normal 0.0340544 0.258669 -0.965366 + outer loop + vertex 0.776457 2.89778 -1 + vertex 1.83697e-16 3 -1 + vertex 2.14313e-16 3.5 -0.866025 + endloop + endfacet + facet normal 0.0340544 0.258669 -0.965366 + outer loop + vertex 0.776457 2.89778 -1 + vertex 2.14313e-16 3.5 -0.866025 + vertex 0.905867 3.38074 -0.866025 + endloop + endfacet + facet normal 0.0926916 0.704063 -0.704063 + outer loop + vertex 0.905867 3.38074 -0.866025 + vertex 2.14313e-16 3.5 -0.866025 + vertex 2.36726e-16 3.86603 -0.5 + endloop + endfacet + facet normal 0.0926916 0.704063 -0.704063 + outer loop + vertex 0.905867 3.38074 -0.866025 + vertex 2.36726e-16 3.86603 -0.5 + vertex 1.0006 3.73429 -0.5 + endloop + endfacet + facet normal 0.126151 0.958209 -0.256751 + outer loop + vertex 1.0006 3.73429 -0.5 + vertex 2.36726e-16 3.86603 -0.5 + vertex 2.44929e-16 4 -2.44929e-16 + endloop + endfacet + facet normal 0.126151 0.958209 -0.256751 + outer loop + vertex 1.0006 3.73429 -0.5 + vertex 2.44929e-16 4 -2.44929e-16 + vertex 1.03528 3.8637 -2.44929e-16 + endloop + endfacet + facet normal -0.126151 0.958209 0.256751 + outer loop + vertex 2.44929e-16 4 0 + vertex -1.03528 3.8637 0 + vertex -1.0006 3.73429 0.5 + endloop + endfacet + facet normal -0.126151 0.958209 0.256751 + outer loop + vertex 2.44929e-16 4 0 + vertex -1.0006 3.73429 0.5 + vertex 2.36726e-16 3.86603 0.5 + endloop + endfacet + facet normal -0.0926916 0.704063 0.704063 + outer loop + vertex 2.36726e-16 3.86603 0.5 + vertex -1.0006 3.73429 0.5 + vertex -0.905867 3.38074 0.866025 + endloop + endfacet + facet normal -0.0926916 0.704063 0.704063 + outer loop + vertex 2.36726e-16 3.86603 0.5 + vertex -0.905867 3.38074 0.866025 + vertex 2.14313e-16 3.5 0.866025 + endloop + endfacet + facet normal -0.0340544 0.258669 0.965366 + outer loop + vertex 2.14313e-16 3.5 0.866025 + vertex -0.905867 3.38074 0.866025 + vertex -0.776457 2.89778 1 + endloop + endfacet + facet normal -0.0340544 0.258669 0.965366 + outer loop + vertex 2.14313e-16 3.5 0.866025 + vertex -0.776457 2.89778 1 + vertex 1.83697e-16 3 1 + endloop + endfacet + facet normal 0.0340544 -0.258669 0.965366 + outer loop + vertex 1.83697e-16 3 1 + vertex -0.776457 2.89778 1 + vertex -0.647048 2.41481 0.866025 + endloop + endfacet + facet normal 0.0340544 -0.258669 0.965366 + outer loop + vertex 1.83697e-16 3 1 + vertex -0.647048 2.41481 0.866025 + vertex 1.53081e-16 2.5 0.866025 + endloop + endfacet + facet normal 0.0926916 -0.704063 0.704063 + outer loop + vertex 1.53081e-16 2.5 0.866025 + vertex -0.647048 2.41481 0.866025 + vertex -0.552313 2.06126 0.5 + endloop + endfacet + facet normal 0.0926916 -0.704063 0.704063 + outer loop + vertex 1.53081e-16 2.5 0.866025 + vertex -0.552313 2.06126 0.5 + vertex 1.30668e-16 2.13397 0.5 + endloop + endfacet + facet normal 0.126151 -0.958209 0.256751 + outer loop + vertex 1.30668e-16 2.13397 0.5 + vertex -0.552313 2.06126 0.5 + vertex -0.517638 1.93185 1.22465e-16 + endloop + endfacet + facet normal 0.126151 -0.958209 0.256751 + outer loop + vertex 1.30668e-16 2.13397 0.5 + vertex -0.517638 1.93185 1.22465e-16 + vertex 1.22465e-16 2 1.22465e-16 + endloop + endfacet + facet normal 0.126151 -0.958209 -0.256751 + outer loop + vertex 1.22465e-16 2 1.22465e-16 + vertex -0.517638 1.93185 1.22465e-16 + vertex -0.552313 2.06126 -0.5 + endloop + endfacet + facet normal 0.126151 -0.958209 -0.256751 + outer loop + vertex 1.22465e-16 2 1.22465e-16 + vertex -0.552313 2.06126 -0.5 + vertex 1.30668e-16 2.13397 -0.5 + endloop + endfacet + facet normal 0.0926916 -0.704063 -0.704063 + outer loop + vertex 1.30668e-16 2.13397 -0.5 + vertex -0.552313 2.06126 -0.5 + vertex -0.647048 2.41481 -0.866025 + endloop + endfacet + facet normal 0.0926916 -0.704063 -0.704063 + outer loop + vertex 1.30668e-16 2.13397 -0.5 + vertex -0.647048 2.41481 -0.866025 + vertex 1.53081e-16 2.5 -0.866025 + endloop + endfacet + facet normal 0.0340544 -0.258669 -0.965366 + outer loop + vertex 1.53081e-16 2.5 -0.866025 + vertex -0.647048 2.41481 -0.866025 + vertex -0.776457 2.89778 -1 + endloop + endfacet + facet normal 0.0340544 -0.258669 -0.965366 + outer loop + vertex 1.53081e-16 2.5 -0.866025 + vertex -0.776457 2.89778 -1 + vertex 1.83697e-16 3 -1 + endloop + endfacet + facet normal -0.0340544 0.258669 -0.965366 + outer loop + vertex 1.83697e-16 3 -1 + vertex -0.776457 2.89778 -1 + vertex -0.905867 3.38074 -0.866025 + endloop + endfacet + facet normal -0.0340544 0.258669 -0.965366 + outer loop + vertex 1.83697e-16 3 -1 + vertex -0.905867 3.38074 -0.866025 + vertex 2.14313e-16 3.5 -0.866025 + endloop + endfacet + facet normal -0.0926916 0.704063 -0.704063 + outer loop + vertex 2.14313e-16 3.5 -0.866025 + vertex -0.905867 3.38074 -0.866025 + vertex -1.0006 3.73429 -0.5 + endloop + endfacet + facet normal -0.0926916 0.704063 -0.704063 + outer loop + vertex 2.14313e-16 3.5 -0.866025 + vertex -1.0006 3.73429 -0.5 + vertex 2.36726e-16 3.86603 -0.5 + endloop + endfacet + facet normal -0.126151 0.958209 -0.256751 + outer loop + vertex 2.36726e-16 3.86603 -0.5 + vertex -1.0006 3.73429 -0.5 + vertex -1.03528 3.8637 -2.44929e-16 + endloop + endfacet + facet normal -0.126151 0.958209 -0.256751 + outer loop + vertex 2.36726e-16 3.86603 -0.5 + vertex -1.03528 3.8637 -2.44929e-16 + vertex 2.44929e-16 4 -2.44929e-16 + endloop + endfacet + facet normal -0.369855 0.892909 0.256751 + outer loop + vertex -1.03528 3.8637 0 + vertex -2 3.4641 0 + vertex -1.93301 3.34808 0.5 + endloop + endfacet + facet normal -0.369855 0.892909 0.256751 + outer loop + vertex -1.03528 3.8637 0 + vertex -1.93301 3.34808 0.5 + vertex -1.0006 3.73429 0.5 + endloop + endfacet + facet normal -0.271758 0.656082 0.704063 + outer loop + vertex -1.0006 3.73429 0.5 + vertex -1.93301 3.34808 0.5 + vertex -1.75 3.03109 0.866025 + endloop + endfacet + facet normal -0.271758 0.656082 0.704063 + outer loop + vertex -1.0006 3.73429 0.5 + vertex -1.75 3.03109 0.866025 + vertex -0.905867 3.38074 0.866025 + endloop + endfacet + facet normal -0.0998425 0.241041 0.965366 + outer loop + vertex -0.905867 3.38074 0.866025 + vertex -1.75 3.03109 0.866025 + vertex -1.5 2.59808 1 + endloop + endfacet + facet normal -0.0998425 0.241041 0.965366 + outer loop + vertex -0.905867 3.38074 0.866025 + vertex -1.5 2.59808 1 + vertex -0.776457 2.89778 1 + endloop + endfacet + facet normal 0.0998425 -0.241041 0.965366 + outer loop + vertex -0.776457 2.89778 1 + vertex -1.5 2.59808 1 + vertex -1.25 2.16506 0.866025 + endloop + endfacet + facet normal 0.0998425 -0.241041 0.965366 + outer loop + vertex -0.776457 2.89778 1 + vertex -1.25 2.16506 0.866025 + vertex -0.647048 2.41481 0.866025 + endloop + endfacet + facet normal 0.271758 -0.656082 0.704063 + outer loop + vertex -0.647048 2.41481 0.866025 + vertex -1.25 2.16506 0.866025 + vertex -1.06699 1.84808 0.5 + endloop + endfacet + facet normal 0.271758 -0.656082 0.704063 + outer loop + vertex -0.647048 2.41481 0.866025 + vertex -1.06699 1.84808 0.5 + vertex -0.552313 2.06126 0.5 + endloop + endfacet + facet normal 0.369855 -0.892909 0.256751 + outer loop + vertex -0.552313 2.06126 0.5 + vertex -1.06699 1.84808 0.5 + vertex -1 1.73205 1.22465e-16 + endloop + endfacet + facet normal 0.369855 -0.892909 0.256751 + outer loop + vertex -0.552313 2.06126 0.5 + vertex -1 1.73205 1.22465e-16 + vertex -0.517638 1.93185 1.22465e-16 + endloop + endfacet + facet normal 0.369855 -0.892909 -0.256751 + outer loop + vertex -0.517638 1.93185 1.22465e-16 + vertex -1 1.73205 1.22465e-16 + vertex -1.06699 1.84808 -0.5 + endloop + endfacet + facet normal 0.369855 -0.892909 -0.256751 + outer loop + vertex -0.517638 1.93185 1.22465e-16 + vertex -1.06699 1.84808 -0.5 + vertex -0.552313 2.06126 -0.5 + endloop + endfacet + facet normal 0.271758 -0.656082 -0.704063 + outer loop + vertex -0.552313 2.06126 -0.5 + vertex -1.06699 1.84808 -0.5 + vertex -1.25 2.16506 -0.866025 + endloop + endfacet + facet normal 0.271758 -0.656082 -0.704063 + outer loop + vertex -0.552313 2.06126 -0.5 + vertex -1.25 2.16506 -0.866025 + vertex -0.647048 2.41481 -0.866025 + endloop + endfacet + facet normal 0.0998425 -0.241041 -0.965366 + outer loop + vertex -0.647048 2.41481 -0.866025 + vertex -1.25 2.16506 -0.866025 + vertex -1.5 2.59808 -1 + endloop + endfacet + facet normal 0.0998425 -0.241041 -0.965366 + outer loop + vertex -0.647048 2.41481 -0.866025 + vertex -1.5 2.59808 -1 + vertex -0.776457 2.89778 -1 + endloop + endfacet + facet normal -0.0998425 0.241041 -0.965366 + outer loop + vertex -0.776457 2.89778 -1 + vertex -1.5 2.59808 -1 + vertex -1.75 3.03109 -0.866025 + endloop + endfacet + facet normal -0.0998425 0.241041 -0.965366 + outer loop + vertex -0.776457 2.89778 -1 + vertex -1.75 3.03109 -0.866025 + vertex -0.905867 3.38074 -0.866025 + endloop + endfacet + facet normal -0.271758 0.656082 -0.704063 + outer loop + vertex -0.905867 3.38074 -0.866025 + vertex -1.75 3.03109 -0.866025 + vertex -1.93301 3.34808 -0.5 + endloop + endfacet + facet normal -0.271758 0.656082 -0.704063 + outer loop + vertex -0.905867 3.38074 -0.866025 + vertex -1.93301 3.34808 -0.5 + vertex -1.0006 3.73429 -0.5 + endloop + endfacet + facet normal -0.369855 0.892909 -0.256751 + outer loop + vertex -1.0006 3.73429 -0.5 + vertex -1.93301 3.34808 -0.5 + vertex -2 3.4641 -2.44929e-16 + endloop + endfacet + facet normal -0.369855 0.892909 -0.256751 + outer loop + vertex -1.0006 3.73429 -0.5 + vertex -2 3.4641 -2.44929e-16 + vertex -1.03528 3.8637 -2.44929e-16 + endloop + endfacet + facet normal -0.588354 0.766758 0.256751 + outer loop + vertex -2 3.4641 0 + vertex -2.82843 2.82843 0 + vertex -2.73369 2.73369 0.5 + endloop + endfacet + facet normal -0.588354 0.766758 0.256751 + outer loop + vertex -2 3.4641 0 + vertex -2.73369 2.73369 0.5 + vertex -1.93301 3.34808 0.5 + endloop + endfacet + facet normal -0.432305 0.56339 0.704063 + outer loop + vertex -1.93301 3.34808 0.5 + vertex -2.73369 2.73369 0.5 + vertex -2.47487 2.47487 0.866025 + endloop + endfacet + facet normal -0.432305 0.56339 0.704063 + outer loop + vertex -1.93301 3.34808 0.5 + vertex -2.47487 2.47487 0.866025 + vertex -1.75 3.03109 0.866025 + endloop + endfacet + facet normal -0.158826 0.206987 0.965366 + outer loop + vertex -1.75 3.03109 0.866025 + vertex -2.47487 2.47487 0.866025 + vertex -2.12132 2.12132 1 + endloop + endfacet + facet normal -0.158826 0.206987 0.965366 + outer loop + vertex -1.75 3.03109 0.866025 + vertex -2.12132 2.12132 1 + vertex -1.5 2.59808 1 + endloop + endfacet + facet normal 0.158826 -0.206987 0.965366 + outer loop + vertex -1.5 2.59808 1 + vertex -2.12132 2.12132 1 + vertex -1.76777 1.76777 0.866025 + endloop + endfacet + facet normal 0.158826 -0.206987 0.965366 + outer loop + vertex -1.5 2.59808 1 + vertex -1.76777 1.76777 0.866025 + vertex -1.25 2.16506 0.866025 + endloop + endfacet + facet normal 0.432305 -0.56339 0.704063 + outer loop + vertex -1.25 2.16506 0.866025 + vertex -1.76777 1.76777 0.866025 + vertex -1.50895 1.50895 0.5 + endloop + endfacet + facet normal 0.432305 -0.56339 0.704063 + outer loop + vertex -1.25 2.16506 0.866025 + vertex -1.50895 1.50895 0.5 + vertex -1.06699 1.84808 0.5 + endloop + endfacet + facet normal 0.588354 -0.766758 0.256751 + outer loop + vertex -1.06699 1.84808 0.5 + vertex -1.50895 1.50895 0.5 + vertex -1.41421 1.41421 1.22465e-16 + endloop + endfacet + facet normal 0.588354 -0.766758 0.256751 + outer loop + vertex -1.06699 1.84808 0.5 + vertex -1.41421 1.41421 1.22465e-16 + vertex -1 1.73205 1.22465e-16 + endloop + endfacet + facet normal 0.588354 -0.766758 -0.256751 + outer loop + vertex -1 1.73205 1.22465e-16 + vertex -1.41421 1.41421 1.22465e-16 + vertex -1.50895 1.50895 -0.5 + endloop + endfacet + facet normal 0.588354 -0.766758 -0.256751 + outer loop + vertex -1 1.73205 1.22465e-16 + vertex -1.50895 1.50895 -0.5 + vertex -1.06699 1.84808 -0.5 + endloop + endfacet + facet normal 0.432305 -0.56339 -0.704063 + outer loop + vertex -1.06699 1.84808 -0.5 + vertex -1.50895 1.50895 -0.5 + vertex -1.76777 1.76777 -0.866025 + endloop + endfacet + facet normal 0.432305 -0.56339 -0.704063 + outer loop + vertex -1.06699 1.84808 -0.5 + vertex -1.76777 1.76777 -0.866025 + vertex -1.25 2.16506 -0.866025 + endloop + endfacet + facet normal 0.158826 -0.206987 -0.965366 + outer loop + vertex -1.25 2.16506 -0.866025 + vertex -1.76777 1.76777 -0.866025 + vertex -2.12132 2.12132 -1 + endloop + endfacet + facet normal 0.158826 -0.206987 -0.965366 + outer loop + vertex -1.25 2.16506 -0.866025 + vertex -2.12132 2.12132 -1 + vertex -1.5 2.59808 -1 + endloop + endfacet + facet normal -0.158826 0.206987 -0.965366 + outer loop + vertex -1.5 2.59808 -1 + vertex -2.12132 2.12132 -1 + vertex -2.47487 2.47487 -0.866025 + endloop + endfacet + facet normal -0.158826 0.206987 -0.965366 + outer loop + vertex -1.5 2.59808 -1 + vertex -2.47487 2.47487 -0.866025 + vertex -1.75 3.03109 -0.866025 + endloop + endfacet + facet normal -0.432305 0.56339 -0.704063 + outer loop + vertex -1.75 3.03109 -0.866025 + vertex -2.47487 2.47487 -0.866025 + vertex -2.73369 2.73369 -0.5 + endloop + endfacet + facet normal -0.432305 0.56339 -0.704063 + outer loop + vertex -1.75 3.03109 -0.866025 + vertex -2.73369 2.73369 -0.5 + vertex -1.93301 3.34808 -0.5 + endloop + endfacet + facet normal -0.588354 0.766758 -0.256751 + outer loop + vertex -1.93301 3.34808 -0.5 + vertex -2.73369 2.73369 -0.5 + vertex -2.82843 2.82843 -2.44929e-16 + endloop + endfacet + facet normal -0.588354 0.766758 -0.256751 + outer loop + vertex -1.93301 3.34808 -0.5 + vertex -2.82843 2.82843 -2.44929e-16 + vertex -2 3.4641 -2.44929e-16 + endloop + endfacet + facet normal -0.766758 0.588354 0.256751 + outer loop + vertex -2.82843 2.82843 0 + vertex -3.4641 2 0 + vertex -3.34808 1.93301 0.5 + endloop + endfacet + facet normal -0.766758 0.588354 0.256751 + outer loop + vertex -2.82843 2.82843 0 + vertex -3.34808 1.93301 0.5 + vertex -2.73369 2.73369 0.5 + endloop + endfacet + facet normal -0.56339 0.432305 0.704063 + outer loop + vertex -2.73369 2.73369 0.5 + vertex -3.34808 1.93301 0.5 + vertex -3.03109 1.75 0.866025 + endloop + endfacet + facet normal -0.56339 0.432305 0.704063 + outer loop + vertex -2.73369 2.73369 0.5 + vertex -3.03109 1.75 0.866025 + vertex -2.47487 2.47487 0.866025 + endloop + endfacet + facet normal -0.206987 0.158826 0.965366 + outer loop + vertex -2.47487 2.47487 0.866025 + vertex -3.03109 1.75 0.866025 + vertex -2.59808 1.5 1 + endloop + endfacet + facet normal -0.206987 0.158826 0.965366 + outer loop + vertex -2.47487 2.47487 0.866025 + vertex -2.59808 1.5 1 + vertex -2.12132 2.12132 1 + endloop + endfacet + facet normal 0.206987 -0.158826 0.965366 + outer loop + vertex -2.12132 2.12132 1 + vertex -2.59808 1.5 1 + vertex -2.16506 1.25 0.866025 + endloop + endfacet + facet normal 0.206987 -0.158826 0.965366 + outer loop + vertex -2.12132 2.12132 1 + vertex -2.16506 1.25 0.866025 + vertex -1.76777 1.76777 0.866025 + endloop + endfacet + facet normal 0.56339 -0.432305 0.704063 + outer loop + vertex -1.76777 1.76777 0.866025 + vertex -2.16506 1.25 0.866025 + vertex -1.84808 1.06699 0.5 + endloop + endfacet + facet normal 0.56339 -0.432305 0.704063 + outer loop + vertex -1.76777 1.76777 0.866025 + vertex -1.84808 1.06699 0.5 + vertex -1.50895 1.50895 0.5 + endloop + endfacet + facet normal 0.766758 -0.588354 0.256751 + outer loop + vertex -1.50895 1.50895 0.5 + vertex -1.84808 1.06699 0.5 + vertex -1.73205 1 1.22465e-16 + endloop + endfacet + facet normal 0.766758 -0.588354 0.256751 + outer loop + vertex -1.50895 1.50895 0.5 + vertex -1.73205 1 1.22465e-16 + vertex -1.41421 1.41421 1.22465e-16 + endloop + endfacet + facet normal 0.766758 -0.588354 -0.256751 + outer loop + vertex -1.41421 1.41421 1.22465e-16 + vertex -1.73205 1 1.22465e-16 + vertex -1.84808 1.06699 -0.5 + endloop + endfacet + facet normal 0.766758 -0.588354 -0.256751 + outer loop + vertex -1.41421 1.41421 1.22465e-16 + vertex -1.84808 1.06699 -0.5 + vertex -1.50895 1.50895 -0.5 + endloop + endfacet + facet normal 0.56339 -0.432305 -0.704063 + outer loop + vertex -1.50895 1.50895 -0.5 + vertex -1.84808 1.06699 -0.5 + vertex -2.16506 1.25 -0.866025 + endloop + endfacet + facet normal 0.56339 -0.432305 -0.704063 + outer loop + vertex -1.50895 1.50895 -0.5 + vertex -2.16506 1.25 -0.866025 + vertex -1.76777 1.76777 -0.866025 + endloop + endfacet + facet normal 0.206987 -0.158826 -0.965366 + outer loop + vertex -1.76777 1.76777 -0.866025 + vertex -2.16506 1.25 -0.866025 + vertex -2.59808 1.5 -1 + endloop + endfacet + facet normal 0.206987 -0.158826 -0.965366 + outer loop + vertex -1.76777 1.76777 -0.866025 + vertex -2.59808 1.5 -1 + vertex -2.12132 2.12132 -1 + endloop + endfacet + facet normal -0.206987 0.158826 -0.965366 + outer loop + vertex -2.12132 2.12132 -1 + vertex -2.59808 1.5 -1 + vertex -3.03109 1.75 -0.866025 + endloop + endfacet + facet normal -0.206987 0.158826 -0.965366 + outer loop + vertex -2.12132 2.12132 -1 + vertex -3.03109 1.75 -0.866025 + vertex -2.47487 2.47487 -0.866025 + endloop + endfacet + facet normal -0.56339 0.432305 -0.704063 + outer loop + vertex -2.47487 2.47487 -0.866025 + vertex -3.03109 1.75 -0.866025 + vertex -3.34808 1.93301 -0.5 + endloop + endfacet + facet normal -0.56339 0.432305 -0.704063 + outer loop + vertex -2.47487 2.47487 -0.866025 + vertex -3.34808 1.93301 -0.5 + vertex -2.73369 2.73369 -0.5 + endloop + endfacet + facet normal -0.766758 0.588354 -0.256751 + outer loop + vertex -2.73369 2.73369 -0.5 + vertex -3.34808 1.93301 -0.5 + vertex -3.4641 2 -2.44929e-16 + endloop + endfacet + facet normal -0.766758 0.588354 -0.256751 + outer loop + vertex -2.73369 2.73369 -0.5 + vertex -3.4641 2 -2.44929e-16 + vertex -2.82843 2.82843 -2.44929e-16 + endloop + endfacet + facet normal -0.892909 0.369855 0.256751 + outer loop + vertex -3.4641 2 0 + vertex -3.8637 1.03528 0 + vertex -3.73429 1.0006 0.5 + endloop + endfacet + facet normal -0.892909 0.369855 0.256751 + outer loop + vertex -3.4641 2 0 + vertex -3.73429 1.0006 0.5 + vertex -3.34808 1.93301 0.5 + endloop + endfacet + facet normal -0.656082 0.271758 0.704063 + outer loop + vertex -3.34808 1.93301 0.5 + vertex -3.73429 1.0006 0.5 + vertex -3.38074 0.905867 0.866025 + endloop + endfacet + facet normal -0.656082 0.271758 0.704063 + outer loop + vertex -3.34808 1.93301 0.5 + vertex -3.38074 0.905867 0.866025 + vertex -3.03109 1.75 0.866025 + endloop + endfacet + facet normal -0.241041 0.0998425 0.965366 + outer loop + vertex -3.03109 1.75 0.866025 + vertex -3.38074 0.905867 0.866025 + vertex -2.89778 0.776457 1 + endloop + endfacet + facet normal -0.241041 0.0998425 0.965366 + outer loop + vertex -3.03109 1.75 0.866025 + vertex -2.89778 0.776457 1 + vertex -2.59808 1.5 1 + endloop + endfacet + facet normal 0.241041 -0.0998425 0.965366 + outer loop + vertex -2.59808 1.5 1 + vertex -2.89778 0.776457 1 + vertex -2.41481 0.647048 0.866025 + endloop + endfacet + facet normal 0.241041 -0.0998425 0.965366 + outer loop + vertex -2.59808 1.5 1 + vertex -2.41481 0.647048 0.866025 + vertex -2.16506 1.25 0.866025 + endloop + endfacet + facet normal 0.656082 -0.271758 0.704063 + outer loop + vertex -2.16506 1.25 0.866025 + vertex -2.41481 0.647048 0.866025 + vertex -2.06126 0.552313 0.5 + endloop + endfacet + facet normal 0.656082 -0.271758 0.704063 + outer loop + vertex -2.16506 1.25 0.866025 + vertex -2.06126 0.552313 0.5 + vertex -1.84808 1.06699 0.5 + endloop + endfacet + facet normal 0.892909 -0.369855 0.256751 + outer loop + vertex -1.84808 1.06699 0.5 + vertex -2.06126 0.552313 0.5 + vertex -1.93185 0.517638 1.22465e-16 + endloop + endfacet + facet normal 0.892909 -0.369855 0.256751 + outer loop + vertex -1.84808 1.06699 0.5 + vertex -1.93185 0.517638 1.22465e-16 + vertex -1.73205 1 1.22465e-16 + endloop + endfacet + facet normal 0.892909 -0.369855 -0.256751 + outer loop + vertex -1.73205 1 1.22465e-16 + vertex -1.93185 0.517638 1.22465e-16 + vertex -2.06126 0.552313 -0.5 + endloop + endfacet + facet normal 0.892909 -0.369855 -0.256751 + outer loop + vertex -1.73205 1 1.22465e-16 + vertex -2.06126 0.552313 -0.5 + vertex -1.84808 1.06699 -0.5 + endloop + endfacet + facet normal 0.656082 -0.271758 -0.704063 + outer loop + vertex -1.84808 1.06699 -0.5 + vertex -2.06126 0.552313 -0.5 + vertex -2.41481 0.647048 -0.866025 + endloop + endfacet + facet normal 0.656082 -0.271758 -0.704063 + outer loop + vertex -1.84808 1.06699 -0.5 + vertex -2.41481 0.647048 -0.866025 + vertex -2.16506 1.25 -0.866025 + endloop + endfacet + facet normal 0.241041 -0.0998425 -0.965366 + outer loop + vertex -2.16506 1.25 -0.866025 + vertex -2.41481 0.647048 -0.866025 + vertex -2.89778 0.776457 -1 + endloop + endfacet + facet normal 0.241041 -0.0998425 -0.965366 + outer loop + vertex -2.16506 1.25 -0.866025 + vertex -2.89778 0.776457 -1 + vertex -2.59808 1.5 -1 + endloop + endfacet + facet normal -0.241041 0.0998425 -0.965366 + outer loop + vertex -2.59808 1.5 -1 + vertex -2.89778 0.776457 -1 + vertex -3.38074 0.905867 -0.866025 + endloop + endfacet + facet normal -0.241041 0.0998425 -0.965366 + outer loop + vertex -2.59808 1.5 -1 + vertex -3.38074 0.905867 -0.866025 + vertex -3.03109 1.75 -0.866025 + endloop + endfacet + facet normal -0.656082 0.271758 -0.704063 + outer loop + vertex -3.03109 1.75 -0.866025 + vertex -3.38074 0.905867 -0.866025 + vertex -3.73429 1.0006 -0.5 + endloop + endfacet + facet normal -0.656082 0.271758 -0.704063 + outer loop + vertex -3.03109 1.75 -0.866025 + vertex -3.73429 1.0006 -0.5 + vertex -3.34808 1.93301 -0.5 + endloop + endfacet + facet normal -0.892909 0.369855 -0.256751 + outer loop + vertex -3.34808 1.93301 -0.5 + vertex -3.73429 1.0006 -0.5 + vertex -3.8637 1.03528 -2.44929e-16 + endloop + endfacet + facet normal -0.892909 0.369855 -0.256751 + outer loop + vertex -3.34808 1.93301 -0.5 + vertex -3.8637 1.03528 -2.44929e-16 + vertex -3.4641 2 -2.44929e-16 + endloop + endfacet + facet normal -0.958209 0.126151 0.256751 + outer loop + vertex -3.8637 1.03528 0 + vertex -4 4.89859e-16 0 + vertex -3.86603 4.73452e-16 0.5 + endloop + endfacet + facet normal -0.958209 0.126151 0.256751 + outer loop + vertex -3.8637 1.03528 0 + vertex -3.86603 4.73452e-16 0.5 + vertex -3.73429 1.0006 0.5 + endloop + endfacet + facet normal -0.704063 0.0926916 0.704063 + outer loop + vertex -3.73429 1.0006 0.5 + vertex -3.86603 4.73452e-16 0.5 + vertex -3.5 4.28626e-16 0.866025 + endloop + endfacet + facet normal -0.704063 0.0926916 0.704063 + outer loop + vertex -3.73429 1.0006 0.5 + vertex -3.5 4.28626e-16 0.866025 + vertex -3.38074 0.905867 0.866025 + endloop + endfacet + facet normal -0.258669 0.0340544 0.965366 + outer loop + vertex -3.38074 0.905867 0.866025 + vertex -3.5 4.28626e-16 0.866025 + vertex -3 3.67394e-16 1 + endloop + endfacet + facet normal -0.258669 0.0340544 0.965366 + outer loop + vertex -3.38074 0.905867 0.866025 + vertex -3 3.67394e-16 1 + vertex -2.89778 0.776457 1 + endloop + endfacet + facet normal 0.258669 -0.0340544 0.965366 + outer loop + vertex -2.89778 0.776457 1 + vertex -3 3.67394e-16 1 + vertex -2.5 3.06162e-16 0.866025 + endloop + endfacet + facet normal 0.258669 -0.0340544 0.965366 + outer loop + vertex -2.89778 0.776457 1 + vertex -2.5 3.06162e-16 0.866025 + vertex -2.41481 0.647048 0.866025 + endloop + endfacet + facet normal 0.704063 -0.0926916 0.704063 + outer loop + vertex -2.41481 0.647048 0.866025 + vertex -2.5 3.06162e-16 0.866025 + vertex -2.13397 2.61337e-16 0.5 + endloop + endfacet + facet normal 0.704063 -0.0926916 0.704063 + outer loop + vertex -2.41481 0.647048 0.866025 + vertex -2.13397 2.61337e-16 0.5 + vertex -2.06126 0.552313 0.5 + endloop + endfacet + facet normal 0.958209 -0.126151 0.256751 + outer loop + vertex -2.06126 0.552313 0.5 + vertex -2.13397 2.61337e-16 0.5 + vertex -2 2.44929e-16 1.22465e-16 + endloop + endfacet + facet normal 0.958209 -0.126151 0.256751 + outer loop + vertex -2.06126 0.552313 0.5 + vertex -2 2.44929e-16 1.22465e-16 + vertex -1.93185 0.517638 1.22465e-16 + endloop + endfacet + facet normal 0.958209 -0.126151 -0.256751 + outer loop + vertex -1.93185 0.517638 1.22465e-16 + vertex -2 2.44929e-16 1.22465e-16 + vertex -2.13397 2.61337e-16 -0.5 + endloop + endfacet + facet normal 0.958209 -0.126151 -0.256751 + outer loop + vertex -1.93185 0.517638 1.22465e-16 + vertex -2.13397 2.61337e-16 -0.5 + vertex -2.06126 0.552313 -0.5 + endloop + endfacet + facet normal 0.704063 -0.0926916 -0.704063 + outer loop + vertex -2.06126 0.552313 -0.5 + vertex -2.13397 2.61337e-16 -0.5 + vertex -2.5 3.06162e-16 -0.866025 + endloop + endfacet + facet normal 0.704063 -0.0926916 -0.704063 + outer loop + vertex -2.06126 0.552313 -0.5 + vertex -2.5 3.06162e-16 -0.866025 + vertex -2.41481 0.647048 -0.866025 + endloop + endfacet + facet normal 0.258669 -0.0340544 -0.965366 + outer loop + vertex -2.41481 0.647048 -0.866025 + vertex -2.5 3.06162e-16 -0.866025 + vertex -3 3.67394e-16 -1 + endloop + endfacet + facet normal 0.258669 -0.0340544 -0.965366 + outer loop + vertex -2.41481 0.647048 -0.866025 + vertex -3 3.67394e-16 -1 + vertex -2.89778 0.776457 -1 + endloop + endfacet + facet normal -0.258669 0.0340544 -0.965366 + outer loop + vertex -2.89778 0.776457 -1 + vertex -3 3.67394e-16 -1 + vertex -3.5 4.28626e-16 -0.866025 + endloop + endfacet + facet normal -0.258669 0.0340544 -0.965366 + outer loop + vertex -2.89778 0.776457 -1 + vertex -3.5 4.28626e-16 -0.866025 + vertex -3.38074 0.905867 -0.866025 + endloop + endfacet + facet normal -0.704063 0.0926916 -0.704063 + outer loop + vertex -3.38074 0.905867 -0.866025 + vertex -3.5 4.28626e-16 -0.866025 + vertex -3.86603 4.73452e-16 -0.5 + endloop + endfacet + facet normal -0.704063 0.0926916 -0.704063 + outer loop + vertex -3.38074 0.905867 -0.866025 + vertex -3.86603 4.73452e-16 -0.5 + vertex -3.73429 1.0006 -0.5 + endloop + endfacet + facet normal -0.958209 0.126151 -0.256751 + outer loop + vertex -3.73429 1.0006 -0.5 + vertex -3.86603 4.73452e-16 -0.5 + vertex -4 4.89859e-16 -2.44929e-16 + endloop + endfacet + facet normal -0.958209 0.126151 -0.256751 + outer loop + vertex -3.73429 1.0006 -0.5 + vertex -4 4.89859e-16 -2.44929e-16 + vertex -3.8637 1.03528 -2.44929e-16 + endloop + endfacet + facet normal -0.958209 -0.126151 0.256751 + outer loop + vertex -4 4.89859e-16 0 + vertex -3.8637 -1.03528 0 + vertex -3.73429 -1.0006 0.5 + endloop + endfacet + facet normal -0.958209 -0.126151 0.256751 + outer loop + vertex -4 4.89859e-16 0 + vertex -3.73429 -1.0006 0.5 + vertex -3.86603 4.73452e-16 0.5 + endloop + endfacet + facet normal -0.704063 -0.0926916 0.704063 + outer loop + vertex -3.86603 4.73452e-16 0.5 + vertex -3.73429 -1.0006 0.5 + vertex -3.38074 -0.905867 0.866025 + endloop + endfacet + facet normal -0.704063 -0.0926916 0.704063 + outer loop + vertex -3.86603 4.73452e-16 0.5 + vertex -3.38074 -0.905867 0.866025 + vertex -3.5 4.28626e-16 0.866025 + endloop + endfacet + facet normal -0.258669 -0.0340544 0.965366 + outer loop + vertex -3.5 4.28626e-16 0.866025 + vertex -3.38074 -0.905867 0.866025 + vertex -2.89778 -0.776457 1 + endloop + endfacet + facet normal -0.258669 -0.0340544 0.965366 + outer loop + vertex -3.5 4.28626e-16 0.866025 + vertex -2.89778 -0.776457 1 + vertex -3 3.67394e-16 1 + endloop + endfacet + facet normal 0.258669 0.0340544 0.965366 + outer loop + vertex -3 3.67394e-16 1 + vertex -2.89778 -0.776457 1 + vertex -2.41481 -0.647048 0.866025 + endloop + endfacet + facet normal 0.258669 0.0340544 0.965366 + outer loop + vertex -3 3.67394e-16 1 + vertex -2.41481 -0.647048 0.866025 + vertex -2.5 3.06162e-16 0.866025 + endloop + endfacet + facet normal 0.704063 0.0926916 0.704063 + outer loop + vertex -2.5 3.06162e-16 0.866025 + vertex -2.41481 -0.647048 0.866025 + vertex -2.06126 -0.552313 0.5 + endloop + endfacet + facet normal 0.704063 0.0926916 0.704063 + outer loop + vertex -2.5 3.06162e-16 0.866025 + vertex -2.06126 -0.552313 0.5 + vertex -2.13397 2.61337e-16 0.5 + endloop + endfacet + facet normal 0.958209 0.126151 0.256751 + outer loop + vertex -2.13397 2.61337e-16 0.5 + vertex -2.06126 -0.552313 0.5 + vertex -1.93185 -0.517638 1.22465e-16 + endloop + endfacet + facet normal 0.958209 0.126151 0.256751 + outer loop + vertex -2.13397 2.61337e-16 0.5 + vertex -1.93185 -0.517638 1.22465e-16 + vertex -2 2.44929e-16 1.22465e-16 + endloop + endfacet + facet normal 0.958209 0.126151 -0.256751 + outer loop + vertex -2 2.44929e-16 1.22465e-16 + vertex -1.93185 -0.517638 1.22465e-16 + vertex -2.06126 -0.552313 -0.5 + endloop + endfacet + facet normal 0.958209 0.126151 -0.256751 + outer loop + vertex -2 2.44929e-16 1.22465e-16 + vertex -2.06126 -0.552313 -0.5 + vertex -2.13397 2.61337e-16 -0.5 + endloop + endfacet + facet normal 0.704063 0.0926916 -0.704063 + outer loop + vertex -2.13397 2.61337e-16 -0.5 + vertex -2.06126 -0.552313 -0.5 + vertex -2.41481 -0.647048 -0.866025 + endloop + endfacet + facet normal 0.704063 0.0926916 -0.704063 + outer loop + vertex -2.13397 2.61337e-16 -0.5 + vertex -2.41481 -0.647048 -0.866025 + vertex -2.5 3.06162e-16 -0.866025 + endloop + endfacet + facet normal 0.258669 0.0340544 -0.965366 + outer loop + vertex -2.5 3.06162e-16 -0.866025 + vertex -2.41481 -0.647048 -0.866025 + vertex -2.89778 -0.776457 -1 + endloop + endfacet + facet normal 0.258669 0.0340544 -0.965366 + outer loop + vertex -2.5 3.06162e-16 -0.866025 + vertex -2.89778 -0.776457 -1 + vertex -3 3.67394e-16 -1 + endloop + endfacet + facet normal -0.258669 -0.0340544 -0.965366 + outer loop + vertex -3 3.67394e-16 -1 + vertex -2.89778 -0.776457 -1 + vertex -3.38074 -0.905867 -0.866025 + endloop + endfacet + facet normal -0.258669 -0.0340544 -0.965366 + outer loop + vertex -3 3.67394e-16 -1 + vertex -3.38074 -0.905867 -0.866025 + vertex -3.5 4.28626e-16 -0.866025 + endloop + endfacet + facet normal -0.704063 -0.0926916 -0.704063 + outer loop + vertex -3.5 4.28626e-16 -0.866025 + vertex -3.38074 -0.905867 -0.866025 + vertex -3.73429 -1.0006 -0.5 + endloop + endfacet + facet normal -0.704063 -0.0926916 -0.704063 + outer loop + vertex -3.5 4.28626e-16 -0.866025 + vertex -3.73429 -1.0006 -0.5 + vertex -3.86603 4.73452e-16 -0.5 + endloop + endfacet + facet normal -0.958209 -0.126151 -0.256751 + outer loop + vertex -3.86603 4.73452e-16 -0.5 + vertex -3.73429 -1.0006 -0.5 + vertex -3.8637 -1.03528 -2.44929e-16 + endloop + endfacet + facet normal -0.958209 -0.126151 -0.256751 + outer loop + vertex -3.86603 4.73452e-16 -0.5 + vertex -3.8637 -1.03528 -2.44929e-16 + vertex -4 4.89859e-16 -2.44929e-16 + endloop + endfacet + facet normal -0.892909 -0.369855 0.256751 + outer loop + vertex -3.8637 -1.03528 0 + vertex -3.4641 -2 0 + vertex -3.34808 -1.93301 0.5 + endloop + endfacet + facet normal -0.892909 -0.369855 0.256751 + outer loop + vertex -3.8637 -1.03528 0 + vertex -3.34808 -1.93301 0.5 + vertex -3.73429 -1.0006 0.5 + endloop + endfacet + facet normal -0.656082 -0.271758 0.704063 + outer loop + vertex -3.73429 -1.0006 0.5 + vertex -3.34808 -1.93301 0.5 + vertex -3.03109 -1.75 0.866025 + endloop + endfacet + facet normal -0.656082 -0.271758 0.704063 + outer loop + vertex -3.73429 -1.0006 0.5 + vertex -3.03109 -1.75 0.866025 + vertex -3.38074 -0.905867 0.866025 + endloop + endfacet + facet normal -0.241041 -0.0998425 0.965366 + outer loop + vertex -3.38074 -0.905867 0.866025 + vertex -3.03109 -1.75 0.866025 + vertex -2.59808 -1.5 1 + endloop + endfacet + facet normal -0.241041 -0.0998425 0.965366 + outer loop + vertex -3.38074 -0.905867 0.866025 + vertex -2.59808 -1.5 1 + vertex -2.89778 -0.776457 1 + endloop + endfacet + facet normal 0.241041 0.0998425 0.965366 + outer loop + vertex -2.89778 -0.776457 1 + vertex -2.59808 -1.5 1 + vertex -2.16506 -1.25 0.866025 + endloop + endfacet + facet normal 0.241041 0.0998425 0.965366 + outer loop + vertex -2.89778 -0.776457 1 + vertex -2.16506 -1.25 0.866025 + vertex -2.41481 -0.647048 0.866025 + endloop + endfacet + facet normal 0.656082 0.271758 0.704063 + outer loop + vertex -2.41481 -0.647048 0.866025 + vertex -2.16506 -1.25 0.866025 + vertex -1.84808 -1.06699 0.5 + endloop + endfacet + facet normal 0.656082 0.271758 0.704063 + outer loop + vertex -2.41481 -0.647048 0.866025 + vertex -1.84808 -1.06699 0.5 + vertex -2.06126 -0.552313 0.5 + endloop + endfacet + facet normal 0.892909 0.369855 0.256751 + outer loop + vertex -2.06126 -0.552313 0.5 + vertex -1.84808 -1.06699 0.5 + vertex -1.73205 -1 1.22465e-16 + endloop + endfacet + facet normal 0.892909 0.369855 0.256751 + outer loop + vertex -2.06126 -0.552313 0.5 + vertex -1.73205 -1 1.22465e-16 + vertex -1.93185 -0.517638 1.22465e-16 + endloop + endfacet + facet normal 0.892909 0.369855 -0.256751 + outer loop + vertex -1.93185 -0.517638 1.22465e-16 + vertex -1.73205 -1 1.22465e-16 + vertex -1.84808 -1.06699 -0.5 + endloop + endfacet + facet normal 0.892909 0.369855 -0.256751 + outer loop + vertex -1.93185 -0.517638 1.22465e-16 + vertex -1.84808 -1.06699 -0.5 + vertex -2.06126 -0.552313 -0.5 + endloop + endfacet + facet normal 0.656082 0.271758 -0.704063 + outer loop + vertex -2.06126 -0.552313 -0.5 + vertex -1.84808 -1.06699 -0.5 + vertex -2.16506 -1.25 -0.866025 + endloop + endfacet + facet normal 0.656082 0.271758 -0.704063 + outer loop + vertex -2.06126 -0.552313 -0.5 + vertex -2.16506 -1.25 -0.866025 + vertex -2.41481 -0.647048 -0.866025 + endloop + endfacet + facet normal 0.241041 0.0998425 -0.965366 + outer loop + vertex -2.41481 -0.647048 -0.866025 + vertex -2.16506 -1.25 -0.866025 + vertex -2.59808 -1.5 -1 + endloop + endfacet + facet normal 0.241041 0.0998425 -0.965366 + outer loop + vertex -2.41481 -0.647048 -0.866025 + vertex -2.59808 -1.5 -1 + vertex -2.89778 -0.776457 -1 + endloop + endfacet + facet normal -0.241041 -0.0998425 -0.965366 + outer loop + vertex -2.89778 -0.776457 -1 + vertex -2.59808 -1.5 -1 + vertex -3.03109 -1.75 -0.866025 + endloop + endfacet + facet normal -0.241041 -0.0998425 -0.965366 + outer loop + vertex -2.89778 -0.776457 -1 + vertex -3.03109 -1.75 -0.866025 + vertex -3.38074 -0.905867 -0.866025 + endloop + endfacet + facet normal -0.656082 -0.271758 -0.704063 + outer loop + vertex -3.38074 -0.905867 -0.866025 + vertex -3.03109 -1.75 -0.866025 + vertex -3.34808 -1.93301 -0.5 + endloop + endfacet + facet normal -0.656082 -0.271758 -0.704063 + outer loop + vertex -3.38074 -0.905867 -0.866025 + vertex -3.34808 -1.93301 -0.5 + vertex -3.73429 -1.0006 -0.5 + endloop + endfacet + facet normal -0.892909 -0.369855 -0.256751 + outer loop + vertex -3.73429 -1.0006 -0.5 + vertex -3.34808 -1.93301 -0.5 + vertex -3.4641 -2 -2.44929e-16 + endloop + endfacet + facet normal -0.892909 -0.369855 -0.256751 + outer loop + vertex -3.73429 -1.0006 -0.5 + vertex -3.4641 -2 -2.44929e-16 + vertex -3.8637 -1.03528 -2.44929e-16 + endloop + endfacet + facet normal -0.766758 -0.588354 0.256751 + outer loop + vertex -3.4641 -2 0 + vertex -2.82843 -2.82843 0 + vertex -2.73369 -2.73369 0.5 + endloop + endfacet + facet normal -0.766758 -0.588354 0.256751 + outer loop + vertex -3.4641 -2 0 + vertex -2.73369 -2.73369 0.5 + vertex -3.34808 -1.93301 0.5 + endloop + endfacet + facet normal -0.56339 -0.432305 0.704063 + outer loop + vertex -3.34808 -1.93301 0.5 + vertex -2.73369 -2.73369 0.5 + vertex -2.47487 -2.47487 0.866025 + endloop + endfacet + facet normal -0.56339 -0.432305 0.704063 + outer loop + vertex -3.34808 -1.93301 0.5 + vertex -2.47487 -2.47487 0.866025 + vertex -3.03109 -1.75 0.866025 + endloop + endfacet + facet normal -0.206987 -0.158826 0.965366 + outer loop + vertex -3.03109 -1.75 0.866025 + vertex -2.47487 -2.47487 0.866025 + vertex -2.12132 -2.12132 1 + endloop + endfacet + facet normal -0.206987 -0.158826 0.965366 + outer loop + vertex -3.03109 -1.75 0.866025 + vertex -2.12132 -2.12132 1 + vertex -2.59808 -1.5 1 + endloop + endfacet + facet normal 0.206987 0.158826 0.965366 + outer loop + vertex -2.59808 -1.5 1 + vertex -2.12132 -2.12132 1 + vertex -1.76777 -1.76777 0.866025 + endloop + endfacet + facet normal 0.206987 0.158826 0.965366 + outer loop + vertex -2.59808 -1.5 1 + vertex -1.76777 -1.76777 0.866025 + vertex -2.16506 -1.25 0.866025 + endloop + endfacet + facet normal 0.56339 0.432305 0.704063 + outer loop + vertex -2.16506 -1.25 0.866025 + vertex -1.76777 -1.76777 0.866025 + vertex -1.50895 -1.50895 0.5 + endloop + endfacet + facet normal 0.56339 0.432305 0.704063 + outer loop + vertex -2.16506 -1.25 0.866025 + vertex -1.50895 -1.50895 0.5 + vertex -1.84808 -1.06699 0.5 + endloop + endfacet + facet normal 0.766758 0.588354 0.256751 + outer loop + vertex -1.84808 -1.06699 0.5 + vertex -1.50895 -1.50895 0.5 + vertex -1.41421 -1.41421 1.22465e-16 + endloop + endfacet + facet normal 0.766758 0.588354 0.256751 + outer loop + vertex -1.84808 -1.06699 0.5 + vertex -1.41421 -1.41421 1.22465e-16 + vertex -1.73205 -1 1.22465e-16 + endloop + endfacet + facet normal 0.766758 0.588354 -0.256751 + outer loop + vertex -1.73205 -1 1.22465e-16 + vertex -1.41421 -1.41421 1.22465e-16 + vertex -1.50895 -1.50895 -0.5 + endloop + endfacet + facet normal 0.766758 0.588354 -0.256751 + outer loop + vertex -1.73205 -1 1.22465e-16 + vertex -1.50895 -1.50895 -0.5 + vertex -1.84808 -1.06699 -0.5 + endloop + endfacet + facet normal 0.56339 0.432305 -0.704063 + outer loop + vertex -1.84808 -1.06699 -0.5 + vertex -1.50895 -1.50895 -0.5 + vertex -1.76777 -1.76777 -0.866025 + endloop + endfacet + facet normal 0.56339 0.432305 -0.704063 + outer loop + vertex -1.84808 -1.06699 -0.5 + vertex -1.76777 -1.76777 -0.866025 + vertex -2.16506 -1.25 -0.866025 + endloop + endfacet + facet normal 0.206987 0.158826 -0.965366 + outer loop + vertex -2.16506 -1.25 -0.866025 + vertex -1.76777 -1.76777 -0.866025 + vertex -2.12132 -2.12132 -1 + endloop + endfacet + facet normal 0.206987 0.158826 -0.965366 + outer loop + vertex -2.16506 -1.25 -0.866025 + vertex -2.12132 -2.12132 -1 + vertex -2.59808 -1.5 -1 + endloop + endfacet + facet normal -0.206987 -0.158826 -0.965366 + outer loop + vertex -2.59808 -1.5 -1 + vertex -2.12132 -2.12132 -1 + vertex -2.47487 -2.47487 -0.866025 + endloop + endfacet + facet normal -0.206987 -0.158826 -0.965366 + outer loop + vertex -2.59808 -1.5 -1 + vertex -2.47487 -2.47487 -0.866025 + vertex -3.03109 -1.75 -0.866025 + endloop + endfacet + facet normal -0.56339 -0.432305 -0.704063 + outer loop + vertex -3.03109 -1.75 -0.866025 + vertex -2.47487 -2.47487 -0.866025 + vertex -2.73369 -2.73369 -0.5 + endloop + endfacet + facet normal -0.56339 -0.432305 -0.704063 + outer loop + vertex -3.03109 -1.75 -0.866025 + vertex -2.73369 -2.73369 -0.5 + vertex -3.34808 -1.93301 -0.5 + endloop + endfacet + facet normal -0.766758 -0.588354 -0.256751 + outer loop + vertex -3.34808 -1.93301 -0.5 + vertex -2.73369 -2.73369 -0.5 + vertex -2.82843 -2.82843 -2.44929e-16 + endloop + endfacet + facet normal -0.766758 -0.588354 -0.256751 + outer loop + vertex -3.34808 -1.93301 -0.5 + vertex -2.82843 -2.82843 -2.44929e-16 + vertex -3.4641 -2 -2.44929e-16 + endloop + endfacet + facet normal -0.588354 -0.766758 0.256751 + outer loop + vertex -2.82843 -2.82843 0 + vertex -2 -3.4641 0 + vertex -1.93301 -3.34808 0.5 + endloop + endfacet + facet normal -0.588354 -0.766758 0.256751 + outer loop + vertex -2.82843 -2.82843 0 + vertex -1.93301 -3.34808 0.5 + vertex -2.73369 -2.73369 0.5 + endloop + endfacet + facet normal -0.432305 -0.56339 0.704063 + outer loop + vertex -2.73369 -2.73369 0.5 + vertex -1.93301 -3.34808 0.5 + vertex -1.75 -3.03109 0.866025 + endloop + endfacet + facet normal -0.432305 -0.56339 0.704063 + outer loop + vertex -2.73369 -2.73369 0.5 + vertex -1.75 -3.03109 0.866025 + vertex -2.47487 -2.47487 0.866025 + endloop + endfacet + facet normal -0.158826 -0.206987 0.965366 + outer loop + vertex -2.47487 -2.47487 0.866025 + vertex -1.75 -3.03109 0.866025 + vertex -1.5 -2.59808 1 + endloop + endfacet + facet normal -0.158826 -0.206987 0.965366 + outer loop + vertex -2.47487 -2.47487 0.866025 + vertex -1.5 -2.59808 1 + vertex -2.12132 -2.12132 1 + endloop + endfacet + facet normal 0.158826 0.206987 0.965366 + outer loop + vertex -2.12132 -2.12132 1 + vertex -1.5 -2.59808 1 + vertex -1.25 -2.16506 0.866025 + endloop + endfacet + facet normal 0.158826 0.206987 0.965366 + outer loop + vertex -2.12132 -2.12132 1 + vertex -1.25 -2.16506 0.866025 + vertex -1.76777 -1.76777 0.866025 + endloop + endfacet + facet normal 0.432305 0.56339 0.704063 + outer loop + vertex -1.76777 -1.76777 0.866025 + vertex -1.25 -2.16506 0.866025 + vertex -1.06699 -1.84808 0.5 + endloop + endfacet + facet normal 0.432305 0.56339 0.704063 + outer loop + vertex -1.76777 -1.76777 0.866025 + vertex -1.06699 -1.84808 0.5 + vertex -1.50895 -1.50895 0.5 + endloop + endfacet + facet normal 0.588354 0.766758 0.256751 + outer loop + vertex -1.50895 -1.50895 0.5 + vertex -1.06699 -1.84808 0.5 + vertex -1 -1.73205 1.22465e-16 + endloop + endfacet + facet normal 0.588354 0.766758 0.256751 + outer loop + vertex -1.50895 -1.50895 0.5 + vertex -1 -1.73205 1.22465e-16 + vertex -1.41421 -1.41421 1.22465e-16 + endloop + endfacet + facet normal 0.588354 0.766758 -0.256751 + outer loop + vertex -1.41421 -1.41421 1.22465e-16 + vertex -1 -1.73205 1.22465e-16 + vertex -1.06699 -1.84808 -0.5 + endloop + endfacet + facet normal 0.588354 0.766758 -0.256751 + outer loop + vertex -1.41421 -1.41421 1.22465e-16 + vertex -1.06699 -1.84808 -0.5 + vertex -1.50895 -1.50895 -0.5 + endloop + endfacet + facet normal 0.432305 0.56339 -0.704063 + outer loop + vertex -1.50895 -1.50895 -0.5 + vertex -1.06699 -1.84808 -0.5 + vertex -1.25 -2.16506 -0.866025 + endloop + endfacet + facet normal 0.432305 0.56339 -0.704063 + outer loop + vertex -1.50895 -1.50895 -0.5 + vertex -1.25 -2.16506 -0.866025 + vertex -1.76777 -1.76777 -0.866025 + endloop + endfacet + facet normal 0.158826 0.206987 -0.965366 + outer loop + vertex -1.76777 -1.76777 -0.866025 + vertex -1.25 -2.16506 -0.866025 + vertex -1.5 -2.59808 -1 + endloop + endfacet + facet normal 0.158826 0.206987 -0.965366 + outer loop + vertex -1.76777 -1.76777 -0.866025 + vertex -1.5 -2.59808 -1 + vertex -2.12132 -2.12132 -1 + endloop + endfacet + facet normal -0.158826 -0.206987 -0.965366 + outer loop + vertex -2.12132 -2.12132 -1 + vertex -1.5 -2.59808 -1 + vertex -1.75 -3.03109 -0.866025 + endloop + endfacet + facet normal -0.158826 -0.206987 -0.965366 + outer loop + vertex -2.12132 -2.12132 -1 + vertex -1.75 -3.03109 -0.866025 + vertex -2.47487 -2.47487 -0.866025 + endloop + endfacet + facet normal -0.432305 -0.56339 -0.704063 + outer loop + vertex -2.47487 -2.47487 -0.866025 + vertex -1.75 -3.03109 -0.866025 + vertex -1.93301 -3.34808 -0.5 + endloop + endfacet + facet normal -0.432305 -0.56339 -0.704063 + outer loop + vertex -2.47487 -2.47487 -0.866025 + vertex -1.93301 -3.34808 -0.5 + vertex -2.73369 -2.73369 -0.5 + endloop + endfacet + facet normal -0.588354 -0.766758 -0.256751 + outer loop + vertex -2.73369 -2.73369 -0.5 + vertex -1.93301 -3.34808 -0.5 + vertex -2 -3.4641 -2.44929e-16 + endloop + endfacet + facet normal -0.588354 -0.766758 -0.256751 + outer loop + vertex -2.73369 -2.73369 -0.5 + vertex -2 -3.4641 -2.44929e-16 + vertex -2.82843 -2.82843 -2.44929e-16 + endloop + endfacet + facet normal -0.369855 -0.892909 0.256751 + outer loop + vertex -2 -3.4641 0 + vertex -1.03528 -3.8637 0 + vertex -1.0006 -3.73429 0.5 + endloop + endfacet + facet normal -0.369855 -0.892909 0.256751 + outer loop + vertex -2 -3.4641 0 + vertex -1.0006 -3.73429 0.5 + vertex -1.93301 -3.34808 0.5 + endloop + endfacet + facet normal -0.271758 -0.656082 0.704063 + outer loop + vertex -1.93301 -3.34808 0.5 + vertex -1.0006 -3.73429 0.5 + vertex -0.905867 -3.38074 0.866025 + endloop + endfacet + facet normal -0.271758 -0.656082 0.704063 + outer loop + vertex -1.93301 -3.34808 0.5 + vertex -0.905867 -3.38074 0.866025 + vertex -1.75 -3.03109 0.866025 + endloop + endfacet + facet normal -0.0998425 -0.241041 0.965366 + outer loop + vertex -1.75 -3.03109 0.866025 + vertex -0.905867 -3.38074 0.866025 + vertex -0.776457 -2.89778 1 + endloop + endfacet + facet normal -0.0998425 -0.241041 0.965366 + outer loop + vertex -1.75 -3.03109 0.866025 + vertex -0.776457 -2.89778 1 + vertex -1.5 -2.59808 1 + endloop + endfacet + facet normal 0.0998425 0.241041 0.965366 + outer loop + vertex -1.5 -2.59808 1 + vertex -0.776457 -2.89778 1 + vertex -0.647048 -2.41481 0.866025 + endloop + endfacet + facet normal 0.0998425 0.241041 0.965366 + outer loop + vertex -1.5 -2.59808 1 + vertex -0.647048 -2.41481 0.866025 + vertex -1.25 -2.16506 0.866025 + endloop + endfacet + facet normal 0.271758 0.656082 0.704063 + outer loop + vertex -1.25 -2.16506 0.866025 + vertex -0.647048 -2.41481 0.866025 + vertex -0.552313 -2.06126 0.5 + endloop + endfacet + facet normal 0.271758 0.656082 0.704063 + outer loop + vertex -1.25 -2.16506 0.866025 + vertex -0.552313 -2.06126 0.5 + vertex -1.06699 -1.84808 0.5 + endloop + endfacet + facet normal 0.369855 0.892909 0.256751 + outer loop + vertex -1.06699 -1.84808 0.5 + vertex -0.552313 -2.06126 0.5 + vertex -0.517638 -1.93185 1.22465e-16 + endloop + endfacet + facet normal 0.369855 0.892909 0.256751 + outer loop + vertex -1.06699 -1.84808 0.5 + vertex -0.517638 -1.93185 1.22465e-16 + vertex -1 -1.73205 1.22465e-16 + endloop + endfacet + facet normal 0.369855 0.892909 -0.256751 + outer loop + vertex -1 -1.73205 1.22465e-16 + vertex -0.517638 -1.93185 1.22465e-16 + vertex -0.552313 -2.06126 -0.5 + endloop + endfacet + facet normal 0.369855 0.892909 -0.256751 + outer loop + vertex -1 -1.73205 1.22465e-16 + vertex -0.552313 -2.06126 -0.5 + vertex -1.06699 -1.84808 -0.5 + endloop + endfacet + facet normal 0.271758 0.656082 -0.704063 + outer loop + vertex -1.06699 -1.84808 -0.5 + vertex -0.552313 -2.06126 -0.5 + vertex -0.647048 -2.41481 -0.866025 + endloop + endfacet + facet normal 0.271758 0.656082 -0.704063 + outer loop + vertex -1.06699 -1.84808 -0.5 + vertex -0.647048 -2.41481 -0.866025 + vertex -1.25 -2.16506 -0.866025 + endloop + endfacet + facet normal 0.0998425 0.241041 -0.965366 + outer loop + vertex -1.25 -2.16506 -0.866025 + vertex -0.647048 -2.41481 -0.866025 + vertex -0.776457 -2.89778 -1 + endloop + endfacet + facet normal 0.0998425 0.241041 -0.965366 + outer loop + vertex -1.25 -2.16506 -0.866025 + vertex -0.776457 -2.89778 -1 + vertex -1.5 -2.59808 -1 + endloop + endfacet + facet normal -0.0998425 -0.241041 -0.965366 + outer loop + vertex -1.5 -2.59808 -1 + vertex -0.776457 -2.89778 -1 + vertex -0.905867 -3.38074 -0.866025 + endloop + endfacet + facet normal -0.0998425 -0.241041 -0.965366 + outer loop + vertex -1.5 -2.59808 -1 + vertex -0.905867 -3.38074 -0.866025 + vertex -1.75 -3.03109 -0.866025 + endloop + endfacet + facet normal -0.271758 -0.656082 -0.704063 + outer loop + vertex -1.75 -3.03109 -0.866025 + vertex -0.905867 -3.38074 -0.866025 + vertex -1.0006 -3.73429 -0.5 + endloop + endfacet + facet normal -0.271758 -0.656082 -0.704063 + outer loop + vertex -1.75 -3.03109 -0.866025 + vertex -1.0006 -3.73429 -0.5 + vertex -1.93301 -3.34808 -0.5 + endloop + endfacet + facet normal -0.369855 -0.892909 -0.256751 + outer loop + vertex -1.93301 -3.34808 -0.5 + vertex -1.0006 -3.73429 -0.5 + vertex -1.03528 -3.8637 -2.44929e-16 + endloop + endfacet + facet normal -0.369855 -0.892909 -0.256751 + outer loop + vertex -1.93301 -3.34808 -0.5 + vertex -1.03528 -3.8637 -2.44929e-16 + vertex -2 -3.4641 -2.44929e-16 + endloop + endfacet + facet normal -0.126151 -0.958209 0.256751 + outer loop + vertex -1.03528 -3.8637 0 + vertex -7.34788e-16 -4 0 + vertex -7.10177e-16 -3.86603 0.5 + endloop + endfacet + facet normal -0.126151 -0.958209 0.256751 + outer loop + vertex -1.03528 -3.8637 0 + vertex -7.10177e-16 -3.86603 0.5 + vertex -1.0006 -3.73429 0.5 + endloop + endfacet + facet normal -0.0926916 -0.704063 0.704063 + outer loop + vertex -1.0006 -3.73429 0.5 + vertex -7.10177e-16 -3.86603 0.5 + vertex -6.4294e-16 -3.5 0.866025 + endloop + endfacet + facet normal -0.0926916 -0.704063 0.704063 + outer loop + vertex -1.0006 -3.73429 0.5 + vertex -6.4294e-16 -3.5 0.866025 + vertex -0.905867 -3.38074 0.866025 + endloop + endfacet + facet normal -0.0340544 -0.258669 0.965366 + outer loop + vertex -0.905867 -3.38074 0.866025 + vertex -6.4294e-16 -3.5 0.866025 + vertex -5.51091e-16 -3 1 + endloop + endfacet + facet normal -0.0340544 -0.258669 0.965366 + outer loop + vertex -0.905867 -3.38074 0.866025 + vertex -5.51091e-16 -3 1 + vertex -0.776457 -2.89778 1 + endloop + endfacet + facet normal 0.0340544 0.258669 0.965366 + outer loop + vertex -0.776457 -2.89778 1 + vertex -5.51091e-16 -3 1 + vertex -4.59243e-16 -2.5 0.866025 + endloop + endfacet + facet normal 0.0340544 0.258669 0.965366 + outer loop + vertex -0.776457 -2.89778 1 + vertex -4.59243e-16 -2.5 0.866025 + vertex -0.647048 -2.41481 0.866025 + endloop + endfacet + facet normal 0.0926916 0.704063 0.704063 + outer loop + vertex -0.647048 -2.41481 0.866025 + vertex -4.59243e-16 -2.5 0.866025 + vertex -3.92005e-16 -2.13397 0.5 + endloop + endfacet + facet normal 0.0926916 0.704063 0.704063 + outer loop + vertex -0.647048 -2.41481 0.866025 + vertex -3.92005e-16 -2.13397 0.5 + vertex -0.552313 -2.06126 0.5 + endloop + endfacet + facet normal 0.126151 0.958209 0.256751 + outer loop + vertex -0.552313 -2.06126 0.5 + vertex -3.92005e-16 -2.13397 0.5 + vertex -3.67394e-16 -2 1.22465e-16 + endloop + endfacet + facet normal 0.126151 0.958209 0.256751 + outer loop + vertex -0.552313 -2.06126 0.5 + vertex -3.67394e-16 -2 1.22465e-16 + vertex -0.517638 -1.93185 1.22465e-16 + endloop + endfacet + facet normal 0.126151 0.958209 -0.256751 + outer loop + vertex -0.517638 -1.93185 1.22465e-16 + vertex -3.67394e-16 -2 1.22465e-16 + vertex -3.92005e-16 -2.13397 -0.5 + endloop + endfacet + facet normal 0.126151 0.958209 -0.256751 + outer loop + vertex -0.517638 -1.93185 1.22465e-16 + vertex -3.92005e-16 -2.13397 -0.5 + vertex -0.552313 -2.06126 -0.5 + endloop + endfacet + facet normal 0.0926916 0.704063 -0.704063 + outer loop + vertex -0.552313 -2.06126 -0.5 + vertex -3.92005e-16 -2.13397 -0.5 + vertex -4.59243e-16 -2.5 -0.866025 + endloop + endfacet + facet normal 0.0926916 0.704063 -0.704063 + outer loop + vertex -0.552313 -2.06126 -0.5 + vertex -4.59243e-16 -2.5 -0.866025 + vertex -0.647048 -2.41481 -0.866025 + endloop + endfacet + facet normal 0.0340544 0.258669 -0.965366 + outer loop + vertex -0.647048 -2.41481 -0.866025 + vertex -4.59243e-16 -2.5 -0.866025 + vertex -5.51091e-16 -3 -1 + endloop + endfacet + facet normal 0.0340544 0.258669 -0.965366 + outer loop + vertex -0.647048 -2.41481 -0.866025 + vertex -5.51091e-16 -3 -1 + vertex -0.776457 -2.89778 -1 + endloop + endfacet + facet normal -0.0340544 -0.258669 -0.965366 + outer loop + vertex -0.776457 -2.89778 -1 + vertex -5.51091e-16 -3 -1 + vertex -6.4294e-16 -3.5 -0.866025 + endloop + endfacet + facet normal -0.0340544 -0.258669 -0.965366 + outer loop + vertex -0.776457 -2.89778 -1 + vertex -6.4294e-16 -3.5 -0.866025 + vertex -0.905867 -3.38074 -0.866025 + endloop + endfacet + facet normal -0.0926916 -0.704063 -0.704063 + outer loop + vertex -0.905867 -3.38074 -0.866025 + vertex -6.4294e-16 -3.5 -0.866025 + vertex -7.10177e-16 -3.86603 -0.5 + endloop + endfacet + facet normal -0.0926916 -0.704063 -0.704063 + outer loop + vertex -0.905867 -3.38074 -0.866025 + vertex -7.10177e-16 -3.86603 -0.5 + vertex -1.0006 -3.73429 -0.5 + endloop + endfacet + facet normal -0.126151 -0.958209 -0.256751 + outer loop + vertex -1.0006 -3.73429 -0.5 + vertex -7.10177e-16 -3.86603 -0.5 + vertex -7.34788e-16 -4 -2.44929e-16 + endloop + endfacet + facet normal -0.126151 -0.958209 -0.256751 + outer loop + vertex -1.0006 -3.73429 -0.5 + vertex -7.34788e-16 -4 -2.44929e-16 + vertex -1.03528 -3.8637 -2.44929e-16 + endloop + endfacet + facet normal 0.126151 -0.958209 0.256751 + outer loop + vertex -7.34788e-16 -4 0 + vertex 1.03528 -3.8637 0 + vertex 1.0006 -3.73429 0.5 + endloop + endfacet + facet normal 0.126151 -0.958209 0.256751 + outer loop + vertex -7.34788e-16 -4 0 + vertex 1.0006 -3.73429 0.5 + vertex -7.10177e-16 -3.86603 0.5 + endloop + endfacet + facet normal 0.0926916 -0.704063 0.704063 + outer loop + vertex -7.10177e-16 -3.86603 0.5 + vertex 1.0006 -3.73429 0.5 + vertex 0.905867 -3.38074 0.866025 + endloop + endfacet + facet normal 0.0926916 -0.704063 0.704063 + outer loop + vertex -7.10177e-16 -3.86603 0.5 + vertex 0.905867 -3.38074 0.866025 + vertex -6.4294e-16 -3.5 0.866025 + endloop + endfacet + facet normal 0.0340544 -0.258669 0.965366 + outer loop + vertex -6.4294e-16 -3.5 0.866025 + vertex 0.905867 -3.38074 0.866025 + vertex 0.776457 -2.89778 1 + endloop + endfacet + facet normal 0.0340544 -0.258669 0.965366 + outer loop + vertex -6.4294e-16 -3.5 0.866025 + vertex 0.776457 -2.89778 1 + vertex -5.51091e-16 -3 1 + endloop + endfacet + facet normal -0.0340544 0.258669 0.965366 + outer loop + vertex -5.51091e-16 -3 1 + vertex 0.776457 -2.89778 1 + vertex 0.647048 -2.41481 0.866025 + endloop + endfacet + facet normal -0.0340544 0.258669 0.965366 + outer loop + vertex -5.51091e-16 -3 1 + vertex 0.647048 -2.41481 0.866025 + vertex -4.59243e-16 -2.5 0.866025 + endloop + endfacet + facet normal -0.0926916 0.704063 0.704063 + outer loop + vertex -4.59243e-16 -2.5 0.866025 + vertex 0.647048 -2.41481 0.866025 + vertex 0.552313 -2.06126 0.5 + endloop + endfacet + facet normal -0.0926916 0.704063 0.704063 + outer loop + vertex -4.59243e-16 -2.5 0.866025 + vertex 0.552313 -2.06126 0.5 + vertex -3.92005e-16 -2.13397 0.5 + endloop + endfacet + facet normal -0.126151 0.958209 0.256751 + outer loop + vertex -3.92005e-16 -2.13397 0.5 + vertex 0.552313 -2.06126 0.5 + vertex 0.517638 -1.93185 1.22465e-16 + endloop + endfacet + facet normal -0.126151 0.958209 0.256751 + outer loop + vertex -3.92005e-16 -2.13397 0.5 + vertex 0.517638 -1.93185 1.22465e-16 + vertex -3.67394e-16 -2 1.22465e-16 + endloop + endfacet + facet normal -0.126151 0.958209 -0.256751 + outer loop + vertex -3.67394e-16 -2 1.22465e-16 + vertex 0.517638 -1.93185 1.22465e-16 + vertex 0.552313 -2.06126 -0.5 + endloop + endfacet + facet normal -0.126151 0.958209 -0.256751 + outer loop + vertex -3.67394e-16 -2 1.22465e-16 + vertex 0.552313 -2.06126 -0.5 + vertex -3.92005e-16 -2.13397 -0.5 + endloop + endfacet + facet normal -0.0926916 0.704063 -0.704063 + outer loop + vertex -3.92005e-16 -2.13397 -0.5 + vertex 0.552313 -2.06126 -0.5 + vertex 0.647048 -2.41481 -0.866025 + endloop + endfacet + facet normal -0.0926916 0.704063 -0.704063 + outer loop + vertex -3.92005e-16 -2.13397 -0.5 + vertex 0.647048 -2.41481 -0.866025 + vertex -4.59243e-16 -2.5 -0.866025 + endloop + endfacet + facet normal -0.0340544 0.258669 -0.965366 + outer loop + vertex -4.59243e-16 -2.5 -0.866025 + vertex 0.647048 -2.41481 -0.866025 + vertex 0.776457 -2.89778 -1 + endloop + endfacet + facet normal -0.0340544 0.258669 -0.965366 + outer loop + vertex -4.59243e-16 -2.5 -0.866025 + vertex 0.776457 -2.89778 -1 + vertex -5.51091e-16 -3 -1 + endloop + endfacet + facet normal 0.0340544 -0.258669 -0.965366 + outer loop + vertex -5.51091e-16 -3 -1 + vertex 0.776457 -2.89778 -1 + vertex 0.905867 -3.38074 -0.866025 + endloop + endfacet + facet normal 0.0340544 -0.258669 -0.965366 + outer loop + vertex -5.51091e-16 -3 -1 + vertex 0.905867 -3.38074 -0.866025 + vertex -6.4294e-16 -3.5 -0.866025 + endloop + endfacet + facet normal 0.0926916 -0.704063 -0.704063 + outer loop + vertex -6.4294e-16 -3.5 -0.866025 + vertex 0.905867 -3.38074 -0.866025 + vertex 1.0006 -3.73429 -0.5 + endloop + endfacet + facet normal 0.0926916 -0.704063 -0.704063 + outer loop + vertex -6.4294e-16 -3.5 -0.866025 + vertex 1.0006 -3.73429 -0.5 + vertex -7.10177e-16 -3.86603 -0.5 + endloop + endfacet + facet normal 0.126151 -0.958209 -0.256751 + outer loop + vertex -7.10177e-16 -3.86603 -0.5 + vertex 1.0006 -3.73429 -0.5 + vertex 1.03528 -3.8637 -2.44929e-16 + endloop + endfacet + facet normal 0.126151 -0.958209 -0.256751 + outer loop + vertex -7.10177e-16 -3.86603 -0.5 + vertex 1.03528 -3.8637 -2.44929e-16 + vertex -7.34788e-16 -4 -2.44929e-16 + endloop + endfacet + facet normal 0.369855 -0.892909 0.256751 + outer loop + vertex 1.03528 -3.8637 0 + vertex 2 -3.4641 0 + vertex 1.93301 -3.34808 0.5 + endloop + endfacet + facet normal 0.369855 -0.892909 0.256751 + outer loop + vertex 1.03528 -3.8637 0 + vertex 1.93301 -3.34808 0.5 + vertex 1.0006 -3.73429 0.5 + endloop + endfacet + facet normal 0.271758 -0.656082 0.704063 + outer loop + vertex 1.0006 -3.73429 0.5 + vertex 1.93301 -3.34808 0.5 + vertex 1.75 -3.03109 0.866025 + endloop + endfacet + facet normal 0.271758 -0.656082 0.704063 + outer loop + vertex 1.0006 -3.73429 0.5 + vertex 1.75 -3.03109 0.866025 + vertex 0.905867 -3.38074 0.866025 + endloop + endfacet + facet normal 0.0998425 -0.241041 0.965366 + outer loop + vertex 0.905867 -3.38074 0.866025 + vertex 1.75 -3.03109 0.866025 + vertex 1.5 -2.59808 1 + endloop + endfacet + facet normal 0.0998425 -0.241041 0.965366 + outer loop + vertex 0.905867 -3.38074 0.866025 + vertex 1.5 -2.59808 1 + vertex 0.776457 -2.89778 1 + endloop + endfacet + facet normal -0.0998425 0.241041 0.965366 + outer loop + vertex 0.776457 -2.89778 1 + vertex 1.5 -2.59808 1 + vertex 1.25 -2.16506 0.866025 + endloop + endfacet + facet normal -0.0998425 0.241041 0.965366 + outer loop + vertex 0.776457 -2.89778 1 + vertex 1.25 -2.16506 0.866025 + vertex 0.647048 -2.41481 0.866025 + endloop + endfacet + facet normal -0.271758 0.656082 0.704063 + outer loop + vertex 0.647048 -2.41481 0.866025 + vertex 1.25 -2.16506 0.866025 + vertex 1.06699 -1.84808 0.5 + endloop + endfacet + facet normal -0.271758 0.656082 0.704063 + outer loop + vertex 0.647048 -2.41481 0.866025 + vertex 1.06699 -1.84808 0.5 + vertex 0.552313 -2.06126 0.5 + endloop + endfacet + facet normal -0.369855 0.892909 0.256751 + outer loop + vertex 0.552313 -2.06126 0.5 + vertex 1.06699 -1.84808 0.5 + vertex 1 -1.73205 1.22465e-16 + endloop + endfacet + facet normal -0.369855 0.892909 0.256751 + outer loop + vertex 0.552313 -2.06126 0.5 + vertex 1 -1.73205 1.22465e-16 + vertex 0.517638 -1.93185 1.22465e-16 + endloop + endfacet + facet normal -0.369855 0.892909 -0.256751 + outer loop + vertex 0.517638 -1.93185 1.22465e-16 + vertex 1 -1.73205 1.22465e-16 + vertex 1.06699 -1.84808 -0.5 + endloop + endfacet + facet normal -0.369855 0.892909 -0.256751 + outer loop + vertex 0.517638 -1.93185 1.22465e-16 + vertex 1.06699 -1.84808 -0.5 + vertex 0.552313 -2.06126 -0.5 + endloop + endfacet + facet normal -0.271758 0.656082 -0.704063 + outer loop + vertex 0.552313 -2.06126 -0.5 + vertex 1.06699 -1.84808 -0.5 + vertex 1.25 -2.16506 -0.866025 + endloop + endfacet + facet normal -0.271758 0.656082 -0.704063 + outer loop + vertex 0.552313 -2.06126 -0.5 + vertex 1.25 -2.16506 -0.866025 + vertex 0.647048 -2.41481 -0.866025 + endloop + endfacet + facet normal -0.0998425 0.241041 -0.965366 + outer loop + vertex 0.647048 -2.41481 -0.866025 + vertex 1.25 -2.16506 -0.866025 + vertex 1.5 -2.59808 -1 + endloop + endfacet + facet normal -0.0998425 0.241041 -0.965366 + outer loop + vertex 0.647048 -2.41481 -0.866025 + vertex 1.5 -2.59808 -1 + vertex 0.776457 -2.89778 -1 + endloop + endfacet + facet normal 0.0998425 -0.241041 -0.965366 + outer loop + vertex 0.776457 -2.89778 -1 + vertex 1.5 -2.59808 -1 + vertex 1.75 -3.03109 -0.866025 + endloop + endfacet + facet normal 0.0998425 -0.241041 -0.965366 + outer loop + vertex 0.776457 -2.89778 -1 + vertex 1.75 -3.03109 -0.866025 + vertex 0.905867 -3.38074 -0.866025 + endloop + endfacet + facet normal 0.271758 -0.656082 -0.704063 + outer loop + vertex 0.905867 -3.38074 -0.866025 + vertex 1.75 -3.03109 -0.866025 + vertex 1.93301 -3.34808 -0.5 + endloop + endfacet + facet normal 0.271758 -0.656082 -0.704063 + outer loop + vertex 0.905867 -3.38074 -0.866025 + vertex 1.93301 -3.34808 -0.5 + vertex 1.0006 -3.73429 -0.5 + endloop + endfacet + facet normal 0.369855 -0.892909 -0.256751 + outer loop + vertex 1.0006 -3.73429 -0.5 + vertex 1.93301 -3.34808 -0.5 + vertex 2 -3.4641 -2.44929e-16 + endloop + endfacet + facet normal 0.369855 -0.892909 -0.256751 + outer loop + vertex 1.0006 -3.73429 -0.5 + vertex 2 -3.4641 -2.44929e-16 + vertex 1.03528 -3.8637 -2.44929e-16 + endloop + endfacet + facet normal 0.588354 -0.766758 0.256751 + outer loop + vertex 2 -3.4641 0 + vertex 2.82843 -2.82843 0 + vertex 2.73369 -2.73369 0.5 + endloop + endfacet + facet normal 0.588354 -0.766758 0.256751 + outer loop + vertex 2 -3.4641 0 + vertex 2.73369 -2.73369 0.5 + vertex 1.93301 -3.34808 0.5 + endloop + endfacet + facet normal 0.432305 -0.56339 0.704063 + outer loop + vertex 1.93301 -3.34808 0.5 + vertex 2.73369 -2.73369 0.5 + vertex 2.47487 -2.47487 0.866025 + endloop + endfacet + facet normal 0.432305 -0.56339 0.704063 + outer loop + vertex 1.93301 -3.34808 0.5 + vertex 2.47487 -2.47487 0.866025 + vertex 1.75 -3.03109 0.866025 + endloop + endfacet + facet normal 0.158826 -0.206987 0.965366 + outer loop + vertex 1.75 -3.03109 0.866025 + vertex 2.47487 -2.47487 0.866025 + vertex 2.12132 -2.12132 1 + endloop + endfacet + facet normal 0.158826 -0.206987 0.965366 + outer loop + vertex 1.75 -3.03109 0.866025 + vertex 2.12132 -2.12132 1 + vertex 1.5 -2.59808 1 + endloop + endfacet + facet normal -0.158826 0.206987 0.965366 + outer loop + vertex 1.5 -2.59808 1 + vertex 2.12132 -2.12132 1 + vertex 1.76777 -1.76777 0.866025 + endloop + endfacet + facet normal -0.158826 0.206987 0.965366 + outer loop + vertex 1.5 -2.59808 1 + vertex 1.76777 -1.76777 0.866025 + vertex 1.25 -2.16506 0.866025 + endloop + endfacet + facet normal -0.432305 0.56339 0.704063 + outer loop + vertex 1.25 -2.16506 0.866025 + vertex 1.76777 -1.76777 0.866025 + vertex 1.50895 -1.50895 0.5 + endloop + endfacet + facet normal -0.432305 0.56339 0.704063 + outer loop + vertex 1.25 -2.16506 0.866025 + vertex 1.50895 -1.50895 0.5 + vertex 1.06699 -1.84808 0.5 + endloop + endfacet + facet normal -0.588354 0.766758 0.256751 + outer loop + vertex 1.06699 -1.84808 0.5 + vertex 1.50895 -1.50895 0.5 + vertex 1.41421 -1.41421 1.22465e-16 + endloop + endfacet + facet normal -0.588354 0.766758 0.256751 + outer loop + vertex 1.06699 -1.84808 0.5 + vertex 1.41421 -1.41421 1.22465e-16 + vertex 1 -1.73205 1.22465e-16 + endloop + endfacet + facet normal -0.588354 0.766758 -0.256751 + outer loop + vertex 1 -1.73205 1.22465e-16 + vertex 1.41421 -1.41421 1.22465e-16 + vertex 1.50895 -1.50895 -0.5 + endloop + endfacet + facet normal -0.588354 0.766758 -0.256751 + outer loop + vertex 1 -1.73205 1.22465e-16 + vertex 1.50895 -1.50895 -0.5 + vertex 1.06699 -1.84808 -0.5 + endloop + endfacet + facet normal -0.432305 0.56339 -0.704063 + outer loop + vertex 1.06699 -1.84808 -0.5 + vertex 1.50895 -1.50895 -0.5 + vertex 1.76777 -1.76777 -0.866025 + endloop + endfacet + facet normal -0.432305 0.56339 -0.704063 + outer loop + vertex 1.06699 -1.84808 -0.5 + vertex 1.76777 -1.76777 -0.866025 + vertex 1.25 -2.16506 -0.866025 + endloop + endfacet + facet normal -0.158826 0.206987 -0.965366 + outer loop + vertex 1.25 -2.16506 -0.866025 + vertex 1.76777 -1.76777 -0.866025 + vertex 2.12132 -2.12132 -1 + endloop + endfacet + facet normal -0.158826 0.206987 -0.965366 + outer loop + vertex 1.25 -2.16506 -0.866025 + vertex 2.12132 -2.12132 -1 + vertex 1.5 -2.59808 -1 + endloop + endfacet + facet normal 0.158826 -0.206987 -0.965366 + outer loop + vertex 1.5 -2.59808 -1 + vertex 2.12132 -2.12132 -1 + vertex 2.47487 -2.47487 -0.866025 + endloop + endfacet + facet normal 0.158826 -0.206987 -0.965366 + outer loop + vertex 1.5 -2.59808 -1 + vertex 2.47487 -2.47487 -0.866025 + vertex 1.75 -3.03109 -0.866025 + endloop + endfacet + facet normal 0.432305 -0.56339 -0.704063 + outer loop + vertex 1.75 -3.03109 -0.866025 + vertex 2.47487 -2.47487 -0.866025 + vertex 2.73369 -2.73369 -0.5 + endloop + endfacet + facet normal 0.432305 -0.56339 -0.704063 + outer loop + vertex 1.75 -3.03109 -0.866025 + vertex 2.73369 -2.73369 -0.5 + vertex 1.93301 -3.34808 -0.5 + endloop + endfacet + facet normal 0.588354 -0.766758 -0.256751 + outer loop + vertex 1.93301 -3.34808 -0.5 + vertex 2.73369 -2.73369 -0.5 + vertex 2.82843 -2.82843 -2.44929e-16 + endloop + endfacet + facet normal 0.588354 -0.766758 -0.256751 + outer loop + vertex 1.93301 -3.34808 -0.5 + vertex 2.82843 -2.82843 -2.44929e-16 + vertex 2 -3.4641 -2.44929e-16 + endloop + endfacet + facet normal 0.766758 -0.588354 0.256751 + outer loop + vertex 2.82843 -2.82843 0 + vertex 3.4641 -2 0 + vertex 3.34808 -1.93301 0.5 + endloop + endfacet + facet normal 0.766758 -0.588354 0.256751 + outer loop + vertex 2.82843 -2.82843 0 + vertex 3.34808 -1.93301 0.5 + vertex 2.73369 -2.73369 0.5 + endloop + endfacet + facet normal 0.56339 -0.432305 0.704063 + outer loop + vertex 2.73369 -2.73369 0.5 + vertex 3.34808 -1.93301 0.5 + vertex 3.03109 -1.75 0.866025 + endloop + endfacet + facet normal 0.56339 -0.432305 0.704063 + outer loop + vertex 2.73369 -2.73369 0.5 + vertex 3.03109 -1.75 0.866025 + vertex 2.47487 -2.47487 0.866025 + endloop + endfacet + facet normal 0.206987 -0.158826 0.965366 + outer loop + vertex 2.47487 -2.47487 0.866025 + vertex 3.03109 -1.75 0.866025 + vertex 2.59808 -1.5 1 + endloop + endfacet + facet normal 0.206987 -0.158826 0.965366 + outer loop + vertex 2.47487 -2.47487 0.866025 + vertex 2.59808 -1.5 1 + vertex 2.12132 -2.12132 1 + endloop + endfacet + facet normal -0.206987 0.158826 0.965366 + outer loop + vertex 2.12132 -2.12132 1 + vertex 2.59808 -1.5 1 + vertex 2.16506 -1.25 0.866025 + endloop + endfacet + facet normal -0.206987 0.158826 0.965366 + outer loop + vertex 2.12132 -2.12132 1 + vertex 2.16506 -1.25 0.866025 + vertex 1.76777 -1.76777 0.866025 + endloop + endfacet + facet normal -0.56339 0.432305 0.704063 + outer loop + vertex 1.76777 -1.76777 0.866025 + vertex 2.16506 -1.25 0.866025 + vertex 1.84808 -1.06699 0.5 + endloop + endfacet + facet normal -0.56339 0.432305 0.704063 + outer loop + vertex 1.76777 -1.76777 0.866025 + vertex 1.84808 -1.06699 0.5 + vertex 1.50895 -1.50895 0.5 + endloop + endfacet + facet normal -0.766758 0.588354 0.256751 + outer loop + vertex 1.50895 -1.50895 0.5 + vertex 1.84808 -1.06699 0.5 + vertex 1.73205 -1 1.22465e-16 + endloop + endfacet + facet normal -0.766758 0.588354 0.256751 + outer loop + vertex 1.50895 -1.50895 0.5 + vertex 1.73205 -1 1.22465e-16 + vertex 1.41421 -1.41421 1.22465e-16 + endloop + endfacet + facet normal -0.766758 0.588354 -0.256751 + outer loop + vertex 1.41421 -1.41421 1.22465e-16 + vertex 1.73205 -1 1.22465e-16 + vertex 1.84808 -1.06699 -0.5 + endloop + endfacet + facet normal -0.766758 0.588354 -0.256751 + outer loop + vertex 1.41421 -1.41421 1.22465e-16 + vertex 1.84808 -1.06699 -0.5 + vertex 1.50895 -1.50895 -0.5 + endloop + endfacet + facet normal -0.56339 0.432305 -0.704063 + outer loop + vertex 1.50895 -1.50895 -0.5 + vertex 1.84808 -1.06699 -0.5 + vertex 2.16506 -1.25 -0.866025 + endloop + endfacet + facet normal -0.56339 0.432305 -0.704063 + outer loop + vertex 1.50895 -1.50895 -0.5 + vertex 2.16506 -1.25 -0.866025 + vertex 1.76777 -1.76777 -0.866025 + endloop + endfacet + facet normal -0.206987 0.158826 -0.965366 + outer loop + vertex 1.76777 -1.76777 -0.866025 + vertex 2.16506 -1.25 -0.866025 + vertex 2.59808 -1.5 -1 + endloop + endfacet + facet normal -0.206987 0.158826 -0.965366 + outer loop + vertex 1.76777 -1.76777 -0.866025 + vertex 2.59808 -1.5 -1 + vertex 2.12132 -2.12132 -1 + endloop + endfacet + facet normal 0.206987 -0.158826 -0.965366 + outer loop + vertex 2.12132 -2.12132 -1 + vertex 2.59808 -1.5 -1 + vertex 3.03109 -1.75 -0.866025 + endloop + endfacet + facet normal 0.206987 -0.158826 -0.965366 + outer loop + vertex 2.12132 -2.12132 -1 + vertex 3.03109 -1.75 -0.866025 + vertex 2.47487 -2.47487 -0.866025 + endloop + endfacet + facet normal 0.56339 -0.432305 -0.704063 + outer loop + vertex 2.47487 -2.47487 -0.866025 + vertex 3.03109 -1.75 -0.866025 + vertex 3.34808 -1.93301 -0.5 + endloop + endfacet + facet normal 0.56339 -0.432305 -0.704063 + outer loop + vertex 2.47487 -2.47487 -0.866025 + vertex 3.34808 -1.93301 -0.5 + vertex 2.73369 -2.73369 -0.5 + endloop + endfacet + facet normal 0.766758 -0.588354 -0.256751 + outer loop + vertex 2.73369 -2.73369 -0.5 + vertex 3.34808 -1.93301 -0.5 + vertex 3.4641 -2 -2.44929e-16 + endloop + endfacet + facet normal 0.766758 -0.588354 -0.256751 + outer loop + vertex 2.73369 -2.73369 -0.5 + vertex 3.4641 -2 -2.44929e-16 + vertex 2.82843 -2.82843 -2.44929e-16 + endloop + endfacet + facet normal 0.892909 -0.369855 0.256751 + outer loop + vertex 3.4641 -2 0 + vertex 3.8637 -1.03528 0 + vertex 3.73429 -1.0006 0.5 + endloop + endfacet + facet normal 0.892909 -0.369855 0.256751 + outer loop + vertex 3.4641 -2 0 + vertex 3.73429 -1.0006 0.5 + vertex 3.34808 -1.93301 0.5 + endloop + endfacet + facet normal 0.656082 -0.271758 0.704063 + outer loop + vertex 3.34808 -1.93301 0.5 + vertex 3.73429 -1.0006 0.5 + vertex 3.38074 -0.905867 0.866025 + endloop + endfacet + facet normal 0.656082 -0.271758 0.704063 + outer loop + vertex 3.34808 -1.93301 0.5 + vertex 3.38074 -0.905867 0.866025 + vertex 3.03109 -1.75 0.866025 + endloop + endfacet + facet normal 0.241041 -0.0998425 0.965366 + outer loop + vertex 3.03109 -1.75 0.866025 + vertex 3.38074 -0.905867 0.866025 + vertex 2.89778 -0.776457 1 + endloop + endfacet + facet normal 0.241041 -0.0998425 0.965366 + outer loop + vertex 3.03109 -1.75 0.866025 + vertex 2.89778 -0.776457 1 + vertex 2.59808 -1.5 1 + endloop + endfacet + facet normal -0.241041 0.0998425 0.965366 + outer loop + vertex 2.59808 -1.5 1 + vertex 2.89778 -0.776457 1 + vertex 2.41481 -0.647048 0.866025 + endloop + endfacet + facet normal -0.241041 0.0998425 0.965366 + outer loop + vertex 2.59808 -1.5 1 + vertex 2.41481 -0.647048 0.866025 + vertex 2.16506 -1.25 0.866025 + endloop + endfacet + facet normal -0.656082 0.271758 0.704063 + outer loop + vertex 2.16506 -1.25 0.866025 + vertex 2.41481 -0.647048 0.866025 + vertex 2.06126 -0.552313 0.5 + endloop + endfacet + facet normal -0.656082 0.271758 0.704063 + outer loop + vertex 2.16506 -1.25 0.866025 + vertex 2.06126 -0.552313 0.5 + vertex 1.84808 -1.06699 0.5 + endloop + endfacet + facet normal -0.892909 0.369855 0.256751 + outer loop + vertex 1.84808 -1.06699 0.5 + vertex 2.06126 -0.552313 0.5 + vertex 1.93185 -0.517638 1.22465e-16 + endloop + endfacet + facet normal -0.892909 0.369855 0.256751 + outer loop + vertex 1.84808 -1.06699 0.5 + vertex 1.93185 -0.517638 1.22465e-16 + vertex 1.73205 -1 1.22465e-16 + endloop + endfacet + facet normal -0.892909 0.369855 -0.256751 + outer loop + vertex 1.73205 -1 1.22465e-16 + vertex 1.93185 -0.517638 1.22465e-16 + vertex 2.06126 -0.552313 -0.5 + endloop + endfacet + facet normal -0.892909 0.369855 -0.256751 + outer loop + vertex 1.73205 -1 1.22465e-16 + vertex 2.06126 -0.552313 -0.5 + vertex 1.84808 -1.06699 -0.5 + endloop + endfacet + facet normal -0.656082 0.271758 -0.704063 + outer loop + vertex 1.84808 -1.06699 -0.5 + vertex 2.06126 -0.552313 -0.5 + vertex 2.41481 -0.647048 -0.866025 + endloop + endfacet + facet normal -0.656082 0.271758 -0.704063 + outer loop + vertex 1.84808 -1.06699 -0.5 + vertex 2.41481 -0.647048 -0.866025 + vertex 2.16506 -1.25 -0.866025 + endloop + endfacet + facet normal -0.241041 0.0998425 -0.965366 + outer loop + vertex 2.16506 -1.25 -0.866025 + vertex 2.41481 -0.647048 -0.866025 + vertex 2.89778 -0.776457 -1 + endloop + endfacet + facet normal -0.241041 0.0998425 -0.965366 + outer loop + vertex 2.16506 -1.25 -0.866025 + vertex 2.89778 -0.776457 -1 + vertex 2.59808 -1.5 -1 + endloop + endfacet + facet normal 0.241041 -0.0998425 -0.965366 + outer loop + vertex 2.59808 -1.5 -1 + vertex 2.89778 -0.776457 -1 + vertex 3.38074 -0.905867 -0.866025 + endloop + endfacet + facet normal 0.241041 -0.0998425 -0.965366 + outer loop + vertex 2.59808 -1.5 -1 + vertex 3.38074 -0.905867 -0.866025 + vertex 3.03109 -1.75 -0.866025 + endloop + endfacet + facet normal 0.656082 -0.271758 -0.704063 + outer loop + vertex 3.03109 -1.75 -0.866025 + vertex 3.38074 -0.905867 -0.866025 + vertex 3.73429 -1.0006 -0.5 + endloop + endfacet + facet normal 0.656082 -0.271758 -0.704063 + outer loop + vertex 3.03109 -1.75 -0.866025 + vertex 3.73429 -1.0006 -0.5 + vertex 3.34808 -1.93301 -0.5 + endloop + endfacet + facet normal 0.892909 -0.369855 -0.256751 + outer loop + vertex 3.34808 -1.93301 -0.5 + vertex 3.73429 -1.0006 -0.5 + vertex 3.8637 -1.03528 -2.44929e-16 + endloop + endfacet + facet normal 0.892909 -0.369855 -0.256751 + outer loop + vertex 3.34808 -1.93301 -0.5 + vertex 3.8637 -1.03528 -2.44929e-16 + vertex 3.4641 -2 -2.44929e-16 + endloop + endfacet + facet normal 0.958209 -0.126151 0.256751 + outer loop + vertex 3.8637 -1.03528 0 + vertex 4 -9.79717e-16 0 + vertex 3.86603 -9.46903e-16 0.5 + endloop + endfacet + facet normal 0.958209 -0.126151 0.256751 + outer loop + vertex 3.8637 -1.03528 0 + vertex 3.86603 -9.46903e-16 0.5 + vertex 3.73429 -1.0006 0.5 + endloop + endfacet + facet normal 0.704063 -0.0926916 0.704063 + outer loop + vertex 3.73429 -1.0006 0.5 + vertex 3.86603 -9.46903e-16 0.5 + vertex 3.5 -8.57253e-16 0.866025 + endloop + endfacet + facet normal 0.704063 -0.0926916 0.704063 + outer loop + vertex 3.73429 -1.0006 0.5 + vertex 3.5 -8.57253e-16 0.866025 + vertex 3.38074 -0.905867 0.866025 + endloop + endfacet + facet normal 0.258669 -0.0340544 0.965366 + outer loop + vertex 3.38074 -0.905867 0.866025 + vertex 3.5 -8.57253e-16 0.866025 + vertex 3 -7.34788e-16 1 + endloop + endfacet + facet normal 0.258669 -0.0340544 0.965366 + outer loop + vertex 3.38074 -0.905867 0.866025 + vertex 3 -7.34788e-16 1 + vertex 2.89778 -0.776457 1 + endloop + endfacet + facet normal -0.258669 0.0340544 0.965366 + outer loop + vertex 2.89778 -0.776457 1 + vertex 3 -7.34788e-16 1 + vertex 2.5 -6.12323e-16 0.866025 + endloop + endfacet + facet normal -0.258669 0.0340544 0.965366 + outer loop + vertex 2.89778 -0.776457 1 + vertex 2.5 -6.12323e-16 0.866025 + vertex 2.41481 -0.647048 0.866025 + endloop + endfacet + facet normal -0.704063 0.0926916 0.704063 + outer loop + vertex 2.41481 -0.647048 0.866025 + vertex 2.5 -6.12323e-16 0.866025 + vertex 2.13397 -5.22673e-16 0.5 + endloop + endfacet + facet normal -0.704063 0.0926916 0.704063 + outer loop + vertex 2.41481 -0.647048 0.866025 + vertex 2.13397 -5.22673e-16 0.5 + vertex 2.06126 -0.552313 0.5 + endloop + endfacet + facet normal -0.958209 0.126151 0.256751 + outer loop + vertex 2.06126 -0.552313 0.5 + vertex 2.13397 -5.22673e-16 0.5 + vertex 2 -4.89859e-16 1.22465e-16 + endloop + endfacet + facet normal -0.958209 0.126151 0.256751 + outer loop + vertex 2.06126 -0.552313 0.5 + vertex 2 -4.89859e-16 1.22465e-16 + vertex 1.93185 -0.517638 1.22465e-16 + endloop + endfacet + facet normal -0.958209 0.126151 -0.256751 + outer loop + vertex 1.93185 -0.517638 1.22465e-16 + vertex 2 -4.89859e-16 1.22465e-16 + vertex 2.13397 -5.22673e-16 -0.5 + endloop + endfacet + facet normal -0.958209 0.126151 -0.256751 + outer loop + vertex 1.93185 -0.517638 1.22465e-16 + vertex 2.13397 -5.22673e-16 -0.5 + vertex 2.06126 -0.552313 -0.5 + endloop + endfacet + facet normal -0.704063 0.0926916 -0.704063 + outer loop + vertex 2.06126 -0.552313 -0.5 + vertex 2.13397 -5.22673e-16 -0.5 + vertex 2.5 -6.12323e-16 -0.866025 + endloop + endfacet + facet normal -0.704063 0.0926916 -0.704063 + outer loop + vertex 2.06126 -0.552313 -0.5 + vertex 2.5 -6.12323e-16 -0.866025 + vertex 2.41481 -0.647048 -0.866025 + endloop + endfacet + facet normal -0.258669 0.0340544 -0.965366 + outer loop + vertex 2.41481 -0.647048 -0.866025 + vertex 2.5 -6.12323e-16 -0.866025 + vertex 3 -7.34788e-16 -1 + endloop + endfacet + facet normal -0.258669 0.0340544 -0.965366 + outer loop + vertex 2.41481 -0.647048 -0.866025 + vertex 3 -7.34788e-16 -1 + vertex 2.89778 -0.776457 -1 + endloop + endfacet + facet normal 0.258669 -0.0340544 -0.965366 + outer loop + vertex 2.89778 -0.776457 -1 + vertex 3 -7.34788e-16 -1 + vertex 3.5 -8.57253e-16 -0.866025 + endloop + endfacet + facet normal 0.258669 -0.0340544 -0.965366 + outer loop + vertex 2.89778 -0.776457 -1 + vertex 3.5 -8.57253e-16 -0.866025 + vertex 3.38074 -0.905867 -0.866025 + endloop + endfacet + facet normal 0.704063 -0.0926916 -0.704063 + outer loop + vertex 3.38074 -0.905867 -0.866025 + vertex 3.5 -8.57253e-16 -0.866025 + vertex 3.86603 -9.46903e-16 -0.5 + endloop + endfacet + facet normal 0.704063 -0.0926916 -0.704063 + outer loop + vertex 3.38074 -0.905867 -0.866025 + vertex 3.86603 -9.46903e-16 -0.5 + vertex 3.73429 -1.0006 -0.5 + endloop + endfacet + facet normal 0.958209 -0.126151 -0.256751 + outer loop + vertex 3.73429 -1.0006 -0.5 + vertex 3.86603 -9.46903e-16 -0.5 + vertex 4 -9.79717e-16 -2.44929e-16 + endloop + endfacet + facet normal 0.958209 -0.126151 -0.256751 + outer loop + vertex 3.73429 -1.0006 -0.5 + vertex 4 -9.79717e-16 -2.44929e-16 + vertex 3.8637 -1.03528 -2.44929e-16 + endloop + endfacet +endsolid torus diff --git a/examples/region_mesh/in.region_mesh b/examples/region_mesh/in.region_mesh new file mode 100644 index 000000000..89bae0d60 --- /dev/null +++ b/examples/region_mesh/in.region_mesh @@ -0,0 +1,44 @@ +################################################################################ +# thermal gas filling the volume enclosed by a triangulated surface mesh +# +# the mesh is a torus, read from an STL file by the region mesh style, +# which is a shape none of the analytic region styles can describe +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 3 +global gridcut 0.0 comm/sort yes + +boundary rr rr rr + +create_box -5 5 -5 5 -5 5 +create_grid 20 20 20 + +balance_grid rcb cell + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the torus in the file has a major radius of 3 and a minor radius of 1 +# and is centered on the origin with its axis along z + +region tor mesh data.torus.stl + +create_particles air n 0 region tor twopass + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 diff --git a/examples/region_mesh/log.26Aug26.mpi_1.region_mesh b/examples/region_mesh/log.26Aug26.mpi_1.region_mesh new file mode 100644 index 000000000..963b4ffa4 --- /dev/null +++ b/examples/region_mesh/log.26Aug26.mpi_1.region_mesh @@ -0,0 +1,122 @@ +SPARTA (24 Sep 2025) +Running on 1 MPI task(s) +################################################################################ +# thermal gas filling the volume enclosed by a triangulated surface mesh +# +# the mesh is a torus, read from an STL file by the region mesh style, +# which is a shape none of the analytic region styles can describe +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 3 +global gridcut 0.0 comm/sort yes + +boundary rr rr rr + +create_box -5 5 -5 5 -5 5 +Created orthogonal box = (-5 -5 -5) to (5 5 5) +create_grid 20 20 20 +Created 8000 child grid cells + CPU time = 0.00235332 secs + create/ghost percent = 46.1043 53.8957 + +balance_grid rcb cell +Balance grid migrated 0 cells + CPU time = 0.00146514 secs + reassign/sort/migrate/ghost percent = 38.6107 2.85419 6.8106 51.7245 + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the torus in the file has a major radius of 3 and a minor radius of 1 +# and is centered on the origin with its axis along z + +region tor mesh data.torus.stl +Reading STL object torus with 576 triangles from file data.torus.stl + -4 -4 -1 to 4 4 1 mesh bounding box + 55.9049 enclosed mesh volume + +create_particles air n 0 region tor twopass +Created 55854 particles + CPU time = 0.0399925 secs + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 +Memory usage per proc in Mbytes: + particles (ave,min,max) = 6.25 6.25 6.25 + grid (ave,min,max) = 1.51379 1.51379 1.51379 + surf (ave,min,max) = 0 0 0 + modify (ave,min,max) = 0 0 0 + total (ave,min,max) = 7.76379 7.76379 7.76379 +Step CPU Np Natt Ncoll c_temp + 0 0 55854 0 0 299.39732 + 20 0.03756757 55854 8447 5725 299.39732 + 40 0.073681199 55854 8101 5481 299.39732 + 60 0.11127468 55854 7469 4918 299.39732 + 80 0.14639508 55854 6738 4337 299.39732 + 100 0.17991576 55854 5985 3839 299.39732 +Loop time of 0.179961 on 1 procs for 100 steps with 55854 particles +Performance: 555.677 timesteps/s, 31.037 Mparticle-step/s + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Move | 0.039847 | 0.039847 | 0.039847 | 0.0 | 22.14 +Coll | 0.11905 | 0.11905 | 0.11905 | 0.0 | 66.16 +Sort | 0.019537 | 0.019537 | 0.019537 | 0.0 | 10.86 +Comm | 0.00010981 | 0.00010981 | 0.00010981 | 0.0 | 0.06 +Modify | 0 | 0 | 0 | 0.0 | 0.00 +Output | 0.0012783 | 0.0012783 | 0.0012783 | 0.0 | 0.71 +MPI Sync| 0.00011196 | 0.00011196 | 0.00011196 | 0.0 | 0.06 +Other | | 2.296e-05 | | | 0.01 + +Particle moves = 5585400 (5.59M) +Cells touched = 5682200 (5.68M) +Particle comms = 0 (0K) +Boundary collides = 0 (0K) +Boundary exits = 0 (0K) +SurfColl checks = 0 (0K) +SurfColl occurs = 0 (0K) +Surf reactions = 0 (0K) +Collide attempts = 757351 (0.757M) +Collide occurs = 505027 (0.505M) +Reactions = 0 (0K) +Particles stuck = 0 +Axisymm bad moves = 0 + +Particle-moves/CPUsec/proc: 3.10368e+07 +Particle-moves/step: 55854 +Cell-touches/particle/step: 1.01733 +Particle comm iterations/step: 1 +Particle fraction communicated: 0 +Particle fraction colliding with boundary: 0 +Particle fraction exiting boundary: 0 +Surface-checks/particle/step: 0 +Surface-collisions/particle/step: 0 +Surf-reactions/particle/step: 0 +Collision-attempts/particle/step: 0.135595 +Collisions/particle/step: 0.0904191 +Reactions/particle/step: 0 + +Particles: 55854 ave 55854 max 55854 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +Cells: 8000 ave 8000 max 8000 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +GhostCell: 0 ave 0 max 0 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +EmptyCell: 0 ave 0 max 0 min +Histogram: 1 0 0 0 0 0 0 0 0 0 diff --git a/examples/region_mesh/log.26Aug26.mpi_4.region_mesh b/examples/region_mesh/log.26Aug26.mpi_4.region_mesh new file mode 100644 index 000000000..7ef24237c --- /dev/null +++ b/examples/region_mesh/log.26Aug26.mpi_4.region_mesh @@ -0,0 +1,123 @@ +SPARTA (24 Sep 2025) +Running on 4 MPI task(s) +################################################################################ +# thermal gas filling the volume enclosed by a triangulated surface mesh +# +# the mesh is a torus, read from an STL file by the region mesh style, +# which is a shape none of the analytic region styles can describe +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 3 +global gridcut 0.0 comm/sort yes + +boundary rr rr rr + +create_box -5 5 -5 5 -5 5 +Created orthogonal box = (-5 -5 -5) to (5 5 5) +create_grid 20 20 20 +WARNING: Could not acquire nearby ghost cells b/c grid partition is not clumped (../grid.cpp:486) +Created 8000 child grid cells + CPU time = 0.00110086 secs + create/ghost percent = 94.588 5.41197 + +balance_grid rcb cell +Balance grid migrated 5600 cells + CPU time = 0.00191228 secs + reassign/sort/migrate/ghost percent = 36.3232 0.584643 33.1003 29.9919 + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the torus in the file has a major radius of 3 and a minor radius of 1 +# and is centered on the origin with its axis along z + +region tor mesh data.torus.stl +Reading STL object torus with 576 triangles from file data.torus.stl + -4 -4 -1 to 4 4 1 mesh bounding box + 55.9049 enclosed mesh volume + +create_particles air n 0 region tor twopass +Created 56099 particles + CPU time = 0.0106426 secs + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 +Memory usage per proc in Mbytes: + particles (ave,min,max) = 1.5625 1.5625 1.5625 + grid (ave,min,max) = 1.51379 1.51379 1.51379 + surf (ave,min,max) = 0 0 0 + modify (ave,min,max) = 0 0 0 + total (ave,min,max) = 3.07629 3.07629 3.07629 +Step CPU Np Natt Ncoll c_temp + 0 0 56099 0 0 298.92493 + 20 0.008557938 56099 8453 5796 298.92493 + 40 0.017359548 56099 8089 5414 298.92493 + 60 0.025594545 56099 7458 4838 298.92493 + 80 0.033416026 56099 6749 4404 298.92493 + 100 0.041033305 56099 5991 3873 298.92493 +Loop time of 0.0410781 on 4 procs for 100 steps with 56099 particles +Performance: 2434.388 timesteps/s, 136.567 Mparticle-step/s + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Move | 0.0090956 | 0.0093462 | 0.0095622 | 0.2 | 22.75 +Coll | 0.024621 | 0.025109 | 0.025766 | 0.3 | 61.13 +Sort | 0.0026634 | 0.0027654 | 0.0028365 | 0.1 | 6.73 +Comm | 0.0010384 | 0.0010568 | 0.0010763 | 0.0 | 2.57 +Modify | 0 | 0 | 0 | 0.0 | 0.00 +Output | 0.00020512 | 0.00028011 | 0.00042722 | 0.0 | 0.68 +MPI Sync| 0.0016101 | 0.0025109 | 0.0031141 | 1.2 | 6.11 +Other | | 9.483e-06 | | | 0.02 + +Particle moves = 5609900 (5.61M) +Cells touched = 5707445 (5.71M) +Particle comms = 3853 (3.85K) +Boundary collides = 3 (0.003K) +Boundary exits = 0 (0K) +SurfColl checks = 0 (0K) +SurfColl occurs = 0 (0K) +Surf reactions = 0 (0K) +Collide attempts = 756950 (0.757M) +Collide occurs = 506143 (0.506M) +Reactions = 0 (0K) +Particles stuck = 0 +Axisymm bad moves = 0 + +Particle-moves/CPUsec/proc: 3.41417e+07 +Particle-moves/step: 56099 +Cell-touches/particle/step: 1.01739 +Particle comm iterations/step: 2 +Particle fraction communicated: 0.000686822 +Particle fraction colliding with boundary: 5.34769e-07 +Particle fraction exiting boundary: 0 +Surface-checks/particle/step: 0 +Surface-collisions/particle/step: 0 +Surf-reactions/particle/step: 0 +Collision-attempts/particle/step: 0.134931 +Collisions/particle/step: 0.0902232 +Reactions/particle/step: 0 + +Particles: 14024.8 ave 14058 max 13982 min +Histogram: 1 0 1 0 0 0 0 0 0 2 +Cells: 2000 ave 2000 max 2000 min +Histogram: 4 0 0 0 0 0 0 0 0 0 +GhostCell: 420 ave 420 max 420 min +Histogram: 4 0 0 0 0 0 0 0 0 0 +EmptyCell: 420 ave 420 max 420 min +Histogram: 4 0 0 0 0 0 0 0 0 0 diff --git a/src/region_mesh.cpp b/src/region_mesh.cpp new file mode 100644 index 000000000..63a24faab --- /dev/null +++ b/src/region_mesh.cpp @@ -0,0 +1,433 @@ +/* ---------------------------------------------------------------------- + SPARTA - Stochastic PArallel Rarefied-gas Time-accurate Analyzer + http://sparta.github.io + Steve Plimpton, sjplimp@gmail.com, Michael Gallis, magalli@sandia.gov + Sandia National Laboratories + + Copyright (2014) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level SPARTA directory. +------------------------------------------------------------------------- */ + +#include "math.h" +#include "stdlib.h" +#include "string.h" +#include "region_mesh.h" +#include "stl_reader.h" +#include "comm.h" +#include "input.h" +#include "math_const.h" +#include "math_extra.h" +#include "memory.h" +#include "error.h" + +using namespace SPARTA_NS; +using namespace MathConst; + +#define EPSILON 1.0e-9 // relative tolerance for a degenerate ray hit +#define JITTER 1.0e-6 // relative offset used to move a degenerate ray +#define MAXJITTER 8 // max # of times a degenerate ray is moved +#define WATERTIGHT 1.0e-6 // relative tolerance on the closure of the mesh +#define MAXBIN 1024 // max # of ray-casting bins in y or z + +// angle between successive jittered rays, the golden angle in radians +// keeps the rays from repeating a direction that was already degenerate + +#define GOLDEN_ANGLE 2.39996322972865332 + +/* ---------------------------------------------------------------------- */ + +RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : + Region(sparta, narg, arg) +{ + if (narg < 3) error->all(FLERR,"Illegal region mesh command"); + + tris = NULL; + tnorm = NULL; + binfirst = binlist = NULL; + + // read the triangles, every proc keeps a copy of the entire mesh + // the reader owns the array it fills, so keep our own copy of it + + STLReader reader(sparta); + double **readtris; + ntri = reader.read_file(arg[2],readtris); + + memory->create(tris,ntri,9,"region/mesh:tris"); + memcpy(&tris[0][0],&readtris[0][0],9*ntri*sizeof(double)); + + // optional args + // side is parsed by the base class, everything else transforms the mesh + // transforms are applied in the order they appear, as in read_surf + + origin[0] = origin[1] = origin[2] = 0.0; + interior = 1; + + int iarg = 3; + while (iarg < narg) { + if (strcmp(arg[iarg],"origin") == 0) { + if (iarg+4 > narg) error->all(FLERR,"Illegal region mesh command"); + origin[0] = input->numeric(FLERR,arg[iarg+1]); + origin[1] = input->numeric(FLERR,arg[iarg+2]); + origin[2] = input->numeric(FLERR,arg[iarg+3]); + iarg += 4; + } else if (strcmp(arg[iarg],"trans") == 0) { + if (iarg+4 > narg) error->all(FLERR,"Illegal region mesh command"); + double dx = input->numeric(FLERR,arg[iarg+1]); + double dy = input->numeric(FLERR,arg[iarg+2]); + double dz = input->numeric(FLERR,arg[iarg+3]); + origin[0] += dx; + origin[1] += dy; + origin[2] += dz; + translate(dx,dy,dz); + iarg += 4; + } else if (strcmp(arg[iarg],"scale") == 0) { + if (iarg+4 > narg) error->all(FLERR,"Illegal region mesh command"); + double sx = input->numeric(FLERR,arg[iarg+1]); + double sy = input->numeric(FLERR,arg[iarg+2]); + double sz = input->numeric(FLERR,arg[iarg+3]); + scale(sx,sy,sz); + iarg += 4; + } else if (strcmp(arg[iarg],"rotate") == 0) { + if (iarg+5 > narg) error->all(FLERR,"Illegal region mesh command"); + double theta = input->numeric(FLERR,arg[iarg+1]); + double rx = input->numeric(FLERR,arg[iarg+2]); + double ry = input->numeric(FLERR,arg[iarg+3]); + double rz = input->numeric(FLERR,arg[iarg+4]); + if (rx == 0.0 && ry == 0.0 && rz == 0.0) + error->all(FLERR,"Illegal region mesh command"); + rotate(theta,rx,ry,rz); + iarg += 5; + } else if (strcmp(arg[iarg],"side") == 0) { + if (iarg+2 > narg) error->all(FLERR,"Illegal region mesh command"); + options(2,&arg[iarg]); + iarg += 2; + } else error->all(FLERR,"Illegal region mesh command"); + } + + // bounding box, enclosed volume, and the bins used by inside() + + setup(); + + // extent of the mesh + // as for the other styles, only an interior region has a bounding box + + if (interior) { + bboxflag = 1; + extent_xlo = bblo[0]; + extent_xhi = bbhi[0]; + extent_ylo = bblo[1]; + extent_yhi = bbhi[1]; + extent_zlo = bblo[2]; + extent_zhi = bbhi[2]; + } else bboxflag = 0; + + if (comm->me == 0) { + if (screen) { + fprintf(screen," %g %g %g to %g %g %g mesh bounding box\n", + bblo[0],bblo[1],bblo[2],bbhi[0],bbhi[1],bbhi[2]); + fprintf(screen," %g enclosed mesh volume\n",volume); + } + if (logfile) { + fprintf(logfile," %g %g %g to %g %g %g mesh bounding box\n", + bblo[0],bblo[1],bblo[2],bbhi[0],bbhi[1],bbhi[2]); + fprintf(logfile," %g enclosed mesh volume\n",volume); + } + } +} + +/* ---------------------------------------------------------------------- */ + +RegMesh::~RegMesh() +{ + memory->destroy(tris); + memory->destroy(tnorm); + memory->destroy(binfirst); + memory->destroy(binlist); +} + +/* ---------------------------------------------------------------------- + inside = 1 if x,y,z is inside the closed surface, else 0 + cast a ray in +x from the point and count the triangles it crosses + an odd count means the point is inside +------------------------------------------------------------------------- */ + +int RegMesh::inside(double *x) +{ + if (x[0] < bblo[0] || x[0] > bbhi[0] || + x[1] < bblo[1] || x[1] > bbhi[1] || + x[2] < bblo[2] || x[2] > bbhi[2]) return 0; + + // a ray that grazes an edge or vertex cannot be counted reliably + // move it a little in the y-z plane and cast it again + + double dy = 0.0; + double dz = 0.0; + + for (int attempt = 0; attempt <= MAXJITTER; attempt++) { + int ncross = crossings(x[0],x[1]+dy,x[2]+dz); + if (ncross >= 0) return ncross & 1; + + double angle = GOLDEN_ANGLE * (attempt+1); + double offset = jitter * (attempt+1); + dy = offset * cos(angle); + dz = offset * sin(angle); + } + + // every ray was degenerate, so the point is on the surface itself + // as for the other region styles, the surface counts as inside + + return 1; +} + +/* ---------------------------------------------------------------------- + count the triangles crossed by a +x ray from the point px,py,pz + return -1 if the ray grazes an edge or vertex, so the count is unreliable +------------------------------------------------------------------------- */ + +int RegMesh::crossings(double px, double py, double pz) +{ + int iy = static_cast ((py-bblo[1]) * invbiny); + iy = MAX(0,MIN(iy,nbiny-1)); + int iz = static_cast ((pz-bblo[2]) * invbinz); + iz = MAX(0,MIN(iz,nbinz-1)); + int ibin = iz*nbiny + iy; + + int ncross = 0; + + for (int m = binfirst[ibin]; m < binfirst[ibin+1]; m++) { + int itri = binlist[m]; + double *t = tris[itri]; + + // 2x the signed areas of the 3 triangles that the point makes with each + // edge, in the y-z plane the ray projects onto + // the point projects inside the triangle if all 3 have the same sign + + double d0 = (t[4]-t[1])*(pz-t[2]) - (t[5]-t[2])*(py-t[1]); + double d1 = (t[7]-t[4])*(pz-t[5]) - (t[8]-t[5])*(py-t[4]); + double d2 = (t[1]-t[7])*(pz-t[8]) - (t[2]-t[8])*(py-t[7]); + + // d0+d1+d2 is 2x the signed area of the projected triangle, + // which is also the x component of the triangle normal + // comparing it to the length of the normal tests how edge-on the + // triangle is to the ray, independent of how big the triangle is + + double area2 = d0 + d1 + d2; + double epsarea = EPSILON * tnorm[itri]; + if (fabs(area2) <= epsarea) continue; + + int nneg = 0; + int npos = 0; + int nzero = 0; + if (d0 > epsarea) npos++; + else if (d0 < -epsarea) nneg++; + else nzero++; + if (d1 > epsarea) npos++; + else if (d1 < -epsarea) nneg++; + else nzero++; + if (d2 > epsarea) npos++; + else if (d2 < -epsarea) nneg++; + else nzero++; + + if (npos && nneg) continue; // ray misses the triangle + if (nzero) return -1; // ray grazes an edge or a vertex + + // x where the ray pierces the triangle, from the barycentric coords + + double xhit = (d1*t[0] + d2*t[3] + d0*t[6]) / area2; + if (fabs(xhit-px) < epslen) return -1; // point is on the triangle + if (xhit > px) ncross++; + } + + return ncross; +} + +/* ---------------------------------------------------------------------- + bounding box, enclosed volume, tolerances, and ray-casting bins +------------------------------------------------------------------------- */ + +void RegMesh::setup() +{ + // bounding box around all the vertices + + bblo[0] = bbhi[0] = tris[0][0]; + bblo[1] = bbhi[1] = tris[0][1]; + bblo[2] = bbhi[2] = tris[0][2]; + + for (int i = 0; i < ntri; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) { + bblo[k] = MIN(bblo[k],tris[i][3*j+k]); + bbhi[k] = MAX(bbhi[k],tris[i][3*j+k]); + } + + // tolerances scale with the size of the mesh + // the jitter is much larger than the tolerance it has to escape + + double meshlen = MAX(bbhi[0]-bblo[0],MAX(bbhi[1]-bblo[1],bbhi[2]-bblo[2])); + if (meshlen == 0.0) error->all(FLERR,"Region mesh has zero extent"); + + epslen = EPSILON * meshlen; + jitter = JITTER * meshlen; + + // normal of each triangle, its length is 2x the area + // enclosed volume from the divergence theorem + // the area-weighted normals of a closed surface sum to zero + + memory->create(tnorm,ntri,"region/mesh:tnorm"); + + double nsum[3] = {0.0,0.0,0.0}; + double asum = 0.0; + double vsum = 0.0; + + for (int i = 0; i < ntri; i++) { + double *a = &tris[i][0]; + double *b = &tris[i][3]; + double *c = &tris[i][6]; + double e1[3],e2[3],n[3]; + + MathExtra::sub3(b,a,e1); + MathExtra::sub3(c,a,e2); + MathExtra::cross3(e1,e2,n); + tnorm[i] = MathExtra::len3(n); + + asum += tnorm[i]; + nsum[0] += n[0]; + nsum[1] += n[1]; + nsum[2] += n[2]; + + MathExtra::cross3(b,c,e1); + vsum += MathExtra::dot3(a,e1); + } + + volume = fabs(vsum) / 6.0; + + if (MathExtra::len3(nsum) > WATERTIGHT * asum) + error->warning(FLERR,"Region mesh surface is not watertight"); + + // bin the triangles by their y-z extent so a +x ray only has to test + // the triangles in one bin + // aim for one triangle per bin, with square bins in the y-z plane + + double ylen = bbhi[1] - bblo[1]; + double zlen = bbhi[2] - bblo[2]; + + double binsize = sqrt(ylen*zlen/ntri); + if (binsize == 0.0) binsize = meshlen; + + nbiny = MAX(1,MIN(static_cast (ylen/binsize) + 1,MAXBIN)); + nbinz = MAX(1,MIN(static_cast (zlen/binsize) + 1,MAXBIN)); + + invbiny = (ylen > 0.0) ? nbiny/ylen : 0.0; + invbinz = (zlen > 0.0) ? nbinz/zlen : 0.0; + + int nbin = nbiny*nbinz; + memory->create(binfirst,nbin+1,"region/mesh:binfirst"); + for (int i = 0; i <= nbin; i++) binfirst[i] = 0; + + // count the triangles in each bin, then turn the counts into offsets + + int iylo,iyhi,izlo,izhi; + + for (int i = 0; i < ntri; i++) { + bin_range(i,iylo,iyhi,izlo,izhi); + for (int iz = izlo; iz <= izhi; iz++) + for (int iy = iylo; iy <= iyhi; iy++) + binfirst[iz*nbiny+iy+1]++; + } + + for (int i = 0; i < nbin; i++) binfirst[i+1] += binfirst[i]; + + memory->create(binlist,binfirst[nbin],"region/mesh:binlist"); + + int *next; + memory->create(next,nbin,"region/mesh:next"); + for (int i = 0; i < nbin; i++) next[i] = binfirst[i]; + + for (int i = 0; i < ntri; i++) { + bin_range(i,iylo,iyhi,izlo,izhi); + for (int iz = izlo; iz <= izhi; iz++) + for (int iy = iylo; iy <= iyhi; iy++) + binlist[next[iz*nbiny+iy]++] = i; + } + + memory->destroy(next); +} + +/* ---------------------------------------------------------------------- + range of bins that triangle I overlaps in the y-z plane +------------------------------------------------------------------------- */ + +void RegMesh::bin_range(int i, int &iylo, int &iyhi, int &izlo, int &izhi) +{ + double *t = tris[i]; + + double ylo = MIN(t[1],MIN(t[4],t[7])); + double yhi = MAX(t[1],MAX(t[4],t[7])); + double zlo = MIN(t[2],MIN(t[5],t[8])); + double zhi = MAX(t[2],MAX(t[5],t[8])); + + iylo = MAX(0,MIN(static_cast ((ylo-bblo[1])*invbiny),nbiny-1)); + iyhi = MAX(0,MIN(static_cast ((yhi-bblo[1])*invbiny),nbiny-1)); + izlo = MAX(0,MIN(static_cast ((zlo-bblo[2])*invbinz),nbinz-1)); + izhi = MAX(0,MIN(static_cast ((zhi-bblo[2])*invbinz),nbinz-1)); +} + +/* ---------------------------------------------------------------------- + translate the mesh by dx,dy,dz +------------------------------------------------------------------------- */ + +void RegMesh::translate(double dx, double dy, double dz) +{ + for (int i = 0; i < ntri; i++) + for (int j = 0; j < 3; j++) { + tris[i][3*j+0] += dx; + tris[i][3*j+1] += dy; + tris[i][3*j+2] += dz; + } +} + +/* ---------------------------------------------------------------------- + scale the mesh by sx,sy,sz around the origin point +------------------------------------------------------------------------- */ + +void RegMesh::scale(double sx, double sy, double sz) +{ + for (int i = 0; i < ntri; i++) + for (int j = 0; j < 3; j++) { + tris[i][3*j+0] = sx*(tris[i][3*j+0]-origin[0]) + origin[0]; + tris[i][3*j+1] = sy*(tris[i][3*j+1]-origin[1]) + origin[1]; + tris[i][3*j+2] = sz*(tris[i][3*j+2]-origin[2]) + origin[2]; + } +} + +/* ---------------------------------------------------------------------- + rotate the mesh theta degrees around the axis rx,ry,rz thru the origin point +------------------------------------------------------------------------- */ + +void RegMesh::rotate(double theta, double rx, double ry, double rz) +{ + double r[3],q[4],d[3],dnew[3]; + double rotmat[3][3]; + + theta *= MY_PI/180.0; + + r[0] = rx; r[1] = ry; r[2] = rz; + MathExtra::norm3(r); + MathExtra::axisangle_to_quat(r,theta,q); + MathExtra::quat_to_mat(q,rotmat); + + for (int i = 0; i < ntri; i++) + for (int j = 0; j < 3; j++) { + d[0] = tris[i][3*j+0] - origin[0]; + d[1] = tris[i][3*j+1] - origin[1]; + d[2] = tris[i][3*j+2] - origin[2]; + MathExtra::matvec(rotmat,d,dnew); + tris[i][3*j+0] = dnew[0] + origin[0]; + tris[i][3*j+1] = dnew[1] + origin[1]; + tris[i][3*j+2] = dnew[2] + origin[2]; + } +} diff --git a/src/region_mesh.h b/src/region_mesh.h new file mode 100644 index 000000000..918867a96 --- /dev/null +++ b/src/region_mesh.h @@ -0,0 +1,82 @@ +/* ---------------------------------------------------------------------- + SPARTA - Stochastic PArallel Rarefied-gas Time-accurate Analyzer + http://sparta.github.io + Steve Plimpton, sjplimp@gmail.com, Michael Gallis, magalli@sandia.gov + Sandia National Laboratories + + Copyright (2014) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level SPARTA directory. +------------------------------------------------------------------------- */ + +#ifdef REGION_CLASS + +RegionStyle(mesh,RegMesh) + +#else + +#ifndef SPARTA_REGION_MESH_H +#define SPARTA_REGION_MESH_H + +#include "region.h" + +namespace SPARTA_NS { + +class RegMesh : public Region { + public: + RegMesh(class SPARTA *, int, char **); + ~RegMesh(); + int inside(double *); + + protected: + int ntri; // # of triangles in the mesh + double **tris; // ntri x 9 = the 3 vertices of each triangle + double *tnorm; // length of the normal = 2x area of each triangle + double origin[3]; // reference point for scale and rotate + double bblo[3],bbhi[3]; // bounding box around the mesh + double volume; // volume enclosed by the mesh + + int nbiny,nbinz; // # of ray-casting bins in y and z + double invbiny,invbinz; // inverse bin sizes + int *binfirst; // index in binlist of 1st tri in each bin, len nbin+1 + int *binlist; // triangle indices, grouped by bin + + double epslen; // tolerance for a point lying on a triangle + double jitter; // ray offset used to dodge a degenerate hit + + void translate(double, double, double); + void scale(double, double, double); + void rotate(double, double, double, double); + void setup(); + void bin_range(int, int &, int &, int &, int &); + int crossings(double, double, double); +}; + +} + +#endif +#endif + +/* ERROR/WARNING messages: + +E: Illegal ... command + +Self-explanatory. Check the input script syntax and compare to the +documentation for the command. You can use -echo screen as a +command-line option when running SPARTA to see the offending line. + +E: Region mesh has zero extent + +All the triangles in the STL file are coincident, so the mesh encloses +no volume. + +W: Region mesh surface is not watertight + +The area-weighted normals of the triangles in the STL file do not sum +to zero, which means the surface has holes in it. A point-in-mesh test +on a surface with holes gives arbitrary answers. + +*/ diff --git a/src/stl_reader.cpp b/src/stl_reader.cpp new file mode 100644 index 000000000..96729b7b4 --- /dev/null +++ b/src/stl_reader.cpp @@ -0,0 +1,365 @@ +/* ---------------------------------------------------------------------- + SPARTA - Stochastic PArallel Rarefied-gas Time-accurate Analyzer + http://sparta.github.io + Steve Plimpton, sjplimp@gmail.com, Michael Gallis, magalli@sandia.gov + Sandia National Laboratories + + Copyright (2014) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level SPARTA directory. +------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------- + Ported from the LAMMPS STLReader class, see stl_reader.h +------------------------------------------------------------------------- */ + +#include "ctype.h" +#include "stdio.h" +#include "stdlib.h" +#include "string.h" +#include "stl_reader.h" +#include "comm.h" +#include "utils.h" +#include "memory.h" +#include "error.h" + +#include + +using namespace SPARTA_NS; + +// byte layout of a binary STL file: +// 80-byte header + 4-byte (uint32) triangle count, then per triangle +// 12 floats (normal + 3 vertices) + a 2-byte attribute count + +static const long STL_BIN_HEADER = 80 + sizeof(uint32_t); // 84 +static const long STL_BIN_PER_TRI = 12*sizeof(float) + sizeof(uint16_t); // 50 + +#define MAXLINE 1024 + +/* ---------------------------------------------------------------------- + local helpers that stand in for the LAMMPS utils and TextFileReader + classes the original version of this file uses +------------------------------------------------------------------------- */ + +// strip leading and trailing whitespace from a string + +static std::string trim(const std::string &text) +{ + std::string::size_type first = 0; + std::string::size_type last = text.size(); + + while (first < last && isspace((unsigned char) text[first])) first++; + while (last > first && isspace((unsigned char) text[last-1])) last--; + + return text.substr(first,last-first); +} + +// split a line on whitespace into a list of words + +static std::vector split_words(const char *line) +{ + std::vector words; + const char *ptr = line; + + while (*ptr) { + while (*ptr && isspace((unsigned char) *ptr)) ptr++; + const char *start = ptr; + while (*ptr && !isspace((unsigned char) *ptr)) ptr++; + if (ptr > start) words.push_back(std::string(start,ptr-start)); + } + + return words; +} + +// convert a word to a double, throw if it is not a valid number + +static double next_double(const std::string &word, const std::string &context) +{ + if (!utils::is_double(word)) + throw STLReaderException("Error parsing STL file: " + context + + ": invalid number " + word); + return atof(word.c_str()); +} + +// return the next non-blank line of a file, or NULL at end of file +// buf must be at least MAXLINE chars + +static char *next_line(FILE *fp, char *buf) +{ + while (fgets(buf,MAXLINE,fp)) { + for (char *ptr = buf; *ptr; ptr++) + if (!isspace((unsigned char) *ptr)) return buf; + } + return NULL; +} + +/* ---------------------------------------------------------------------- + a class instance is only needed for read_file(), which owns the storage + for the triangle vertices that it returns +------------------------------------------------------------------------- */ + +STLReader::STLReader(SPARTA *sparta) : Pointers(sparta) +{ + ntris = maxtris = 0; + tris = NULL; +} + +/* ---------------------------------------------------------------------- + free the storage for the triangle vertices +------------------------------------------------------------------------- */ + +STLReader::~STLReader() +{ + memory->destroy(tris); +} + +/* ---------------------------------------------------------------------- + read an STL file on proc 0 and communicate the vertices to all procs + the facet normals are discarded + the storage for the vertices is owned by the class instance, + so it is freed by the destructor and the caller must copy what it needs + reading an empty or unreadable file aborts SPARTA with an error message + return # of triangles +------------------------------------------------------------------------- */ + +int STLReader::read_file(const char *filename, double **&caller_tris) +{ + int me = comm->me; + + if (me == 0) { + try { + std::string title; + std::vector parsed = parse(filename,&title); + ntris = (int) parsed.size(); + maxtris = ntris; + utils::logmesg(sparta,"Reading STL object " + + (title.empty() ? std::string("(unnamed)") : title) + " with " + + std::to_string(ntris) + " triangles from file " + filename + "\n"); + + memory->create(tris,(ntris > 0) ? ntris : 1,9,"stl_reader:tris"); + for (int i = 0; i < ntris; i++) { + int m = 0; + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + tris[i][m++] = parsed[i].vert[j][k]; + } + } catch (std::exception &e) { + error->one(FLERR,e.what()); + } + } + + MPI_Bcast(&ntris,1,MPI_INT,0,world); + if (ntris == 0) { + std::string mesg = "STL file " + std::string(filename) + " has no triangles"; + error->all(FLERR,mesg.c_str()); + } + if (me) memory->create(tris,ntris,9,"stl_reader:tris"); + + // allow for 9*ntris to exceed the max allowed size of a single MPI_Bcast() + + bigint ntotal = (bigint) ntris * 9; + if (ntotal < MAXSMALLINT) + MPI_Bcast(&tris[0][0],9*ntris,MPI_DOUBLE,0,world); + else { + double *source = &tris[0][0]; + bigint n = 0; + while (n < ntotal) { + int nsize = MIN(MAXSMALLINT,ntotal-n); + MPI_Bcast(&source[n],nsize,MPI_DOUBLE,0,world); + n += nsize; + } + } + + caller_tris = tris; + return ntris; +} + +/* ---------------------------------------------------------------------- + read and parse an STL file and return its triangles + independent of any SPARTA instance and does not communicate + whether the file is text or binary is detected from its contents: + a binary STL file has a size of exactly 84 + 50*N bytes for N triangles, + which is more robust than looking for the word "solid" at the start of + the file, since the header of a binary file may legally begin with it + any error while opening, reading, or parsing the file is reported by + throwing an STLReaderException + a file with zero triangles is not an error, it gives an empty vector +------------------------------------------------------------------------- */ + +std::vector +STLReader::parse(const std::string &filename, std::string *title_out) +{ + FILE *fp = fopen(filename.c_str(),"rb"); + if (!fp) throw STLReaderException("Cannot open STL file " + filename); + + // determine the file size and the triangle count claimed by a binary header + + bool is_binary = false; + if (fseek(fp,0,SEEK_END) == 0) { + long filesize = ftell(fp); + if (filesize >= STL_BIN_HEADER) { + uint32_t ntri_claim = 0; + if (fseek(fp,80,SEEK_SET) == 0 && + fread(&ntri_claim,sizeof(ntri_claim),1,fp) == 1) { + bigint expected = (bigint) STL_BIN_HEADER + + (bigint) ntri_claim * STL_BIN_PER_TRI; + if (expected == (bigint) filesize) is_binary = true; + } + } + } + rewind(fp); + + std::string title; + std::vector triangles; + + try { + if (is_binary) { + triangles = parse_binary(fp,title); + } else { + fclose(fp); + fp = NULL; + triangles = parse_text(filename,title); + } + } catch (...) { + if (fp) fclose(fp); + throw; + } + if (fp) fclose(fp); + + if (title_out) *title_out = title; + return triangles; +} + +/* ---------------------------------------------------------------------- + parse an ASCII STL file +------------------------------------------------------------------------- */ + +std::vector +STLReader::parse_text(const std::string &filename, std::string &title) +{ + char buf[MAXLINE]; + + FILE *fp = fopen(filename.c_str(),"r"); + if (!fp) throw STLReaderException("Cannot open STL file " + filename); + + char *line = next_line(fp,buf); + if (!line || !utils::strmatch(line,"^ *solid")) { + fclose(fp); + throw STLReaderException("File " + filename + " is not a valid ASCII STL file"); + } + + // solid name may be empty; use a std::string so there is no risk of running + // past the end of the buffer (as bare pointer arithmetic on "solid" would) + + std::string header(line); + std::string::size_type pos = header.find("solid"); + title = trim(header.substr(pos+5)); + + std::vector triangles; + + try { + while ((line = next_line(fp,buf))) { + std::vector words = split_words(line); + if (words.empty()) continue; + if (utils::strmatch(words[0],"^endsolid")) break; + if (!utils::strmatch(words[0],"^facet")) + throw STLReaderException(std::string("Expected 'facet' or 'endsolid' in " + "STL file, got: ") + trim(line)); + + Triangle tri; + tri.normal[0] = tri.normal[1] = tri.normal[2] = 0.0; + + // facet line is "facet normal nx ny nz"; the normal is optional and + // tolerated to be absent or unparsable (it is recomputed when needed) + + if (words.size() >= 5 && utils::strmatch(words[1],"^normal") && + utils::is_double(words[2]) && utils::is_double(words[3]) && + utils::is_double(words[4])) + for (int k = 0; k < 3; k++) tri.normal[k] = atof(words[2+k].c_str()); + + line = next_line(fp,buf); + if (!line || !utils::strmatch(line,"^ *outer *loop")) + throw STLReaderException("Error reading 'outer loop' in STL file"); + + for (int k = 0; k < 3; k++) { + line = next_line(fp,buf); + std::vector values = line ? split_words(line) : + std::vector(); + if (values.size() < 4 || values[0] != "vertex") + throw STLReaderException("Error reading vertex " + std::to_string(k+1) + + " of facet in STL file"); + for (int m = 0; m < 3; m++) + tri.vert[k][m] = next_double(values[1+m],"vertex " + std::to_string(k+1)); + } + + line = next_line(fp,buf); + if (!line || !utils::strmatch(line,"^ *endloop")) + throw STLReaderException("Error reading 'endloop' in STL file"); + line = next_line(fp,buf); + if (!line || !utils::strmatch(line,"^ *endfacet")) + throw STLReaderException("Error reading 'endfacet' in STL file"); + + triangles.push_back(tri); + } + } catch (...) { + fclose(fp); + throw; + } + + fclose(fp); + return triangles; +} + +/* ---------------------------------------------------------------------- + parse a binary STL file: 80-byte header, uint32 triangle count, then + per triangle 12 little-endian floats (normal + 3 vertices) + 2-byte attr +------------------------------------------------------------------------- */ + +std::vector +STLReader::parse_binary(FILE *fp, std::string &title) +{ + rewind(fp); + + char head[80]; + if (fread(head,1,80,fp) != 80) + throw STLReaderException("Unexpected end of binary STL file while reading header"); + + // the header is a fixed 80-byte field that need not be null terminated + + std::size_t len = 0; + while (len < sizeof(head) && head[len] != '\0') ++len; + title = trim(std::string(head,len)); + + uint32_t ntri = 0; + if (fread(&ntri,sizeof(ntri),1,fp) != 1) + throw STLReaderException("Unexpected end of binary STL file while reading " + "triangle count"); + if (ntri > (uint32_t) MAXSMALLINT) + throw STLReaderException("Number of triangles in STL file exceeds integer limit"); + + std::vector triangles; + triangles.reserve(ntri); + + float buf[12]; + uint16_t attr; + for (uint32_t i = 0; i < ntri; i++) { + if (fread(buf,sizeof(float),12,fp) != 12) + throw STLReaderException("Unexpected end of binary STL file at triangle " + + std::to_string(i+1) + " of " + std::to_string(ntri)); + if (fread(&attr,sizeof(attr),1,fp) != 1) + throw STLReaderException("Unexpected end of binary STL file reading " + "attributes of triangle " + std::to_string(i+1)); + + Triangle tri; + for (int k = 0; k < 3; k++) tri.normal[k] = buf[k]; + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + tri.vert[j][k] = buf[3 + 3*j + k]; + triangles.push_back(tri); + } + + return triangles; +} diff --git a/src/stl_reader.h b/src/stl_reader.h new file mode 100644 index 000000000..da8f6b5b9 --- /dev/null +++ b/src/stl_reader.h @@ -0,0 +1,118 @@ +/* ---------------------------------------------------------------------- + SPARTA - Stochastic PArallel Rarefied-gas Time-accurate Analyzer + http://sparta.github.io + Steve Plimpton, sjplimp@gmail.com, Michael Gallis, magalli@sandia.gov + Sandia National Laboratories + + Copyright (2014) Sandia Corporation. Under the terms of Contract + DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains + certain rights in this software. This software is distributed under + the GNU General Public License. + + See the README file in the top-level SPARTA directory. +------------------------------------------------------------------------- */ + +/* ---------------------------------------------------------------------- + Ported from the LAMMPS STLReader class (src/stl_reader.h, + src/stl_reader.cpp), which is used by the LAMMPS create_atoms mesh + command. The LAMMPS TextFileReader and ValueTokenizer classes that the + original uses have no SPARTA equivalent, so the ASCII parser here reads + and splits the lines itself, but the grammar it accepts, the text vs + binary detection, and the error messages are all unchanged. +------------------------------------------------------------------------- */ + +#ifndef SPARTA_STL_READER_H +#define SPARTA_STL_READER_H + +#include "stdio.h" +#include "pointers.h" +#include "spaexception.h" + +#include +#include + +namespace SPARTA_NS { + +// thrown by the STLReader class for malformed or unreadable files + +class STLReaderException : public SpartaException { + public: + explicit STLReaderException(const std::string &msg) : SpartaException(msg) {} +}; + +/* ---------------------------------------------------------------------- + read triangle meshes from files in STL format + + an STL file stores a triangle mesh as a flat list of triangles, each of + them an outward facing normal vector and the coords of its 3 vertices + both variants of the format are supported, plain text ("ASCII") and + binary, and which one a file is, is detected from its contents, not from + its name + + most of the work is done by the static parse() method, which is + independent of any SPARTA instance and reports all error conditions by + throwing an STLReaderException, so the caller decides how to handle them + + read_file() is the alternative for the case where the mesh should be read + by proc 0 only and then communicated to all the other procs. It needs a + class instance, aborts SPARTA when the file cannot be read, and stores + the vertices in an array owned by the class instance. +------------------------------------------------------------------------- */ + +class STLReader : protected Pointers { + public: + + // a single triangle of an STL mesh + + struct Triangle { + double normal[3]; // outward facing normal vector of the facet + double vert[3][3]; // x,y,z coords of the 3 vertices + }; + + STLReader(class SPARTA *); + ~STLReader() override; + + int read_file(const char *, double **&); + + static std::vector parse(const std::string &, std::string * = NULL); + + private: + static std::vector parse_text(const std::string &, std::string &); + static std::vector parse_binary(FILE *, std::string &); + + int ntris,maxtris; + double **tris; +}; + +} + +#endif + +/* ERROR/WARNING messages: + +E: Cannot open STL file %s + +Self-explanatory. + +E: File %s is not a valid ASCII STL file + +The file does not start with the solid keyword and its size does not +match the layout of a binary STL file either. + +E: Expected 'facet' or 'endsolid' in STL file, got: %s + +The file does not follow the ASCII STL grammar. + +E: Error reading vertex %d of facet in STL file + +A vertex line inside an outer loop is missing or has too few coords. + +E: Unexpected end of binary STL file ... + +The file is truncated. + +E: STL file %s has no triangles + +The file was parsed successfully but contains no facets. + +*/ From d921882396a6b3fdff0ca40df67548d64214bdde Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 01:58:16 +0000 Subject: [PATCH 2/3] Read SPARTA surf files in region mesh, including 2d The mesh region now accepts surfaces in the format read_surf reads, as well as STL. Which format a file is in is detected from its contents, not its name, so no new keyword is needed. SPARTA already ships meshes in both formats (data/*.stl and data/sdata.*), and either can now define a region directly. The surf format carries 2d surfaces as line segments, so this is also what makes a 2d mesh region possible: a closed curve in the x-y plane. The ray is cast in that plane and segment crossings are counted, and the z coord of the point is not tested, so the region is the curve extruded to infinity in z. That is what a 2d simulation wants, and it stays consistent with regions being 3d objects in SPARTA, so a 2d mesh used in a 3d simulation defines a prism. Unlike read_surf, the file's dimensionality need not match the simulation's. RegMesh now stores one flat vertex array with meshdim vertices per element rather than a triangle array, so the bounding box, the transforms, and the binning are shared by both dimensionalities and only the crossing test itself is per-dimension. The degeneracy tolerance is scaled by each element's own size, the length of a segment or the normal of a triangle, which keeps the 2d and 3d tests parallel. Points sections are optional and a per-surface type column is skipped if present, matching read_surf. Per-surface custom attributes are not supported and are reported rather than misread. The parser is careful to use Error::one() and never Input::numeric(), because the latter reports a bad value with Error::all(), which opens with an MPI_Barrier the other procs would never reach while proc 0 alone reads the file. Verified with 1, 2, 3, and 4 MPI ranks and with create_particles/kk at 1 and 4 threads. A square polygon read from a surf file creates byte-identical particle sets to region block, in all 3 accepted record layouts (with a Points section, with inline coords, and with a type column); SPARTA's own examples/ambi/data.circle matches region cylinder exactly; a 2d mesh in a 3d simulation matches region block with INF z bounds exactly; and examples/sphere/data.sphere tracks region sphere to within its faceting error. The new 2d example is an annulus, whose two loops exercise a ray that crosses the surface four times. Co-Authored-By: Stan Moore Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ACVHM1qF2xS53wZi4kirAH --- data/README | 6 +- doc/region.html | 62 +- doc/region.txt | 62 +- examples/region_mesh/data.annulus | 202 ++++++ examples/region_mesh/in.region_mesh.2d | 44 ++ ...ion_mesh => log.27Aug26.mpi_1.region_mesh} | 40 +- .../log.27Aug26.mpi_1.region_mesh.2d | 122 ++++ ...ion_mesh => log.27Aug26.mpi_4.region_mesh} | 40 +- .../log.27Aug26.mpi_4.region_mesh.2d | 123 ++++ src/region_mesh.cpp | 603 ++++++++++++++---- src/region_mesh.h | 76 ++- src/stl_reader.cpp | 38 ++ src/stl_reader.h | 5 + 13 files changed, 1212 insertions(+), 211 deletions(-) create mode 100644 examples/region_mesh/data.annulus create mode 100644 examples/region_mesh/in.region_mesh.2d rename examples/region_mesh/{log.26Aug26.mpi_1.region_mesh => log.27Aug26.mpi_1.region_mesh} (74%) create mode 100644 examples/region_mesh/log.27Aug26.mpi_1.region_mesh.2d rename examples/region_mesh/{log.26Aug26.mpi_4.region_mesh => log.27Aug26.mpi_4.region_mesh} (75%) create mode 100644 examples/region_mesh/log.27Aug26.mpi_4.region_mesh.2d diff --git a/data/README b/data/README index d04586f15..635b45637 100644 --- a/data/README +++ b/data/README @@ -19,7 +19,9 @@ Files that end in ".tce" list reactions gas-phase chemistry for the Total Collision Energy (TCE) model and are read by the react command. Files that start with "sdata." contain lists of surface elements and -are read by the read_surf command. +are read by the read_surf command. They can also be read by the region +command with its mesh style, which defines the region as the volume +enclosed by the surface. Files that end in ".stl" are STL-formatted surface files which can be converted to SPARTA formatted surface files via the stl2surf.py tool @@ -37,6 +39,6 @@ species = doc/species.html species.vib = doc/species.html vss/vhs/hs = doc/collide.html tce = doc/react.html -sdata = doc/read_surf.html +sdata = doc/read_surf.html and doc/region.html stl = doc/Section_tools.html#stl2surf and doc/region.html surf = doc/surf_react.html diff --git a/doc/region.html b/doc/region.html index 2a847f7ff..2f31e3ca9 100644 --- a/doc/region.html +++ b/doc/region.html @@ -27,7 +27,8 @@

region command radius = cylinder radius (distance units) lo,hi = bounds of cylinder in dim (distance units) mesh args = file - file = name of file with a closed triangulated surface in STL format + file = name of file with a closed surface, in STL format or in the + format the read_surf command reads plane or plane/kk args = px py pz nx ny nz px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) @@ -67,7 +68,8 @@

region command region void cylinder y 2 3 5 -5.0 INF region outside union 4 side1 side2 side3 side4 region flask mesh flask.stl -region duct mesh duct.stl scale 0.001 0.001 0.001 side out +region duct mesh duct.stl scale 0.001 0.001 0.001 side out +region shell mesh data.circle side out

Description:

@@ -96,27 +98,43 @@

region command extending in the y-direction from -5.0 to infinity.

The mesh style defines the region as the volume enclosed by a closed -triangulated surface, which is read from a file in STL -(stereolithography) format. Both variants of that format are supported, -plain text ("ASCII") and binary. Which one a file is, is detected from -its contents, not from its name. The direction of the triangle normals -stored in the file is ignored. Every processor reads and stores the -entire mesh, so this style is best suited to small and medium sized -meshes. -

-

A point is tested against the mesh by casting a ray from the point in +surface which is read from a file. Two file formats are supported and +SPARTA detects which one a file is from its contents, not from its name: +

+

STL (stereolithography) files, in either of the two variants of that +format, plain text ("ASCII") or binary. An STL file always stores a 3d +triangulated surface. +

+

Surface files in the format the read_surf command +reads, which store a 3d surface as triangles or a 2d surface as line +segments. As for read_surf, a Points section is optional, and a +per-surface type column is skipped if it is present. Per-surface custom +attributes are not supported. +

+

Only the geometry in the file is used. Surface types, surface IDs, and +the direction of the normals are all ignored. Every processor reads and +stores the entire mesh, so this style is best suited to small and medium +sized meshes. +

+

A point is tested against a 3d mesh by casting a ray from the point in the +x direction and counting how many triangles it crosses. An odd -count means the point is inside the surface. This requires the surface -to be closed (watertight). SPARTA prints a warning if the triangles in -the file do not enclose a volume, in which case the inside/outside test -gives arbitrary answers. As for the other styles, a point that lies -exactly on the surface is considered to be inside it, and the side -keyword selects whether the region is the volume inside or outside the -mesh. +count means the point is inside the surface. A 2d mesh is a closed +curve in the x-y plane, so the ray is cast in that plane and crossings +of the line segments are counted instead. The z coordinate of the point +does not matter for a 2d mesh, which means the region it defines is the +curve extruded to infinity in both z directions. A 2d mesh can thus be +used in a 3d simulation as well, where it defines a prism. +

+

Either way this requires the surface to be closed, and SPARTA prints a +warning if the elements in the file do not enclose a volume (or an area +in 2d), in which case the inside/outside test gives arbitrary answers. +As for the other styles, a point that lies exactly on the surface is +considered to be inside it, and the side keyword selects whether the +region is the volume inside or outside the mesh.

The origin, trans, scale, and rotate keywords can only be used with the mesh style. They transform the mesh after it is read, which -is useful when the STL file uses different units or is positioned +is useful when the file uses different units or is positioned differently than the simulation box. They are applied in the order they appear in the command, and follow the same conventions as the read_surf keywords of the same name. Trans @@ -173,6 +191,12 @@

region command

Restrictions:

+

An STL file always describes a 3d surface, so a 2d region can only be +defined with a read_surf format file containing lines. +Unlike read_surf, the mesh style does not require the dimensionality +of the file to match the dimension of the simulation, +since as noted above regions in SPARTA are 3d objects in either case. +

There is no mesh/kk style. The create_particles command tests points against a region on the host, so a mesh region can be used with diff --git a/doc/region.txt b/doc/region.txt index 22e0ae09f..0ed0be3de 100644 --- a/doc/region.txt +++ b/doc/region.txt @@ -22,7 +22,8 @@ style = {block} or {cylinder} or {mesh} or {plane} or {sphere} or {union} or {in radius = cylinder radius (distance units) lo,hi = bounds of cylinder in dim (distance units) {mesh} args = file - file = name of file with a closed triangulated surface in STL format + file = name of file with a closed surface, in STL format or in the + format the "read_surf"_read_surf.html command reads {plane} or {plane/kk} args = px py pz nx ny nz px,py,pz = point on the plane (distance units) nx,ny,nz = direction normal to plane (distance units) @@ -58,7 +59,8 @@ region 2 sphere 0.0 0.0 0.0 5 side out region void cylinder y 2 3 5 -5.0 INF region outside union 4 side1 side2 side3 side4 region flask mesh flask.stl -region duct mesh duct.stl scale 0.001 0.001 0.001 side out :pre +region duct mesh duct.stl scale 0.001 0.001 0.001 side out +region shell mesh data.circle side out :pre [Description:] @@ -87,27 +89,43 @@ y-direction located at x = 2.0 and z = 3.0, with a radius of 5.0, and extending in the y-direction from -5.0 to infinity. The {mesh} style defines the region as the volume enclosed by a closed -triangulated surface, which is read from a file in STL -(stereolithography) format. Both variants of that format are supported, -plain text ("ASCII") and binary. Which one a file is, is detected from -its contents, not from its name. The direction of the triangle normals -stored in the file is ignored. Every processor reads and stores the -entire mesh, so this style is best suited to small and medium sized -meshes. - -A point is tested against the mesh by casting a ray from the point in +surface which is read from a file. Two file formats are supported and +SPARTA detects which one a file is from its contents, not from its name: + +STL (stereolithography) files, in either of the two variants of that +format, plain text ("ASCII") or binary. An STL file always stores a 3d +triangulated surface. + +Surface files in the format the "read_surf"_read_surf.html command +reads, which store a 3d surface as triangles or a 2d surface as line +segments. As for read_surf, a {Points} section is optional, and a +per-surface type column is skipped if it is present. Per-surface custom +attributes are not supported. + +Only the geometry in the file is used. Surface types, surface IDs, and +the direction of the normals are all ignored. Every processor reads and +stores the entire mesh, so this style is best suited to small and medium +sized meshes. + +A point is tested against a 3d mesh by casting a ray from the point in the +x direction and counting how many triangles it crosses. An odd -count means the point is inside the surface. This requires the surface -to be closed (watertight). SPARTA prints a warning if the triangles in -the file do not enclose a volume, in which case the inside/outside test -gives arbitrary answers. As for the other styles, a point that lies -exactly on the surface is considered to be inside it, and the {side} -keyword selects whether the region is the volume inside or outside the -mesh. +count means the point is inside the surface. A 2d mesh is a closed +curve in the x-y plane, so the ray is cast in that plane and crossings +of the line segments are counted instead. The z coordinate of the point +does not matter for a 2d mesh, which means the region it defines is the +curve extruded to infinity in both z directions. A 2d mesh can thus be +used in a 3d simulation as well, where it defines a prism. + +Either way this requires the surface to be closed, and SPARTA prints a +warning if the elements in the file do not enclose a volume (or an area +in 2d), in which case the inside/outside test gives arbitrary answers. +As for the other styles, a point that lies exactly on the surface is +considered to be inside it, and the {side} keyword selects whether the +region is the volume inside or outside the mesh. The {origin}, {trans}, {scale}, and {rotate} keywords can only be used with the {mesh} style. They transform the mesh after it is read, which -is useful when the STL file uses different units or is positioned +is useful when the file uses different units or is positioned differently than the simulation box. They are applied in the order they appear in the command, and follow the same conventions as the "read_surf"_read_surf.html keywords of the same name. {Trans} @@ -164,6 +182,12 @@ effectively. [Restrictions:] +An STL file always describes a 3d surface, so a 2d region can only be +defined with a "read_surf"_read_surf.html format file containing lines. +Unlike read_surf, the {mesh} style does not require the dimensionality +of the file to match the "dimension"_dimension.html of the simulation, +since as noted above regions in SPARTA are 3d objects in either case. + There is no {mesh/kk} style. The "create_particles"_create_particles.html command tests points against a region on the host, so a {mesh} region can be used with diff --git a/examples/region_mesh/data.annulus b/examples/region_mesh/data.annulus new file mode 100644 index 000000000..c6b6eadc9 --- /dev/null +++ b/examples/region_mesh/data.annulus @@ -0,0 +1,202 @@ +surf file for an annulus, outer radius 4, inner radius 2 + +96 points +96 lines + +Points + +1 4 0 +2 3.980738907 0.3920685613 +3 3.923141122 0.7803612881 +4 3.827761343 1.161138709 +5 3.69551813 1.530733729 +6 3.527685057 1.885586947 +7 3.325878449 2.222280932 +8 3.092041813 2.537573137 +9 2.828427125 2.828427125 +10 2.537573137 3.092041813 +11 2.222280932 3.325878449 +12 1.885586947 3.527685057 +13 1.530733729 3.69551813 +14 1.161138709 3.827761343 +15 0.7803612881 3.923141122 +16 0.3920685613 3.980738907 +17 2.449293598e-16 4 +18 -0.3920685613 3.980738907 +19 -0.7803612881 3.923141122 +20 -1.161138709 3.827761343 +21 -1.530733729 3.69551813 +22 -1.885586947 3.527685057 +23 -2.222280932 3.325878449 +24 -2.537573137 3.092041813 +25 -2.828427125 2.828427125 +26 -3.092041813 2.537573137 +27 -3.325878449 2.222280932 +28 -3.527685057 1.885586947 +29 -3.69551813 1.530733729 +30 -3.827761343 1.161138709 +31 -3.923141122 0.7803612881 +32 -3.980738907 0.3920685613 +33 -4 4.898587197e-16 +34 -3.980738907 -0.3920685613 +35 -3.923141122 -0.7803612881 +36 -3.827761343 -1.161138709 +37 -3.69551813 -1.530733729 +38 -3.527685057 -1.885586947 +39 -3.325878449 -2.222280932 +40 -3.092041813 -2.537573137 +41 -2.828427125 -2.828427125 +42 -2.537573137 -3.092041813 +43 -2.222280932 -3.325878449 +44 -1.885586947 -3.527685057 +45 -1.530733729 -3.69551813 +46 -1.161138709 -3.827761343 +47 -0.7803612881 -3.923141122 +48 -0.3920685613 -3.980738907 +49 -7.347880795e-16 -4 +50 0.3920685613 -3.980738907 +51 0.7803612881 -3.923141122 +52 1.161138709 -3.827761343 +53 1.530733729 -3.69551813 +54 1.885586947 -3.527685057 +55 2.222280932 -3.325878449 +56 2.537573137 -3.092041813 +57 2.828427125 -2.828427125 +58 3.092041813 -2.537573137 +59 3.325878449 -2.222280932 +60 3.527685057 -1.885586947 +61 3.69551813 -1.530733729 +62 3.827761343 -1.161138709 +63 3.923141122 -0.7803612881 +64 3.980738907 -0.3920685613 +65 1.961570561 -0.390180644 +66 1.847759065 -0.7653668647 +67 1.662939225 -1.111140466 +68 1.414213562 -1.414213562 +69 1.111140466 -1.662939225 +70 0.7653668647 -1.847759065 +71 0.390180644 -1.961570561 +72 -3.673940397e-16 -2 +73 -0.390180644 -1.961570561 +74 -0.7653668647 -1.847759065 +75 -1.111140466 -1.662939225 +76 -1.414213562 -1.414213562 +77 -1.662939225 -1.111140466 +78 -1.847759065 -0.7653668647 +79 -1.961570561 -0.390180644 +80 -2 2.449293598e-16 +81 -1.961570561 0.390180644 +82 -1.847759065 0.7653668647 +83 -1.662939225 1.111140466 +84 -1.414213562 1.414213562 +85 -1.111140466 1.662939225 +86 -0.7653668647 1.847759065 +87 -0.390180644 1.961570561 +88 1.224646799e-16 2 +89 0.390180644 1.961570561 +90 0.7653668647 1.847759065 +91 1.111140466 1.662939225 +92 1.414213562 1.414213562 +93 1.662939225 1.111140466 +94 1.847759065 0.7653668647 +95 1.961570561 0.390180644 +96 2 0 + +Lines + +1 1 2 +2 2 3 +3 3 4 +4 4 5 +5 5 6 +6 6 7 +7 7 8 +8 8 9 +9 9 10 +10 10 11 +11 11 12 +12 12 13 +13 13 14 +14 14 15 +15 15 16 +16 16 17 +17 17 18 +18 18 19 +19 19 20 +20 20 21 +21 21 22 +22 22 23 +23 23 24 +24 24 25 +25 25 26 +26 26 27 +27 27 28 +28 28 29 +29 29 30 +30 30 31 +31 31 32 +32 32 33 +33 33 34 +34 34 35 +35 35 36 +36 36 37 +37 37 38 +38 38 39 +39 39 40 +40 40 41 +41 41 42 +42 42 43 +43 43 44 +44 44 45 +45 45 46 +46 46 47 +47 47 48 +48 48 49 +49 49 50 +50 50 51 +51 51 52 +52 52 53 +53 53 54 +54 54 55 +55 55 56 +56 56 57 +57 57 58 +58 58 59 +59 59 60 +60 60 61 +61 61 62 +62 62 63 +63 63 64 +64 64 1 +65 65 66 +66 66 67 +67 67 68 +68 68 69 +69 69 70 +70 70 71 +71 71 72 +72 72 73 +73 73 74 +74 74 75 +75 75 76 +76 76 77 +77 77 78 +78 78 79 +79 79 80 +80 80 81 +81 81 82 +82 82 83 +83 83 84 +84 84 85 +85 85 86 +86 86 87 +87 87 88 +88 88 89 +89 89 90 +90 90 91 +91 91 92 +92 92 93 +93 93 94 +94 94 95 +95 95 96 +96 96 65 diff --git a/examples/region_mesh/in.region_mesh.2d b/examples/region_mesh/in.region_mesh.2d new file mode 100644 index 000000000..1cf80f947 --- /dev/null +++ b/examples/region_mesh/in.region_mesh.2d @@ -0,0 +1,44 @@ +################################################################################ +# thermal gas filling the area enclosed by a 2d surface mesh +# +# the mesh is an annulus, read from a file in the same format read_surf +# reads, so a ray cast from a point in the gas crosses both of its loops +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 2 +global gridcut 0.0 comm/sort yes + +boundary rr rr p + +create_box -5 5 -5 5 -0.5 0.5 +create_grid 40 40 1 + +balance_grid rcb cell + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the annulus in the file has an outer radius of 4 and an inner radius of 2 +# and is centered on the origin + +region ann mesh data.annulus + +create_particles air n 0 region ann twopass + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 diff --git a/examples/region_mesh/log.26Aug26.mpi_1.region_mesh b/examples/region_mesh/log.27Aug26.mpi_1.region_mesh similarity index 74% rename from examples/region_mesh/log.26Aug26.mpi_1.region_mesh rename to examples/region_mesh/log.27Aug26.mpi_1.region_mesh index 963b4ffa4..246328170 100644 --- a/examples/region_mesh/log.26Aug26.mpi_1.region_mesh +++ b/examples/region_mesh/log.27Aug26.mpi_1.region_mesh @@ -22,13 +22,13 @@ create_box -5 5 -5 5 -5 5 Created orthogonal box = (-5 -5 -5) to (5 5 5) create_grid 20 20 20 Created 8000 child grid cells - CPU time = 0.00235332 secs - create/ghost percent = 46.1043 53.8957 + CPU time = 0.00188663 secs + create/ghost percent = 44.943 55.057 balance_grid rcb cell Balance grid migrated 0 cells - CPU time = 0.00146514 secs - reassign/sort/migrate/ghost percent = 38.6107 2.85419 6.8106 51.7245 + CPU time = 0.00122457 secs + reassign/sort/migrate/ghost percent = 41.7409 3.72498 9.41123 45.1229 species air.species N O mixture air N O vstream 0.0 0.0 0.0 temp 300.0 @@ -46,7 +46,7 @@ Reading STL object torus with 576 triangles from file data.torus.stl create_particles air n 0 region tor twopass Created 55854 particles - CPU time = 0.0399925 secs + CPU time = 0.0395966 secs stats 20 compute temp temp @@ -64,25 +64,25 @@ Memory usage per proc in Mbytes: total (ave,min,max) = 7.76379 7.76379 7.76379 Step CPU Np Natt Ncoll c_temp 0 0 55854 0 0 299.39732 - 20 0.03756757 55854 8447 5725 299.39732 - 40 0.073681199 55854 8101 5481 299.39732 - 60 0.11127468 55854 7469 4918 299.39732 - 80 0.14639508 55854 6738 4337 299.39732 - 100 0.17991576 55854 5985 3839 299.39732 -Loop time of 0.179961 on 1 procs for 100 steps with 55854 particles -Performance: 555.677 timesteps/s, 31.037 Mparticle-step/s + 20 0.0523511 55854 8447 5725 299.39732 + 40 0.098892916 55854 8101 5481 299.39732 + 60 0.13834398 55854 7469 4918 299.39732 + 80 0.17101096 55854 6738 4337 299.39732 + 100 0.20099609 55854 5985 3839 299.39732 +Loop time of 0.201027 on 1 procs for 100 steps with 55854 particles +Performance: 497.445 timesteps/s, 27.784 Mparticle-step/s MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Move | 0.039847 | 0.039847 | 0.039847 | 0.0 | 22.14 -Coll | 0.11905 | 0.11905 | 0.11905 | 0.0 | 66.16 -Sort | 0.019537 | 0.019537 | 0.019537 | 0.0 | 10.86 -Comm | 0.00010981 | 0.00010981 | 0.00010981 | 0.0 | 0.06 +Move | 0.044279 | 0.044279 | 0.044279 | 0.0 | 22.03 +Coll | 0.13465 | 0.13465 | 0.13465 | 0.0 | 66.98 +Sort | 0.02067 | 0.02067 | 0.02067 | 0.0 | 10.28 +Comm | 9.7879e-05 | 9.7879e-05 | 9.7879e-05 | 0.0 | 0.05 Modify | 0 | 0 | 0 | 0.0 | 0.00 -Output | 0.0012783 | 0.0012783 | 0.0012783 | 0.0 | 0.71 -MPI Sync| 0.00011196 | 0.00011196 | 0.00011196 | 0.0 | 0.06 -Other | | 2.296e-05 | | | 0.01 +Output | 0.0011631 | 0.0011631 | 0.0011631 | 0.0 | 0.58 +MPI Sync| 0.00015136 | 0.00015136 | 0.00015136 | 0.0 | 0.08 +Other | | 1.971e-05 | | | 0.01 Particle moves = 5585400 (5.59M) Cells touched = 5682200 (5.68M) @@ -98,7 +98,7 @@ Reactions = 0 (0K) Particles stuck = 0 Axisymm bad moves = 0 -Particle-moves/CPUsec/proc: 3.10368e+07 +Particle-moves/CPUsec/proc: 2.77843e+07 Particle-moves/step: 55854 Cell-touches/particle/step: 1.01733 Particle comm iterations/step: 1 diff --git a/examples/region_mesh/log.27Aug26.mpi_1.region_mesh.2d b/examples/region_mesh/log.27Aug26.mpi_1.region_mesh.2d new file mode 100644 index 000000000..4e995016e --- /dev/null +++ b/examples/region_mesh/log.27Aug26.mpi_1.region_mesh.2d @@ -0,0 +1,122 @@ +SPARTA (24 Sep 2025) +Running on 1 MPI task(s) +################################################################################ +# thermal gas filling the area enclosed by a 2d surface mesh +# +# the mesh is an annulus, read from a file in the same format read_surf +# reads, so a ray cast from a point in the gas crosses both of its loops +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 2 +global gridcut 0.0 comm/sort yes + +boundary rr rr p + +create_box -5 5 -5 5 -0.5 0.5 +Created orthogonal box = (-5 -5 -0.5) to (5 5 0.5) +create_grid 40 40 1 +Created 1600 child grid cells + CPU time = 0.000863477 secs + create/ghost percent = 79.1457 20.8543 + +balance_grid rcb cell +Balance grid migrated 0 cells + CPU time = 0.000254395 secs + reassign/sort/migrate/ghost percent = 51.31 0.952456 7.77177 39.9658 + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the annulus in the file has an outer radius of 4 and an inner radius of 2 +# and is centered on the origin + +region ann mesh data.annulus +Reading 96 lines from surf file data.annulus + -4 -4 0 to 4 4 0 mesh bounding box + 37.699 enclosed mesh area + +create_particles air n 0 region ann twopass +Created 37647 particles + CPU time = 0.00909305 secs + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 +Memory usage per proc in Mbytes: + particles (ave,min,max) = 4.6875 4.6875 4.6875 + grid (ave,min,max) = 1.51379 1.51379 1.51379 + surf (ave,min,max) = 0 0 0 + modify (ave,min,max) = 0 0 0 + total (ave,min,max) = 6.20129 6.20129 6.20129 +Step CPU Np Natt Ncoll c_temp + 0 0 37647 0 0 298.58341 + 20 0.021459459 37647 6375 4473 298.58341 + 40 0.056167176 37647 6291 4258 298.58341 + 60 0.078458566 37647 6069 4076 298.58341 + 80 0.1005359 37647 5802 3840 298.58341 + 100 0.12196574 37647 5481 3607 298.58341 +Loop time of 0.121991 on 1 procs for 100 steps with 37647 particles +Performance: 819.734 timesteps/s, 30.861 Mparticle-step/s + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Move | 0.02247 | 0.02247 | 0.02247 | 0.0 | 18.42 +Coll | 0.085601 | 0.085601 | 0.085601 | 0.0 | 70.17 +Sort | 0.0090205 | 0.0090205 | 0.0090205 | 0.0 | 7.39 +Comm | 6.9983e-05 | 6.9983e-05 | 6.9983e-05 | 0.0 | 0.06 +Modify | 0 | 0 | 0 | 0.0 | 0.00 +Output | 0.0047351 | 0.0047351 | 0.0047351 | 0.0 | 3.88 +MPI Sync| 7.9022e-05 | 7.9022e-05 | 7.9022e-05 | 0.0 | 0.06 +Other | | 1.556e-05 | | | 0.01 + +Particle moves = 3764700 (3.76M) +Cells touched = 3860633 (3.86M) +Particle comms = 0 (0K) +Boundary collides = 9 (0.009K) +Boundary exits = 0 (0K) +SurfColl checks = 0 (0K) +SurfColl occurs = 0 (0K) +Surf reactions = 0 (0K) +Collide attempts = 607009 (0.607M) +Collide occurs = 412535 (0.413M) +Reactions = 0 (0K) +Particles stuck = 0 +Axisymm bad moves = 0 + +Particle-moves/CPUsec/proc: 3.08605e+07 +Particle-moves/step: 37647 +Cell-touches/particle/step: 1.02548 +Particle comm iterations/step: 1 +Particle fraction communicated: 0 +Particle fraction colliding with boundary: 2.39063e-06 +Particle fraction exiting boundary: 0 +Surface-checks/particle/step: 0 +Surface-collisions/particle/step: 0 +Surf-reactions/particle/step: 0 +Collision-attempts/particle/step: 0.161237 +Collisions/particle/step: 0.10958 +Reactions/particle/step: 0 + +Particles: 37647 ave 37647 max 37647 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +Cells: 1600 ave 1600 max 1600 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +GhostCell: 0 ave 0 max 0 min +Histogram: 1 0 0 0 0 0 0 0 0 0 +EmptyCell: 0 ave 0 max 0 min +Histogram: 1 0 0 0 0 0 0 0 0 0 diff --git a/examples/region_mesh/log.26Aug26.mpi_4.region_mesh b/examples/region_mesh/log.27Aug26.mpi_4.region_mesh similarity index 75% rename from examples/region_mesh/log.26Aug26.mpi_4.region_mesh rename to examples/region_mesh/log.27Aug26.mpi_4.region_mesh index 7ef24237c..311d8571b 100644 --- a/examples/region_mesh/log.26Aug26.mpi_4.region_mesh +++ b/examples/region_mesh/log.27Aug26.mpi_4.region_mesh @@ -23,13 +23,13 @@ Created orthogonal box = (-5 -5 -5) to (5 5 5) create_grid 20 20 20 WARNING: Could not acquire nearby ghost cells b/c grid partition is not clumped (../grid.cpp:486) Created 8000 child grid cells - CPU time = 0.00110086 secs - create/ghost percent = 94.588 5.41197 + CPU time = 0.0399973 secs + create/ghost percent = 20.0147 79.9853 balance_grid rcb cell Balance grid migrated 5600 cells - CPU time = 0.00191228 secs - reassign/sort/migrate/ghost percent = 36.3232 0.584643 33.1003 29.9919 + CPU time = 0.299996 secs + reassign/sort/migrate/ghost percent = 49.3344 2.66504 13.3774 34.6232 species air.species N O mixture air N O vstream 0.0 0.0 0.0 temp 300.0 @@ -47,7 +47,7 @@ Reading STL object torus with 576 triangles from file data.torus.stl create_particles air n 0 region tor twopass Created 56099 particles - CPU time = 0.0106426 secs + CPU time = 0.0337464 secs stats 20 compute temp temp @@ -65,25 +65,25 @@ Memory usage per proc in Mbytes: total (ave,min,max) = 3.07629 3.07629 3.07629 Step CPU Np Natt Ncoll c_temp 0 0 56099 0 0 298.92493 - 20 0.008557938 56099 8453 5796 298.92493 - 40 0.017359548 56099 8089 5414 298.92493 - 60 0.025594545 56099 7458 4838 298.92493 - 80 0.033416026 56099 6749 4404 298.92493 - 100 0.041033305 56099 5991 3873 298.92493 -Loop time of 0.0410781 on 4 procs for 100 steps with 56099 particles -Performance: 2434.388 timesteps/s, 136.567 Mparticle-step/s + 20 0.9640167 56099 8453 5796 298.92493 + 40 1.8520067 56099 8089 5414 298.92493 + 60 2.7880367 56099 7458 4838 298.92493 + 80 3.8134074 56099 6749 4404 298.92493 + 100 4.7640505 56099 5991 3873 298.92493 +Loop time of 4.779 on 4 procs for 100 steps with 56099 particles +Performance: 20.925 timesteps/s, 1.174 Mparticle-step/s MPI task timing breakdown: Section | min time | avg time | max time |%varavg| %total --------------------------------------------------------------- -Move | 0.0090956 | 0.0093462 | 0.0095622 | 0.2 | 22.75 -Coll | 0.024621 | 0.025109 | 0.025766 | 0.3 | 61.13 -Sort | 0.0026634 | 0.0027654 | 0.0028365 | 0.1 | 6.73 -Comm | 0.0010384 | 0.0010568 | 0.0010763 | 0.0 | 2.57 +Move | 0.010177 | 0.010261 | 0.010431 | 0.1 | 0.21 +Coll | 0.027048 | 0.027708 | 0.028343 | 0.3 | 0.58 +Sort | 0.0075804 | 0.0083599 | 0.010366 | 1.3 | 0.17 +Comm | 3.4012 | 3.5247 | 3.6188 | 4.8 | 73.75 Modify | 0 | 0 | 0 | 0.0 | 0.00 -Output | 0.00020512 | 0.00028011 | 0.00042722 | 0.0 | 0.68 -MPI Sync| 0.0016101 | 0.0025109 | 0.0031141 | 1.2 | 6.11 -Other | | 9.483e-06 | | | 0.02 +Output | 0.11893 | 0.12648 | 0.13431 | 1.7 | 2.65 +MPI Sync| 0.99722 | 1.0814 | 1.2078 | 8.5 | 22.63 +Other | | 2.098e-05 | | | 0.00 Particle moves = 5609900 (5.61M) Cells touched = 5707445 (5.71M) @@ -99,7 +99,7 @@ Reactions = 0 (0K) Particles stuck = 0 Axisymm bad moves = 0 -Particle-moves/CPUsec/proc: 3.41417e+07 +Particle-moves/CPUsec/proc: 293466 Particle-moves/step: 56099 Cell-touches/particle/step: 1.01739 Particle comm iterations/step: 2 diff --git a/examples/region_mesh/log.27Aug26.mpi_4.region_mesh.2d b/examples/region_mesh/log.27Aug26.mpi_4.region_mesh.2d new file mode 100644 index 000000000..84f9d0d62 --- /dev/null +++ b/examples/region_mesh/log.27Aug26.mpi_4.region_mesh.2d @@ -0,0 +1,123 @@ +SPARTA (24 Sep 2025) +Running on 4 MPI task(s) +################################################################################ +# thermal gas filling the area enclosed by a 2d surface mesh +# +# the mesh is an annulus, read from a file in the same format read_surf +# reads, so a ray cast from a point in the gas crosses both of its loops +# +# Note: +# - The "comm/sort" option to the "global" command is used to match MPI runs. +# - The "twopass" option is used to match Kokkos runs. +# The "comm/sort" and "twopass" options should not be used for production runs. +################################################################################ + +seed 12345 +dimension 2 +global gridcut 0.0 comm/sort yes + +boundary rr rr p + +create_box -5 5 -5 5 -0.5 0.5 +Created orthogonal box = (-5 -5 -0.5) to (5 5 0.5) +create_grid 40 40 1 +WARNING: Could not acquire nearby ghost cells b/c grid partition is not clumped (../grid.cpp:486) +Created 1600 child grid cells + CPU time = 0.0440137 secs + create/ghost percent = 18.198 81.802 + +balance_grid rcb cell +Balance grid migrated 1200 cells + CPU time = 0.222461 secs + reassign/sort/migrate/ghost percent = 95.3043 3.5935 1.04958 0.0525962 + +species air.species N O +mixture air N O vstream 0.0 0.0 0.0 temp 300.0 + +global nrho 1.0e20 +global fnum 1.0e17 + +# the annulus in the file has an outer radius of 4 and an inner radius of 2 +# and is centered on the origin + +region ann mesh data.annulus +Reading 96 lines from surf file data.annulus + -4 -4 0 to 4 4 0 mesh bounding box + 37.699 enclosed mesh area + +create_particles air n 0 region ann twopass +Created 37678 particles + CPU time = 0.0188268 secs + +stats 20 +compute temp temp +stats_style step cpu np nattempt ncoll c_temp + +collide vss air air.vss + +timestep 1.0e-5 +run 100 +Memory usage per proc in Mbytes: + particles (ave,min,max) = 1.5625 1.5625 1.5625 + grid (ave,min,max) = 1.51379 1.51379 1.51379 + surf (ave,min,max) = 0 0 0 + modify (ave,min,max) = 0 0 0 + total (ave,min,max) = 3.07629 3.07629 3.07629 +Step CPU Np Natt Ncoll c_temp + 0 0 37678 0 0 300.95773 + 20 0.009582402 37678 6410 4434 300.95773 + 40 0.019269321 37678 6265 4228 300.95773 + 60 0.048919185 37678 6070 4045 300.95773 + 80 0.35980408 37678 5775 3801 300.95773 + 100 0.86410683 37678 5487 3623 300.95773 +Loop time of 0.864184 on 4 procs for 100 steps with 37678 particles +Performance: 115.716 timesteps/s, 4.360 Mparticle-step/s + +MPI task timing breakdown: +Section | min time | avg time | max time |%varavg| %total +--------------------------------------------------------------- +Move | 0.0075852 | 0.0092281 | 0.0099604 | 1.0 | 1.07 +Coll | 0.026225 | 0.036557 | 0.042412 | 3.2 | 4.23 +Sort | 0.0019483 | 0.0020117 | 0.0020507 | 0.1 | 0.23 +Comm | 0.54616 | 0.58986 | 0.62014 | 4.0 | 68.26 +Modify | 0 | 0 | 0 | 0.0 | 0.00 +Output | 0.023883 | 0.02496 | 0.02787 | 1.1 | 2.89 +MPI Sync| 0.17263 | 0.20155 | 0.24227 | 6.0 | 23.32 +Other | | 1.782e-05 | | | 0.00 + +Particle moves = 3767800 (3.77M) +Cells touched = 3864319 (3.86M) +Particle comms = 2670 (2.67K) +Boundary collides = 6 (0.006K) +Boundary exits = 0 (0K) +SurfColl checks = 0 (0K) +SurfColl occurs = 0 (0K) +Surf reactions = 0 (0K) +Collide attempts = 607828 (0.608M) +Collide occurs = 412809 (0.413M) +Reactions = 0 (0K) +Particles stuck = 0 +Axisymm bad moves = 0 + +Particle-moves/CPUsec/proc: 1.08999e+06 +Particle-moves/step: 37678 +Cell-touches/particle/step: 1.02562 +Particle comm iterations/step: 2 +Particle fraction communicated: 0.000708636 +Particle fraction colliding with boundary: 1.59244e-06 +Particle fraction exiting boundary: 0 +Surface-checks/particle/step: 0 +Surface-collisions/particle/step: 0 +Surf-reactions/particle/step: 0 +Collision-attempts/particle/step: 0.161322 +Collisions/particle/step: 0.109562 +Reactions/particle/step: 0 + +Particles: 9419.5 ave 9466 max 9363 min +Histogram: 1 0 1 0 0 0 0 0 0 2 +Cells: 400 ave 400 max 400 min +Histogram: 4 0 0 0 0 0 0 0 0 0 +GhostCell: 41 ave 41 max 41 min +Histogram: 4 0 0 0 0 0 0 0 0 0 +EmptyCell: 41 ave 41 max 41 min +Histogram: 4 0 0 0 0 0 0 0 0 0 diff --git a/src/region_mesh.cpp b/src/region_mesh.cpp index 63a24faab..5d3b3e540 100644 --- a/src/region_mesh.cpp +++ b/src/region_mesh.cpp @@ -21,23 +21,60 @@ #include "input.h" #include "math_const.h" #include "math_extra.h" +#include "utils.h" #include "memory.h" #include "error.h" using namespace SPARTA_NS; using namespace MathConst; +#define BIG 1.0e20 // same "infinity" the other region styles use #define EPSILON 1.0e-9 // relative tolerance for a degenerate ray hit #define JITTER 1.0e-6 // relative offset used to move a degenerate ray #define MAXJITTER 8 // max # of times a degenerate ray is moved -#define WATERTIGHT 1.0e-6 // relative tolerance on the closure of the mesh +#define CLOSED 1.0e-6 // relative tolerance on the closure of the mesh #define MAXBIN 1024 // max # of ray-casting bins in y or z +#define MAXLINE 1024 +#define MAXWORD 16 // more words on a line than any valid format has // angle between successive jittered rays, the golden angle in radians // keeps the rays from repeating a direction that was already degenerate #define GOLDEN_ANGLE 2.39996322972865332 +/* ---------------------------------------------------------------------- + return the next non-blank line of a file with any comment stripped off, + or NULL at end of file +------------------------------------------------------------------------- */ + +static char *next_line(FILE *fp, char *line) +{ + while (fgets(line,MAXLINE,fp)) { + char *ptr = strchr(line,'#'); + if (ptr) *ptr = '\0'; + if (strspn(line," \t\n\r") != strlen(line)) return line; + } + return NULL; +} + +/* ---------------------------------------------------------------------- + split a line into whitespace delimited words, return # of words + words point into line, which is modified +------------------------------------------------------------------------- */ + +static int split_line(char *line, char **words) +{ + int nwords = 0; + char *ptr = strtok(line," \t\n\r\f"); + + while (ptr && nwords < MAXWORD) { + words[nwords++] = ptr; + ptr = strtok(NULL," \t\n\r\f"); + } + + return nwords; +} + /* ---------------------------------------------------------------------- */ RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : @@ -45,19 +82,13 @@ RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : { if (narg < 3) error->all(FLERR,"Illegal region mesh command"); - tris = NULL; - tnorm = NULL; + verts = NULL; + esize = NULL; binfirst = binlist = NULL; - // read the triangles, every proc keeps a copy of the entire mesh - // the reader owns the array it fills, so keep our own copy of it + // read the surface, every proc keeps a copy of the entire mesh - STLReader reader(sparta); - double **readtris; - ntri = reader.read_file(arg[2],readtris); - - memory->create(tris,ntri,9,"region/mesh:tris"); - memcpy(&tris[0][0],&readtris[0][0],9*ntri*sizeof(double)); + read_mesh(arg[2]); // optional args // side is parsed by the base class, everything else transforms the mesh @@ -114,6 +145,7 @@ RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : // extent of the mesh // as for the other styles, only an interior region has a bounding box + // a 2d mesh is a closed curve in x-y, so the region is a prism in z if (interior) { bboxflag = 1; @@ -121,20 +153,26 @@ RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : extent_xhi = bbhi[0]; extent_ylo = bblo[1]; extent_yhi = bbhi[1]; - extent_zlo = bblo[2]; - extent_zhi = bbhi[2]; + if (meshdim == 3) { + extent_zlo = bblo[2]; + extent_zhi = bbhi[2]; + } else { + extent_zlo = -BIG; + extent_zhi = BIG; + } } else bboxflag = 0; if (comm->me == 0) { + const char *what = (meshdim == 2) ? "area" : "volume"; if (screen) { fprintf(screen," %g %g %g to %g %g %g mesh bounding box\n", bblo[0],bblo[1],bblo[2],bbhi[0],bbhi[1],bbhi[2]); - fprintf(screen," %g enclosed mesh volume\n",volume); + fprintf(screen," %g enclosed mesh %s\n",enclosed,what); } if (logfile) { fprintf(logfile," %g %g %g to %g %g %g mesh bounding box\n", bblo[0],bblo[1],bblo[2],bbhi[0],bbhi[1],bbhi[2]); - fprintf(logfile," %g enclosed mesh volume\n",volume); + fprintf(logfile," %g enclosed mesh %s\n",enclosed,what); } } } @@ -143,26 +181,270 @@ RegMesh::RegMesh(SPARTA *sparta, int narg, char **arg) : RegMesh::~RegMesh() { - memory->destroy(tris); - memory->destroy(tnorm); + memory->destroy(verts); + memory->destroy(esize); memory->destroy(binfirst); memory->destroy(binlist); } +/* ---------------------------------------------------------------------- + read the surface from an STL file or a SPARTA surf file + which one it is is detected from the contents of the file +------------------------------------------------------------------------- */ + +void RegMesh::read_mesh(char *file) +{ + int stlflag = 0; + + if (comm->me == 0) { + try { + stlflag = STLReader::is_stl_file(file); + } catch (std::exception &e) { + error->one(FLERR,e.what()); + } + } + MPI_Bcast(&stlflag,1,MPI_INT,0,world); + + if (stlflag) read_stl_file(file); + else read_surf_file(file); +} + +/* ---------------------------------------------------------------------- + read triangles from an ASCII or binary STL file +------------------------------------------------------------------------- */ + +void RegMesh::read_stl_file(char *file) +{ + STLReader reader(sparta); + double **tris; + + meshdim = 3; + nelem = reader.read_file(file,tris); + nvert = 3*nelem; + + // the reader owns the array it fills, so keep our own copy of it + + memory->create(verts,nvert,3,"region/mesh:verts"); + + for (int i = 0; i < nelem; i++) + for (int j = 0; j < 3; j++) + for (int k = 0; k < 3; k++) + verts[3*i+j][k] = tris[i][3*j+k]; +} + +/* ---------------------------------------------------------------------- + read lines or triangles from a SPARTA surf file, the same format + read_surf reads, on proc 0, then broadcast them + only the geometry is used, so a type column is skipped and the ids are + ignored, but per-surf custom attributes are not supported +------------------------------------------------------------------------- */ + +void RegMesh::read_surf_file(char *file) +{ + if (comm->me == 0) { + char line[MAXLINE]; + char *words[MAXWORD]; + double **pts = NULL; + + FILE *fp = fopen(file,"r"); + if (fp == NULL) surf_error(file,"Cannot open mesh file"); + + // 1st line of the file is a comment + + if (fgets(line,MAXLINE,fp) == NULL) + surf_error(file,"Unexpected end of surf file"); + + // header = keyword lines with counts on them + // it ends at the 1st line with no header keyword, the section keyword + + int npoint = 0; + int nline = 0; + int ntri = 0; + + while (1) { + if (next_line(fp,line) == NULL) { + line[0] = '\0'; + break; + } + if (strstr(line,"points")) npoint = atoi(line); + else if (strstr(line,"lines")) nline = atoi(line); + else if (strstr(line,"triangles")) ntri = atoi(line); + else break; + } + + if (nline && ntri) + surf_error(file,"Surf file contains both lines and triangles"); + if (nline <= 0 && ntri <= 0) + surf_error(file,"Surf file does not contain lines or triangles"); + + meshdim = nline ? 2 : 3; + nelem = nline ? nline : ntri; + nvert = meshdim*nelem; + memory->create(verts,nvert,3,"region/mesh:verts"); + + // sections, in whatever order they appear + // a Points section is optional, elements may carry their own coords + + int nelemread = 0; + + while (strlen(line)) { + if (strstr(line,"Points")) { + if (npoint <= 0) surf_error(file,"Surf file has no points count"); + memory->create(pts,npoint,3,"region/mesh:pts"); + + for (int i = 0; i < npoint; i++) { + if (next_line(fp,line) == NULL) + surf_error(file,"Unexpected end of surf file"); + int nwords = split_line(line,words); + if (nwords != 3 && nwords != 4) + surf_error(file,"Incorrect point format in surf file"); + pts[i][0] = surf_numeric(file,words[1]); + pts[i][1] = surf_numeric(file,words[2]); + pts[i][2] = (nwords == 4) ? surf_numeric(file,words[3]) : 0.0; + } + + } else if (strstr(line,"Lines") || strstr(line,"Triangles")) { + int nvper = meshdim; + + // # of words per element, with and without a leading type column + // an id is always present, and point indices replace the coords + // if the file has a Points section + + int nbase = npoint ? 1+nvper : 1+3*nvper; + if (meshdim == 2 && !npoint) nbase = 1+2*nvper; + + for (int i = 0; i < nelem; i++) { + if (next_line(fp,line) == NULL) + surf_error(file,"Unexpected end of surf file"); + int nwords = split_line(line,words); + if (nwords != nbase && nwords != nbase+1) + surf_error(file,(meshdim == 2) ? + "Incorrect line format in surf file" : + "Incorrect triangle format in surf file"); + + // skip the id, and the type column if there is one + + int iw = (nwords == nbase) ? 1 : 2; + + for (int j = 0; j < nvper; j++) { + double *v = verts[nvper*i+j]; + if (npoint) { + int ipoint = surf_inumeric(file,words[iw++]); + if (ipoint < 1 || ipoint > npoint) + surf_error(file,"Invalid point index in surf file"); + v[0] = pts[ipoint-1][0]; + v[1] = pts[ipoint-1][1]; + v[2] = pts[ipoint-1][2]; + } else { + v[0] = surf_numeric(file,words[iw++]); + v[1] = surf_numeric(file,words[iw++]); + v[2] = (meshdim == 3) ? surf_numeric(file,words[iw++]) : 0.0; + } + } + } + + nelemread = nelem; + + } else surf_error(file,"Unknown section in surf file"); + + if (next_line(fp,line) == NULL) line[0] = '\0'; + } + + memory->destroy(pts); + fclose(fp); + + if (nelemread == 0) + surf_error(file,(meshdim == 2) ? + "Surf file has no Lines section" : + "Surf file has no Triangles section"); + + if (screen) + fprintf(screen,"Reading %d %s from surf file %s\n", + nelem,(meshdim == 2) ? "lines" : "triangles",file); + if (logfile) + fprintf(logfile,"Reading %d %s from surf file %s\n", + nelem,(meshdim == 2) ? "lines" : "triangles",file); + } + + bcast_verts(); +} + +/* ---------------------------------------------------------------------- + send the mesh proc 0 read to all the other procs +------------------------------------------------------------------------- */ + +void RegMesh::bcast_verts() +{ + MPI_Bcast(&meshdim,1,MPI_INT,0,world); + MPI_Bcast(&nelem,1,MPI_INT,0,world); + MPI_Bcast(&nvert,1,MPI_INT,0,world); + + if (comm->me) memory->create(verts,nvert,3,"region/mesh:verts"); + + // allow for 3*nvert to exceed the max allowed size of a single MPI_Bcast() + + bigint ntotal = (bigint) nvert * 3; + if (ntotal < MAXSMALLINT) + MPI_Bcast(&verts[0][0],3*nvert,MPI_DOUBLE,0,world); + else { + double *source = &verts[0][0]; + bigint n = 0; + while (n < ntotal) { + int nsize = MIN(MAXSMALLINT,ntotal-n); + MPI_Bcast(&source[n],nsize,MPI_DOUBLE,0,world); + n += nsize; + } + } +} + +/* ---------------------------------------------------------------------- */ + +void RegMesh::surf_error(const char *file, const char *mesg) +{ + char str[256]; + snprintf(str,256,"%s: %s",mesg,file); + error->one(FLERR,str); +} + +/* ---------------------------------------------------------------------- + convert a word of a surf file to a number + Input::numeric() cannot be used here, it reports a bad value with + Error::all(), which the other procs would never reach while proc 0 is + the only one reading the file +------------------------------------------------------------------------- */ + +double RegMesh::surf_numeric(const char *file, const char *word) +{ + if (!utils::is_double(word)) + surf_error(file,"Expected floating point value in surf file"); + return atof(word); +} + +/* ---------------------------------------------------------------------- */ + +int RegMesh::surf_inumeric(const char *file, const char *word) +{ + if (!utils::is_integer(word)) + surf_error(file,"Expected integer value in surf file"); + return atoi(word); +} + /* ---------------------------------------------------------------------- inside = 1 if x,y,z is inside the closed surface, else 0 - cast a ray in +x from the point and count the triangles it crosses + cast a ray in +x from the point and count the elements it crosses an odd count means the point is inside + a 2d mesh is a closed curve in the x-y plane, so the ray is cast in that + plane and the z coord of the point does not matter ------------------------------------------------------------------------- */ int RegMesh::inside(double *x) { if (x[0] < bblo[0] || x[0] > bbhi[0] || - x[1] < bblo[1] || x[1] > bbhi[1] || - x[2] < bblo[2] || x[2] > bbhi[2]) return 0; + x[1] < bblo[1] || x[1] > bbhi[1]) return 0; + if (meshdim == 3 && (x[2] < bblo[2] || x[2] > bbhi[2])) return 0; - // a ray that grazes an edge or vertex cannot be counted reliably - // move it a little in the y-z plane and cast it again + // a ray that grazes an edge or a vertex cannot be counted reliably + // move it a little across the ray direction and cast it again double dy = 0.0; double dz = 0.0; @@ -171,10 +453,15 @@ int RegMesh::inside(double *x) int ncross = crossings(x[0],x[1]+dy,x[2]+dz); if (ncross >= 0) return ncross & 1; - double angle = GOLDEN_ANGLE * (attempt+1); double offset = jitter * (attempt+1); - dy = offset * cos(angle); - dz = offset * sin(angle); + if (meshdim == 2) { + dy = (attempt & 1) ? -offset : offset; + dz = 0.0; + } else { + double angle = GOLDEN_ANGLE * (attempt+1); + dy = offset * cos(angle); + dz = offset * sin(angle); + } } // every ray was degenerate, so the point is on the surface itself @@ -184,8 +471,8 @@ int RegMesh::inside(double *x) } /* ---------------------------------------------------------------------- - count the triangles crossed by a +x ray from the point px,py,pz - return -1 if the ray grazes an edge or vertex, so the count is unreliable + count the elements crossed by a +x ray from the point px,py,pz + return -1 if the ray grazes an edge or a vertex, so the count is unreliable ------------------------------------------------------------------------- */ int RegMesh::crossings(double px, double py, double pz) @@ -196,19 +483,72 @@ int RegMesh::crossings(double px, double py, double pz) iz = MAX(0,MIN(iz,nbinz-1)); int ibin = iz*nbiny + iy; + if (meshdim == 2) + return crossings_line(px,py,binfirst[ibin],binfirst[ibin+1]); + return crossings_tri(px,py,pz,binfirst[ibin],binfirst[ibin+1]); +} + +/* ---------------------------------------------------------------------- + crossings of the line segments in one bin, for a 2d mesh +------------------------------------------------------------------------- */ + +int RegMesh::crossings_line(double px, double py, int mlo, int mhi) +{ + int ncross = 0; + + for (int m = mlo; m < mhi; m++) { + int ielem = binlist[m]; + double *a = verts[2*ielem]; + double *b = verts[2*ielem+1]; + + // how far the point is from each end of the segment, measured along y + // the 2 are the same sign only if the segment spans the ray + + double d0 = py - a[1]; + double d1 = b[1] - py; + + // d0+d1 is the extent of the segment in y + // comparing it to the length of the segment tests how parallel the + // segment is to the ray, independent of how long the segment is + + double dy = d0 + d1; + double epsproj = EPSILON * esize[ielem]; + if (fabs(dy) <= epsproj) continue; + + if (fabs(d0) <= epsproj || fabs(d1) <= epsproj) return -1; + if ((d0 > 0.0) != (d1 > 0.0)) continue; + + // x where the ray crosses the segment, from the interpolation weights + + double xhit = (d1*a[0] + d0*b[0]) / dy; + if (fabs(xhit-px) < epslen) return -1; // point is on the segment + if (xhit > px) ncross++; + } + + return ncross; +} + +/* ---------------------------------------------------------------------- + crossings of the triangles in one bin, for a 3d mesh +------------------------------------------------------------------------- */ + +int RegMesh::crossings_tri(double px, double py, double pz, int mlo, int mhi) +{ int ncross = 0; - for (int m = binfirst[ibin]; m < binfirst[ibin+1]; m++) { - int itri = binlist[m]; - double *t = tris[itri]; + for (int m = mlo; m < mhi; m++) { + int ielem = binlist[m]; + double *a = verts[3*ielem]; + double *b = verts[3*ielem+1]; + double *c = verts[3*ielem+2]; // 2x the signed areas of the 3 triangles that the point makes with each // edge, in the y-z plane the ray projects onto // the point projects inside the triangle if all 3 have the same sign - double d0 = (t[4]-t[1])*(pz-t[2]) - (t[5]-t[2])*(py-t[1]); - double d1 = (t[7]-t[4])*(pz-t[5]) - (t[8]-t[5])*(py-t[4]); - double d2 = (t[1]-t[7])*(pz-t[8]) - (t[2]-t[8])*(py-t[7]); + double d0 = (b[1]-a[1])*(pz-a[2]) - (b[2]-a[2])*(py-a[1]); + double d1 = (c[1]-b[1])*(pz-b[2]) - (c[2]-b[2])*(py-b[1]); + double d2 = (a[1]-c[1])*(pz-c[2]) - (a[2]-c[2])*(py-c[1]); // d0+d1+d2 is 2x the signed area of the projected triangle, // which is also the x component of the triangle normal @@ -216,20 +556,20 @@ int RegMesh::crossings(double px, double py, double pz) // triangle is to the ray, independent of how big the triangle is double area2 = d0 + d1 + d2; - double epsarea = EPSILON * tnorm[itri]; - if (fabs(area2) <= epsarea) continue; + double epsproj = EPSILON * esize[ielem]; + if (fabs(area2) <= epsproj) continue; int nneg = 0; int npos = 0; int nzero = 0; - if (d0 > epsarea) npos++; - else if (d0 < -epsarea) nneg++; + if (d0 > epsproj) npos++; + else if (d0 < -epsproj) nneg++; else nzero++; - if (d1 > epsarea) npos++; - else if (d1 < -epsarea) nneg++; + if (d1 > epsproj) npos++; + else if (d1 < -epsproj) nneg++; else nzero++; - if (d2 > epsarea) npos++; - else if (d2 < -epsarea) nneg++; + if (d2 > epsproj) npos++; + else if (d2 < -epsproj) nneg++; else nzero++; if (npos && nneg) continue; // ray misses the triangle @@ -237,7 +577,7 @@ int RegMesh::crossings(double px, double py, double pz) // x where the ray pierces the triangle, from the barycentric coords - double xhit = (d1*t[0] + d2*t[3] + d0*t[6]) / area2; + double xhit = (d1*a[0] + d2*b[0] + d0*c[0]) / area2; if (fabs(xhit-px) < epslen) return -1; // point is on the triangle if (xhit > px) ncross++; } @@ -253,16 +593,15 @@ void RegMesh::setup() { // bounding box around all the vertices - bblo[0] = bbhi[0] = tris[0][0]; - bblo[1] = bbhi[1] = tris[0][1]; - bblo[2] = bbhi[2] = tris[0][2]; + bblo[0] = bbhi[0] = verts[0][0]; + bblo[1] = bbhi[1] = verts[0][1]; + bblo[2] = bbhi[2] = verts[0][2]; - for (int i = 0; i < ntri; i++) - for (int j = 0; j < 3; j++) - for (int k = 0; k < 3; k++) { - bblo[k] = MIN(bblo[k],tris[i][3*j+k]); - bbhi[k] = MAX(bbhi[k],tris[i][3*j+k]); - } + for (int i = 0; i < nvert; i++) + for (int k = 0; k < 3; k++) { + bblo[k] = MIN(bblo[k],verts[i][k]); + bbhi[k] = MAX(bbhi[k],verts[i][k]); + } // tolerances scale with the size of the mesh // the jitter is much larger than the tolerance it has to escape @@ -273,53 +612,78 @@ void RegMesh::setup() epslen = EPSILON * meshlen; jitter = JITTER * meshlen; - // normal of each triangle, its length is 2x the area - // enclosed volume from the divergence theorem + // size of each element = 2x area of a triangle, length of a line + // enclosed volume from the divergence theorem, area from the shoelace sum // the area-weighted normals of a closed surface sum to zero - memory->create(tnorm,ntri,"region/mesh:tnorm"); + memory->create(esize,nelem,"region/mesh:esize"); double nsum[3] = {0.0,0.0,0.0}; - double asum = 0.0; - double vsum = 0.0; - - for (int i = 0; i < ntri; i++) { - double *a = &tris[i][0]; - double *b = &tris[i][3]; - double *c = &tris[i][6]; - double e1[3],e2[3],n[3]; - - MathExtra::sub3(b,a,e1); - MathExtra::sub3(c,a,e2); - MathExtra::cross3(e1,e2,n); - tnorm[i] = MathExtra::len3(n); - - asum += tnorm[i]; - nsum[0] += n[0]; - nsum[1] += n[1]; - nsum[2] += n[2]; - - MathExtra::cross3(b,c,e1); - vsum += MathExtra::dot3(a,e1); - } + double ssum = 0.0; + double gsum = 0.0; + + if (meshdim == 3) { + for (int i = 0; i < nelem; i++) { + double *a = verts[3*i]; + double *b = verts[3*i+1]; + double *c = verts[3*i+2]; + double e1[3],e2[3],n[3]; + + MathExtra::sub3(b,a,e1); + MathExtra::sub3(c,a,e2); + MathExtra::cross3(e1,e2,n); + esize[i] = MathExtra::len3(n); + + ssum += esize[i]; + nsum[0] += n[0]; + nsum[1] += n[1]; + nsum[2] += n[2]; + + MathExtra::cross3(b,c,e1); + gsum += MathExtra::dot3(a,e1); + } + enclosed = fabs(gsum) / 6.0; + + } else { + for (int i = 0; i < nelem; i++) { + double *a = verts[2*i]; + double *b = verts[2*i+1]; + + double dx = b[0] - a[0]; + double dy = b[1] - a[1]; + esize[i] = sqrt(dx*dx + dy*dy); - volume = fabs(vsum) / 6.0; + // the normal of a segment has the same length as the segment - if (MathExtra::len3(nsum) > WATERTIGHT * asum) - error->warning(FLERR,"Region mesh surface is not watertight"); + ssum += esize[i]; + nsum[0] += dy; + nsum[1] -= dx; - // bin the triangles by their y-z extent so a +x ray only has to test - // the triangles in one bin - // aim for one triangle per bin, with square bins in the y-z plane + gsum += a[0]*b[1] - b[0]*a[1]; + } + enclosed = fabs(gsum) / 2.0; + } + + if (MathExtra::len3(nsum) > CLOSED * ssum) + error->warning(FLERR,"Region mesh surface is not closed"); + + // bin the elements by their y-z extent so a +x ray only has to test + // the elements in one bin + // in 3d aim for one triangle per bin, with square bins in the y-z plane + // in 2d there is nothing to bin in z double ylen = bbhi[1] - bblo[1]; double zlen = bbhi[2] - bblo[2]; - double binsize = sqrt(ylen*zlen/ntri); - if (binsize == 0.0) binsize = meshlen; - - nbiny = MAX(1,MIN(static_cast (ylen/binsize) + 1,MAXBIN)); - nbinz = MAX(1,MIN(static_cast (zlen/binsize) + 1,MAXBIN)); + if (meshdim == 2) { + nbiny = MAX(1,MIN(nelem,MAXBIN)); + nbinz = 1; + } else { + double binsize = sqrt(ylen*zlen/nelem); + if (binsize == 0.0) binsize = meshlen; + nbiny = MAX(1,MIN(static_cast (ylen/binsize) + 1,MAXBIN)); + nbinz = MAX(1,MIN(static_cast (zlen/binsize) + 1,MAXBIN)); + } invbiny = (ylen > 0.0) ? nbiny/ylen : 0.0; invbinz = (zlen > 0.0) ? nbinz/zlen : 0.0; @@ -328,11 +692,11 @@ void RegMesh::setup() memory->create(binfirst,nbin+1,"region/mesh:binfirst"); for (int i = 0; i <= nbin; i++) binfirst[i] = 0; - // count the triangles in each bin, then turn the counts into offsets + // count the elements in each bin, then turn the counts into offsets int iylo,iyhi,izlo,izhi; - for (int i = 0; i < ntri; i++) { + for (int i = 0; i < nelem; i++) { bin_range(i,iylo,iyhi,izlo,izhi); for (int iz = izlo; iz <= izhi; iz++) for (int iy = iylo; iy <= iyhi; iy++) @@ -347,7 +711,7 @@ void RegMesh::setup() memory->create(next,nbin,"region/mesh:next"); for (int i = 0; i < nbin; i++) next[i] = binfirst[i]; - for (int i = 0; i < ntri; i++) { + for (int i = 0; i < nelem; i++) { bin_range(i,iylo,iyhi,izlo,izhi); for (int iz = izlo; iz <= izhi; iz++) for (int iy = iylo; iy <= iyhi; iy++) @@ -358,17 +722,23 @@ void RegMesh::setup() } /* ---------------------------------------------------------------------- - range of bins that triangle I overlaps in the y-z plane + range of bins that element I overlaps in the y-z plane ------------------------------------------------------------------------- */ void RegMesh::bin_range(int i, int &iylo, int &iyhi, int &izlo, int &izhi) { - double *t = tris[i]; + int first = meshdim*i; - double ylo = MIN(t[1],MIN(t[4],t[7])); - double yhi = MAX(t[1],MAX(t[4],t[7])); - double zlo = MIN(t[2],MIN(t[5],t[8])); - double zhi = MAX(t[2],MAX(t[5],t[8])); + double ylo,yhi,zlo,zhi; + ylo = yhi = verts[first][1]; + zlo = zhi = verts[first][2]; + + for (int j = 1; j < meshdim; j++) { + ylo = MIN(ylo,verts[first+j][1]); + yhi = MAX(yhi,verts[first+j][1]); + zlo = MIN(zlo,verts[first+j][2]); + zhi = MAX(zhi,verts[first+j][2]); + } iylo = MAX(0,MIN(static_cast ((ylo-bblo[1])*invbiny),nbiny-1)); iyhi = MAX(0,MIN(static_cast ((yhi-bblo[1])*invbiny),nbiny-1)); @@ -382,12 +752,11 @@ void RegMesh::bin_range(int i, int &iylo, int &iyhi, int &izlo, int &izhi) void RegMesh::translate(double dx, double dy, double dz) { - for (int i = 0; i < ntri; i++) - for (int j = 0; j < 3; j++) { - tris[i][3*j+0] += dx; - tris[i][3*j+1] += dy; - tris[i][3*j+2] += dz; - } + for (int i = 0; i < nvert; i++) { + verts[i][0] += dx; + verts[i][1] += dy; + verts[i][2] += dz; + } } /* ---------------------------------------------------------------------- @@ -396,12 +765,11 @@ void RegMesh::translate(double dx, double dy, double dz) void RegMesh::scale(double sx, double sy, double sz) { - for (int i = 0; i < ntri; i++) - for (int j = 0; j < 3; j++) { - tris[i][3*j+0] = sx*(tris[i][3*j+0]-origin[0]) + origin[0]; - tris[i][3*j+1] = sy*(tris[i][3*j+1]-origin[1]) + origin[1]; - tris[i][3*j+2] = sz*(tris[i][3*j+2]-origin[2]) + origin[2]; - } + for (int i = 0; i < nvert; i++) { + verts[i][0] = sx*(verts[i][0]-origin[0]) + origin[0]; + verts[i][1] = sy*(verts[i][1]-origin[1]) + origin[1]; + verts[i][2] = sz*(verts[i][2]-origin[2]) + origin[2]; + } } /* ---------------------------------------------------------------------- @@ -420,14 +788,13 @@ void RegMesh::rotate(double theta, double rx, double ry, double rz) MathExtra::axisangle_to_quat(r,theta,q); MathExtra::quat_to_mat(q,rotmat); - for (int i = 0; i < ntri; i++) - for (int j = 0; j < 3; j++) { - d[0] = tris[i][3*j+0] - origin[0]; - d[1] = tris[i][3*j+1] - origin[1]; - d[2] = tris[i][3*j+2] - origin[2]; - MathExtra::matvec(rotmat,d,dnew); - tris[i][3*j+0] = dnew[0] + origin[0]; - tris[i][3*j+1] = dnew[1] + origin[1]; - tris[i][3*j+2] = dnew[2] + origin[2]; - } + for (int i = 0; i < nvert; i++) { + d[0] = verts[i][0] - origin[0]; + d[1] = verts[i][1] - origin[1]; + d[2] = verts[i][2] - origin[2]; + MathExtra::matvec(rotmat,d,dnew); + verts[i][0] = dnew[0] + origin[0]; + verts[i][1] = dnew[1] + origin[1]; + verts[i][2] = dnew[2] + origin[2]; + } } diff --git a/src/region_mesh.h b/src/region_mesh.h index 918867a96..a261ff33e 100644 --- a/src/region_mesh.h +++ b/src/region_mesh.h @@ -21,6 +21,7 @@ RegionStyle(mesh,RegMesh) #ifndef SPARTA_REGION_MESH_H #define SPARTA_REGION_MESH_H +#include "stdio.h" #include "region.h" namespace SPARTA_NS { @@ -32,27 +33,39 @@ class RegMesh : public Region { int inside(double *); protected: - int ntri; // # of triangles in the mesh - double **tris; // ntri x 9 = the 3 vertices of each triangle - double *tnorm; // length of the normal = 2x area of each triangle + int meshdim; // 2 if the mesh is lines, 3 if it is triangles + int nelem; // # of lines or triangles in the mesh + int nvert; // # of vertices = meshdim per element + double **verts; // nvert x 3 coords, meshdim in a row per element + double *esize; // 2x area of each tri, or length of each line double origin[3]; // reference point for scale and rotate double bblo[3],bbhi[3]; // bounding box around the mesh - double volume; // volume enclosed by the mesh + double enclosed; // volume enclosed by the mesh, area if 2d int nbiny,nbinz; // # of ray-casting bins in y and z double invbiny,invbinz; // inverse bin sizes - int *binfirst; // index in binlist of 1st tri in each bin, len nbin+1 - int *binlist; // triangle indices, grouped by bin + int *binfirst; // index in binlist of 1st elem in each bin, nbin+1 + int *binlist; // element indices, grouped by bin - double epslen; // tolerance for a point lying on a triangle + double epslen; // tolerance for a point lying on the surface double jitter; // ray offset used to dodge a degenerate hit + void read_mesh(char *); + void read_stl_file(char *); + void read_surf_file(char *); + void bcast_verts(); + void surf_error(const char *, const char *); + double surf_numeric(const char *, const char *); + int surf_inumeric(const char *, const char *); + void translate(double, double, double); void scale(double, double, double); void rotate(double, double, double, double); void setup(); void bin_range(int, int &, int &, int &, int &); int crossings(double, double, double); + int crossings_line(double, double, int, int); + int crossings_tri(double, double, double, int, int); }; } @@ -68,15 +81,52 @@ Self-explanatory. Check the input script syntax and compare to the documentation for the command. You can use -echo screen as a command-line option when running SPARTA to see the offending line. +E: Cannot open mesh file: %s + +Self-explanatory. + +E: Surf file does not contain lines or triangles: %s + +The header of the file has neither a lines nor a triangles keyword, so +there is no surface to define the region with. + +E: Surf file contains both lines and triangles: %s + +A surf file defines either a 2d or a 3d surface, not both. + +E: Incorrect point format in surf file: %s + +A line of the Points section does not have 3 or 4 values on it. + +E: Incorrect line format in surf file: %s +E: Incorrect triangle format in surf file: %s + +A line of the Lines or Triangles section does not have a valid number of +values on it. Per-surface custom attributes are not supported by the +region mesh style. + +E: Invalid point index in surf file: %s + +A line or triangle refers to a point that is not in the Points section. + +E: Expected floating point value in surf file: %s +E: Expected integer value in surf file: %s + +A value in the file could not be converted to a number. + +E: Unexpected end of surf file: %s + +The file is truncated. + E: Region mesh has zero extent -All the triangles in the STL file are coincident, so the mesh encloses -no volume. +All the vertices in the file are coincident, so the mesh encloses +nothing. -W: Region mesh surface is not watertight +W: Region mesh surface is not closed -The area-weighted normals of the triangles in the STL file do not sum -to zero, which means the surface has holes in it. A point-in-mesh test -on a surface with holes gives arbitrary answers. +The area-weighted normals of the surface elements in the file do not sum +to zero, which means the surface has gaps in it. A point-in-mesh test +on a surface with gaps gives arbitrary answers. */ diff --git a/src/stl_reader.cpp b/src/stl_reader.cpp index 96729b7b4..0f1ab9f89 100644 --- a/src/stl_reader.cpp +++ b/src/stl_reader.cpp @@ -177,6 +177,44 @@ int STLReader::read_file(const char *filename, double **&caller_tris) return ntris; } +/* ---------------------------------------------------------------------- + 1 if the file is in STL format, else 0 + a binary STL file has a size of exactly 84 + 50*N bytes for N triangles + an ASCII STL file starts with the solid keyword + throws an STLReaderException if the file cannot be opened +------------------------------------------------------------------------- */ + +bool STLReader::is_stl_file(const std::string &filename) +{ + FILE *fp = fopen(filename.c_str(),"rb"); + if (!fp) throw STLReaderException("Cannot open mesh file " + filename); + + bool is_stl = false; + + if (fseek(fp,0,SEEK_END) == 0) { + long filesize = ftell(fp); + if (filesize >= STL_BIN_HEADER) { + uint32_t ntri_claim = 0; + if (fseek(fp,80,SEEK_SET) == 0 && + fread(&ntri_claim,sizeof(ntri_claim),1,fp) == 1) { + bigint expected = (bigint) STL_BIN_HEADER + + (bigint) ntri_claim * STL_BIN_PER_TRI; + if (expected == (bigint) filesize) is_stl = true; + } + } + } + + if (!is_stl) { + rewind(fp); + char buf[MAXLINE]; + char *line = next_line(fp,buf); + if (line && utils::strmatch(line,"^ *solid")) is_stl = true; + } + + fclose(fp); + return is_stl; +} + /* ---------------------------------------------------------------------- read and parse an STL file and return its triangles independent of any SPARTA instance and does not communicate diff --git a/src/stl_reader.h b/src/stl_reader.h index da8f6b5b9..ca007eca0 100644 --- a/src/stl_reader.h +++ b/src/stl_reader.h @@ -76,6 +76,11 @@ class STLReader : protected Pointers { static std::vector parse(const std::string &, std::string * = NULL); + // not part of the LAMMPS class, for callers that accept more than one + // mesh format and have to decide which reader to use + + static bool is_stl_file(const std::string &); + private: static std::vector parse_text(const std::string &, std::string &); static std::vector parse_binary(FILE *, std::string &); From de90351de6459a6aa1690c6a3849ef21367f432a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 27 Aug 2026 16:10:38 +0000 Subject: [PATCH 3/3] doc: cover the union and intersect cases master's new kk styles introduce master added region union/kk and intersect/kk, so note in Restrictions that a mesh region nested inside a union or intersect behaves the same way a bare one does: create_particles/kk tests it on the host and works, while a device consumer such as fix emit/face errors on it either way. Co-Authored-By: Stan Moore Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01ACVHM1qF2xS53wZi4kirAH --- doc/region.html | 8 +++++--- doc/region.txt | 11 +++++++---- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/doc/region.html b/doc/region.html index dd134660b..466f5dcc3 100644 --- a/doc/region.html +++ b/doc/region.html @@ -200,10 +200,12 @@

region command

There is no mesh/kk style. The create_particles command tests points against a region on the host, so a mesh region can be used with -create_particles/kk. Commands that test regions on the device, such as -fix emit/face and fix ave/histo, +create_particles/kk, either on its own or as one of the regions listed +by a union or intersect region. Commands that test regions on the +device, such as fix emit/face and fix ave/histo, will generate an error if they are given a mesh region when running -with the KOKKOS package. +with the KOKKOS package, whether it is given to them directly or nested +inside a union or intersect region.

Related commands:

diff --git a/doc/region.txt b/doc/region.txt index debe50335..e4b40014b 100644 --- a/doc/region.txt +++ b/doc/region.txt @@ -191,10 +191,13 @@ since as noted above regions in SPARTA are 3d objects in either case. There is no {mesh/kk} style. The "create_particles"_create_particles.html command tests points against a region on the host, so a {mesh} region can be used with -create_particles/kk. Commands that test regions on the device, such as -"fix emit/face"_fix_emit_face.html and "fix ave/histo"_fix_ave_histo.html, -will generate an error if they are given a {mesh} region when running -with the KOKKOS package. +create_particles/kk, either on its own or as one of the regions listed +by a {union} or {intersect} region. Commands that test regions on the +device, such as "fix emit/face"_fix_emit_face.html and "fix +ave/histo"_fix_ave_histo.html, will generate an error if they are given +a {mesh} region when running with the KOKKOS package, whether it is +given to them directly or nested inside a {union} or {intersect} +region. [Related commands:]