Note
Go to the end to download the full example code.
Extrusion and fragmentation modes
This example demonstrates the qtcad.builder.Builder.extrude operation
and the different strategies that are available when merging new
objects into the existing geometry (see
qtcad.builder.FragmentationMode) on the example of a smiling
face.
Initialization
from qtcad.builder import Builder, MeshAlgorithm3D
from qtcad.builder import Mask, Polygon
Initializing the Builder
Below, we initialize a new instance of qtcad.builder.Builder and load the
shapes we’ll later user from an OASIS layout. An overview of the
currently registered masks and shapes can be obtained through
qtcad.builder.Builder.print_mask_tree(). We then select all masks and thus all
shapes in all masks and visualize them using
qtcad.builder.Builder.view_shapes().
- Builder.load_layout(file_path: str | Path, cell_name: str | None = None, name_property: str = 'name', plot_when_fail: bool = False, cache: bool = True, path_tolerance: float | None = None) Self
[⚡ Utility] Read the cell named
cell_name(default is the top cell) from aGDSor anOASISfilefile_pathand parse its structure. Each layer will become aMaskappropriately named and the ID from the layout file (provided there are no conflicting IDs) containing polygons for each shape found in the layer. The name of each shape is taken from the user propertyname_property. The length units in the file are assumed to be10**length_unit_exponentmeters (default is nanometres).- Parameters:
file_path – The path to the
GDSorOASISfile.cell_name – The name of the cell that the device is specified in. If
None, use the top cell.name_property – The user property that contains the name of a shape.
plot_when_fail – Whether to show debugging plots when the parsing of the imported shapes fails. Currently these plots will show polygons whose hull and holes cannot be differentiated.
path_tolerance – Tolerance used when converting paths to polygons (smaller = more points).
Fig. 18 The photomask layout for the smiling face.
builder = (
Builder()
.load_layout("./layouts/smile.oas")
.print_mask_tree()
.use_all_masks()
.view_shapes(
save="figs/shapes_in_mask.svg",
font_size=20,
)
)
[14:51:43] INFO Loading layout from cache misc.py:266
/home/hiro/.cache/qtcad/builder/layout_81b718d3fc80
f7ba952a6a2af4b5e620aad1f3566effb66f7b572da7713de5f
5_None_name_None_-9.pkl
INFO Adding mask 'footprint' from cache masks.py:776
INFO Adding mask 'smile' from cache masks.py:776
Layout
├── Mask 1 "footprint"
│ └── 0 Polygon "footprint" (16)
└── Mask 2 "smile"
├── 0 Polygon "left_eye" (12)
├── 1 Polygon "right_eye" (10)
└── 2 Polygon "mouth" (11)
INFO Using mask 'footprint-smile' (implicitly _builder.py:827
selecting shapes [Polygon 2 'right_eye',
Polygon 3 'mouth', Polygon 1 'left_eye',
Polygon 0 'footprint'])
INFO Viewing shapes: [Polygon 2 'right_eye', _builder.py:2055
Polygon 3 'mouth', Polygon 1 'left_eye',
Polygon 0 'footprint'] using a temporary
builder instance
The shape names are either read from a user property (name by
default) or from text labels (as is the case for the right_eye
here).
Building the geometry
Next, select the mask "footprint" (the outline of the smiling
face) extrude that mask to create a volume. We used
qtcad.builder.Builder.set_mesh_size to set the mesh characteristic length
and qtcad.builder.Builder.set_mesh_size to tell the Builder not to
try and lower the mesh size based on the resulting volume size. We
use qtcad.builder.Builder.differentiate_hull_surfaces with the argument
False to make sure all the hull surfaces get the same name.
- Builder.extrude(height: float) Self
[🏗️ Operation] Create a solid extrusion of a plane shape of height
heightat the currentz_coordinate. Also advances the currentz_coordinatebyheight. The resulting surfaces are automatically named using the current naming strategy by appending unique suffixes.- Parameters:
height – The distance by which the shape will be extruded into three dimensional space.
(
builder.set_mesh_size(10)
.set_min_elements(1)
.differentiate_hull_surfaces(False)
.use_mask("footprint")
.set_z(-10)
.extrude(20)
)
[14:51:44] INFO Using mask 'footprint' (implicitly selecting _builder.py:827
shapes [Polygon 0 'footprint'])
INFO Setting z-coordinate to -10 _builder.py:929
INFO Extruding shape 0 from mask 'footprint' with _builder.py:2895
name 'footprint' by 20 at z=-10
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_hull'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'footprint'
INFO Setting z-coordinate to 10 _builder.py:929
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
The above results in a volume named footprint (named after the shape).
builder.view(
surfaces=False,
volume_labels=True,
angles=(-45, 0, 45),
save="figs/extrusion_foot.svg",
font_size=20,
surface_labels=False,
)
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
The extrusion advances the current z-coordinate to the top of the extruded volume.
print(builder.get_z())
10
The surfaces of created by the extrusion operation are automatically added to physical groups named according to the volume:
builder.view(
surfaces=True,
volume_labels=False,
surface_labels=True,
angles=(-45, 0, 45),
save="figs/extrusion_foot_surfs.svg",
font_size=20,
)
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Displace mode
Next, we insert the left eye using qtcad.builder.Builder.displace_mode (the
default mode). Note that we used qtcad.builder.Builder.set_z to set the
base coordinate of the extrusion and qtcad.builder.Builder.use_shape to
select a specific shape in the mask to extrude. By default selecting
a mask also selects all shapes in the masks for subsequent operation
(see for example Adding the gate surfaces in the
Gated Quantum Dot tutorial). We
also use qtcad.builder.Builder.set_mesh_size to set a finer mesh size for
subsequently created volumes. We re-enable hull surface differ with
the argument True.
- Builder.displace_mode(dim: int | list[int] | None = None) Self
[🔧 Modifier] When creating new entities, override the physical groups where the new entities intersect with the old ones to correspond to the physical group of the new elements.
- Parameters:
dim – See
use_mode.
(
builder.set_mesh_size(5)
.use_mask("smile")
.set_z(0)
.displace_mode()
.differentiate_hull_surfaces(True)
.use_shape("left_eye")
.extrude(20)
)
[14:51:45] INFO Using mask 'smile' (implicitly selecting shapes _builder.py:827
[Polygon 1 'right_eye', Polygon 2 'mouth',
Polygon 0 'left_eye'])
INFO Setting z-coordinate to 0 _builder.py:929
INFO Selected shapes: [Polygon 0 'left_eye'] _builder.py:880
INFO Extruding shape 0 from mask 'smile' with name _builder.py:2895
'left_eye' by 20 at z=0
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'left_eye_bottom'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'left_eye_side'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'left_eye_top'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'left_eye'
INFO Identifying potential intersections... fragmenter.py:290
INFO Fragmenting... fragmenter.py:320
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'smile'
INFO Setting z-coordinate to 20 _builder.py:929
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
The resulting volume intersects with the the footprint. Because we
chose the displace mode the resulting fragments are all added to the
physical group left_eye (name generated from the shape name, see
also qtcad.builder.Builder.set_group_name(), qtcad.builder.Builder.group_from_mask(),
and qtcad.builder.Builder.group_from_shape()).
builder.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_left_eye.svg",
font_size=20,
)
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Note that the hull surfaces are now differentiated into bottom, side, and top.
builder.view(
surfaces=False,
volume_labels=False,
surface_labels=True,
groups=["left_eye"],
angles=(-45, 0, 45),
font_size=5,
save="figs/extrusion_left_eye_surfs.svg",
)
[14:51:46] INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Fill mode
Next, we will use qtcad.builder.Builder.fill_mode to add the right eye. The
fill mode is the conceptual opposite of the displace mode:
- Builder.fill_mode(dim: int | list[int] | None = None) Self
[🔧 Modifier] When creating new entities, inherit the physical groups where the new entities intersect with the old ones.
- Parameters:
dim – See
use_mode.
We also demonstrate here qtcad.builder.Builder.number_hull_surfaces to
number the hull surfaces consecutively according to the direction
they are facing in addition of naming them according to their
position (bottom, side, top). This is useful, for example, to apply
boundary conditions only to select surfaces:
builder.set_z(0).fill_mode().number_hull_surfaces().use_shapes("right_eye").extrude(30)
INFO Setting z-coordinate to 0 _builder.py:929
INFO Selected shapes: [Polygon 1 'right_eye'] _builder.py:880
INFO Extruding shape 1 from mask 'smile' with name _builder.py:2895
'right_eye' by 30 at z=0
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_bottom'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[0]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[1]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[2]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[3]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[4]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[5]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[6]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[7]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[8]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_side[9]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'right_eye_top'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'right_eye'
INFO Identifying potential intersections... fragmenter.py:290
INFO Fragmenting... fragmenter.py:320
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_bottom'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[0]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[1]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[2]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[3]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[4]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[5]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[6]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[7]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[8]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_side[9]'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_top'
INFO Setting z-coordinate to 30 _builder.py:929
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
We find, this results in the part of the geometry intersecting with
the "footprint" volume inherit the physical group of that volume.
builder.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_right_eye.svg",
font_size=20,
)
builder.view(
surfaces=False,
volume_labels=False,
surface_labels=True,
groups=["right_eye"],
angles=(-45, 0, 45),
font_size=5,
save="figs/extrusion_right_eye_surfs.svg",
)
INFO Saving figure _builder.py:1994
[14:51:47] INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Overlay mode
Finally, we will use qtcad.builder.Builder.overlay_mode to add the right
eye. The overlay mode is a middle ground between displacing and filling.
- Builder.overlay_mode(dim: int | list[int] | None = None) Self
[🔧 Modifier] Like
fill_mode, but instead of inheriting the group from the intersecting entities the intersecting elements will be added to physical groups named like[intersecting_group].[new_entity_group].- Parameters:
dim – See
use_mode.
(
builder.set_z(0)
.differentiate_hull_surfaces(False)
.overlay_mode()
.use_shapes("mouth")
.extrude(30)
)
INFO Setting z-coordinate to 0 _builder.py:929
INFO Selected shapes: [Polygon 2 'mouth'] _builder.py:880
INFO Extruding shape 2 from mask 'smile' with name _builder.py:2895
'mouth' by 30 at z=0
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'mouth_hull'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'mouth'
INFO Identifying potential intersections... fragmenter.py:290
INFO Fragmenting... fragmenter.py:320
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint.mouth_hull'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'footprint.mouth'
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'footprint_hull.smile_0'
INFO Setting z-coordinate to 30 _builder.py:929
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
We find, this results in the part of the geometry intersecting with
the "footprint" volume to be named "footprint.mouth".
builder.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_mouth.svg",
font_size=20,
)
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
When viewing just the mouth, we can make out how the new volume and surfaces are named.
builder.view(
surfaces=True,
volume_labels=False,
surface_labels=True,
angles=(-45, 0, 45),
save="figs/extrusion_mouth_only.svg",
font_size=10,
groups=["mouth", "footprint.mouth"],
)
[14:51:48] INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
The final mesh then becomes:
builder.mesh(algorithm3d=MeshAlgorithm3D.HXT)
builder.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_mesh.png",
font_size=20,
)
[14:51:49] INFO Preparing to mesh _builder.py:1587
INFO Detecting appropriate random factor _builder.py:3867
INFO Random factor is now appropriate _builder.py:3891
INFO Meshing _builder.py:1603
INFO Checking mesh conformity _builder.py:3953
INFO Writing _builder.py:1725
../../../../../../../../../../../tmp/tmprwbxuc
uv/mesh.msh
[14:51:52] INFO Checking connectivity _builder.py:3980
nodes: 0%| | 0/7189 [00:00<?, ?it/s]
nodes: 0%| | 0/7189 [00:00<?, ?it/s]
nodes: 0%| | 0/7189 [00:00<?, ?it/s]
nodes: 100%|██████████| 7189/7189 [00:00<00:00, 36399.61it/s]
nodes: 100%|██████████| 7189/7189 [00:00<00:00, 36371.77it/s]
nodes: 100%|██████████| 7189/7189 [00:00<00:00, 36357.91it/s]
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Filling around the entire model
Fill mode is also usefull to add material around an existing structure. To that end we first create a container shape that is slightly larger than the bounding box of the existing geometry. We then add that shape as a mask and use fill mode to extrude it around the existing geometry.
container = (
Polygon.from_bbox(*builder.bbox, name="container").scaled(1.2).translated(-2, 0)
)
container_mask = Mask("container")
container_mask.add_shape(container)
(
builder.clear_mesh()
.add_mask(container_mask)
.inherit_mesh_size()
.fill_mode()
.set_z(-21)
.extrude(60)
.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_container.svg",
font_size=20,
)
)
[14:51:53] INFO Clearing mesh _builder.py:1548
INFO Setting z-coordinate to -21 _builder.py:929
INFO Extruding shape 0 from mask 'container' with _builder.py:2895
name 'container' by 60 at z=-21
INFO Creating new physical group of dimension 2 _builder.py:2679
with name 'container_hull'
INFO Creating new physical group of dimension 3 _builder.py:2679
with name 'container'
INFO Identifying potential intersections... fragmenter.py:290
INFO Fragmenting... fragmenter.py:320
INFO Setting z-coordinate to 39 _builder.py:929
INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
When we view the just-created volume in isolation, we see that the existing geometry has been “stenciled out”.
(
builder.view(
surfaces=False,
volume_labels=True,
surface_labels=False,
angles=(-45, 0, 45),
save="figs/extrusion_container_only.svg",
font_size=20,
groups=["container"],
)
)
# Local Variables:
# jinx-languages: "en_CA"
# End:
[14:51:54] INFO Saving figure _builder.py:1994
<qtcad.builder.builder.Builder object at 0x7ff520ffd310>
Total running time of the script: (0 minutes 10.718 seconds)