SIMION®
The field and particle trajectory simulator
Industry standard charged particle optics software
Refine your design: model > simulate > view > analyze > program > optimize it.
SIMION v8.1.3.0-TEST posted.
About | Documentation | Community/Support | Downloads | Ordering

GEM Geometry File

A SIMION geometry file (GEM) defines electrode geometries using constructive solid geometry (CSG) primitives. CSG operations define shapes using unions and intersections of other basic shapes (e.g. a rectangular slab with a cylinder hole cut through it), a bit similar in concept to machining. GEM files are text files and have the file name extension of ”.GEM”. SIMION can convert a GEM file to a PA file.

The GEM file appendix in the printed manual is perhaps the first place to start. See also SIMION Example: geometry for examples. “courses\advanced” also has some notes on GEM files. Additional tips are given on this page.

Extensions in 8.2

SIMION 8.2 Early Access Mode supports the following GEM syntax extensions. They are detailed below, but here are a few highlights:

  • New commands to define shapes easier or with fewer rotations:

    • Use cylinder3d (rather than cylinder) to easily define a cylinder between two endpoints in any orientation without bothering with locate rotations. cylinder3d(5,5,0, 5,5,10, 3)
    • Use extrude_xy, extrude_yz and extrude_zx when you want to extrude a 2D image drawn in any plane, not just XY. For example, polyline normally takes an XY plane drawing, which can then be wrapped in a rotation (locate) to another plane, but the rotation can be tricky. These commands make it more clear: extrude_yz() { polyline(0,0, 5,10, 10,0) } ; y,z points . Even XY extrusions can be specified more simply; for example to extrude from z=3 to z=5: extrude_xy(3,5) { polyline(0,0, 5,10, 10,0) } ; x,y points .
    • Use revolve_xy, revolve_yz, revolve_zx rather than rotate_fill in general. These directly define volumes of rotation in any plane without wrapping in an additional rotation. This command also defines a shape, so it can be used wherever shapes can be used, such as intersections, which is not possible with rotate_fill (except via the workaround of cutting away from a rotate_fill using a subsequent n(0) { } but that may cut more than the current shape).
    • Use half_volume rather than box3d to define a volume below an infinite plane defined by a point and normal vector. No need for rotations. half_volume(5,0,0, 1,1,1). This can be simpler than box3d for some cases.
    • Use rotate_x, rotate_y, rotate_z to rotate rather than the locate commands. These are more clear. They rotate counter-clockwise (CCW) looking down the positive end of the given axis. Many times you can omit rotate_x|y|z as well when the commands above are used.
  • Use shape to define you own custom shapes from analytical equations. Example: e(1) { shape(function(x,y,z) return x^2 + y^2 > z end) . Complex surfaces can be defined directly in the GEM file. No need for doing this outside of a GEM file with the simion.pas pa:fill() API.

  • Simpler syntax:

    • Omit fill and within when not necessary, for shorter GEM code. Use e(1) { circle(0,0, 50) } rather than e(1) { fill { within { circle(0,0, 50) } } } .

    • Use intersect rather than within. These are synonyms, but the name intersect makes the meaning more clear. e(1) { intersect { box(0,0, 10,10) circle(0,0, 10) } } .

    • Use named parameters on pa_define . pa_define {101, 101, dx=0.1, surface='fractional'} .

    • Define PA sizes in mm units rather than grid points. The above example can be rewritten as pa_define {wx=10, xy=10, dx=0.1, surface='fractional'} . It defines a 10 * 10 mm^2 region.

    • Use improved ways to pass variables to/from include GEM files (see examples below).

    • Use inf to represent infinity rather than a large number (e.g. 1e6). box(-inf,0, inf, 2). This is more clear.

    • Omit $(...) and # around Lua code. They are no longer necessary:

      local w = 2
      e(1) {
        for i=1,3 do
          box(i*10,0, i*10+w, 10)
        end
      }
      
  • Upcomming features:

    • Include STL files in GEM file. stl("lens.gem").

Improved preprocessor

  • Variables and code used in GEM commands need not be escaped anymore:

    local len = 10
    local nx = len+1
    local ny = nx
    pa_define(nx,ny,1, "planar")
    e(1) { fill { within { circle(len/2, len/2, len/2) }}}
    
    ; rather than
    # local len = 10
    # local nx = len+1
    # local ny = nx
    pa_define($(nx),$(ny),1, "planar")
    e(1) { fill { within { circle($(len/2), $(len/2), $(len/2)) }}}
    

    The # and $(...) syntax needn’t be used anymore for most purposes.

  • String parameters in pa_define() and include() now can be written with quotes:

    pa_define(101,101,101,"planar","xyz", "electrostatic",, 0.05,,,"gu", "fractional") ; new
    pa_define(101,101,101,planar,xyz, electrostatic,, 0.05,,,gu,fractional)  ; old style
    

    Quotes actually are required when using the new {...} syntax:

    pa_define{101,101,101,"planar","xyz", "electrostatic",, 0.05,,,"gu", "fractional"}
    
  • Support Lua style ‘–’ comments in addition to ‘;’:

    ; a comment
    -- a comment
    
  • No maximum line length.

  • Caveats: Some semantics of preprocessing have changed because it is now part of the language rather than as a separate preprocessing step that operatings on strings. If the old $(...) syntax is used, it is now more strict and may break some existing code in obscure cases.

    This used to be valid but is no longer valid because the if and { are not properly nested:

    #if x then
    locate(10) {
    #end
    box(0,0,10,10)
    #if x then
    }
    #end
    

    Do this instead:

    function f()
      box(0,0,10,10)
    end
    if x then
      locate(10) { f() }
    else
      f()
    end
    

    Keep in mind $(...) can now only be used in the context of a command or parameter, and $(x) is usually equivalent to x except when x is a string, where it is interpreted as GEM code to be compiled. So $("box(0,0,10,10)") is equivalent to box(0,0,10,10) . One obscure behavior:

    local function f() return "" end
    local function g() return "2,3" end
    box3d($(f()), $(g()), $(g()))
    

    will be interpreted as box3d(2, 3, 2, 3).

omit fill/within commands

The fill and within statements can now be omitted, to give simpler syntax.

The following examples are equivalent (original followed by simpler syntax):

e(1) { circle(0,0, 10) }  ; new
e(1) { fill { within { circle(0,0, 10) } } }  ; old

e(1) { box(0,0, 10,10) notin { box(2,2, 8, 8) } }  ; new
e(1) { fill { within { box(0,0, 10,10) } notin { box(2,2, 8, 8) } } }  ; old

e(1) { within { box(0,0, 10,10) circle(0,0, 10) } }  ; new (AND)
e(1) { fill { within { box(0,0, 10,10) circle(0,0, 10) } } } ; old

e(1) { box(0,0, 10,10) circle(0,0, 10) }  ; new (OR)
e(1) { fill { within { box(0,0, 10,10) } within { circle(0,0, 10) } } } ; old

e(1) { box(0,0, 10,10) notin{box(1,1, 9, 9)} box(3,3, 7,7) notin{box(4,4, 6,6)} } ; new
e(1) { fill { within { box(0,0, 10,10) } notin { box(1,1, 9,9) } }
       fill { within { box(3,3, 7,7)   } notin { box(3,3, 7,7) } } }  ; old

However:

  • within is still required if you need to take the intersection (AND) of two shapes rather than union (OR), as shown above.
  • Inside a fill the order of notin‘s and within‘s is not important, but multiple fill‘s are applied in order, and if fill is omitted, then each within that follows a notin creates a new fill. Therefore, order can be important when fill is omitted. This is seen in the example above: e(1) { box(0,0, 10,10) notin{box(1,1, 9, 9)} box(3,3, 7,7) notin{box(4,4, 6,6)} } - the notin‘s only cuts from the immediately prior within‘(s), not the following ones, nor ones before previous notin‘s.

Also intersect is now a synonym for within:

e(1) { intersect { box(0,0, 10,10) circle(0,0, 10) } }

Compatibility: Added in 8.2EA-20170324 (Early Access Mode).

pa_define

pa_define can now accept named parameters:

pa_define{nx=nx,ny=ny,nz=nz, gx=gx,gy=gy,gz=gz, wx=wx,wy=wy,wz=wz,
          symmetry=symmetry,mirror=mirror, type=type, ng=ng,
          dx=dx,dy=dy,dz=dz,unit=unit,surface=surface}  ; new syntax

Example:

pa_define{11, 12, 3, symmetry='planar[x]',  dx=1,dy=2,dz=0.5, surface='auto') ; new syntax

pa_define(11, 12, 3, planar, x, e,, 1, 2, 0.5,, auto) ; old syntax

Named parameters require using {...} not (...), and string values must be quoted (single or double quotation marks, like surface='auto' not surface=auto).

When using the {...} syntax, there are a few differences:

  • values are checked more strictly:
    • symmetry must be ‘planar’ (or ‘p’) or ‘cylindrical’ (or ‘c’). Other abbreviations or capitalization like ‘Plan’ are not allowed.
    • mirror must be ‘’ (none), ‘x’, ‘y’, ‘z’, ‘xy’, ‘xz’, ‘yz’, or ‘xyz’. This field can be combined with symmetry, like symmetry='planar[xy]' rather than symmetry='planar', mirror='xy'.
    • type must be ‘electric’ (or ‘e’) or ‘magnetic’ (or ‘m’).
  • default values differ:
    • symmetry defaults to 'planar' (not 'cylindrical').
    • mirror defaults to ‘’none’` (not “y”). So, pa_define(11,11,1,p,n) can now be written pa_define{11,11,1}, and pa_define(11,11,1) can now be written pa_define{11,11,1,'c'}.

When using the older (...) syntax, the following enhancements were also made:

  • symmetry now defaults to planar when nz > 1, rather than raising an error, so you can now do pa_define(11,11,11), and mirroring will be none in that case.

The following parameters were added in 8.2EA:

  • gx, gy, gz - number of grid units. These are one less than the number of grid points. These are requivalent:

    pa_define{gx=10, gy=20, gz=0} ; new style
    pa_define(11,21,1,p,n)        ; old style
    
  • wx, wy, wz - distance in mm. These are equivalent:

    pa_define{wx=5, wy=10, dx=0.1}   ; new style
    pa_define(51,101,1, p,n,e,, 0.1) ; old style
    

    If the distance converted to grid units is a fractional amount, the distance is rounded up to the nearest grid unit.

The following parameters were adddded in 8.1:

  • dx, dy, dz - Grid cell sizes in mm in x, y, and z directions. If dx omitted, it defaults to 1. If dy omitted, it defaults to dx. If dz omitted, it detaults to dy.
  • unit - 'gu' or nil (default) If 'gu', then coordinates in the GEM file are in grid units rather than mm. Normally, coordinates are in mm. However, if dx, dy, dz are all 1, then mm and gu are the same.
  • surface - nil (default - old SIMION 7.0/8.0), 'auto' (automatic) or 'fractional' (automatic with surface enhancement). See surface=auto option in pa_define [8.1.1.25].

Compatibility: Changes added in 8.2EA-20170324.

include

You can now pass additional parameters via the include() statement, which are accessible via the expression ... in the included GEM file:

; 1.gem
pa_define(11,11,1, p)
include("2.gem", 5,6)

; 2.gem
local a, b = ...
box(a, b, a+5, b+5)

You can now also use global variables defined in a GEM file in an included GEM file:

; 1.gem
pa_define(11,11,1, p)
# x = 5
e(1) { include("2.gem") }

; 2.gem
circle(x,x, 5)

Note: for this to work you must do x = 5 not local x = 5 (the latter defined a local variable only within the scope of the current file).

You can also define variables (as well as functions in the included GEM and for use in the main GEM file:

; myshapes.gem
function xplane(x0)
  return function(x,y,z) return x == x0 end
end
function myobject(r)
e(2) { circle(5,5, r) }
end

; 1.gem
pa_define(11,11,1, p)
include("myshapes.gem")
e(1) { xplane(3) }
myobject(5)

Compatibility: Added in 8.2EA-20170324 (Early Access Mode).

shape

gem.shape()
shape(f)

Defines a custom GEM shape from a Lua function. The given function must return another function that given the coordinates (x,y,z) returns true or false whether that points is inside the shape.

Example:

-- defining a paraboloid
e(1) {
  shape(function(x,y,z) return x^2 + y^2 > z end)
}

It’s convenient to wrap these in a named function for reuse:

local function paraboloid(x0,y0,z0, a, b)
  shape(function(x,y,z) return ((x-x0)/a)^2 + ((y-y0)/b)^2 > (z-z0) end)
end
e(1) { paraboloid(0,0,0, 1, 1) }

Other examples:

pa_define(101,101,1, p, surface=fractional)

local function yplane(y0)
  shape(function(x,y,z) return y==y0 end)
end
local function rbox(x0,y0, r)
  shape(function(x,y,z)
    return (x-x0)^4 + (y-y0)^4 <= r^3
  end)
end

e(1) {
  yplane(50)
}
e(0) {
  rbox(50,50, 40)
  notin { circle(50,50, 10) }
}

Compatibility: Added in 8.2EA-20170324 (Early Access Mode).

intersect

gem.intersect()
intersect { ... }
_images/gem_intersect.png

This is now a synonym of within but provided for improved readability. intersect (or within) defines a volume that is inside the all the listed shapes (i.e. their “intersected” or boolean “and” volume).

Example:

e(1) {
  intersect {
    circle(0,0, 10)
    polyline(0,0, 20,0, 20,5)
  }
  notin { circle(0,0, 5) }
}

Compatibility: Added in 8.2EA-20170324 (Early Access Mode).

revolve_xy

gem.revolve_xy()
revolve_xy(angle) { ... }
_images/gem_revolve.png

Forms a volume of revolution with the given angle (defaults to 360 degrees) of the XY plane around the X-axis (CCW facing down positive X axis). This is similar to rotate_fill but can be used in shape context. The contents inside the brackets can be within’s and notin’s.

Example:

pa_define(21,21,21, p, surface=fractional)
e(1) {
  fill {
    within { revolve_xy() { within { box(0,1, 7,8) } } }
    notin { points(5,5) }
}}

The above is the same as this:

pa_define(21,21,21, p, surface=fractional)
e(1) {
  rotate_fill(360) { within { box(0,1, 7,8) } }
}
n(0) {
  fill { within { points(5,5) } }
}

Furthermore, within can be omitted (see omit fill/within commands), so these two are equivalent (standard and simpler syntax):

revolve_xy() { within { box(0,1, 7,8) } }
revolve_xy() { box(0,1, 7,8) }

The revolve_xy can be more natural and powerful than rotate_fill.

Compatibility: Added in 8.2EA-20170324.

TODO:FIX: revolve_xy take two parameters (start and end angles)?

revolve_yz

gem.revolve_yz()
revolve_yz(angle) { ... }

This is similar to revolve_xy but revolves the YZ plane figure around the Y axis (CCW facing down positive Y axis).

It is equivalent to rotate_y(90) { rotate_z(90) { revolve_xy(angle) { ... } } }.

Compatibility: Added in 8.2EA-20170324.

revolve_zx

gem.revolve_zx()
revolve_zx(angle) { ... }

This is similar to revolve_xy but revolves the ZX plane figure around the Z axis (CCW facing down positive Z axis).

It is equivalent to rotate_z(-90) { rotate_y(-90) { revolve_xy(angle) { ... } } }.

Compatibility: Added in 8.2EA-20170324.

extrude_xy

gem.extrude_xy()
extrude_xy(z1, z2) { ... }
_images/gem_extrude.png

Extrudes the figure drawn in the XY plane in the Z direction from Z=z1 to Z=z2.

Example:

e(1) {
  extrude_xy(2, 8) {
    polyline(1,1, 10,19, 19,1)
    notin { circle(10,8, 2) }
  }
}

The above is equivalent to:

e(1) {
  within { polyline(1,1, 10,19, 19,1) box3d(-100,-100,2, 100,100,8) }
  notin  { circle(10,8, 2) }
}

The extrude_xy can provide a cleaner approach and can also be intersected with other shapes like within { extrude() { ... } sphere(...) }.

If both z1 and z2 are omitted, then the extrusion is infinite in the +-Z directions. If only one is omitted, then the omitted one is assumed to be 0. The order of z1 and z2 (swapped) doesn’t matter.

Example:

e(1) {
  extrude_xy() {  ; infinite extrusion in Z
    polyline(1,1, 10,19, 19,1)
    notin { circle(10,8, 2) }
  }
}

Note that e(1) { fill { within { extrude_xy(z2,z2) { fill { within { box() } } } } } } can be written more simply as e(1) { extrude_xy(z2,z2) { box() } }.

Compatibility: Added in 8.2EA-20170324.

extrude_yz

gem.extrude_yz()
extrude_yz(x1, x2) { ... }

This is similar to extrude_xy but extrudes a YZ plane figure in the X direction.

It is equivalent to rotate_y(0) { rotate_z(90) { extrude_xy(x1, x2) { ... } } }.

Compatibility: Added in 8.2EA-20170324.

extrude_zx

gem.extrude_zx()
extrude_zx(y1, y2) { ... }

This is similar to extrude_xy but extrudes a ZX plane figure in the Y direction.

It is equivalent to rotate_z(-90) { rotate_y(-90) { extrude_xy(y1, y2) { ... } } }.

Compatibility: Added in 8.2EA-20170324.

scale

gem.scale()
scale(sx) { ... }

scale(sx, sy, sz) { ... }

Scales in the x, y, and z directions by the given factors. If sy or sz is omitted it defaults to sx.

This is identical to:

locate(0,0,0, sx, 0,0,0, sx,sy,sz) { ... }

Compatibility: Added in 8.2EA-20170324.

rotate_x

gem.rotate_x()
rotate_x(anglex) { ... }

Rotate counter-clockwise looking down positive x-axis.

This is identical to locate(,,,,,, anglex) (rot rotation).

Compatibility: Added in 8.2EA-20170324.

rotate_y

gem.rotate_y()
rotate_y(angley) { ... }

Rotate counter-clockwise looking down positive y-axis.

This is identical to locate(,,,, angley) (azimuth rotation).

Compatibility: Added in 8.2EA-20170324.

rotate_z

gem.rotate_z()
rotate_z(anglez) { ... }

Rotate counter-clockwise looking down positive z-axis.

This is identical to locate(,,,,, 2) (elevation rotation).

Compatibility: Added in 8.2EA-20170324.

cylinder3d

gem.cylinder3d()
cylinder3d(x1,y1,z1, x2,y2,z2, r) { ... }
_images/gem_cylinder.png

Defines a cylinder volume of radius r whose circular bases have centers (x1,y1,z1) and (x2,y2,z2).

This is identical to locate(x1,y1,z1,1,?,0,?) { cylinder(0,0,?, r,r,?) } for appropriate values of ? but is more convenient.

Compatibility: Added in 8.2EA-20170324.

half_volume

gem.half_volume()
half_volume(x1,y1,z1, ux,uy,uz)
_images/gem_half_volume.png

Defines the volume below the given plane. The plane is defined by the point (x1,y1,z1) and surface normal vector (ux,uy,uz) on that plane. The fill is in the direction opposite the normal vector. The normal vector length is not important (need not be normalized).

For example, the region x <= 2 is defined by half_volume(2,0,0, 1,0,0). The region x + y <= 4 is defined by half_volume(2,2,0, 1,1,0).

This is useful for cutting another shape in half, like to define a half-sphere:

e(1) { within { sphere(10,10,10, 5) half_volume(10,10,10, 1,0,0) } }

Compatibility: Added in 8.2EA-20170324.

stl

stl(filename)

Imports an STL file.

Examples:

e(1) {
  stl("lens.gem")
}
e(2) {
  stl("../other/complex.gem")
}

TODO: FIX: This command NOT YET IMPLEMENTED.

e, n

e(f) { ... }
n(f) { ... }

The electrode and non-electrode commands will accept a function as the parameter, allow gradient voltages over electrode surface or analytical potentials in space to be defined.

TODO: FIX: This enhancement is NOT YET IMPLEMENTED.

Examples:

-- electrode with linear gradient potential
local function linear(x1,v1, x2,v2)
  return function(x,y,z)
    local f = (x - x1) / (x2 - x1)
    return f * v1 + (1 - f) * v2
  end
end
e(linear(5,100, 10,200)) {
  circle(0,0, 20)
}

-- quadrupole field in space
local function quad()
  local rx = 10
  local ry = 10
  local amp = 100
  return amp * ((x/rx)^2 - (y/ry)^2)
end
n(quad) {
  box(-10,-10, 10,10)
}

inf

gem.inf()
inf

This constant represents infinity. Previously a large number like 1e6 was used.

Example:

box(-inf,0, inf, 2)

Compatibility: Added in 8.2EA-20170324. FIX?

TODO?

Possible enhancements:

One proposal is a gu variable for size of one grid unit (when other dimensions are in mm):

pa_define{101,101,101, dx=0.1, dy=0.2}
box(50,0, 50+gu,100)  ; 50.1
box(0,0, 100,gu)      ;  0.2
circle(0,0, 10+gu)    ; is r = 10.1 or 0.2 ?

Problems:

  • It’s a little ugly to fit into the design (though can be done)
  • As seen in the last statement above it may lead to some ambiguities.
  • Extending PA’s to variable mesh densities complicates further.
  • I’m not sure it entirely solves the intended problem: A surface might be exactly 1 gu thick but that doesn’t mean it necessary starts exactly on a grid point. Maybe we are looking for box(gu_nearest(50),0, gu_nearest(50)+gu,100)  ; 50.1 .

I’m siding against it at this time due to such complications and because there already are simple ways to do this with just a variable. Example:

local dx = 0.1
pa_define{101,101,101, dx=dx}
box(50,0, 50+dx,100)  ; 1 gu width (splat)
box(0,0, 0,100) ; 0 gu width (100% transparent)

You can also force splats with user programs (ion_splat = 1).

Extensions in 8.1

Macro Support (e.g. variable expansion and preprocessing) - 8.0.4

As of SIMION 8.0.4, the GEM processor incorporates a preprocessor that allows things like variable expansion and loops (Issue-I296). which allows GEM files to be more maintainable and easily modified. (This new feature is not described in the 8.0.4 manual except in a footnote.)

A new example GEM file (examples\geometry\macro.gem) illustrates the use of this new feature:

; Example SIMION GEM file using preprocessing to expand Lua variables
; and macros.
; Preprocessing is a new feature SIMION 8.0.4.
;
; Lines starting with '#' or text inside $() are interpreted as Lua
; code and evaluated.  If $() contains an expression (rather than a
; statement), the result is outputted.  All other text is outputted
; verbatim.

; These variables are intended to be adjusted by the user.
; Note: both # and $ syntax are shown for demonstration.
# local ro = 45     -- outer radius, gu
# local ri = 40     -- inner radius, gi
$(local nshells = 4) ; number of shells

; Now do some calculations on those variables.  Note: math.ceil(x) is
; the ceiling function: it returns the integer closest to but no
; greater than x.
# local hw = math.ceil(ro * 1.1) -- half width, gu
# local nx = hw * 2 + 1          -- num array points in x

; Define array size.
pa_define($(nx),$(nx),$(nx), planar, non-mirrored)

; Declare a function to be used later.
; This returns the voltage for electrode number n.
$( local function volts(n)
     return n^2 * 10 + n + 1
   end )

; This locate centers the shells in the array.
locate($(hw),$(hw),$(hw), 1, 0,0,0) {
; Now define a loop that creates each shell.
# for n=1,nshells do
#   local rop = ro * n / nshells
#   local rip = ri * n / nshells
    electrode($(volts(n))) {  ; example calling a function
      fill {
        within{sphere(0,0,0, $(rop))}
        notin {sphere(0,0,0, $(rip - 1))}
      }
    }
# end
}

; Note: the following two lines are equivalent:
fill { within { box3d(0,0,0, $(nx),0,$(nx)) }}
# _put("fill { within { box3d(0,0,0, " .. nx .. ",0," .. nx .. ") }}")

; Print output to the log window.  This can be
; useful for debugging.
# print("Hello from GEM file. nx=" .. nx)

The example examples\geometry\polygon_regular.gem illustrates subroutines in GEM files, allowing you to in effect create your own reusable shapes:

# -- builds regular polygon in XY plane
# -- with center (x0,y0), radius r, and n sides.
# function regular_polygon(x0,y0, r,n)
  polyline(
    # for i=1,n do
    #   local theta = math.rad(i/n*360)
        $(x0+r*math.cos(theta)), $(y0+r*math.sin(theta))
    # end
  )
# end

e(1) { fill {
  within { $(regular_polygon(50,50, 40,6)) }
  notin  { $(regular_polygon(50,50, 20,6)) }
} }

When SIMION loads a GEM file containing macros (e.g. macro.gem), it processes those macros, writes the result to temporary file (e.g. macro.processed.gem), and loads that temporary file.

It also possible to do your own GEM file preprocessing outside of SIMION with your own programming tools. This was more popular prior to the introduction of the above feature but can still be useful in some cases.

Note

This page is abridged from the full SIMION 8.1.1 “Supplemental Documentation” (Help file). The following additional sections can be found in the full version of this page accessible via the “Help > Supplemental Documentation” menu in SIMION 8.1.1:
  • Helpful Tips for Authoring GEM Files

surface=auto option in pa_define [8.1.1.25]

Obtaining optimal field accuracy of curved surfaces had been tricky prior to the addition of the Electrode Surface Enhancement / Fractional Grid Units feature in SIMION 8.1.1. Electrode surfaces that did not exactly align to PA grid points had to be placed instead on nearby grid points, and certain ways of making this placement were more accurate than others. Cutout volumes (e.g. notin GEM commands) were also prone to error (Intersecting within and notin_inside). Consider the example of defining the field between concentric spheres of radii 40 and 80 mm. We can now accurately and simply define this in a GEM file using the (“fractional”) surface enhancement option like this:

pa_define(101,101,1, cylindrical,xy, surface=fractional)
e(1) { fill { within { circle(0,0, 40) } } }
e(2) { fill { notin  { circle(0,0, 80) } } }

However, perhaps there are still cases where you prefer not to use surface enhancement. For whatever the reason (see below), SIMION 8.1.1.25 supports a new surface=auto option in the pa_define statement of GEM files to achieve a more accurate alignment of surfaces in a simple manner when not using surface enhancement. It can be used simply like this:

pa_define(101,101,1, cylindrical,xy, surface=auto)
e(1) { fill { within { circle(0,0, 40) } } }
e(2) { fill { notin  { circle(0,0, 80) } } }

To understand this feature, let’s consider the way this used to be done in SIMION 7.0/8.0....

You might naively define the concentric spheres like this:

pa_define(101,101,1, cylindrical,xy)
e(1) { fill { within { circle(0,0, 40) } } }
e(2) { fill { notin  { circle(0,0, 80) } } }

However, without care, surfaces could be off effectively by about 0.5-1.0 grid unit (gu), thereby distorting fields by the same amount as if the electrodes were misaligned by this distance in the real physical system (e.g. machining or assembly error). The above is nearly equivalent to this:

pa_define(101,101,1, cylindrical,xy)
e(1) { fill { within_inside_or_on { circle(0,0, 40.5) } } }
e(2) { fill { notin_inside_or_on  { circle(0,0, 80.5) } } }

The within and notin commands apply a +0.5 grid unit (gu) adjustment to the radius of the fill, which improves field accuracy for the within, but for the notin we actually want more like a -0.5 gu adjustment. In fact, electrode #2 in the above two examples is visibily off slightly when measured in the View screen. We have typically recommended using notin_inside rather than notin:

pa_define(101,101,1, cylindrical,xy)
e(1) { fill { within        { circle(0,0, 40) } } }
e(2) { fill { notin_inside  { circle(0,0, 80) } } }

Visually that looks about right if you examine the PA on the View screen, and the calculated field is reasonably good. However, the notin_inside (as with within_inside) actually applies a nearly 0 gu not -0.5 gu adjustment, so fields are slightly biased. But even this is not ideal in the case of spheres. In fact, the optimal adjustment is often not 0.5 gu but about 0.35 gu, as has been observed for spherical capacitor studies ref and other internal studies on flat and curved shapes rasterized to PA’s.

In SIMION 8.1.1.25, a new surface=auto option has been added that automatically applies the best adjustment to within and notin fill types. It can be used simply like this:

pa_define(101,101,1, cylindrical,xy, surface=auto)
e(1) { fill { within { circle(0,0, 40) } } }
e(2) { fill { notin  { circle(0,0, 80) } } }

and it’s nearly identical to this:

pa_define(101,101,1, cylindrical,xy)
e(1) { fill { within_inside { circle(0,0, 40.35) } } }
e(2) { fill { notin_inside  { circle(0,0, 79.65) } } }

surface=auto also affects other shape types like box, parabola, hyperbola, and polyline by applying approximately 0.35 gu offsets in the appropriate directions.

surface=auto is even useful when electrodes do exactly align to PA grid points (like surfaces that are flat, orthogonal to the axes, and have coordinates that are integral multiples of grid units) because it will automatically get the surfaces boundary points right. Consider defining a 4x4 mm box with a 2x2 mm hole inside, which obviously easily aligns to points on a 1 mm/gu grid. You might naively try to do it like this:

pa_define(11,11,1, planar,n)
e(1) { fill {
  within { box(1,1,  5,5) }
  notin  { box(2,2,  4,4) }
} }

However, the notin { box(2,2, 4,4) } (as is also true when using notin_inside_or_on) excludes from the cut the PA grid points on both the inside and border of the 2x2 box. You want to instead use a notin_inside to only exclude the grid points on the “inside” (not border) of the 2x2 box:

pa_define(11,11,1, planar,n)
e(1) { fill {
  within       { box(1,1,  5,5) }
  notin_inside { box(2,2,  4,4) }
} }

That’s pretty simple once you know about it, but it’s easier to do this right just by enabling surface=auto like this:

pa_define(11,11,1, planar,n, surface=auto)
e(1) { fill {
  within { box(1,1,  5,5) }
  notin  { box(2,2,  4,4) }
} }

or even (if you prefer) expressing it like this:

pa_define(11,11,1, planar,n, surface=auto)
e(1) { fill { within { box(1,1,  5,5) } } }
n(0) { fill { within { box(2,2,  4,4) } } }

When surface=auto is enabled (and this is also true of surface=fractional as of 8.1.1.25), any PA grid points that precisely align to an edge of a within or notin fill volume are made to be electrode points, which is usually what you want. In other words, a notin inside an e or a within inside an n (both of which remove electrode material) will be treated as an “inside” fill to avoid removing the electrode points from the border. This largely avoids fiddling with within_inside/within_inside_or_on/notin_inside/notin_inside_or_on fill variants since within and notin will now just do the right thing. In fact, with surface=auto, it’s not usually needed nor recommended to use fill types other than within and notin; for example, the following is not correct but behaves similar to the “naive” approach mentioned earlier:

pa_define(11,11,1, planar,n, surface=auto)
e(1) { fill { within{box(1,1, 5,5)}  notin_inside_or_on{box(2,2, 4,4)} } }

In fact, the following four GEM files are all correct and generate exactly the same PA:

pa_define(11,11,1, planar,n)
e(1) { fill { within{box(1,1, 5,5)}  notin_inside{box(2,2, 4,4)} } }

pa_define(11,11,1, planar,n, surface=auto)
e(1) { fill { within{box(1,1, 5,5)}  notin{box(2,2, 4,4)} } }

pa_define(11,11,1, planar,n, surface=fractional)
e(1) { fill { within{box(1,1, 5,5)}  notin{box(2,2, 4,4)} } }

pa_define(11,11,1, planar,n, surface=auto)
e(1) { fill { within_inside_or_on{box(1,1, 5,5)}
                     notin_inside{box(2,2, 4,4)} } }

Please note, however, that although surface=auto is generally preferred over surface=none (i.e. the default old behavior in SIMION 7.0/8.0) for ease and accuracy reasons, surface=fractional (see Electrode Surface Enhancement / Fractional Grid Units) is still preferred over both of these. So, there’s not really a reason to use surface=auto unless for some reason you don’t want to use surface enhancement. It’s not really clear what that reason would be except maybe to generate PA’s that are refinable under SIMION 8.0 or to compare accuracy differences with/without surface enhancement. A GEM file using surface=fractional can be quickly switched to surface=auto without any changes, but switching to surface=none may cause 1 gu errors unless additional changes are made to within/notin fill types due to the semantic difference these have under surface=none. surface=auto is somewhat of an anachronism that behaves between surface=none and surface=fractional but happened to be implemented after both for completeness.

Changes

  • 8.1.2.4: Macro lines beginning with ‘#’ can now be indented with tabs and spaces. This allows cleaner indenting of GEM code.
  • 8.1.1.25: A GEM: New surface=auto parameter in pa_define. This provides a simple way to more accurately position curved surfaces and cutout (notin) volumes when not using surface enhancement (surface=fractional). This particularly applies to circle/sphere/cylinder, hyperbola (Issue-I371), parabola, polyline, notin, and others. Surface enhancement is still better, but if you don’t want to use surface enhancement for some reason, then this is better than nothing. See surface=auto option in pa_define [8.1.1.25].
  • 8.1.1.25: C GEM: within_inside_or_on/notin_inside_or_on now shift the electrode surface outward by a small 0.0001 gu offset. (within_inside and notin_inside already apply this offset but in the reverse redirection.) This more reliably ensures points “on” the shape boundary are filled even when small numerical round-off occurs in operations like polyline and scaling. Example: fill{within_inside_or_on{box(1,1,5,5)}} and fill{within_inside_or_on{polyline(1,1, 5,1, 5,5, 1,5)}} behave identically now, as expected. [*]
  • x GEM/polyline: polyline GEM command accuracy has been improved. Previously, the points on the polygon edge might not be optimally filled with the desired electrode/non-electrode point type. This particularly affected within_inside/notin_inside/within_inside_or_on/notin_inside_or_on rather than within. However, under surface=fractional, it also affected within/notin (but is somewhat cosmetic since electrode point types on the surface are not critical for field accuracy under surface=fractional). Example: polyline(10,5 15,5 15,10 20,10 20,15 15,15 15,20 10,20 10,15 5,15 5,10 10,10) (cross).
  • 8.1.1.25: C GEM/Surface: PA grid points that precisely align to an edge of a within/notin fill volume are made to be electrode points if surface=fractional or surface=auto is enabled. In other words, a notin inside an e or a within inside an n (both of which remove electrode material) will be treated as an “inside” fill to avoid removing the electrode points from the border. This largely avoids fiddling with within_inside/within_inside_or_on/notin_inside/notin_inside_or_on fill variants since within and notin will now just do the right thing. The default option (surface=none) retains the old behavior for compatibility though. Example: e(1){fill{within{box(1,1,5,5)} notin{box(1,1,5,5)}}} and e(1){fill{within{box(1,1,5,5)}}} n(0){fill{notin{box(1,1,5,5)}}} both create a 4x4 hollow box with infinitesimally thin walls under surface=auto or surface=fractional. See surface=auto option in pa_define [8.1.1.25].
  • 8.1.1.25: c GEM: polyline, points, and points3d commands can now accept an arbitrary number of points. Previously these were limited to 100, 100, and 65 points.
  • 8.1.1.25: x GEM: Fix polyline problems when using exactly 100 points. [*sf-t1338]
Any comments on this web page? (will be sent to SIS SIMION Support)
[Optional] Your name: email: phone/fax: