apeGmsh transforms¶
A guide to the transform and sweep operations in apeGmsh — translating,
rotating, scaling, mirroring, copying, extruding, revolving, sweeping
along paths, and lofting through sections. These operations live on the
g.model.transforms composite and act directly on Gmsh OCC entities.
This document is written for structural engineers who work with FEM
daily but may not be familiar with Gmsh's OpenCASCADE kernel; it
explains each operation in terms of the structural modeling problems it
solves.
The guide is grounded in the current source:
src/apeGmsh/core/_model_transforms.py— the_Transformsclass that exposes every method described here
All snippets assume an open session:
1. How transforms work in apeGmsh¶
Every transform method lives on g.model.transforms. That object is a
thin, ergonomic wrapper around Gmsh's OCC kernel calls
(gmsh.model.occ.translate, gmsh.model.occ.rotate, etc.) with three
additions that matter for day-to-day use:
-
Flexible tag input. You can pass a bare integer, a list of integers, or a list of
(dim, tag)tuples. The library normalizes them internally through_as_dimtags, so you never have to think about the(dim, tag)pair format that raw Gmsh demands. -
Automatic synchronization. Every method accepts
sync=True(the default). When set, it callsgmsh.model.occ.synchronize()after the operation, which makes the change visible to Gmsh immediately. You can passsync=Falseto defer synchronization when you are chaining several transforms and want to pay the sync cost only once at the end — useful in tight loops over hundreds of entities. -
Method chaining. The five rigid transforms (
translate,rotate,scale,mirror,copy) returnself, so you can chain calls:g.model.transforms.translate(...).rotate(...). The generative operations (extrude,revolve,sweep,thru_sections) return the list of newly created(dim, tag)pairs instead, because you almost always need those tags for subsequent operations.
There are two families of operations on the composite:
-
Rigid transforms —
translate,rotate,scale,mirror,copy. These reposition or duplicate existing entities without creating new topological dimensions. A volume stays a volume; a curve stays a curve. -
Generative sweeps —
extrude,revolve,sweep,thru_sections. These create geometry one dimension up: a point becomes a curve, a curve becomes a surface, a surface becomes a volume. They are the primary way to build 3-D structural volumes from 2-D profiles.
2. Shared parameters¶
Several parameters appear on every transform method. Understanding them once saves you from re-reading each signature:
-
tags— the entities to act on. Accepts a single integer tag, a list of integer tags, a single(dim, tag)tuple, or a list of such tuples. When you pass bare integers, thedimparameter decides which dimension they refer to. -
dim— default dimension for bare integer tags. The rigid transforms default todim=3(volumes);extrudeandrevolvedefault todim=2(surfaces), because you most often extrude a surface into a volume. If your tags are already(dim, tag)tuples, this parameter is ignored. -
sync— whether to synchronize the OCC kernel after the call. DefaultTrue. Set toFalsewhen batching many transforms, then callgmsh.model.occ.synchronize()yourself at the end.
Two important behavioral notes apply to all transforms:
-
In-place modification. Every transform except
copymodifies the entities in place.translate(box, 5, 0, 0)movesbox— it does not create a second box at the new position. The original tag remains valid and refers to the entity at its new location. -
Labels survive. Because the entity tag does not change, any label or physical group membership you assigned before the transform remains intact. You can label a column, translate it into position, and the label still resolves to the same volume.
-
Sub-entity propagation. Transforms affect the entire topological hierarchy. Translating a volume moves its bounding surfaces, their bounding curves, and their endpoints. You never need to translate sub-entities separately.
3. translate — positioning parts in space¶
translate shifts entities by a displacement vector (dx, dy, dz).
This is the workhorse for assembly workflows: you create a part at the
origin, then translate copies to their grid positions.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to move |
dx, dy, dz |
float |
— | Translation vector components |
dim |
int |
3 |
Default dimension for bare integer tags |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: self (for chaining).
The simplest structural use case is placing a column. You model the column once at the origin and then translate it to each grid intersection:
col = g.model.geometry.add_box(0, 0, 0, 0.4, 0.4, 3.5, label="col_A1")
# move it to grid position (6.0, 0.0, 0.0)
g.model.transforms.translate(col, 6.0, 0.0, 0.0)
For a column grid, combine copy and translate (see section 7 for
the full pattern). The key insight is that translate is an in-place
operation — if you forget to copy first, you will move the original
instead of creating a second instance.
4. rotate — orienting structural members¶
rotate turns entities around an arbitrary axis passing through a
point. You specify the rotation center (cx, cy, cz), the axis
direction (ax, ay, az), and the sweep angle in radians.
g.model.transforms.rotate(
tags, angle, *,
ax=0.0, ay=0.0, az=1.0,
cx=0.0, cy=0.0, cz=0.0,
dim=3, sync=True,
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to rotate |
angle |
float |
— | Rotation angle in radians |
ax, ay, az |
float |
0, 0, 1 |
Axis direction vector |
cx, cy, cz |
float |
0, 0, 0 |
Point on the rotation axis |
dim |
int |
3 |
Default dimension for bare integer tags |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: self (for chaining).
The axis defaults point are worth memorizing. With the defaults
(ax, ay, az) = (0, 0, 1) and (cx, cy, cz) = (0, 0, 0), you get
a rotation around the global Z axis through the origin — the most
common case for plan-view rotations of structural elements.
The 4-argument form uses the defaults and is the concise version for simple rotations:
import math
# Rotate a brace 45 degrees about global Z at the origin
g.model.transforms.rotate(brace, math.pi / 4)
The 7-argument form specifies an arbitrary axis and center, which you need when rotating around a member's own axis or around a point that is not the origin:
import math
# Rotate a beam 90 degrees around its own longitudinal axis (X)
# passing through the beam's start point at (3.0, 0.0, 0.0)
g.model.transforms.rotate(
beam, math.pi / 2,
ax=1.0, ay=0.0, az=0.0,
cx=3.0, cy=0.0, cz=0.0,
)
Angles are always in radians. Use math.radians(45) or
math.pi / 4 — never pass 45 and wonder why your column ended up
at an impossible orientation. The library logs the angle in degrees
for readability, but the input is strictly radians.
5. scale — parametric studies and unit conversion¶
scale applies a dilation (uniform or non-uniform) centered at a
point. Each axis can have a different scale factor.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to scale |
sx, sy, sz |
float |
— | Scale factors per axis |
cx, cy, cz |
float |
0, 0, 0 |
Center of dilation |
dim |
int |
3 |
Default dimension for bare integer tags |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: self (for chaining).
Uniform scaling (sx == sy == sz) preserves shape and is the safe
choice for parametric studies and unit conversion. Non-uniform scaling
distorts the geometry and should be used with care — it will turn
circles into ellipses and squares into rectangles.
Two practical uses come up regularly in structural work:
Unit conversion. You imported a STEP file modeled in millimeters but your analysis runs in meters:
# Scale everything from mm to m (all entities in the model)
all_vols = [t for _, t in gmsh.model.getEntities(3)]
g.model.transforms.scale(all_vols, 0.001, 0.001, 0.001)
Parametric column study. You want to run the same model with column sections scaled by a factor:
factor = 1.25 # 25% larger cross-section
g.model.transforms.scale(column, factor, factor, 1.0,
cx=col_x, cy=col_y, cz=0.0)
Here sz=1.0 preserves the column height while sx and sy enlarge
the cross-section. The center (col_x, col_y, 0.0) ensures the
column scales outward from its own axis rather than from the origin.
Note that scale wraps gmsh.model.occ.dilate, which is the OCC
term for this operation. The apeGmsh name scale is more familiar to
engineers.
6. mirror — exploiting structural symmetry¶
mirror reflects entities through a plane defined by the equation
ax + by + cz + d = 0. This is the operation that turns a half-model
into a full model.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to mirror |
a, b, c, d |
float |
— | Plane equation coefficients |
dim |
int |
3 |
Default dimension for bare integer tags |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: self (for chaining).
For instance-level work, the higher-level alternative is
inst.edit.mirror(plane=..., normal=..., point=...), which accepts a
named plane ("xy", "xz", "yz") or a normal vector through a point
instead of the raw ax+by+cz+d=0 form.
The plane equation can feel abstract, but the common cases are simple:
| Plane | Equation | (a, b, c, d) |
|---|---|---|
| YZ plane (x = 0) | 1x + 0y + 0z + 0 = 0 |
(1, 0, 0, 0) |
| XZ plane (y = 0) | 0x + 1y + 0z + 0 = 0 |
(0, 1, 0, 0) |
| XY plane (z = 0) | 0x + 0y + 1z + 0 = 0 |
(0, 0, 1, 0) |
| Plane x = 5 | 1x + 0y + 0z - 5 = 0 |
(1, 0, 0, -5) |
Mirror is an in-place operation: it moves the original entities to
their reflected positions. If you want to keep the original and create
a reflected copy, you must copy first:
# Build half a bridge, then mirror to get the full model
half_tags = [deck, pier_left, cable_left]
mirrored = g.model.transforms.copy(half_tags)
g.model.transforms.mirror(mirrored, 1, 0, 0, -L/2)
# Original half is at x < L/2, mirror is at x > L/2
This copy-then-mirror pattern is the standard workflow for symmetric
structures: you model one half (or one quarter) of the structure, get
it right, then mirror to produce the rest. The d coefficient offsets
the plane from the origin — here, d = -L/2 places the mirror plane
at x = L/2, the midspan of the bridge.
After mirroring, the original and the copy are separate entities. If
you need displacement continuity at the symmetry plane, add an
equal_dof constraint between the two halves (see guide_constraints.md).
7. copy — duplicating entities¶
copy creates duplicates of existing entities. Unlike every other
transform, it does not modify the originals — it returns a list of new
tags pointing to identical entities at the same location.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to duplicate |
dim |
int |
3 |
Default dimension for bare integer tags |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: list[Tag] — tags of the newly created copies.
copy is almost never used alone. You copy, then immediately
translate (or rotate, or mirror) the copies into position. This is the
fundamental pattern for repetitive geometry — column grids, floor
plates, truss panels:
# Create a 3x3 column grid at 6 m spacing
col = g.model.geometry.add_box(0, 0, 0, 0.4, 0.4, 3.5, label="col_origin")
spacing = 6.0
for i in range(3):
for j in range(3):
if i == 0 and j == 0:
# The original is already at (0, 0) — just translate it
g.model.transforms.translate(col, 0, 0, 0)
continue
copy_tag = g.model.transforms.copy(col)
g.model.transforms.translate(copy_tag, i * spacing, j * spacing, 0)
A cleaner version uses a list to collect all column tags:
col = g.model.geometry.add_box(0, 0, 0, 0.4, 0.4, 3.5, label="col")
columns = [col]
for i in range(3):
for j in range(3):
if i == 0 and j == 0:
continue
[c] = g.model.transforms.copy(col)
g.model.transforms.translate(c, i * spacing, j * spacing, 0)
columns.append(c)
The destructured assignment [c] = ... is handy when you copy a single
entity and want a plain integer tag rather than a one-element list.
8. extrude — sweeping profiles into solids¶
extrude is the most commonly used generative operation in structural
modeling. It sweeps entities along a straight vector, creating geometry
one dimension higher: a point becomes a curve, a curve becomes a
surface, a surface becomes a volume. Extruding a floor-plan surface
along the Z axis to create a slab is the textbook case.
result = g.model.transforms.extrude(
tags, dx, dy, dz, *,
dim=2,
num_elements=None,
heights=None,
recombine=False,
sync=True,
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to extrude |
dx, dy, dz |
float |
— | Extrusion vector |
dim |
int |
2 |
Default dimension for bare integer tags |
num_elements |
list[int] or None |
None |
Structured layer counts |
heights |
list[float] or None |
None |
Relative heights per layer |
recombine |
bool |
False |
Use hex/quad instead of tet/tri |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: list[DimTag] — all generated (dim, tag) pairs.
The return value deserves attention. For a surface extruded into a volume, the list contains several entries in a fixed order:
result[0]— the top face (a surface, the extruded copy of the input at the far end of the vector)result[1]— the volume created by the sweepresult[2:]— the lateral faces (one per edge of the input surface)
This ordering lets you grab the volume for meshing and the top face for applying boundary conditions without searching:
# Extrude a floor outline into a 250 mm slab
slab_out = g.model.transforms.extrude(floor_surf, 0, 0, 0.25,
num_elements=[4])
top_face = slab_out[0] # (2, tag) — for applying roof loads
slab_vol = slab_out[1] # (3, tag) — for material assignment
Structured layers. The num_elements and heights parameters
control through-thickness meshing. num_elements=[10] creates 10
uniform layers of elements through the extrusion. For non-uniform
layering — denser near the top and bottom of a slab, coarser in the
middle — combine num_elements with heights:
# 3 layers: 30% of thickness with 4 elements, 40% with 2, 30% with 4
out = g.model.transforms.extrude(
surf, 0, 0, 0.3,
num_elements=[4, 2, 4],
heights=[0.3, 0.7, 1.0], # cumulative fractions, must end at 1.0
)
Note that heights are cumulative fractions of the total extrusion
length, and the list must reach 1.0. The number of entries in
heights must match num_elements.
Recombination. Pass recombine=True to produce hexahedral (brick)
elements instead of tetrahedra. This requires structured layers
(num_elements must be set) and works best when the base surface mesh
is also quadrilateral. Hex meshes are preferred for many solid
mechanics problems because they exhibit less volumetric locking and
need fewer elements for the same accuracy.
9. revolve — axisymmetric parts¶
revolve sweeps entities around an axis, creating solids of
revolution. This is the operation for pipes, circular foundations,
cylindrical tanks, and any geometry with rotational symmetry.
result = g.model.transforms.revolve(
tags, angle, *,
x=0.0, y=0.0, z=0.0,
ax=0.0, ay=0.0, az=1.0,
dim=2,
num_elements=None,
heights=None,
recombine=False,
sync=True,
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
tags |
TagsLike |
— | Entities to revolve |
angle |
float |
— | Sweep angle in radians |
x, y, z |
float |
0, 0, 0 |
Point on the rotation axis |
ax, ay, az |
float |
0, 0, 1 |
Axis direction vector |
dim |
int |
2 |
Default dimension for bare integer tags |
num_elements |
list[int] or None |
None |
Structured layers around the sweep |
heights |
list[float] or None |
None |
Relative arc lengths per layer |
recombine |
bool |
False |
Hex/quad elements |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: list[DimTag] — all generated (dim, tag) pairs.
The num_elements, heights, and recombine parameters work the
same way as in extrude, but they control the circumferential
discretization rather than a linear one.
A full revolution uses angle = 2 * math.pi. A half revolution
(math.pi) gives you a half-pipe or a semicircular arch. Partial
sweeps are useful when you only need part of the ring and want to
save elements.
import math
# Revolve a rectangular cross-section 360 degrees around Y
# to create a circular foundation pad
profile = g.model.geometry.add_rectangle(2.0, 0.0, 0.0, 0.5, 0.3)
out = g.model.transforms.revolve(
profile, 2 * math.pi,
ay=1.0, # revolve around Y axis
num_elements=[24], # 24 elements around the circumference
)
foundation_vol = out[1] # the solid ring
10. sweep — profiles along arbitrary paths¶
sweep pushes a profile along an arbitrary wire (a curve built from
lines, arcs, splines, or combinations). Unlike extrude, the path
does not have to be straight — it can follow any trajectory you define.
result = g.model.transforms.sweep(
profiles, path, *,
dim=2,
trihedron="DiscreteTrihedron",
label=None,
sync=True,
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
profiles |
TagsLike |
— | Profile(s) to sweep |
path |
Tag |
— | Tag of the wire to sweep along |
dim |
int |
2 |
Default dimension for profile tags |
trihedron |
str |
"DiscreteTrihedron" |
Frame transport method |
label |
str or None |
None |
Label for the highest-dim result |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: list[DimTag] — all generated (dim, tag) pairs.
The path must be a wire — build it with g.model.geometry.add_wire
from individual curve segments. A wire is an ordered sequence of
connected curves; OCC uses it to define the trajectory.
The trihedron parameter controls how the profile's orientation is
maintained as it travels along the path. The default
"DiscreteTrihedron" works well for most structural paths. Use
"Frenet" for smooth curves without inflection points (it follows
the natural curvature frame). Use "Fixed" to keep the profile's
orientation constant in world space, which is useful for straight-ish
paths where you want no twisting.
Sweep is the right tool for curved beams, ramps, helical staircases, and any member whose centerline is not a straight line:
# Sweep an I-beam cross-section along a curved beam path
section = g.model.geometry.add_plane_surface(i_loop, label="I_section")
path = g.model.geometry.add_wire([arc1, line1, arc2],
label="beam_path")
out = g.model.transforms.sweep(section, path, label="curved_beam")
A practical consideration: sweep can produce degenerate geometry if the profile is large relative to the curvature radius of the path. If you get OCC errors during a sweep, check whether the profile extends past the center of curvature at any point along the wire. Shrinking the profile or increasing the curve radius usually fixes it.
11. thru_sections — variable cross-sections¶
thru_sections (also called "loft") creates a smooth solid that
interpolates between an ordered set of wire profiles. This is the
operation when the cross-section changes along the length — tapered
columns, flared pylons, transition pieces between different flange
sizes.
result = g.model.transforms.thru_sections(
wires, *,
make_solid=True,
make_ruled=False,
max_degree=-1,
continuity="",
parametrization="",
smoothing=False,
label=None,
sync=True,
)
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
wires |
list[Tag] |
— | Ordered wire tags (at least 2) |
make_solid |
bool |
True |
Cap the ends to produce a solid |
make_ruled |
bool |
False |
Force ruled (linearly interpolated) faces |
max_degree |
int |
-1 |
Maximum surface degree (-1 = OCC default) |
continuity |
str |
"" |
"C0", "G1", "C1", "G2", "C2", "C3", or "CN" |
parametrization |
str |
"" |
"ChordLength", "Centripetal", or "IsoParametric" |
smoothing |
bool |
False |
Apply a smoothing pass |
label |
str or None |
None |
Label for the highest-dim result |
sync |
bool |
True |
Synchronize OCC after the operation |
Returns: list[DimTag] — all generated (dim, tag) pairs.
Each wire defines one cross-section at its position in space. OCC builds a surface (or solid) that passes through all of them in order. The wires should be topologically similar — same number of edges in the same order — for reliable results.
make_solid=True (the default) caps the two ends and returns a
volume. Set it to False if you only need the skinned surface shell,
for instance when modeling a thin-walled structure that you will
assign shell elements to.
make_ruled=True forces the lateral faces to be ruled surfaces
(straight-line interpolation between adjacent sections). This
produces flat panels between sections, which can be desirable for
fabricated steel structures where plates are cut flat and welded at
the section boundaries.
# Tapered column: 500x500 mm base, 300x300 mm top, 4 m tall
w_base = g.model.geometry.add_wire([lb1, lb2, lb3, lb4])
w_top = g.model.geometry.add_wire([lt1, lt2, lt3, lt4])
out = g.model.transforms.thru_sections(
[w_base, w_top],
make_solid=True,
label="tapered_column",
)
You can add intermediate sections to control the taper profile:
# Column with a mid-height bulge (400x400 at z=2.0)
w_base = g.model.geometry.add_wire(base_curves)
w_mid = g.model.geometry.add_wire(mid_curves)
w_top = g.model.geometry.add_wire(top_curves)
out = g.model.transforms.thru_sections(
[w_base, w_mid, w_top],
make_solid=True,
label="bulged_column",
)
The minimum number of wires is two. Passing fewer raises a
ValueError.
12. Practical examples¶
The following examples combine multiple transforms to solve common structural modeling tasks. They illustrate the typical workflow: create geometry at the origin, copy and position it, then use sweeps to build 3-D volumes.
12.1 Column grid via copy + translate¶
A 3-bay by 2-bay frame with columns at 8 m and 6 m spacing:
import math
from apeGmsh import apeGmsh
g = apeGmsh(model_name="column_grid")
g.begin()
# One column at the origin
col = g.model.geometry.add_box(0, 0, 0, 0.5, 0.5, 4.0, label="col")
nx, ny = 4, 3 # 4 columns in X, 3 in Y
sx, sy = 8.0, 6.0 # spacings
columns = [col]
for i in range(nx):
for j in range(ny):
if i == 0 and j == 0:
continue
[c] = g.model.transforms.copy(col)
g.model.transforms.translate(c, i * sx, j * sy, 0.0)
columns.append(c)
# 12 columns are now positioned on the grid
g.end()
The pattern is always the same: create once, copy N-1 times, translate each copy. Collecting the tags into a list lets you assign materials or labels to all columns in a single loop afterward.
12.2 Symmetric bridge via copy + mirror¶
Model the left half of a simply-supported bridge, then mirror across the midspan plane:
import math
from apeGmsh import apeGmsh
g = apeGmsh(model_name="bridge")
g.begin()
L = 30.0 # total span
# Build the left half: deck slab and one pier
deck_left = g.model.geometry.add_box(0, 0, 4.0, L/2, 12.0, 0.3,
label="deck_left")
pier = g.model.geometry.add_box(L/4 - 0.5, 4.5, 0, 1.0, 3.0, 4.0,
label="pier_left")
left_half = [deck_left, pier]
# Copy and mirror across the plane x = L/2
right_half = g.model.transforms.copy(left_half)
g.model.transforms.mirror(right_half, 1, 0, 0, -L/2)
# Now the full bridge exists from x=0 to x=30
# Use boolean union or constraints to join the halves at midspan
g.end()
The mirror plane (1, 0, 0, -L/2) corresponds to the equation
x - L/2 = 0, i.e., the vertical plane at x = 15 m. Everything to
the left stays in place; the copies are reflected to the right.
12.3 Extruding a floor plan into a slab¶
Start with a 2-D floor outline (perhaps imported from a DXF or built from geometry primitives), then extrude it vertically to create a concrete slab:
from apeGmsh import apeGmsh
g = apeGmsh(model_name="slab")
g.begin()
# Floor outline as a rectangle (could be any planar surface)
floor = g.model.geometry.add_rectangle(0, 0, 3.5, 20.0, 10.0,
label="floor_outline")
# Extrude 250 mm upward with 4 structured layers
slab_out = g.model.transforms.extrude(floor, 0, 0, 0.25,
num_elements=[4],
recombine=True)
top_face = slab_out[0] # for applying live loads
slab_vol = slab_out[1] # for assigning concrete material
g.end()
The recombine=True flag requests hexahedral elements, which are
preferred for thin slabs because they avoid the volumetric locking
that plagues linear tetrahedra in bending. Combined with
num_elements=[4], you get four layers of hex elements through the
slab thickness — enough to capture the bending stress gradient.
12.4 Revolving a cross-section into a circular foundation¶
Create a rectangular cross-section in the XZ plane, then revolve it 360 degrees around the Y axis to produce a ring foundation:
import math
from apeGmsh import apeGmsh
g = apeGmsh(model_name="ring_foundation")
g.begin()
# Cross-section: a rectangle in the XZ plane
# at radial distance 3.0 m from the Y axis
profile = g.model.geometry.add_rectangle(
3.0, 0, -0.5, # corner at (x=3, y=0, z=-0.5)
0.6, 0.5, # width=0.6 m radial, depth=0.5 m
label="ring_section",
)
# Full revolution around Y axis with 32 circumferential elements
out = g.model.transforms.revolve(
profile, 2 * math.pi,
ay=1.0,
num_elements=[32],
recombine=True,
)
ring_vol = out[1]
g.end()
The cross-section is placed at x = 3.0, which becomes the inner
radius of the ring. The rectangle's width of 0.6 m means the ring
spans from radius 3.0 to 3.6 m, with a depth of 0.5 m below grade.
The 32 circumferential elements give roughly 11-degree increments,
which is fine for a foundation ring.
13. Transform chaining¶
The rigid transforms return self, so you can chain them in a single
expression. This is a stylistic convenience, not a performance
optimization — each call still synchronizes OCC by default. But it
reads well for compound positioning:
# Copy a column, move it to grid (8, 6), then rotate it 45 degrees
[c] = g.model.transforms.copy(col)
g.model.transforms \
.translate(c, 8.0, 6.0, 0.0) \
.rotate(c, math.pi / 4)
If you are chaining many transforms in a loop, consider passing
sync=False to each call and synchronizing once at the end:
for i in range(100):
[c] = g.model.transforms.copy(col, sync=False)
g.model.transforms.translate(c, i * 2.0, 0, 0, sync=False)
gmsh.model.occ.synchronize() # one sync for all 100 copies
This can noticeably speed up models with hundreds of repeated parts. The OCC synchronization is the expensive step — it rebuilds the internal topology graph — and batching it avoids doing that work 100 times.
14. Common pitfalls¶
Forgetting to copy before transforming. translate, rotate,
scale, and mirror modify entities in place. If you translate a
column without copying it first, the original column moves and you
have lost the template. Always copy before positioning when you need
the original to remain.
Degrees vs radians. rotate and revolve expect radians.
Passing 90 instead of math.pi / 2 gives you a roughly 25.8-full-
revolution rotation, which is almost certainly wrong. Use math.radians
as a guard.
Extrude default dim. extrude and revolve default to dim=2
(surfaces), while the rigid transforms default to dim=3 (volumes).
If you pass a bare integer tag that is actually a volume to extrude,
it will be interpreted as a surface tag and Gmsh will either error or
extrude the wrong entity. Be explicit with (dim, tag) tuples when
mixing dimensions.
Structured layer mismatch. When using num_elements and
heights together in extrude or revolve, the two lists must have
the same length. heights values are cumulative fractions and must end
at 1.0. Violating either condition produces confusing OCC errors.
Sweep curvature. In sweep, if your profile is wider than the
local radius of curvature of the path, OCC produces self-intersecting
geometry and raises an error. Reduce the profile size or increase the
curve radius.
thru_sections wire order. The wires passed to thru_sections
must be ordered along the loft direction. Swapping two wires creates a
self-intersecting surface. If you get unexpected geometry, check that
the wire positions progress monotonically along the intended axis.
15. Transforms and the FEM pipeline¶
Transforms happen at the geometry stage, before meshing. They modify (or create) OCC entities, and those entities are what the mesher sees. This means:
-
Labels and physical groups assigned before a transform survive, because the entity tag does not change. You can label a column, copy it, translate the copy, and both the original and the copy carry their respective labels into the mesh.
-
Mesh sizes set on entities before a transform also survive. If you set a characteristic length on a surface and then translate it, the mesh density travels with the surface.
-
Generative operations (
extrude,revolve,sweep,thru_sections) create new entities that have no labels yet. Thelabelparameter onsweepandthru_sectionsis a convenience to assign a label to the highest-dimension result immediately. Forextrudeandrevolve, you index into the returned list and label the entities yourself. -
After all transforms are done and the mesh is generated, the FEM broker (
get_fem_data()) extracts nodes, elements, and connectivity from the final mesh. The broker does not know or care how the geometry was built — it sees only the mesh.
See also¶
guide_parts_assembly.md— part-based assembly workflows that use transforms for positioningguide_selection.md— building physical groups and mesh selections on the transformed geometryguide_loads.md— applying loads to entities after transformsguide_constraints.md— coupling parts at shared boundaries after positioning