Selection of shapes and naming modes

from qtcad.builder import Builder, Polygon, Mask

Creating the demonstration shapes

We begin with creating three parallel boxes as a demonstration.

box_mask = Mask("boxes")

for x in [-1, 0, 1]:
    box = Polygon.box(0.4, 10, name=f"box_{x}").centered().translated(x, 0)
    box_mask.add_shape(box)

Finally, we add a box with a duplicate name.

box_mask.add_shape(box.translated(1, 0))
Polygon(name='box_1', id=3, hull=[(1.8, -5), (1.8, 5), (2.2, 5), (2.2, -5)], holes=[])

Selecting masks

First, we add the mask to the newly instantiated Builder.

builder = Builder().add_mask(box_mask)
builder.print_mask_tree()
Layout
└── Mask 0 "boxes"
    ├── 0  Polygon  "box_-1"
    ├── 1  Polygon  "box_0"
    ├── 2  Polygon  "box_1"
    └── 3  Polygon  "box_1"

<qtcad.builder.builder.Builder object at 0x7f17465fb890>

As we see above, shapes are grouped into “Masks”. Each mask and shape may be identified by their name or ID. The name of a shape doesn’t have to be unique and can be set in the shape constructor (see qtcad.builder.Polygon) and as a user property in the layout file (MaskContainer.load_layout). To select all shapes in a mask we employ qtcad.builder.Builder.use_mask.

Builder.use_mask(mask: int | str | Mask | Callable[[Mask], bool] | list[int | str | Mask | Callable[[Mask], bool]]) Self

[🔧 Modifier] Switch to use another mask as the working one. The mask parameter can be either the ID or name of a mask or a function taking a mask and returning True if the mask is to be selected. A list of the above can also be provided (see MaskContainer.get_mask). If multiple masks are selected, they are merged into a temporary mask.

This operation also selects all shapes in the mask (see also use_shape).

Parameters:

mask – Name or identifier (index) of the mask.

builder.use_mask("boxes")
[02:00:22 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750

<qtcad.builder.builder.Builder object at 0x7f17465fb890>

Group from shape names

If we now add entities the selected shapes into the model using qtcad.builder.Builder.group_from_shape the resulting entities will be added to physical groups named after the shapes they are created from.

Builder.group_from_shape() Self

[🔧 Modifier] Any entities created after calling this method will be assigned to a physical group that is named according the name of the shape that the entity is created from.

builder.group_from_shape().add_surface()

builder.view(
    surfaces=True,
    volume_labels=False,
    surface_labels=True,
    angles=(-45, 0, 45),
    save="figs/from_shape.svg",
    font_size=30,
)
naming
              INFO     Creating a surface from shape 0 with name 'box_-1' from mask 'boxes' at z=0                                                         builder.py:2386
              INFO     Creating new physical group with name 'box_-1'                                                                                      builder.py:2482
              INFO     Creating a surface from shape 1 with name 'box_0' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_0'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 2 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_1'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/from_shape.svg                builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17465fb890>

We can always take individual entities and manually assign them to groups with qtcad.builder.Builder.assign_to_group. Entity tags can be obtained through the Gmsh GUI or by using qtcad.builder.Builder.get_group to get the entities in a group.

Builder.assign_to_group(dim: int, tag: list[int] | int, group: str) Self

[⚡ Utility] Assign one or multiple entities tag of dimension dim to group group. If the group does not exist, it will be created. If there already exists a group with this name, but in another dimension, then make the name unique by appending a suffix.

print(builder.get_group("box_1"))
builder.assign_to_group(2, 4, "custom_group").view(
    surfaces=True,
    volume_labels=False,
    surface_labels=True,
    angles=(-45, 0, 45),
    save="figs/manual_group_move.svg",
    font_size=30,
)
naming
Physical Group 'box_1' of dimension 2
[02:00:23 PM] INFO     Creating new physical group with name 'custom_group'                                                                                builder.py:2482
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/manual_group_move.svg         builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17465fb890>

Note that the group names have to be unique in Gmsh. If the group already exists, a dimension-specific suffix is appended to the group name. Let us demonstrate this behavior by extruding a volume in top of the surfaces we just created (in qtcad.builder.Builder.fill_mode to preserve the surface naming).

builder.fill_mode().extrude(0.1).displace_mode()
builder.view(
    surfaces=True,
    volume_labels=True,
    surface_labels=False,
    angles=(-45, 0, 45),
    save="figs/vol_ambig.svg",
    font_size=30,
).finalize()
naming
[02:00:24 PM] INFO     Extruding shape 0 from mask 'boxes' with name 'box_-1' by 0.1 at z=0                                                                builder.py:2695
              INFO     Creating new physical group with name 'box_-1_bottom'                                                                               builder.py:2482
              INFO     Creating new physical group with name 'box_-1_side'                                                                                 builder.py:2482
              INFO     Creating new physical group with name 'box_-1_top'                                                                                  builder.py:2482
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_-1__vol'                                                                                 builder.py:2482
/home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/src/qtcad/builder/core/builder.py:3552: UserWarning: Removing orphaned entities: [(2, 3), (2, 4), (2, 2)]
  warnings.warn(
              INFO     Extruding shape 1 from mask 'boxes' with name 'box_0' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Creating new physical group with name 'box_0_bottom'                                                                                builder.py:2482
              INFO     Creating new physical group with name 'box_0_side'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_0_top'                                                                                   builder.py:2482
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_0__vol'                                                                                  builder.py:2482
              INFO     Extruding shape 2 from mask 'boxes' with name 'box_1' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Creating new physical group with name 'box_1_bottom'                                                                                builder.py:2482
              INFO     Creating new physical group with name 'box_1_side'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_1_top'                                                                                   builder.py:2482
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_1__vol'                                                                                  builder.py:2482
              INFO     Extruding shape 3 from mask 'boxes' with name 'box_1' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
[02:00:25 PM] INFO     Setting z-coordinate to 0.1                                                                                                          builder.py:849
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/vol_ambig.svg                 builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17465fb890>

Above, the resulting volumes have been added to physical groups with the suffix "__vol" as the name without the suffix is already taken up by the surfaces. If we start from scratch and extrude without adding the surfaces first, the volume groups will not have the suffix:

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .extrude(0.1)
    .view(
        surfaces=True,
        volume_labels=True,
        surface_labels=False,
        angles=(-45, 0, 45),
        save="figs/vol_nonambig.svg",
        font_size=30,
    )
    .finalize()
)
naming
[02:00:30 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Extruding shape 0 from mask 'boxes' with name 'box_-1' by 0.1 at z=0                                                                builder.py:2695
              INFO     Creating new physical group with name 'box_-1_bottom'                                                                               builder.py:2482
              INFO     Creating new physical group with name 'box_-1_side'                                                                                 builder.py:2482
              INFO     Creating new physical group with name 'box_-1_top'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_-1'                                                                                      builder.py:2482
              INFO     Extruding shape 1 from mask 'boxes' with name 'box_0' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Creating new physical group with name 'box_0_bottom'                                                                                builder.py:2482
              INFO     Creating new physical group with name 'box_0_side'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_0_top'                                                                                   builder.py:2482
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_0'                                                                                       builder.py:2482
              INFO     Extruding shape 2 from mask 'boxes' with name 'box_1' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Creating new physical group with name 'box_1_bottom'                                                                                builder.py:2482
              INFO     Creating new physical group with name 'box_1_side'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_1_top'                                                                                   builder.py:2482
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_1'                                                                                       builder.py:2482
              INFO     Extruding shape 3 from mask 'boxes' with name 'box_1' by 0.1 at z=0                                                                 builder.py:2695
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Setting z-coordinate to 0.1                                                                                                          builder.py:849
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/vol_nonambig.svg              builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17465fb250>

Group from mask name

On the other hand, if we now add entities the selected shapes into the model using qtcad.builder.Builder.group_from_mask the resulting entities will be added to physical groups named after the mask they are created from.

Builder.group_from_mask() Self

[🔧 Modifier] Any entities created after calling this method will be assigned to a physical group that is named according to the name of the mask they belong to.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .group_from_mask()
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/from_mask.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:33 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 0 with name 'box_-1' from mask 'boxes' at z=0                                                         builder.py:2386
              INFO     Creating new physical group with name 'boxes'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 1 with name 'box_0' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating a surface from shape 2 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
[02:00:34 PM] INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/from_mask.svg                 builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17465fa0d0>

As promised, the shapes are now named after the mask "boxes".

Manual group naming

Finally we can manually specify a physical group name for subsequent operations through qtcad.builder.Builder.set_group_name.

Builder.set_group_name(name: str) Self

[🔧 Modifier] Any entities created after calling this method will be assigned to the physical group with name name.

See also group_from_mask and group_from_shape.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .set_group_name("custom_name")
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/manual_name.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:36 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 0 with name 'box_-1' from mask 'boxes' at z=0                                                         builder.py:2386
              INFO     Creating new physical group with name 'custom_name'                                                                                 builder.py:2482
              INFO     Creating a surface from shape 1 with name 'box_0' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating a surface from shape 2 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/manual_name.svg               builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fa210>

Selecting individual shapes

Using qtcad.builder.Builder.use_shape we can select a single shape by name or id

Builder.use_shape(shape: str | int | list[str | int] | Callable[[Shape], bool] | None = None) Self

[🔧 Modifier] Select shapes in the current mask that will be used for all following operations.

Parameters:

shape – Name or index or a list of names and indices of the shape(s) to be selected. If None, all shapes in the current mask will be selected. Alternatively, a callable can be provided which accepts a shape and returns True if it should be selected and False otherwise.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .use_shape("box_0")
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/one_shape.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:38 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 1 with name 'box_0' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Creating new physical group with name 'box_0'                                                                                       builder.py:2482
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/one_shape.svg                 builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464f9d10>

If selection is performed using a name, all shapes in the currently selected mask are selected.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .use_shape("box_1")
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/duplicate_shape.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:41 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 2 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Creating new physical group with name 'box_1'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/duplicate_shape.svg           builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fbb10>

In this case, we can select an individual shape using its numerical id.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .use_shape(3)
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/duplicate_shape_select.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:43 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Creating new physical group with name 'box_1'                                                                                       builder.py:2482
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/duplicate_shape_select.svg    builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fbc50>

We can also pass multiple names/indices to select multiple shapes at once.

(
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .use_shapes(["box_-1", "box_0", 3])
    .add_surface()
    .view(
        surfaces=True,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/select_multi.svg",
        font_size=30,
    )
).finalize()
naming
[02:00:45 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Creating a surface from shape 1 with name 'box_0' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Creating new physical group with name 'box_0'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 3 with name 'box_1' from mask 'boxes' at z=0                                                          builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_1'                                                                                       builder.py:2482
              INFO     Creating a surface from shape 0 with name 'box_-1' from mask 'boxes' at z=0                                                         builder.py:2386
              INFO     Fragmenting...                                                                                                                    fragmenter.py:234
              INFO     Creating new physical group with name 'box_-1'                                                                                      builder.py:2482
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/select_multi.svg              builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fbd90>

Group manipulation

There are various methods to manipulate groups after they have been created. For example, we can rename groups using qtcad.builder.Builder.rename_group.

Builder.rename_group(old: str, new: str) Self

[⚡ Utility] Rename an existing physical group old to new.

Raises:

ValueError – If the group already exists.

builder = (
    Builder()
    .add_mask(box_mask)
    .use_mask("boxes")
    .use_shapes(["box_0"])
    .extrude(1)
    .view(
        surfaces=False,
        volume_labels=True,
        surface_labels=False,
        angles=(-45, 0, 45),
        save="figs/rename_before.svg",
        font_size=30,
    )
)

builder.rename_group("box_0", "new_name").view(
    surfaces=False,
    volume_labels=True,
    surface_labels=False,
    angles=(-45, 0, 45),
    save="figs/rename_after.svg",
    font_size=30,
)
naming
naming
[02:00:48 PM] INFO     Using mask 'boxes'                                                                                                                   builder.py:750
              INFO     Extruding shape 1 from mask 'boxes' with name 'box_0' by 1 at z=0                                                                   builder.py:2695
              INFO     Creating new physical group with name 'box_0_bottom'                                                                                builder.py:2482
              INFO     Creating new physical group with name 'box_0_side'                                                                                  builder.py:2482
              INFO     Creating new physical group with name 'box_0_top'                                                                                   builder.py:2482
              INFO     Creating new physical group with name 'box_0'                                                                                       builder.py:2482
              INFO     Setting z-coordinate to 1                                                                                                            builder.py:849
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/rename_before.svg             builder.py:1863
[02:00:49 PM] INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/rename_after.svg              builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fbed0>

Similarly, we can delete groups using qtcad.builder.Builder.dissolve_physical_group.

Builder.dissolve_physical_group(group: str | list[str] | Callable[[PhysicalGroup], bool]) Self

[⚡ Utility] Dissolve (remove) the physical group(s) with name name (see get_group). The entities in the group are not removed from the model.

Parameters:

group – The name of the group to dissolve, a list of names or a callable that takes a string (the group name) and returns True if the group should be dissolved. See get_group for details.

(
    builder.dissolve_physical_group("new_name").view(
        surfaces=False,
        volume_labels=True,
        surface_labels=False,
        angles=(-45, 0, 45),
        save="figs/dissolve.svg",
        font_size=30,
    )
)

# We can also merge groups using :any:`qtcad.builder.Builder.merge_physical_groups`.
#
# .. automethod:: qtcad.builder.Builder.merge_physical_groups
#    :no-index:
#
# As an example, let us merge all the surface groups of the above volume into a single group.

(
    builder.merge_groups(
        lambda group: group.name.startswith("box_0"), "box_0_hull"
    ).view(
        surfaces=False,
        volume_labels=False,
        surface_labels=True,
        angles=(-45, 0, 45),
        save="figs/merge.svg",
        font_size=30,
    )
).finalize()
naming
naming
[02:00:50 PM] INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/dissolve.svg                  builder.py:1863
[02:00:51 PM] INFO     Merging groups ['box_0_bottom', 'box_0_side', 'box_0_top'] into 'box_0_hull'                                                        builder.py:1377
              INFO     Creating new physical group with name 'box_0_hull'                                                                                  builder.py:2482
              INFO     Saving /home/hiro/Documents/org/roam/code/qtcad_flake/qtcad/packages/builder/examples/operations/figs/merge.svg                     builder.py:1863

<qtcad.builder.builder.Builder object at 0x7f17464fbed0>

Above, we merged all groups whose name starts with "box_0" into a new group using a lambda function as a filter. Alternatively, we could have passed a list of group names to be merged.

Total running time of the script: (0 minutes 31.472 seconds)

Gallery generated by Sphinx-Gallery