Concepts
James builds a mesh the way a sculptor works a block. A part begins as a rectangular arrangement of blocks of elements. The script then carves, moves, attaches and projects it until it fits the shape you want. This chapter explains the ideas every later chapter relies on. They are the two meshes and the map between them, blocks and partitions, reduced indices, regions and progressions, projection, the fixed order in which James applies the commands of a part, and the three phases of a script.
The two meshes and the map
Every node of a mesh has two descriptions.
The first is computational. It is a triplet of whole numbers i, j and k, which says which node it is. These indices name a place in a fixed lattice. They do not say where the node is in space. Moving, bending or projecting a part never changes which (i, j, k) names a node. The indices change only when you change the part’s topology or density, with insprt, mseq or lmseq, or by editing the block lists.
The second is physical. It is a triplet of coordinates x, y and z, which says where the node is in the space of the object you model. This is the half you reshape. You start from a block and move it toward the shape you want. Then you project its faces onto surfaces, and James fills in the interior by interpolation.
A mesh is the map that takes each triplet of indices to a triplet of coordinates. Every node carries six numbers, and the mesh is the rule that connects them. Only the whole-number points of the lattice are ever evaluated, because only they are nodes.
This is why a command names indices and never coordinates. A command such as sfi or dei says which nodes it acts on by their place in the lattice. If it named coordinates, a change to the shape would break it. If it named raw index numbers, a change to the density would break it. Because indices stay put while coordinates move, you can reshape a part and refine it later without rewriting the commands that refer to it. James keeps the same split inside: a command picks nodes by exact integer comparison on indices, and never by comparing coordinates.
Blocks and partitions
The block command opens a part and lays out its starting grid with six lists, separated by semicolons:
block i-list ; j-list ; k-list ; x-list ; y-list ; z-list ;
The first three lists are whole numbers. They give node positions along the i, j and k axes. The last three hold one real coordinate for every entry of the matching index list. The cylinder command has the same six lists, but its coordinate lists are the radius, the angle in degrees and the height.
Each entry of an index list names a partition. A partition is a plane that bounds one or more blocks along that axis. The first and last entries are the ends of the part in that direction. The number of elements between two neighbouring partitions is the difference of their indices. The list 1 3 5 7 makes three blocks, each two elements wide, over seven nodes. Along one direction, the span between two neighbouring partitions is that direction’s region; mseq, lmseq and partmode count elements per region.
The word “block” has two meanings. The command opens a part. A block of that part is one cell of the partition grid, between neighbouring partitions in each direction. Read the word by its context.
The sign of an entry carries meaning in the language. A positive entry is an ordinary partition. A negative entry is a shell partition. A zero is a gap that disconnects the part. This version of James takes positive entries only. It refuses a list that starts with anything but 1 (E0401), a negative entry (E0400) and a zero (E0413). A negative entry does not delete anything, and deleting is a separate step.
You sculpt a shape by deleting blocks. A disc meshes well as a square core with four ring blocks around it. A single block gives a disc with badly distorted corners. Cutting the corners away from a larger grid is simpler than building the pieces as separate parts and joining them. The de command removes the elements of a region from the part, and dei does it for a progression. Deleted elements are written nowhere.
The first topology is not final. insprt adds a partition to a part that exists. mseq and lmseq change how many elements a region holds without changing the topology. meshscal scales the counts of every part, and partmode makes the lists count elements instead of node positions. insprt renumbers the commands issued before it, so they keep pointing at the partitions they named.
Reduced indices
A node’s full indices are its actual place along each axis, counted from 1. Full indices change whenever you refine the mesh.
A reduced index is the position of an entry in a block list. It numbers the partitions, not the nodes. Take this part:
block 1 5 9;1 6;1 7; ...
The i-list has three entries, so the reduced i-indices are 1, 2 and 3. They stand for the full indices 1, 5 and 9. The j-list has two entries, so its reduced indices are 1 and 2, standing for 1 and 6. Reduced index 3 does not exist in j, even though the number 6 is written there. A reduced index counts entries. It never equals a value written in the list.
Almost every command of the Part phase addresses the mesh by reduced indices. If you later change the density with mseq, or edit the block command, those commands still point at the same partitions.
The zero rule applies to the minimum and the maximum of a region. A 0 as a minimum means the lowest reduced index, which is 1. A 0 as a maximum means the highest reduced index in that direction. A 0 for both the minimum and the maximum means the whole range in that direction. For the part above, 0 0 0 0 0 0 is the region 1 1 1 3 2 2. The region 2 0 1 2 0 2 is 2 1 1 2 2 2, the face at i = 2.
A 0 has two other jobs in the language, which the next section and the list at the end of this chapter keep apart.
Regions and progressions
A region names a rectangular piece of the part by six numbers, the smallest and largest reduced index in each direction:
imin jmin kmin imax jmax kmax
What a region names depends on how many of its three pairs coincide.
| Pairs that coincide | The region is |
|---|---|
| None | A block (a volume) |
| One | A face |
| Two | An edge |
| Three | A vertex |
In the part above, 1 1 1 3 2 2 is the whole block, 1 1 1 3 2 1 is the face at k = 1, and 1 1 1 3 1 1 is the edge along i.
A progression names one region or several in one argument. It is three lists, one for i, one for j and one for k, and each list ends in ;:
i-list ; j-list ; k-list ;
A command whose name ends in i takes a progression in place of a region. The suffix means “takes a progression”. It never means inverse: dei deletes, sfi projects and lini interpolates, each over the regions the progression selects.
Inside one list, these rules apply:
- A
0separates groups. The list selects the union of its groups. - A group of two or more numbers is an interval. It runs from the first number to the last, taken without signs.
- A negative number is pinned. A group of one negative number is a single pinned point. A negative number inside an interval pins that position, at an end or in the middle.
- A group of one positive number selects nothing, and James warns (E0101).
- An empty list, nothing before its
;, means the whole range in that direction.
James expands a progression to a list of ordinary regions with a product rule. It takes one interval or pinned point from each direction at a time and forms every combination. Intervals in all three directions give volumes. Pinned points in all three give vertices. Pinned points in two directions and intervals in the third give edges. Anything else gives one face for each pinned value, across the intervals of the other two lists. An interval that holds a pinned value gives no volume. If every direction still has an interval with no pinned value, those intervals give volumes as well.
Here are two expansions. Both use a part whose lists have five, four and three entries, so its reduced ranges are i 1 to 5, j 1 to 4 and k 1 to 3.
The first progression is 1 2 0 4 5;2 3;;. The i-list has two groups, the intervals 1 to 2 and 4 to 5. The j-list is the one interval 2 to 3. The empty k-list is the interval 1 to 3. No list holds a pinned value, so the product gives two volumes:
1 2 1 2 3 3
4 2 1 5 3 3
The second progression is 1 3;-1 2;;. The i-list is the interval 1 to 3. The j-list is the interval 1 to 2 with its first end pinned at 1, and the k-list is empty, so it is 1 to 3. One list holds a pinned value, so the faces rule applies. The pinned j-value 1 gives the j-face at j = 1, across the i-interval and the k-interval. The j-interval holds a pinned value, so it gives no volume, and the j-direction has no other interval, so no volume is left. The result is one face:
1 1 1 3 1 3
James gives the same two answers in --dump-ir. The example at the end of this chapter shows a third.
Shaping by projection
A projection moves a node to the nearest point of a surface. The segment from the old position to the new one meets the surface at a right angle. If a surface has several candidate points, the node goes to the closest.
The nearest point decides which way a face bulges, so the position of a surface’s centre matters. A node moves along the line from the sphere’s centre through the node, to the near side of the sphere. Take a face that lies between the centre and the rim of the sphere, with the centre on the far side of the block. The face goes out to the sphere and bulges away from the centre. If the centre lies on the near side instead, below the bottom face for example, each node moves the other way, across the block, and the part turns inside out. Swap the two spheres in the lens and James warns that all 144 elements have a negative Jacobian. In the lens of the getting started chapter, the bottom face is projected onto the sphere centred above it, and the top face onto the sphere centred below it. Each face bulges away from its own centre, so the block becomes a lens.
A node can be held to one, two or three surfaces. James counts them for each node and picks the matching procedure. A face projects onto one surface, an edge lies on the intersection of two, and a vertex on the intersection of three. An edge inherits the surfaces of the faces it joins, and a vertex those of its three faces, so you do not name a surface for an edge or a vertex. James allows at most three independent surfaces at a node.
James places the nodes in a fixed order. It places the vertices first, then the edges, then the faces, and last the interior. An edge starts as a straight line between its two end vertices, and then James projects it. A face starts as a blend of its four edges, and then James projects it. The interior is a blend of the six finished faces, with nothing left to project.
A face does not have to cover its surface. It goes to the nearest points, so it can take up a small part of a large surface, and one surface can serve several faces. The lens uses the cylinder for four faces. It also means that surfaces that do not quite meet leave no gap in the mesh, because each node goes to the nearest point of its own surface.
The order James applies commands
James does not run a part’s commands as it reads them. It collects every command of a part and applies them all when the part ends. It sorts them into 21 fixed steps. This table is James’s own, reduced to this version’s commands:
| Step | What happens | Commands in this version |
|---|---|---|
| 1i | Start the block or cylinder coordinates | block, cylinder |
| 1ii | Freeze the interface nodes of the constrained side | none |
| 1iii | Place vertices | pb, mb, q, tr, ilin |
| 2 | Attach edges to curves | cur, curf, curs, cure, splint |
| 3 | Project vertices to surfaces | sf, ms |
| 4 | Specified edge interpolations | lin |
| 5 | Default edge interpolations | automatic |
| 6 | Project edges to surfaces | sf, ms |
| 7 | Specified bi-linear face interpolations | lin |
| 8 | Default modified bi-linear face interpolation | automatic |
| 9 | Project faces to surfaces | sf, ms |
| 10 | Transfinite interpolation of faces | tf |
| 11 | Equipotential relaxation of faces | none |
| 12 | Smoothing of faces | none |
| 13 | Re-interpolate and project the edges and faces that steps 11 and 12 changed | automatic |
| 14 | Specified tri-linear solid interpolations | lin |
| 15 | Default tri-linear solid interpolation (no pinch correction in this version) | automatic |
| 16 | Transfinite interpolation of solids | tf |
| 17 | Equipotential relaxation of solids | none |
| 18 | Smoothing of solids | none |
| 19 | Uniform smoothing of solids | none |
| 20 | Evaluate the coordinate equations | none |
| 21 | Block boundary interface of the controlling side | none |
The i form of a command, such as sfi, lini, tfi or mbi, goes to the same steps as its base. The spacing commands res, drs, as and das act with the edge interpolations in steps 4 and 5. James reads nds and refuses it. A deletion with de or dei is not a step. James folds it into the part’s element mask before step 1. The commands edge, patch, spp, pbs and the others that the compatibility chapter lists are read but refused, so they fill no step.
This fixed order has consequences you must know.
- Input order does not matter across steps. A command in a later step always runs after one in an earlier step, wherever you wrote it. Two scripts that list the same commands in a different order give the same mesh.
- Input order does matter within a step. Two commands in the same step that act on the same vertices apply in the order you wrote them, and the second reads what the first left. Two
pbcommands on one vertex are the usual case. - Three commands do not fit a step.
insprt,mseqandlmseqrewrite the part’s topology or element counts as James reads them, so the part’s definition is settled before any step runs. Their place in the script matters, andinsprtrenumbers the commands issued before it. - James repeats the pipeline. It runs steps 1 to 3 once, then repeats steps 4 to 21, up to
mxppasses. It stops early when no node moves more than a small tolerance, and it accepts the last pass if it has not converged.
The three phases
A script is always in one of three phases: Control, Part or Merge. It starts in Control. Only named commands change the phase. Each command is legal in the phases its page names.
| From | Command | To |
|---|---|---|
| Control | block, cylinder | Part |
| Merge | block, cylinder | Part |
| Part | block, cylinder | Part (James closes the open part first, then opens a new one) |
| Control | merge | Merge |
| Part | merge | Merge (James closes the open part first) |
| Part | endpart, control | Control |
| Part | abort | Control (James discards the open part, including the command that opened it) |
| Merge | merge | refused (E0410) |
| Control, Merge | endpart, control, abort | refused (E0411) |
| any | end, or the end of the input | the script ends |
A part that a transition closes is final. You cannot reopen it or change it. If you need to change it, edit the script. A script ends at end or at the end of its input. A part still open then is kept, and James warns (E0416).
Control is where you set up the whole model. Surfaces and curves can be defined in any phase. Part is where you work on one part. Merge is where James joins coincident nodes under a tolerance and where write produces the file. James writes a mesh file only when a write runs. Entering Merge writes nothing.
The scripting layer
A script is more than a list of commands. It has a cleaner that strips comments, a tokenizer, named values (para, array, def), expressions in brackets, and two separate families of control statements, if and when/for/while. James resolves all of them before it reads a single command. The writing chapter explains them.
One example
The golden case 018-notched-plate shows progressions and projection at work. It builds a plate as a grid of nine blocks, deletes two of them with one progression, and projects the outer faces onto a cylinder.
The script is tests/golden/018-notched-plate/input.tg:
c A notched plate: a 3 by 3 grid of blocks, the top and bottom blocks of
c the middle column deleted by one progression with a union, and the
c outer faces of what remains projected onto a cylinder, so the square
c plate becomes a round one with two rectangular notches cut in from the
c rim.
sd 1 cyli 0 0 0 0 0 1 4
block 1 4 7 10;1 4 7 10;1 3;-3 -1 1 3;-3 -1 1 3;0 1;
dei 2 3;1 2 0 3 4;;
sfi -1 -4;;;sd 1
sfi ;-1 -4;;sd 1
endpart
merge
neutral
write
The block lists give four partitions in i and j, so the part has 3 by 3 by 1 blocks. The square runs from -3 to 3 in x and y. The dei progression has one interval in i, from 2 to 3, which is the middle column. Its j-list has two groups, 1 to 2 and 3 to 4, joined by the 0 into a union. The empty k-list covers the whole height. No value is pinned, so the product rule gives two volumes. James stores them as the regions 2 1 1 3 2 2 and 2 3 1 3 4 2, the bottom and top blocks of the middle column. It deletes them, and seven blocks are left.
The sfi progressions use the rule for faces. In -1 -4;;; the i-list is one interval from 1 to 4 with both ends pinned. James reads it as the two i-faces at i = 1 and i = 4, each across the whole range of j and k, the regions 1 1 1 1 4 2 and 4 1 1 4 4 2. The second sfi gives the two j-faces, 1 1 1 4 1 2 and 1 4 1 4 4 2. The four outer faces go onto the cylinder of surface 1, of radius 4. Wherever sfi sits in the script, James applies it at steps 3, 6 and 9.
The projection makes the square plate round. The plate’s corners, which were at a distance of about 4.24 from the axis, move in onto the cylinder. The rest of the rim moves out onto it. The notches stay cut in from the rim. Their floors, at y = -1 and y = 1, stay where they were, and their walls follow the rim nodes that the projection moved. The neutral file holds 264 nodes and 126 elements. Of the nodes, 96 lie on the cylinder.
Common misconceptions
These are the mistakes readers make most.
- The
isuffix means a progression. It never means inverse. - A reduced index is a position in a
blocklist. It is not a node number, and it never exceeds the number of entries in its list. - A
0has three jobs. In ablocklist it marks a gap, which this version refuses. In a region’s minimum or maximum it means the lowest or the highest reduced index. In a progression it separates groups into a union. - Input order is not execution order. The 21 steps decide it, and only same-step commands on the same nodes keep your order.
- A negative entry in a
blocklist is a shell partition, not a deletion. Deletion isdeanddei. - The coordinate lists of
cylinderare radius, angle in degrees and height. The merge sees Cartesian coordinates. - The word “block” means the command, the part’s starting shape or one cell of the partition grid, by context.
- A list’s entry count is not its last value. The list
1 3has two entries and a reduced range of 1 to 2. - A single positive number in a progression list selects nothing, and the whole progression is empty if any list selects nothing.
linon one block, face or edge changes nothing under the default interpolation. It matters when its region spans several.ifand thewhen,forandwhilefamily never nest inside each other. Usewheninside a loop.insprtrenumbers the commands issued before it. It does not touch the ones after it.