Shaping a part
Once a part’s blocks exist, the script shapes them: vertices are moved, edges are attached to curves and their nodes spaced along them, vertices, edges and faces are projected onto surfaces, and the interior is interpolated. James applies these commands in a fixed order of steps, whatever order they have in the script, and the steps interleave the kinds of command: vertices are projected before edges are laid out, edges before faces, faces before the interior. The concepts chapter gives the steps. This chapter goes by the kind of command, in the order a script most often uses them: vertices, then edges and their spacing, then projection, then interpolation.
| Section | Steps it covers |
|---|---|
| Placing vertices | 1iii |
| Edges and their spacing | 2, 4 and 5 |
| Projecting onto surfaces | 3, 6 and 9 |
| Interpolating the interior | 7, 8, 10, 14, 15 and 16 |
The steps run in numeric order, so a vertex is projected (step 3) before an edge is spaced (steps 4 and 5) and an edge is projected (step 6) before a face is interpolated (steps 7 and 8). Steps 1 to 3 run once. Steps 4 to 21 form a pass, and James repeats the pass when the part has not settled. The section on interpolation says how.
A part keeps its shape in two layers. The vertices and the edge curves (except curf) are starting positions, and later steps may move what they set. A projection is different. It stays in force, and James projects a node again whenever a later step moves it.
Placing vertices
A block is a grid of nodes, and its vertices are the corner points that the block lists name. block and cylinder give every vertex a first position (step 1i). The vertex commands then move them (step 1iii). The nodes between the vertices follow later, by interpolation.
| Command | What it does to the vertices of its region |
|---|---|
| pb | sets the coordinates you name |
| mb | adds an offset to the coordinates you name |
| q | copies all three coordinates of one vertex onto another |
| tr | moves the vertices by a transformation built from operators |
| ilin | sets the interior vertices of a region from its boundary vertices |
pb and mb name the coordinates they touch with a component keyword: x, y, z, xy, xz, yz or xyz. You write one value for each letter, in the order of the letters. The coordinates that the keyword does not name keep their values. In a cylinder part the three coordinates are the radius, the angle in degrees and the height.
tr builds one transformation from a list of operators and applies it to every vertex of the region. There are thirteen operators. mx, my and mz translate along an axis, v translates by a vector, rx, ry and rz rotate about an axis through the origin, raxis rotates about an axis you give, inv inverts the transformation so far, csca scales all three coordinates and xsca, ysca and zsca scale one. The list ends in ;. In a cylinder part tr works in Cartesian coordinates and converts back.
mbi, tri and ilini are mb, tr and ilin with an index progression in place of the region. The i means the command takes a progression. It never means inverse. A vertex that several regions of one progression reach is changed once.
James applies the vertex commands in the order the script wrote them, each on top of what the earlier ones left. A later command on the same vertex wins. A later pb on a vertex that ilin set replaces the interpolated position, and a later ilin replaces what an earlier pb set in its interior.
The script tests/corpus/169-tr-rotates-face-about-z/input.tg, which make test runs, rotates a face:
c tr rotates the i = 1 face by 15 degrees about the z axis.
block 1 3 5;1 3 5;1 2;1 2 3;0 1 2;0 1;
tr 1 1 1 1 3 2 rz 15;
endpart
The region 1 1 1 1 3 2 is the face at the lowest reduced i. The tr rotates its vertices by 15 degrees about the z axis. In the mesh, node 1 moves from (1, 0, 0) to (0.9659, 0.2588, 0). The nodes between the face and the others follow by interpolation.
The script tests/corpus/171-ilin-face-region/input.tg uses pb and ilin together:
c pb lifts one boundary vertex; ilin pulls the face interior toward it.
block 1 3 5;1 3 5;1 2;0 1 2;0 1 2;0 1;
pb 2 1 1 2 1 1 z 0.4
ilin 1 1 1 3 3 1
endpart
The pb lifts the middle vertex of one edge of the k = 1 face by 0.4. The ilin sets the one interior vertex of that face from the face boundary, and it rises to z = 0.2. James takes the weights of an ilin from the full indices, the node counts between the vertices, and not from distances in space.
A q collapses an edge or makes two corners coincide, which is how a block becomes a wedge. q takes two vertices, six numbers in all, and a seventh is refused. The page of q shows the error.
Edges and their spacing
Each edge of the part’s grid is a row of nodes between two vertices. James places the nodes of every edge in three steps. Step 2 attaches an edge to a curve if you asked. Step 4 lays out the edges that a command reached: lin on an edge region, and the four spacing commands. Step 5 spaces every other edge equally along the straight line between its end vertices.
Putting an edge on a curve
A 3D curve comes from curd, which the geometry chapter explains. An edge command names a region that is an edge, two of its three pairs of indices equal, and a curve number.
| Command | Where the edge goes |
|---|---|
| cur | the end vertices move to the closest points of the curve, and the nodes lie along the curve between them |
| curf | as cur, and the nodes are then frozen |
| cure | the end vertices go to the two ends of the curve, whatever their own positions |
| curs | a composite edge, one that spans several blocks: every vertex goes to its closest point, and each simple edge is placed as cur places it |
| splint | for a region of any class and a direction, one cubic spline through the vertices of each edge of the region along that direction, as they stand, with no curve number |
James spaces the nodes by arc length along the curve. On a closed curve, cur and curf take the shorter arc between the ends of the edge, while cure on a loop curve covers the whole loop.
cur, cure and curs place the edge and leave it a starting position. A later step may project a vertex of the edge off the curve, and the edge is not pulled back. curf holds the edge. Once the placement of the edge is complete, after step 5, no later step moves its nodes. Projection and every interpolation skip a frozen node and use its position as boundary data for the nodes around it. A vertex of the edge can still be projected at step 3, because the freeze takes hold after that.
The script tests/corpus/173-curf-freezes-edge/input.tg shows both halves of that rule:
c curf freezes the i edge on curve 1; the sfi that follows leaves its interior nodes alone.
curd 1 lp3 0 0 0 1 0.3 0 2 0 0;;
sd 1 plan 0 0 0.2 0 0 1
block 1 5 9;1 3;1 2;0 1 2;0 1;0 1;
curf 1 1 1 3 1 1 1
sfi ;;-1;sd 1
endpart
The curve is a polyline that rises from (0, 0, 0) through (1, 0.3, 0) to (2, 0, 0), and the plane is z = 0.2. The sfi projects the k = 1 face onto the plane. The three vertices of the edge are projected at step 3, so they lie at z = 0.2. The six nodes between them are frozen and stay on the polyline, for example node 3 at (0.5, 0.15, 0). Without the curf, every node of the edge lands at z = 0.2.
edge and patch ask for an edge to follow the boundary of a surface. James reads and checks them and the engine refuses both, because none of the surfaces James has own a boundary edge or four sides. The compatibility chapter says when that changes.
Spacing the nodes along an edge
Four commands set where the nodes of an edge lie between its ends. Each takes a region, a direction i, j or k, and numbers.
| Command | What you give | Result |
|---|---|---|
| res | one ratio | the intervals form a geometric series, each interval the ratio times the one before it, counted from the end with the lowest index |
| drs | two ratios | one series from each end, meeting in the middle |
| as | a flag and a size | the first interval (flag 0) or the last (flag 1) has the size, and the others follow one geometric series |
| das | two sizes | the first and the last interval have the sizes, and two series grow toward the middle |
James scales each series so that it fills the edge. A ratio of 1 is equal spacing, which is the default. For an edge of length 7 cut into three intervals, a ratio of 2 gives the nodes 0, 1, 3 and 7.
The script tests/corpus/076-drs-both-ends/input.tg spaces an edge from both ends:
block 1; 1; 1 5;
0;
0;
0 17;
drs 1 1 1 1 1 2 k 2 3
endpart
The edge runs along k from 0 to 17 in four intervals. The ratio 2 from the low end and the ratio 3 from the high end give the intervals 3, 6, 6 and 2, and the nodes at 0, 3, 9, 15 and 17. James wrote those nodes when merge, neutral and write were added to the script and --write-unref-nodes was on the command line. The flag is needed because the part has no element.
A region that spans several blocks along the direction is one composite edge. James ignores the blocks inside the span, and the vertices inside it slide to their places in the series.
A spacing command works along the edge as it stands, so an edge attached to a curve is spaced along the curve. James spaces an edge again after step 6 projects it onto a surface, along the projected edge and by the same rule, and repeats until the nodes settle.
Two rules guard the sizes. A ratio or a size that is zero or negative is an error. For das, gap1 plus gap2 may not exceed the length of the edge. A sum equal to the length is accepted and leaves every interval between the two gaps at zero length. James checks the sum against the edge’s current length each time it spaces the edge, so a projected edge is held to it too. The script tests/corpus/079-das-gap-sum-exceeds-rejected/input.tg gives gaps of 6 and 8 on an edge of length 11:
block 1; 1 5; 1;
0;
0 11;
0;
das 1 1 1 1 2 1 j 6 8
endpart
James prints the error that tests/corpus/079-das-gap-sum-exceeds-rejected/expected.diag.txt holds:
input.tg:5:1: error [E0903]: das gap1 + gap2 exceeds the edge's total arc length
james: 1 error, 0 warnings
nds asks for spacing by a density function that ndd would define. James does not have ndd, so it reads every nds and refuses it.
On a cylinder part James measures an edge in the part’s own coordinates. Along an edge in the angle direction, a size or a gap counts degrees, not a distance.
lin on an edge region also acts at step 4. It places the nodes on the straight chord between the end vertices, spaced by the rule in force for the edge. The next sections come back to lin.
Projecting onto surfaces
A projection moves a node to the nearest point of a surface. Three commands project.
| Command | What it projects |
|---|---|
| sf | a vertex, an edge or a face onto one surface |
| sfi | the regions of an index progression, each as sf does |
| ms | one face per reduced index along a direction, each onto its own surface of a sequence |
A surface comes from sd and is named by number or name, or it is defined on the spot inside the command. The geometry chapter explains the six kinds. A vertex is projected at step 3, the nodes of an edge at step 6 and the nodes of a face at step 9. A command that names a face therefore also projects the face’s edges and vertices, at their own steps. A region that is a block is an error, because projecting a solid has no meaning. Name its faces.
The nearest-point rule
James does not map the mesh onto the surface. It takes each node where it stands and moves it to the point of the surface closest to it. The segment from the old position to the new one meets the surface at a right angle.
That rule decides which way a face bulges, and it decides it by where the surface’s own centre lies. Take a sphere. The nearest point of a sphere to a node lies on the ray from the sphere’s centre through the node. A node inside the sphere moves outward along that ray. A node outside moves inward. A flat face that lies between the centre and the sphere, at a distance from the centre smaller than the radius, therefore bulges away from the centre, toward the sphere, and its middle moves farthest. If the centre lies on the same side as the bulge you wanted, the face bulges the wrong way. Place the centre of the sphere on the side of the mesh opposite to the bulge you want.
The lens
A lens shows the rule. Its top and bottom faces are spheres of radius 4 and its sides are a cylinder of radius 2. The script tests/golden/017-lens/input.tg, which make test runs, is:
c A lens: one block part, its top and bottom faces projected onto two
c spheres and its four sides onto a cylinder, so the square block becomes a
c round biconvex lens 0.6 thick at the rim and 1.67 at the centre. The mesh
c is written twice, as the neutral file and as Exodus II.
sd 1 cyli 0 0 0 0 0 1 2
sd 2 sphe 0 0 3.164 4
sd 3 sphe 0 0 -3.164 4
block 1 7;1 7;1 5;-1.4 1.4;-1.4 1.4;-0.6 0.6;
sfi -1 -2;;;sd 1
sfi ;-1 -2;;sd 1
sfi ;;-1;sd 2
sfi ;;-2;sd 3
endpart
merge
neutral
write
exodusii
write
The part is one block, a square slab 2.8 across and 1.2 thick. The cylinder sd 1 has radius 2 and the two sfi commands that name it pin i and j to their lowest and highest reduced index, so they cover the four side faces. The sphere sd 2 has its centre at z = 3.164, above the slab. The sfi ;;-1; command takes the face at the lowest reduced k, the bottom face at z = -0.6, and projects it onto that sphere. The face lies between the centre and the sphere’s lower pole, so it bulges away from the centre, downward. The sphere sd 3 is the mirror image, with its centre at z = -3.164, and it carries the top face upward.
James wrote the mesh with the neutral file’s lines, 245 nodes and 144 elements. The column of nodes on the axis runs from z = -0.836 to z = 0.836, so the lens is 1.672 thick at its centre. The nodes of the rim lie on the cylinder and on a sphere at once, at z = -0.300 and z = 0.300, so the rim is 0.600 thick. The rim is thinner than the slab’s 1.2 because the rim and corner nodes lie outside the sphere, at a distance from its centre greater than the radius, so they move toward the centre, as a node outside a sphere does. A face that lies wholly outside a sphere dishes toward the centre in the same way. The script writes the mesh twice, as the neutral file and as Exodus II, so james input.tg -o lens makes the files lens and lens.0001.
Two and three surfaces at a node
A node that several projection commands reach is projected onto all of its surfaces at once.
- One surface: the node goes to the nearest point of that surface.
- Two surfaces: the node goes to a point on both, on the curve where they cross.
- Three surfaces: the node goes to the point where the three meet.
A node carries at most three surfaces. James counts the distinct surfaces that reach one vertex, in the order of the commands, and a composite surface from sds counts as one. A fourth is an error, reported once per part for the first vertex that reaches it. The reason is geometric: three independent surfaces fix a point, and a fourth would over-determine it.
The script tests/corpus/066-sf-two-surface-intersection/input.tg puts an edge on two planes:
sd 1 plan 0 0 0 0 0 1
sd 2 plan 0 0 0 1 0 0
block 1 2 3;1 2 3;1 2 3; 0 1 2; 0 1 2; 0 1 2;
sf 1 1 1 1 3 1 sd 1
sf 1 1 1 1 3 1 sd 2
endpart
Both sf commands name the edge 1 1 1 1 3 1, which runs along j on the line where the planes z = 0 and x = 0 cross. Every node of the edge carries two surfaces. The edge already lies on both planes, so nothing moves.
An inline surface that you repeat is a second surface, because James does not recognise a repeat. A surface meant for several commands is defined once with sd and named.
The shorthands of ms stand for a whole sequence of surfaces. The script tests/corpus/140-ms-shorthand-planes/input.tg projects three faces onto three planes:
block 1 2 3;1 2;1 2 3;0 1 2;0 1;0 1 2;
ms 1 1 1 3 2 3 i ppx 0 1 2
ms 1 1 1 3 2 3 k pon 0 0 0 0 0 2 0 1 2
endpart
The first ms spans three i faces, and ppx 0 1 2 gives them the planes x = 0, 1 and 2. The second spans three k faces, and pon gives them the planes z = 0, 1 and 2. The number of surfaces must equal the number of faces.
A projection that cannot be solved stops the run with an error that names the node and the part. A node at the centre of a sphere is the usual case, since every point of the sphere is equally near. James never turns such a failure into a warning.
spp projects along rays through a template mesh. James reads every argument and then refuses it, because the command tmplt that would define the template does not exist in this version.
Interpolating the interior
After the edges and the projections, James places the nodes inside faces and blocks. It always interpolates, and a script may choose which kind.
| Where | Edges | Faces | Blocks |
|---|---|---|---|
| Default, automatic | step 5, the chord | step 8, modified bilinear | step 15, trilinear |
lin | step 4 | step 7 | step 14 |
tf | refused | step 10 | step 16 |
The default needs no command. A face is filled from its four bounding edges by a bilinear blend that James modifies where an edge row pinches, and a block is filled from its six faces by the trilinear blend, with no pinch correction. The hierarchy names step 15 “default modified tri-linear”, but this version applies no correction in 3D. lin asks for the linear kind on a region you name. tf asks for transfinite interpolation, which places each node by its relative arc length along the boundary edges, so the spacing you gave the edges shapes the interior. James does no smoothing afterwards, and a boundary that folds the arc-length map gives inverted elements.
lin and tf take a region that may span several blocks. James treats the region as one edge, face or block, takes its outer boundary and ignores the blocks inside. The nodes at the boundary never move. A lin over several blocks places their interior partition lines and faces as interior nodes, and a node that a surface constrains is projected onto its surface again afterwards. James chooses the step from the shape of the region and from nothing else, so lini and tfi can place the members of one progression at different steps.
A lin over a region that is already one block, face or edge changes nothing under the default intyp 1, because the default is the same blend. intyp chooses the default. Under intyp 2 the default for every face and block is transfinite, and then a tf over one simple region changes nothing, while a lin over one simple region forces the linear blend and changes the nodes. The page of intyp describes the setting.
The script tests/corpus/178-lin-face-region/input.tg shows lin over a whole face:
c lin over a face region whose centre vertex pb lifted
block 1 3 5;1 3 5;1 2;0 1 2;0 1 2;0 1;
pb 2 2 1 2 2 1 z 0.4
lin 1 1 1 3 3 1
endpart
The part has 2 by 2 blocks in i and j and is one layer thick. The pb lifts the centre vertex of the k = 1 face to z = 0.4. The region 1 1 1 3 3 1 is the whole face, so James stores the lin for step 7. The centre vertex is an interior vertex of that region, so lin recomputes it from the outer boundary. With the lin, the centre node returns to z = 0, and so do the four nodes beside it, which sat at z = 0.2, and the four diagonal nodes, which sat at z = 0.1. Without it they stay lifted.
The script tests/corpus/180-tfi-face-progression/input.tg shows transfinite interpolation over two faces:
c tfi over the two k faces of a part of two blocks
block 1 3 5;1 3;1 3;0 1 2;0 1;0 1;
pb 2 2 1 2 2 1 xy 1.2 1.1
tfi ;;-1 -2;
endpart
The progression ;;-1 -2; pins k to 1 and 2, so James expands it to the two k faces and stores each for step 10. The pb moves the vertex 2 2 1 to x = 1.2, y = 1.1, and the tfi moves the interior nodes of both faces a little. Node 7 goes from (0.55, 0.525, 0) to (0.54996, 0.52293, 0). A node that tf moves is projected again onto the surfaces that constrain it.
On a cylinder part James interpolates the angle as an ordinary number, as you wrote it. Boundary angles of 350 and 10 interpolate through 180. To go the short way, write 370.
Passes
Some parts need more than one run of the interpolation and projection steps, because one region’s boundary can lie inside another region, and each needs the other’s result. A pass is steps 4 to 21. James runs a pass, compares every node with its place after the pass before, and runs another while any node still moves by more than a small tolerance. mxp sets the most passes, 4 by default. A part that settles sooner stops sooner. A part that has not settled after mxp passes keeps the last result, with no error and no warning. Steps 1 to 3 run once, whatever mxp says. Raising mxp costs time only.
James does not have the smoothing and relaxation steps of the hierarchy (steps 11 to 13 and 17 to 19). The compatibility chapter lists relax, esm, tme and unifm as refused.
Reading the warning about inverted elements
An element is right-handed when the triple product of its edges at a corner is positive, which means its volume has a positive sign. James numbers the corners of every element in one order. The first four corners are the k face, counterclockwise as seen from the k + 1 side, and the last four are the k + 1 face in the same order. The merge and output chapter draws it. An element whose corners fall in a different handedness has a negative sign, and James calls it inverted.
When James builds a part, it tests every element and prints one warning for each part that has inverted elements:
part #n has k elements with a negative Jacobian
The number after # is the part’s position in the script, counted from zero, so part #0 is the first part. A part with one inverted element reads has 1 element. The line has the position of the part’s opening command.
The script tests/corpus/041-pb-two-same-vertex/input.tg moves one vertex past its neighbour:
block 1 2;1 2;1 2;0 1;0 1;0 1;
pb 1 1 1 1 1 1 x 1.0
pb 1 1 1 1 1 1 x 2.0
endpart
The second pb wins, so the vertex ends at x = 2, past the vertex at x = 1 beside it, and the element folds. James prints the warning that tests/corpus/041-pb-two-same-vertex/expected.diag.txt holds:
input.tg:1:1: warning: part #0 has 1 element with a negative Jacobian
james: 0 errors, 1 warning
Read the count against the part. A count of one or a few means a vertex or a curve pushed an element through its neighbour, as above, or an interpolation folded where the boundary bends sharply, which tf can do. A count equal to the number of elements of the part means the part is inverted as a whole. The usual cause is a coordinate list in a block that decreases, so that the i, j, k axes of the grid make a left-handed system. One decreasing list inverts every element of the part, and so do three. Two decreasing lists cancel, because each reverses one axis and the grid is right-handed again. A part that holds 16 elements and a decreasing x list gets the count 16.
James writes an inverted element as it is. It does not reorder the corners and it does not stop. A solver reads the file’s corner order, so an inverted part needs fixing before the file is used. Write the coordinate lists in increasing order. A later version, M9, reorders the corners of an inverted part, and the compatibility chapter says so.
An element can also come out with zero volume after a merge, which has its own warning. The merge and output chapter covers it.