Parts — g.parts¶
A Part owns an isolated Gmsh session and exports a shape to STEP.
g.parts is the assembly-side registry that imports those STEPs back
in and tracks which tags belong to which label.
Part¶
apeGmsh.core.Part.Part ¶
Bases: _SessionBase
An isolated geometry unit — no meshing, no solver state.
Carries geometry plus Tier-1 naming (labels + auto-created
physical groups from label= kwargs, persisted via the STEP
sidecar). For independently-meshed parts use a full session per
part + g.compose instead — see the module docstring.
Parameters¶
name : str
Descriptive name (also used as the Gmsh model name).
auto_persist : bool, default True
When True, the Part writes its geometry to an OS tempfile
on end() if save() was not called explicitly. The
tempfile is reclaimed via weakref.finalize when the
Part is garbage-collected, or eagerly via cleanup().
Set to False to opt out — in that case parts.add(part)
will raise FileNotFoundError unless you called save()
by hand.
Source code in src/apeGmsh/core/Part.py
begin ¶
Open the Part's Gmsh session.
If the Part is being reused — a previous with part: block
auto-persisted a tempfile and this call re-enters — the stale
tempfile is cleaned up before the new session starts so the
next end() can auto-persist fresh geometry.
Source code in src/apeGmsh/core/Part.py
end ¶
Close the Part's Gmsh session.
When auto_persist=True and the user did not call
save() inside the session, the geometry is written to
an OS tempfile before Gmsh is finalised so the Part can
flow straight into assembly.parts.add(part).
Exceptions raised by auto-persist itself are caught and emitted as a warning rather than masking any exception the user's build code may have raised. Gmsh finalisation always runs.
Source code in src/apeGmsh/core/Part.py
cleanup ¶
Delete any auto-persisted tempfile now, without waiting for garbage collection.
Safe to call multiple times. Safe to call on a Part whose
file_path was set by explicit save() — the
_owns_file guard means the user's file is never
touched. After cleanup(), has_file returns False
and the Part can be re-built via a new with block.
Source code in src/apeGmsh/core/Part.py
save ¶
save(file_path: str | Path | None = None, *, fmt: str | None = None, write_anchors: bool = True, _internal_autopersist: bool = False) -> Path
Export the Part geometry to a CAD file.
Calling save() with a user-supplied path transfers
ownership of the output file to the caller — any
tempfile previously created by auto-persist is cleaned up
immediately, and the library will never delete the new
output.
Parameters¶
file_path : str, Path, or None
Destination path. If None, defaults to
"{name}.step". The extension determines the format
unless fmt overrides it.
fmt : str, optional
Force format: "step" or "iges".
write_anchors : bool, default True
Write a JSON sidecar ({file_path}.apegmsh.json)
carrying the label -> center-of-mass map for every
user-named entity in the Part. This is what lets
assembly.parts.add(part) expose the instance's
labels via inst.by_label('name'). The sidecar is
silently omitted when the Part has no user-named
entities, so there is no cost for small throwaway
Parts. Pass write_anchors=False to suppress
unconditionally — useful when publishing a CAD file
to third-party tools that shouldn't see apeGmsh
metadata.
Returns¶
Path Resolved path of the written file.
Source code in src/apeGmsh/core/Part.py
320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 | |
g.parts — registry¶
apeGmsh.core._parts_registry.PartsRegistry ¶
Bases: _PartsFragmentationMixin
Instance management composite — registered as g.parts.
Source code in src/apeGmsh/core/_parts_registry.py
part ¶
Track entities created inside the block as a named part.
Yields the label string. After the block, any entities that exist now but didn't before are stored as an Instance.
Example::
with g.parts.part("beam"):
g.model.geometry.add_box(0, 0, 0, 1, 0.5, 10)
Source code in src/apeGmsh/core/_parts_registry.py
register ¶
register(name: str, dimtags: list[DimTag] | None = None, *, label: str | None = None, pg: str | None = None, dim: int | None = None) -> Instance
Tag existing entities under a part name.
Exactly one of dimtags, label, or pg must be given.
Parameters¶
name : str
Unique part name.
dimtags : list of (dim, tag), optional
Entities to assign directly. Also accepted positionally
as the second argument.
label : str, optional
Name of an apeGmsh label (g.labels) whose entities
should be adopted.
pg : str, optional
Name of a physical group (g.physical) whose entities
should be adopted.
dim : int, optional
Forwarded to g.labels.entities(label, dim=dim) when
using label= and the label spans multiple dimensions.
Returns¶
Instance
Source code in src/apeGmsh/core/_parts_registry.py
231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | |
from_model ¶
Adopt entities already in the Gmsh session as a named part.
Useful after g.model.io.load_step() or g.model.io.load_iges()
when you want the imported geometry tracked for constraints
and fragmentation.
Parameters¶
label : str Part name. dim : int, optional Dimension to adopt. If None, adopts all dimensions. tags : list[int], optional Specific entity tags to adopt. If None, adopts all untracked entities (not already assigned to a part).
Returns¶
Instance
Examples¶
::
# Load geometry, then adopt it
g.model.io.load_step("bracket.step")
g.parts.from_model("bracket")
# Adopt only specific volumes
g.parts.from_model("slab", dim=3, tags=[1, 2])
Source code in src/apeGmsh/core/_parts_registry.py
339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 | |
add ¶
add(part: 'Part', *, label: str | None = None, translate: tuple[float, float, float] = (0.0, 0.0, 0.0), rotate: tuple[float, ...] | None = None, highest_dim_only: bool = True) -> Instance
Import a saved Part into the session.
Parameters¶
part : Part
Must have been save()-d to disk.
label : str, optional
Auto-generated as "{part.name}_1" if omitted.
translate, rotate : placement transforms.
highest_dim_only : keep only highest-dim entities from the CAD.
Source code in src/apeGmsh/core/_parts_registry.py
add_plane_wave_box ¶
add_plane_wave_box(*, x: tuple[float, int], y: tuple[float, int], z, skin_thickness=None, center: tuple[float, float, float] = (0.0, 0.0, 0.0), rotation_z_deg: float = 0.0, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True)
Build a structured soil box wrapped by an ASDAbsorbingBoundary skin.
A plane-wave box is an axis-aligned structured soil box plus a
one-element-thick absorbing offset shell on its five truncation
faces (the local +Z top is the free surface and is never shelled).
Soil + shell form one rectangular block; the shell is decomposed into
face / vertical-edge / bottom-edge / bottom-corner regions, each tagged
with its OpenSees btype. The companion bridge element
(ASDAbsorbingBoundary3D) fans out one element per skin-region hex.
Built directly in the live session (no Part/STEP round-trip); pairs with
— but does not use — :meth:add_DRM_box. See ADR 0054.
Parameters¶
x, y : (size, n_elements)
Lateral soil extent (symmetric, centred) and element count.
z : (depth, n_elements) | list[(depth, n_elements)]
Vertical soil extent (downward, free surface at the top) and element
count. Pass a top → bottom list of layers for a stratified column
(e.g. z=[(15, 3), (25, 5)]); each layer gets its own soil + lateral
skin PGs, so it can take its own absorbing material via
ops.element.absorbing_boundary(materials=[m0, m1, …]) (ADR 0054 AB-1c).
skin_thickness : float | (tx, ty, tz) | None
Absorbing-skin thickness. None (default) matches the adjacent
soil element size per face. A skin much thicker than the adjacent
soil element warns (WarnAbsorbingSkinAspect) — it absorbs poorly.
center : (cx, cy, cz)
World location of the soil top-face centre (free surface).
rotation_z_deg : float
Must be 0 — the ASDAbsorbingBoundary3D element requires
boundary-face normals along global X or Y, so a rotated absorbing
box is rejected by the solver.
name, names, apply_transfinite :
PG-name prefix, per-PG override dict, and transfinite toggle —
mirroring :meth:add_DRM_box.
Returns¶
AbsorbingSkinResult
PG names (soil_pg, skin_pgs by btype, skin_all_pg,
bottom_pgs, free_surface_pg), axes, and placement.
Example¶
::
res = g.parts.add_plane_wave_box(
x=(605, 22), y=(605, 20), z=(420, 16),
)
g.mesh.generation.generate(dim=3)
# res.skin_pgs["L"], res.skin_all_pg, res.bottom_pgs ...
Source code in src/apeGmsh/core/_parts_registry.py
476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 | |
add_DRM_box_from_h5drm ¶
add_DRM_box_from_h5drm(*, h5drm: str, crd_scale: float = 1000.0, buffer: int = 0, absorbing: bool = False, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True)
Build a structured soil box matched to an .h5drm station grid.
Reads a ShakerMaker-style .h5drm DRM dataset and builds, in the live
session, a single transfinite hex box whose nodes land EXACTLY on the
dataset stations (so OpenSees' H5DRM node-matching is trivial), tags the
soil volume + the six outer boundary faces (the dataset "b" shell) as
physical groups, and returns the frame contract the matching
ops.pattern.H5DRM(...) consumes — so the user never re-derives the
km→m / centred / z-down handshake. See ADR 0066.
The dataset-keyed sibling of the parametric :meth:add_DRM_box (an SSI
inner/transition/outer layout, NOT keyed to a dataset). Geometry + PGs
only — assign the soil material and elements via the bridge
(ops.nDMaterial + ops.element.stdBrick(pg=result.soil_pg)).
Parameters¶
h5drm : str
Path to the .h5drm dataset (DRM_Data/{xyz,internal} +
DRM_Metadata/drmbox_x0). The station grid must be a complete,
uniform, isotropic regular grid.
crd_scale : float
Station-units → model-units scale. ShakerMaker stations are in km,
FE models in m ⇒ default 1000.0.
buffer : int
Number of exterior soil layers to add OUTWARD on the four sides + the
bottom (never the free surface), at the same grid spacing. 0
(default) builds just the inner DRM box. A free DRM box diverges
(rigid-body null-space excited by the residual), so a real run needs a
buffer + a far boundary: the buffer hexes carry only NON-dataset
nodes, so H5DRM excludes them from the effective-force set
(H5DRMLoadPattern.cpp:580). Apply the boundary on
result.exterior_pgs via the bridge (ops.fix for the validated
fixed far field).
absorbing : bool
When True (requires buffer >= 1), wrap the buffered box in a
one-element ASD absorbing skin (btype-tagged ghost layer) on the
sides + bottom — the production-SSI boundary (ADR 0054). The skin
sits on the buffer's outer (NON-dataset) faces, so it never lands on
the DRM b shell. result.skin is then an AbsorbingSkinResult
ready for ops.element.absorbing_boundary(skin=result.skin, ...) +
the staged s.activate_absorbing() flip.
name, names, apply_transfinite :
PG-name prefix, per-PG override dict, and transfinite toggle —
mirroring :meth:add_DRM_box.
Returns¶
DRMBoxFromH5Result
soil_pg, boundary_pgs (by face key), boundary_all_pg,
free_surface_pg, exterior_pgs (sides+bottom), the frame
contract (crd_scale / transform / x0 / center), and
the grid descriptor (origin / spacing / counts).
Example¶
::
drm = g.parts.add_DRM_box_from_h5drm("motions.h5drm")
g.mesh.generation.generate(dim=3)
fem = g.mesh.queries.get_fem_data(dim=3)
ops = apeSees(fem)
soil = ops.nDMaterial.ElasticIsotropic(E=E, nu=nu, rho=rho)
ops.element.stdBrick(pg=drm.soil_pg, material=soil)
with ops.pattern.H5DRM(h5drm="motions.h5drm"): # defaults match drm
pass
Source code in src/apeGmsh/core/_parts_registry.py
555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 | |
add_absorbing_shell ¶
add_absorbing_shell(*, box, element_size, skin_thickness=None, faces: tuple[str, ...] | None = None, layers: list[tuple[float, int]] | None = None, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True)
Weld a one-element ASDAbsorbingBoundary skin onto your own soil box.
The bring-your-own-box counterpart to :meth:add_plane_wave_box: you
build the soil box (its placement, PGs, the material/structure you later
put on it), and this wraps a one-element-thick absorbing skin onto its
five truncation faces — the local +Z top is the free surface and is
never shelled. Returns the same :class:AbsorbingSkinResult as
:meth:add_plane_wave_box, so the bridge element
(ops.element.absorbing_boundary) and the staged flip
(s.activate_absorbing) consume it identically. See ADR 0054 (AB-1b).
The skin discretization is size-based and (re)applied to box + skin
together after the weld: gmsh cannot report transfinite counts back and
the boolean fragment renumbers entities, so the box's prior mesh state
is irrelevant — this call makes box + skin one structured hex region.
Parameters¶
box :
The soil box — a PG / label name or a volume handle. Must resolve to
exactly one axis-aligned rectangular volume (fail-loud otherwise;
rotated / curved / multi-volume boxes are out of scope for this slice).
element_size : float | (sx, sy, sz)
Target soil element size; sets the structured node counts on box+skin.
skin_thickness : float | (tx, ty, tz) | None
Absorbing-skin thickness. None (default) matches element_size
per axis (one element thick).
faces : tuple[str, ...] | None
Restrict the skin to a subset of ("L","R","F","K","B") (e.g. omit a
symmetry plane). None (default) shells all five truncation faces.
layers : list[(depth, n_elements)] | None
Stratify the box top → bottom (depths must sum to the box's z-extent).
Slices the box into per-layer soil volumes and splits the lateral skin
per layer, so each layer can take its own absorbing material via
ops.element.absorbing_boundary(materials=[m0, m1, …]) (ADR 0054
AB-1c). None (default) = homogeneous.
name, names, apply_transfinite :
PG-name prefix, per-PG override dict, and transfinite toggle — mirroring
:meth:add_plane_wave_box. When box is a name, soil_pg is
reported as that name (no duplicate PG is created).
Returns¶
AbsorbingSkinResult
PG names (soil_pg, skin_pgs by btype, skin_all_pg,
bottom_pgs, free_surface_pg), axes, and placement.
Example¶
::
g.model.geometry.add_box(0, 0, -40, 20, 20, 40, label="soil")
res = g.parts.add_absorbing_shell(box="soil", element_size=2.5)
g.mesh.generation.generate(dim=3)
# res.skin_all_pg, res.bottom_pgs, res.free_surface_pg ...
Source code in src/apeGmsh/core/_parts_registry.py
646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 | |
add_plane_wave_box_2d ¶
add_plane_wave_box_2d(*, x: tuple[float, int], y, skin_thickness=None, center: tuple[float, float] = (0.0, 0.0), rotation_z_deg: float = 0.0, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True)
Build a 2D plane-strain soil box wrapped by an absorbing skin.
The 2D sibling of :meth:add_plane_wave_box (ADR 0054, AB-5): a
structured soil rectangle in the global X–Y plane at z = 0 (X
lateral, Y vertical, free surface at the local top y = 0) plus a
one-element-thick absorbing skin on its three truncation faces.
Skin regions carry the 2D btypes — L = min-X, R = max-X,
B = min-Y, corners BL/BR — and fan out to
ASDAbsorbingBoundary2D quads via
ops.element.absorbing_boundary(skin=…, thickness=…) (the 2D
element needs the out-of-plane slab thickness).
Parameters¶
x : (size, n_elements)
Lateral soil extent (symmetric, centred) and element count.
y : (depth, n_elements) | list[(depth, n_elements)]
Vertical soil extent (downward, free surface at the top). Pass a
top → bottom list of layers for a stratified column; each
layer gets its own soil + lateral skin PGs for per-layer
absorbing materials (materials=[…]).
skin_thickness : float | (tx, ty) | None
Absorbing-skin thickness. None (default) matches the
adjacent soil element size per face.
center : (cx, cy)
World location of the soil top-face centre (free surface).
rotation_z_deg : float
Must be 0 — the ASDAbsorbingBoundary2D element has no
distortion handling (it sizes itself from sorted nodal x/y
coordinates), so a rotated skin runs with silently wrong terms.
name, names, apply_transfinite :
PG-name prefix, per-PG override dict, and transfinite toggle.
Returns¶
AbsorbingSkinResult
Same shape as the 3D result (ndm == 2; free_surface_pg
is a dim-1 edge PG).
Example¶
::
res = g.parts.add_plane_wave_box_2d(x=(100, 20), y=(50, 10))
g.mesh.generation.generate(dim=2)
# res.skin_pgs -> {"B": ..., "L": ..., "R": ..., "BL": ..., "BR": ...}
Source code in src/apeGmsh/core/_parts_registry.py
add_absorbing_shell_2d ¶
add_absorbing_shell_2d(*, box, element_size, skin_thickness=None, faces: tuple[str, ...] | None = None, layers: list[tuple[float, int]] | None = None, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True)
Weld a one-element absorbing skin onto your own 2D soil rectangle.
The bring-your-own-box 2D entry (ADR 0054, AB-5), mirroring
:meth:add_absorbing_shell: box must resolve to exactly one
axis-aligned rectangular surface lying flat in a z = const
plane. The skin goes on the L/R/B truncation edges (the
top is the free surface); faces= restricts it (subset of
("L", "R", "B"), e.g. drop a symmetry edge). layers
stratifies box + lateral skin top → bottom (depths must sum to the
box's y-extent). Discretization is size-based and (re)applied to
box + skin together after the weld, as in 3D.
Returns¶
AbsorbingSkinResult
Same shape as the 3D result (ndm == 2).
Example¶
::
g.model.geometry.add_rectangle(0, -50, 0, 100, 50, label="soil")
res = g.parts.add_absorbing_shell_2d(box="soil", element_size=5.0)
g.mesh.generation.generate(dim=2)
Source code in src/apeGmsh/core/_parts_registry.py
add_DRM_box ¶
add_DRM_box(*, x_inner: tuple[float, int], x_layer: tuple[float, int], x_outer: tuple[float, int], y_inner: tuple[float, int], y_layer: tuple[float, int], y_outer: tuple[float, int], z_top: tuple[float, int], z_mid: tuple[float, int], z_bottom: tuple[float, int], center: tuple[float, float, float] = (0.0, 0.0, 0.0), rotation_z_deg: float = 0.0, name: str | None = None, names: dict[str, str] | None = None, apply_transfinite: bool = True, tag_line_pgs: bool = True)
Build, place, and tag a Domain-Reduction-Method soil box.
A DRM box is a layered solid with three concentric regions
per lateral axis (inner core | transition layer | outer
absorbing layer) and a downward Z stack (top | mid | bottom).
The classic symmetric case has 5 * 5 * 3 = 75 axis-aligned
hex sub-volumes, each meshed structured-hex with per-region
element counts.
center=(0, 0, 0) puts the top-face centre of the inner
box at the origin (free-surface convention). Rotation is
applied CCW about +Z at center; the rotated frame
survives every step (volume PGs, line PGs, transfinite
cascade) because we classify by world-coords transformed
back to the local frame.
Parameters¶
x_inner, x_layer, x_outer, y_inner, y_layer, y_outer :
(size, n_elements) tuples — symmetric layered lateral
axes. Each segment's element count drives the
transfinite cascade.
z_top, z_mid, z_bottom :
(size, n_elements) tuples — downward Z stack with the
free surface at z = 0 (inner-box top).
center :
World-coordinate location for the top-face centre of the
inner box.
rotation_z_deg :
CCW rotation about +Z applied at center, in
degrees.
name :
Instance label and default PG prefix. When None,
uses "drm_box". PGs default to inner_box /
transition_box / outer_box (and the matching
lines_* curves); when name is given they become
{name}_inner_box etc.
names :
Per-PG override dict. Keys: inner_pg, transition_pg,
outer_pg, line_pg_<region>_<axis> (e.g.
line_pg_inner_x, line_pg_top_z). Each override
replaces the entire PG name (the name prefix is
ignored for that key).
apply_transfinite :
When True (default), apply the structured-hex transfinite
cascade to every sub-volume using the per-region element
counts in axis_x / axis_y / axis_z.
tag_line_pgs :
When True (default), tag axis-parallel edges by region
into curve PGs lines_{region}_{axis}. When False,
result.line_pgs is empty.
Returns¶
DRMBoxResult
Frozen summary with PG names, Axis1D descriptors, the
applied center and rotation_z (in radians).
Example¶
::
res = g.parts.add_DRM_box(
x_inner=(605, 10), x_layer=(10, 1), x_outer=(20, 2),
y_inner=(605, 10), y_layer=(10, 1), y_outer=(20, 2),
z_top=(50, 5), z_mid=(50, 5), z_bottom=(200, 20),
center=(0, 0, 0),
)
g.mesh.generation.generate(dim=3)
# res.inner_pg == "inner_box", res.transition_pg == "transition_box",
# res.outer_pg == "outer_box"
Source code in src/apeGmsh/core/_parts_registry.py
855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 1151 1152 1153 1154 1155 1156 1157 1158 1159 1160 1161 1162 1163 1164 1165 1166 | |
import_step ¶
import_step(file_path: str | Path, *, label: str | None = None, translate: tuple[float, float, float] = (0.0, 0.0, 0.0), rotate: tuple[float, ...] | None = None, highest_dim_only: bool = True, heal: bool | float | str = False, dedupe: bool | float = False, properties: dict[str, Any] | None = None) -> Instance
Import a STEP or IGES file as a named instance.
Parameters¶
file_path : path
STEP (.step, .stp) or IGES (.iges, .igs) file.
label : str, optional
Auto-generated from file stem if omitted.
translate, rotate : placement transforms.
heal : bool, float, or "auto"
Heal the imported CAD immediately after import — same
semantics as :meth:g.model.io.load_step <_IO.load_step>:
True / "auto" use a scale-aware tolerance, a float
overrides, False (default) imports raw and emits a
:class:WarnGeomImportHealth advisory if slivers are found.
Best-effort for sidecar-carrying parts (healing renumbers,
so anchors rebind against the healed geometry).
dedupe : bool or float
Merge coincident entities after import (and after heal).
properties : arbitrary metadata.
Source code in src/apeGmsh/core/_parts_registry.py
build_node_map ¶
Partition mesh nodes by instance bounding box.
Returns {label: {node_tag, ...}}.
Source code in src/apeGmsh/core/_parts_registry.py
build_face_map ¶
Partition surface elements by instance node ownership.
Returns {label: face_connectivity_array}.
Source code in src/apeGmsh/core/_parts_registry.py
get ¶
Return the Instance registered under label.
Useful when you didn't store the return value of
:meth:add / :meth:import_step and want to access an
Instance later — e.g. to apply inst.edit.* transforms::
g.parts.add(beam, label="b1")
g.parts.get("b1").edit.translate(0, 0, 50)
Raises¶
KeyError
If no instance is registered under label. The error
message lists the available labels so you can spot a typo.
Source code in src/apeGmsh/core/_parts_registry.py
labels ¶
rename ¶
Rename an instance.
Raises¶
KeyError if old_label does not exist. ValueError if new_label already exists.
Source code in src/apeGmsh/core/_parts_registry.py
delete ¶
Remove an instance from the registry.
The entities remain in the Gmsh session — they become "untracked" and will appear under the Untracked group in the viewer's Parts tab.
Raises¶
KeyError if label does not exist.
Source code in src/apeGmsh/core/_parts_registry.py
Instance¶
apeGmsh.core._parts_registry.Instance
dataclass
¶
Instance(label: str, part_name: str, file_path: Path | None = None, entities: dict[int, list[int]] = dict(), translate: tuple[float, float, float] = (0.0, 0.0, 0.0), rotate: tuple[float, ...] | None = None, properties: dict[str, Any] = dict(), bbox: tuple[float, float, float, float, float, float] | None = None, label_names: list[str] = list())
Bookkeeping record for one part placement.
Attributes¶
label : unique name inside the session
part_name : name of the source Part or file stem
file_path : CAD file that was imported (None for inline parts)
entities : {dim: [tag, ...]} — updated in-place by fragment
translate : applied translation (dx, dy, dz)
rotate : applied rotation (angle_rad, ax, ay, az[, cx, cy, cz])
properties : arbitrary user metadata
bbox : axis-aligned bounding box (xmin, ymin, zmin, xmax, ymax, zmax)
label_names : label names created for this instance (Tier 1
naming, e.g. ["col_A.shaft", "col_A.top"]).
Populated by _import_cad when the Part's CAD
file has a .apegmsh.json sidecar carrying
label definitions. These are NOT solver-facing
physical groups — use g.labels.entities(name)
to resolve entity tags, and
g.labels.promote_to_physical(name) to create
a solver PG when ready.
Part edit composite — part.edit¶
apeGmsh.core._part_edit.PartEdit ¶
Whole-Part operations composite. Registered as part.edit.
Source code in src/apeGmsh/core/_part_edit.py
translate ¶
Translate every entity in the Part by (dx, dy, dz).
Parameters¶
dx, dy, dz : float Translation components in model units.
Returns¶
PartEdit
self for chaining.
Raises¶
RuntimeError If the Part's session is not active.
Source code in src/apeGmsh/core/_part_edit.py
rotate ¶
rotate(angle: float, ax: float, ay: float, az: float, *, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'PartEdit'
Rotate every entity by angle (radians) about an axis.
Parameters¶
angle : float
Rotation angle in radians. Right-hand rule: thumb
along (ax, ay, az), fingers curl positive.
ax, ay, az : float
Axis direction. Auto-normalized by gmsh.
center : (cx, cy, cz), default (0, 0, 0)
Point that the axis passes through.
Returns¶
PartEdit
self for chaining.
Source code in src/apeGmsh/core/_part_edit.py
mirror ¶
mirror(*, plane: str | None = None, normal: tuple[float, float, float] | None = None, point: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'PartEdit'
Reflect every entity across a plane.
Specify the plane in one of two equivalent ways:
plane="xy"/"xz"/"yz"— coordinate plane throughpoint(default origin).normal=(nx, ny, nz)— explicit plane normal; the plane passes throughpointperpendicular to this vector.
Pass exactly one of plane or normal.
Returns¶
PartEdit
self for chaining.
Raises¶
ValueError
If neither or both of plane / normal are given,
or plane is not one of the recognized names.
Source code in src/apeGmsh/core/_part_edit.py
scale ¶
Uniform scale every entity by factor about center.
factor=2.0 doubles size, factor=0.001 is mm→m.
Source code in src/apeGmsh/core/_part_edit.py
dilate ¶
dilate(sx: float, sy: float, sz: float, *, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'PartEdit'
Non-uniform scale by (sx, sy, sz) about center.
Source code in src/apeGmsh/core/_part_edit.py
affine ¶
Apply a general 4×4 affine transform.
Parameters¶
matrix4x4 : 16-element sequence, 4×4 nested list, or ndarray
Row-major. Last row typically [0, 0, 0, 1] (gmsh
ignores it but it must be present).
Source code in src/apeGmsh/core/_part_edit.py
delete ¶
Remove every entity from the Part's Gmsh session.
Useful when scrapping and rebuilding within the same with
block. Labels that pointed at the deleted entities are now
stale.
Returns¶
None
Source code in src/apeGmsh/core/_part_edit.py
copy ¶
Create a duplicate Part with a new label.
The duplicate is a brand-new :class:Part with its own
STEP file (and sidecar copy if present), _owns_file=True
so its tempfile is reclaimed when it's garbage-collected.
The duplicate is not entered as an active session — it
sits on disk ready to be consumed by g.parts.add() or
re-entered with with new_part: if you need to edit it
further.
Works whether the source Part is currently active or not:
- Active source — current geometry is dumped to a fresh
tempfile via
gmsh.write(does not disturb the source'sfile_path). - Inactive source — the existing STEP and sidecar are
file-copied via
shutil.
Parameters¶
label : str, required New Part name. If the name is already in use by another live Part in this process, a 4-char random suffix is appended and a warning is emitted.
Returns¶
Part
New Part with has_file=True, not yet active.
Raises¶
ValueError
If label is empty.
RuntimeError
If the source has no current geometry to copy.
Source code in src/apeGmsh/core/_part_edit.py
346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 | |
pattern_linear ¶
Create n translated copies along a line.
Each copy i (1..n) is shifted by (i*dx, i*dy, i*dz)
from the source. The source itself is not modified and
is not included in the returned list.
Source Part must be non-active (outside its with block).
Parameters¶
label : str
Base name. Generated names are {label}_1 … {label}_n.
Clashes get a random suffix per item, with a warning.
n : int
Number of copies (>= 1).
dx, dy, dz : float
Per-step translation increment.
Returns¶
list[Part]
n new Parts, each with its translated geometry baked
into its own STEP file.
Source code in src/apeGmsh/core/_part_edit.py
pattern_polar ¶
pattern_polar(*, label: str, n: int, axis: tuple[float, float, float], total_angle: float, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> list['Part']
Create n rotated copies around an axis.
Each copy i (1..n) is rotated by i * total_angle / n
about the axis through center. total_angle is in
radians. For a full revolution use total_angle=2*pi;
for n=4 evenly spaced this gives 90° increments.
Source Part must be non-active.
Parameters¶
label : str
Base name; copies labeled {label}_1 … {label}_n.
n : int
Number of copies.
axis : (ax, ay, az)
Rotation axis direction.
total_angle : float
Total swept angle in radians (last copy at this angle).
center : (cx, cy, cz), default (0, 0, 0)
Point on the rotation axis.
Source code in src/apeGmsh/core/_part_edit.py
align_to ¶
align_to(other: 'Part', *, source: str, target: str, on: 'str | tuple[str, ...]', offset: float = 0.0) -> 'PartEdit'
Translate this Part so its source label aligns with
other's target label along the chosen axes.
Computes source centroid in this Part's live session,
reads target centroid from other's STEP sidecar (so
other must have been saved — auto-persist counts), then
applies the masked translation via :meth:translate.
Parameters¶
other : Part
Reference Part. Must have a saved sidecar (has_file
true and a .apegmsh.json written next to the STEP).
Passing an Instance is rejected — use
:meth:Instance.edit.align_to for the assembly side.
source : str
Label name on this Part (the feature that moves).
target : str
Label name on other (the feature it lands on).
on : {"x", "y", "z", "all"} or iterable of those
Axes on which to match centroids. Other axes untouched.
offset : float, default 0.0
Signed gap along the single on axis. Combining a
non-zero offset with multi-axis on raises ValueError.
Returns¶
PartEdit
self for chaining.
Raises¶
RuntimeError
If this Part is not active or other has no sidecar.
TypeError
If other is not a Part (e.g. an Instance).
LookupError
If source or target cannot be resolved.
Source code in src/apeGmsh/core/_part_edit.py
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 | |
align_to_point ¶
align_to_point(point: tuple[float, float, float], *, source: str, on: 'str | tuple[str, ...]', offset: float = 0.0) -> 'PartEdit'
Translate this Part so its source label centroid lands
at point along the chosen axes.
Like :meth:align_to but the target is a coordinate rather
than another Part's labeled feature. No sidecar lookup
needed.
Parameters¶
point : (px, py, pz)
World point in this Part's local frame (i.e. the same
frame in which source lives).
source : str
Label on this Part.
on : {"x", "y", "z", "all"} or iterable
Axes to align.
offset : float, default 0.0
Signed gap along the single on axis.
Source code in src/apeGmsh/core/_part_edit.py
Instance edit composite — inst.edit¶
apeGmsh.core._instance_edit.InstanceEdit ¶
Operations on a placed :class:Instance. Registered as inst.edit.
Source code in src/apeGmsh/core/_instance_edit.py
translate ¶
Translate the instance by (dx, dy, dz).
Returns self for chaining.
Source code in src/apeGmsh/core/_instance_edit.py
rotate ¶
rotate(angle: float, ax: float, ay: float, az: float, *, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'InstanceEdit'
Rotate the instance by angle (radians) about an axis.
See :meth:Part.edit.rotate for the parameter reference.
Source code in src/apeGmsh/core/_instance_edit.py
mirror ¶
mirror(*, plane: str | None = None, normal: tuple[float, float, float] | None = None, point: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'InstanceEdit'
Reflect the instance across a plane.
See :meth:Part.edit.mirror for the parameter reference.
Source code in src/apeGmsh/core/_instance_edit.py
scale ¶
Uniform scale by factor about center.
Source code in src/apeGmsh/core/_instance_edit.py
dilate ¶
dilate(sx: float, sy: float, sz: float, *, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> 'InstanceEdit'
Non-uniform scale by (sx, sy, sz) about center.
Source code in src/apeGmsh/core/_instance_edit.py
affine ¶
Apply a general 4×4 affine transform.
See :meth:Part.edit.affine for the parameter reference.
Source code in src/apeGmsh/core/_instance_edit.py
delete ¶
Remove the instance's entities from the assembly session
and unregister from g.parts._instances.
After delete(), subsequent calls on this edit object
raise RuntimeError. The label is freed and may be reused
by a fresh parts.add().
Source code in src/apeGmsh/core/_instance_edit.py
copy ¶
Duplicate this instance's geometry into a new Instance.
Uses gmsh.model.occ.copy() to clone the dimtags (the new
entities live in the same assembly session). All Part-level
labels carried by this instance are recreated under the new
instance's label prefix — so e.g. b1.top_flange becomes
b2.top_flange on the copy.
Parameters¶
label : str, required New instance label. If the requested label is already taken in this session, a 4-character random hex suffix is appended and a warning emitted.
Returns¶
Instance
The new Instance, registered in g.parts and ready
for further edits.
Raises¶
RuntimeError
If this instance has already been deleted.
ValueError
If label is empty.
Source code in src/apeGmsh/core/_instance_edit.py
pattern_linear ¶
Create n translated copies of this instance.
Each copy i (1..n) is shifted by (i*dx, i*dy, i*dz)
from the source. The source itself is not modified.
Returns a list of n new :class:Instance objects, all
registered in g.parts.
Source code in src/apeGmsh/core/_instance_edit.py
pattern_polar ¶
pattern_polar(*, label: str, n: int, axis: tuple[float, float, float], total_angle: float, center: tuple[float, float, float] = (0.0, 0.0, 0.0)) -> list['Instance']
Create n rotated copies of this instance.
Each copy i (1..n) is rotated by i * total_angle / n
about axis through center. total_angle is in
radians (2*pi for a full revolution).
Source code in src/apeGmsh/core/_instance_edit.py
align_to ¶
align_to(other: 'Instance', *, source: str, target: str, on: 'str | tuple[str, ...]', offset: float = 0.0) -> 'InstanceEdit'
Translate this instance so its source label aligns
with other's target label along the chosen axes.
Both instances must live in the same session (the assembly). Both centroids are read live from gmsh — no sidecar lookup needed.
Parameters¶
other : Instance
Reference instance. Cross-Part alignment is rejected
(Parts live in their own sessions; use Part.edit.align_to
instead).
source : str
Label suffix on this instance (e.g. "top_flange").
Resolved to f"{self.label}.{source}".
target : str
Label suffix on other, resolved to
f"{other.label}.{target}".
on : {"x","y","z","all"} or iterable
Axes on which to match centroids.
offset : float, default 0.0
Signed gap along the (single) on axis.
Returns¶
InstanceEdit
self for chaining.
Source code in src/apeGmsh/core/_instance_edit.py
align_to_point ¶
align_to_point(point: tuple[float, float, float], *, source: str, on: 'str | tuple[str, ...]', offset: float = 0.0) -> 'InstanceEdit'
Translate this instance so its source label centroid
lands at point along the chosen axes.
Source code in src/apeGmsh/core/_instance_edit.py
Labels¶
apeGmsh.core.Labels.Labels ¶
Bases: _HasLogging
Geometry-time entity naming composite (g.labels).
Backed by Gmsh physical groups with an internal _label:
prefix. See the module docstring for the two-tier naming
architecture.
Source code in src/apeGmsh/core/Labels.py
add ¶
Create a label for the given entities.
If a label with the same name and dimension already exists, the tags are merged into the existing PG rather than creating a duplicate.
Parameters¶
dim : int Entity dimension (0–3). tags : list[int] Entity tags to label. name : str Human-readable label name (without prefix).
Returns¶
int The Gmsh physical-group tag backing this label.
Source code in src/apeGmsh/core/Labels.py
1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141 1142 1143 1144 1145 1146 1147 1148 1149 1150 | |
entities ¶
Return entity tags for a label.
Parameters¶
name : str
Label name (without prefix).
dim : int, optional
Restrict to a single dimension. When None, searches
all dimensions. If the label exists at exactly one
dimension, returns those entities. If it exists at
multiple dimensions, raises ValueError asking the
caller to specify dim=.
Returns¶
list[int] Entity tags.
Raises¶
KeyError
When no label with this name exists.
ValueError
When dim=None and the label exists at multiple
dimensions.
Source code in src/apeGmsh/core/Labels.py
1170 1171 1172 1173 1174 1175 1176 1177 1178 1179 1180 1181 1182 1183 1184 1185 1186 1187 1188 1189 1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201 1202 1203 1204 1205 1206 1207 1208 1209 1210 1211 1212 1213 1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233 1234 1235 1236 1237 1238 1239 1240 1241 1242 1243 1244 | |
get_all ¶
Return all label names (without prefix).
Parameters¶
dim : int, default -1
Filter by dimension. -1 returns all dimensions.
Source code in src/apeGmsh/core/Labels.py
summary ¶
DataFrame describing every label in the model.
Mirrors :meth:PhysicalGroups.summary but returns only the
internal label PGs (with the _label: prefix stripped).
Returns¶
pd.DataFrame indexed by (dim, pg_tag) with columns
name, n_entities, entity_tags.
Source code in src/apeGmsh/core/Labels.py
has ¶
Return True if a label with this name exists.
Source code in src/apeGmsh/core/Labels.py
remove ¶
Delete a label (and its backing physical group).
Parameters¶
name : str Label name (without prefix). dim : int, optional Restrict to a single dimension. When None, removes the label at all dimensions where it exists.
Raises¶
KeyError When no label with this name exists.
Source code in src/apeGmsh/core/Labels.py
rename ¶
Rename a label in place, preserving its entity membership.
Parameters¶
old_name : str Current label name (without prefix). new_name : str New label name (without prefix). dim : int, optional Restrict to a single dimension. When None, renames the label at all dimensions where it exists.
Raises¶
KeyError When no label with old_name exists.
Source code in src/apeGmsh/core/Labels.py
promote_to_physical ¶
Copy a label's entities into a solver-facing physical group.
The label remains intact — this is a copy, not a move.
The new PG is visible to g.physical, fem.physical,
and the OpenSees exporter.
Parameters¶
label_name : str Label to promote. pg_name : str, optional Name for the new physical group. Defaults to the label name (without prefix). dim : int, optional Dimension to promote. Required when the label exists at multiple dimensions.
Returns¶
int Physical-group tag of the new PG.
Source code in src/apeGmsh/core/Labels.py
1399 1400 1401 1402 1403 1404 1405 1406 1407 1408 1409 1410 1411 1412 1413 1414 1415 1416 1417 1418 1419 1420 1421 1422 1423 1424 1425 1426 1427 1428 1429 1430 1431 1432 1433 1434 1435 1436 1437 1438 1439 1440 1441 1442 1443 1444 1445 1446 1447 1448 1449 1450 1451 1452 1453 1454 1455 1456 1457 1458 1459 | |
reverse_map ¶
Build a (dim, tag) -> label_name reverse lookup.
Useful when callers need to find labels for many entities at
once without repeated entities() calls.
Parameters¶
dim : int, default -1
Filter by dimension. -1 returns all dimensions.
Source code in src/apeGmsh/core/Labels.py
labels_for_entity ¶
Return all label names that contain the given entity.