mphkit 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
mphkit/geometry.py ADDED
@@ -0,0 +1,537 @@
1
+ """Geometry creation, geometry features, and coordinate systems."""
2
+ from __future__ import annotations
3
+
4
+ import numpy
5
+ from mph import Model, Node
6
+ from mph.node import escape
7
+
8
+ from . import _comsol
9
+ from ._comsol import WorkPlaneNode
10
+
11
+
12
+ def geometry(model: Model, dim: int = 3, *, length_unit: str = None,
13
+ name: str = None) -> Node:
14
+ """
15
+ Creates a new component with a geometry of dimension `dim`.
16
+
17
+ The component is created explicitly, so this also works in models that
18
+ already have components. `length_unit` is, for example, `'mm'`; plain
19
+ numbers given to other helpers are then interpreted in that unit.
20
+ Returns the geometry node.
21
+ """
22
+ java = model.java
23
+ taken = _comsol.labels(java.geom())
24
+ if name is not None:
25
+ _comsol.pick_label(name, name, taken)
26
+ ctag = str(java.component().uniquetag('comp'))
27
+ component = java.component().create(ctag, True)
28
+ gtag = str(java.geom().uniquetag('geom'))
29
+ geom = component.geom().create(gtag, dim)
30
+ label = _comsol.pick_label(name, str(geom.label()), taken)
31
+ geom.label(label)
32
+ if length_unit is not None:
33
+ geom.lengthUnit(length_unit)
34
+ node = model/'geometries'/escape(label)
35
+ _comsol.check_tag(node, gtag)
36
+ return node
37
+
38
+
39
+ def component_of(geom: Node) -> Node:
40
+ """
41
+ Returns the component node that contains the geometry (does not create).
42
+
43
+ Useful for the parts of a model that live under the component, e.g.
44
+ `mk.component_of(geom).java.coordSystem()`. For coordinate systems use
45
+ `coordinate_system()`.
46
+ """
47
+ java = _comsol.component_of(geom)
48
+ node = geom.model/'components'/escape(str(java.label()))
49
+ _comsol.check_tag(node, str(java.tag()))
50
+ return node
51
+
52
+
53
+ def coordinate_system(geom: Node, type: str, /, *, selection: Node = None,
54
+ name: str = None, **properties) -> Node:
55
+ """
56
+ Creates a coordinate system of `type` for the geometry.
57
+
58
+ For example a perfectly matched layer:
59
+ `coordinate_system(geom, 'PML', selection=pml, stretchingType='rational')`.
60
+ Other types include `'InfiniteElement'`, `'AbsorbingLayer'` and
61
+ `'Scaling'`, which take a domain `selection`, and `'Rotated'`,
62
+ `'Cylindrical'`, `'Spherical'` or `'Boundary'`, which do not. Keyword
63
+ arguments are COMSOL property names. Returns the node under
64
+ `model/'coordinates'`.
65
+
66
+ Plain MPh works as well when the geometry tag comes first,
67
+ `(model/'coordinates').create(geom.tag(), 'PML')`. Without it, COMSOL
68
+ leaves a broken node that makes every later physics node fail.
69
+ """
70
+ model = geom.model
71
+ container = _comsol.component_of(geom).coordSystem()
72
+ everything = model.java.coordSystem()
73
+ tag, label = _comsol.create_java(container, 'coordinates', type, name,
74
+ _comsol.labels(everything),
75
+ tags=everything, args=(geom.tag(),))
76
+ node = model/'coordinates'/escape(label)
77
+ try:
78
+ _comsol.check_tag(node, tag)
79
+ java = container.get(tag)
80
+ _comsol.set_properties(java, properties)
81
+ if selection is not None:
82
+ try:
83
+ target = java.selection()
84
+ except Exception:
85
+ raise ValueError(f'A "{type}" coordinate system has no '
86
+ 'selection.') from None
87
+ chosen = _comsol.check_selection(geom, selection)
88
+ if list(chosen.dimension()) != list(target.dimension()):
89
+ raise ValueError(f'Selection "{selection}" is not at the '
90
+ f'level a "{type}" coordinate system '
91
+ 'takes.')
92
+ node.select(selection)
93
+ except Exception:
94
+ container.remove(tag)
95
+ raise
96
+ return node
97
+
98
+
99
+ def feature(parent: Node, type: str, /, *, name: str = None,
100
+ **properties) -> Node:
101
+ """
102
+ Creates a geometry feature of any COMSOL `type` and sets its properties.
103
+
104
+ `parent` is a geometry or a work plane. Keyword arguments are COMSOL
105
+ property names. Input selections (such as `input` and `input2` of a
106
+ Difference) accept geometry nodes, names, tags, or lists of those, or
107
+ one selection: made with `sel.*(..., where='geometry')` (also at the
108
+ `'object'` level), made in the work plane itself (`sel.box(plane,
109
+ ...)`), or `sel.cumulative()`. Lists that
110
+ mix numbers and expressions are converted for COMSOL. Arguments that
111
+ are `None` are skipped.
112
+
113
+ If setting a property fails, the new feature is removed again and the
114
+ error is raised. Returns the feature node.
115
+ """
116
+ container = _comsol.feature_container(parent)
117
+ workplane = _comsol.is_workplane(parent.java)
118
+ taken = _comsol.labels(container)
119
+ if (type.endswith('Selection')
120
+ or properties.get('selresult') in (True, 'on')):
121
+ taken |= _comsol.selection_labels(parent.model)
122
+ tag, label = _comsol.create_java(container, 'geometries', type, name,
123
+ taken)
124
+ cls = WorkPlaneNode if workplane or type == 'WorkPlane' else Node
125
+ node = _comsol.child_node(parent, label, cls)
126
+ try:
127
+ _comsol.check_tag(node, tag)
128
+ java = container.get(tag)
129
+ _comsol.set_properties(java, properties, parent, container)
130
+ except Exception:
131
+ container.remove(tag)
132
+ raise
133
+ return node
134
+
135
+
136
+ #############################
137
+ # Primitives and operations #
138
+ #############################
139
+
140
+ def block(geom: Node, /, size=None, pos=None, *, name: str = None,
141
+ **properties) -> Node:
142
+ """
143
+ Creates a Block. `base='center'` centers it on `pos`.
144
+
145
+ Layers, e.g. a perfectly matched layer, are shells on chosen faces:
146
+ `layername=['pml'], layer=[5], layertop=True, layerbottom=False`. The
147
+ faces are `layerleft`/`layerright` (−x/+x), `layerfront`/`layerback`
148
+ (−y/+y) and `layerbottom`/`layertop` (−z/+z); only `layerbottom` is on
149
+ by default. Several layers stack from the outer face inward in the
150
+ order of `layername`. Select them with `sel.layer()`.
151
+ """
152
+ return feature(geom, 'Block', name=name, size=size, pos=pos,
153
+ **properties)
154
+
155
+
156
+ def cylinder(geom: Node, /, r=None, h=None, pos=None, *, name: str = None,
157
+ **properties) -> Node:
158
+ """
159
+ Creates a Cylinder with radius `r` and height `h`.
160
+
161
+ The cylinder stands on `pos` along the z axis; COMSOL's `axistype` and
162
+ `axis` properties give another direction, e.g. `axistype='x'`.
163
+ """
164
+ return feature(geom, 'Cylinder', name=name, r=r, h=h, pos=pos,
165
+ **properties)
166
+
167
+
168
+ def sphere(geom: Node, /, r=None, pos=None, *, name: str = None,
169
+ **properties) -> Node:
170
+ """Creates a Sphere with radius `r` centered at `pos`."""
171
+ return feature(geom, 'Sphere', name=name, r=r, pos=pos, **properties)
172
+
173
+
174
+ def point(geom: Node, /, p, *, name: str = None, **properties) -> Node:
175
+ """Creates a Point at coordinates `p`."""
176
+ return feature(geom, 'Point', name=name, p=p, **properties)
177
+
178
+
179
+ def union(geom: Node, /, input, *, name: str = None, **properties) -> Node:
180
+ """Creates a Union of the `input` objects."""
181
+ return feature(geom, 'Union', name=name, input=input, **properties)
182
+
183
+
184
+ def difference(geom: Node, /, input, input2, *, name: str = None,
185
+ **properties) -> Node:
186
+ """Creates a Difference: `input` objects minus `input2` objects."""
187
+ return feature(geom, 'Difference', name=name, input=input,
188
+ input2=input2, **properties)
189
+
190
+
191
+ def rigid_transform(geom: Node, /, input, *, name: str = None,
192
+ **properties) -> Node:
193
+ """
194
+ Creates a Rigid Transform (move and rotate) of the `input` objects.
195
+
196
+ For example `displ=(dx, dy, dz)`, `specify='eulerang'`,
197
+ `eulerang=(180, 90, 0)`.
198
+ """
199
+ return feature(geom, 'RigidTransform', name=name, input=input,
200
+ **properties)
201
+
202
+
203
+ def intersection(parent: Node, /, input, *, name: str = None,
204
+ **properties) -> Node:
205
+ """Creates an Intersection: the part the `input` objects share."""
206
+ return feature(parent, 'Intersection', name=name, input=input,
207
+ **properties)
208
+
209
+
210
+ def delete(parent: Node, /, input, *, name: str = None,
211
+ **properties) -> Node:
212
+ """
213
+ Creates a Delete of the `input` objects.
214
+
215
+ `input` may also be one selection: at the `'object'` level or a
216
+ cumulative selection, whole objects are deleted; a domain or boundary
217
+ selection made with `sel.*(..., where='geometry')` deletes just those.
218
+ In a work plane, use a selection made in that plane.
219
+ """
220
+ return feature(parent, 'Delete', name=name, input=input, **properties)
221
+
222
+
223
+ ##############
224
+ # Transforms #
225
+ ##############
226
+
227
+ def array(parent: Node, /, input, *, size, displ, name: str = None,
228
+ **properties) -> Node:
229
+ """
230
+ Creates an Array of copies of the `input` objects.
231
+
232
+ `size` is the number of copies per axis, e.g. `size=(5, 5, 1)`, or a
233
+ single number (or expression) for a linear array along `displ`.
234
+ `displ` is the spacing, e.g. `displ=(10, 10, 0)`. Both are
235
+ keyword-only so they cannot be swapped by accident. `size` becomes
236
+ COMSOL's `fullsize` or, for a linear array, `linearsize` with
237
+ `type='linear'`. Works in a work plane as well.
238
+ """
239
+ if isinstance(size, bool):
240
+ raise TypeError(f'size must be a number or one per axis, not {size!r}.')
241
+ _comsol.check_vector(parent, 'displ', displ)
242
+ if isinstance(size, (list, tuple, numpy.ndarray)):
243
+ _comsol.check_vector(parent, 'size', size)
244
+ properties['fullsize'] = size
245
+ else:
246
+ properties.setdefault('type', 'linear')
247
+ properties['linearsize'] = size
248
+ return feature(parent, 'Array', name=name, input=input, displ=displ,
249
+ **properties)
250
+
251
+
252
+ def move(parent: Node, /, input, displ, *, name: str = None,
253
+ **properties) -> Node:
254
+ """
255
+ Creates a Move of the `input` objects by `displ`, e.g. `(0, 0, 5)`.
256
+
257
+ A component given as a list makes several copies, e.g.
258
+ `displ=([10, 20], 0, 0)`. Pass `keep=True` to keep the originals.
259
+ """
260
+ _comsol.check_vector(parent, 'displ', displ)
261
+ for axis, value in zip('xyz', displ):
262
+ properties[f'displ{axis}'] = value
263
+ return feature(parent, 'Move', name=name, input=input, **properties)
264
+
265
+
266
+ def rotate(parent: Node, /, input, rot, *, pos=None, axis=None,
267
+ name: str = None, **properties) -> Node:
268
+ """
269
+ Creates a Rotate of the `input` objects by `rot` degrees.
270
+
271
+ A list of angles makes several copies. `pos` is a point on the axis.
272
+ In 3D, `axis` is `'x'`, `'y'`, `'z'` (default) or a direction vector;
273
+ 2D rotations (also in a work plane) have no axis.
274
+ """
275
+ if pos is not None:
276
+ _comsol.check_vector(parent, 'pos', pos)
277
+ if axis is not None:
278
+ if _comsol.parent_dim(parent) == 2:
279
+ raise ValueError('A 2D rotation has no axis; leave out axis.')
280
+ if not isinstance(axis, str):
281
+ _comsol.check_vector(parent, 'axis', axis)
282
+ properties.update(_comsol.axis_properties(axis))
283
+ return feature(parent, 'Rotate', name=name, input=input, rot=rot,
284
+ pos=pos, **properties)
285
+
286
+
287
+ def mirror(parent: Node, /, input, axis, *, pos=None, name: str = None,
288
+ **properties) -> Node:
289
+ """
290
+ Creates a Mirror of the `input` objects.
291
+
292
+ `axis` is the **normal** of the mirror plane (3D) or line (2D), as in
293
+ COMSOL: `axis=(1, 0, 0)` mirrors in the plane x = 0, so x changes
294
+ sign. `pos` is a point on the plane. Pass `keep=True` to keep the
295
+ originals.
296
+ """
297
+ _comsol.check_vector(parent, 'axis', axis)
298
+ if pos is not None:
299
+ _comsol.check_vector(parent, 'pos', pos)
300
+ return feature(parent, 'Mirror', name=name, input=input, axis=axis,
301
+ pos=pos, **properties)
302
+
303
+
304
+ def revolve(geom: Node, /, input, angle=None, *, pos=None, axis=None,
305
+ name: str = None, **properties) -> Node:
306
+ """
307
+ Creates a Revolve of `input`, usually a work plane, in a 3D geometry.
308
+
309
+ `angle` is left out for a full turn, one value in degrees (from 0), or
310
+ two values (start, end). The axis is given in the work plane's own
311
+ coordinates by default: `pos` and `axis` with two values each, the
312
+ work plane's y axis if left out. Three values, or `axis='x'|'y'|'z'`,
313
+ give an axis in 3D coordinates instead (the model's x axis, not the
314
+ work plane's); a 3D axis without `pos` passes through the origin.
315
+ """
316
+ if _comsol.parent_dim(geom) != 3 or _comsol.is_workplane(geom.java):
317
+ raise ValueError('Revolve needs a 3D geometry.')
318
+ if angle is not None:
319
+ if 'angle1' in properties or 'angle2' in properties:
320
+ raise ValueError('Give either angle or angle1/angle2.')
321
+ properties['angtype'] = 'specang'
322
+ if isinstance(angle, (list, tuple, numpy.ndarray)):
323
+ properties['angle1'], properties['angle2'] = angle
324
+ else:
325
+ properties['angle1'], properties['angle2'] = 0, angle
326
+ if isinstance(axis, str):
327
+ if axis not in ('x', 'y', 'z'):
328
+ raise ValueError(f"axis must be 'x', 'y', 'z' or a vector, not "
329
+ f'{axis!r}.')
330
+ axis = [int(a == axis) for a in 'xyz']
331
+ sizes = {len(v) for v in (pos, axis) if v is not None}
332
+ if len(sizes) > 1 or not sizes <= {2, 3}:
333
+ raise ValueError('pos and axis need two values each (work plane) or '
334
+ 'three each (3D).')
335
+ if sizes == {3}:
336
+ if axis is None:
337
+ raise ValueError('A 3D pos needs an axis as well.')
338
+ properties.update(axistype='3d', axis3=axis,
339
+ pos3=pos if pos is not None else (0, 0, 0))
340
+ elif sizes == {2}:
341
+ properties.update(axistype='2d', pos=pos, axis=axis)
342
+ return feature(geom, 'Revolve', name=name, input=input, **properties)
343
+
344
+
345
+ def partition(parent: Node, /, input, tool, *, name: str = None,
346
+ **properties) -> Node:
347
+ """
348
+ Creates a Partition of the `input` objects by `tool`.
349
+
350
+ `tool` is geometry objects, or a work plane (node, tag or name) to cut
351
+ along its plane. `keepinput`/`keeptool` keep the originals.
352
+ """
353
+ plane = _workplane_tag(parent, tool)
354
+ if plane is not None:
355
+ properties.update(partitionwith='workplane', workplane=plane)
356
+ else:
357
+ properties['tool'] = tool
358
+ return feature(parent, 'Partition', name=name, input=input, **properties)
359
+
360
+
361
+ def _workplane_tag(parent: Node, ref) -> str | None:
362
+ """Returns the tag if `ref` (node, tag or name) is a work plane."""
363
+ if isinstance(ref, Node):
364
+ if not _comsol.is_workplane(ref.java):
365
+ return None
366
+ if _comsol.geometry_of(ref) != _comsol.geometry_of(parent):
367
+ raise ValueError(f'Work plane "{ref}" does not belong to '
368
+ f'"{parent}".')
369
+ return ref.tag()
370
+ if isinstance(ref, str):
371
+ container = _comsol.feature_container(parent)
372
+ try:
373
+ tag = _comsol.resolve_object(container, ref)
374
+ except LookupError:
375
+ return None
376
+ if (tag in [str(t) for t in container.tags()]
377
+ and _comsol.is_workplane(container.get(tag))):
378
+ return tag
379
+ return None
380
+
381
+
382
+ def fillet(parent: Node, /, input, radius, *, name: str = None,
383
+ **properties) -> Node:
384
+ """
385
+ Rounds edges (3D) or corners (2D and work planes) with `radius`.
386
+
387
+ `input` is geometry objects, meaning all their edges or corners, or
388
+ one selection of edges (3D) or points (2D) made with
389
+ `sel.*(..., where='geometry')` or `sel.cumulative()`. In a work plane,
390
+ pick single corners with a selection made in the plane, e.g.
391
+ `fillet(plane, sel.box(plane, 'point', x=1, y=1), 0.3)`. This is COMSOL's
392
+ Fillet3D `edge` in 3D and Fillet `point` in 2D. Array copies can be
393
+ named like `'arr1(1,1,1)'`; such names are only checked when the
394
+ geometry is built.
395
+ """
396
+ return _round(parent, 'Fillet', input, name, radius=radius,
397
+ **properties)
398
+
399
+
400
+ def chamfer(parent: Node, /, input, dist, *, name: str = None,
401
+ **properties) -> Node:
402
+ """
403
+ Bevels edges (3D) or corners (2D and work planes) by `dist`.
404
+
405
+ `input` works as in `fillet()`, including selections made in a work
406
+ plane for single corners. This is COMSOL's Chamfer3D `edge` with the
407
+ distance called `radius` in 3D, and Chamfer `point` with `dist` in 2D.
408
+ """
409
+ if _comsol.parent_dim(parent) == 3:
410
+ return _round(parent, 'Chamfer', input, name, radius=dist,
411
+ **properties)
412
+ return _round(parent, 'Chamfer', input, name, dist=dist, **properties)
413
+
414
+
415
+ def _round(parent: Node, kind: str, input, name, **properties) -> Node:
416
+ """Creates a fillet or chamfer on edges (3D) or points (2D)."""
417
+ dim = _comsol.parent_dim(parent)
418
+ if dim == 3:
419
+ return feature(parent, f'{kind}3D', name=name, edge=input,
420
+ **properties)
421
+ if dim == 2:
422
+ return feature(parent, kind, name=name, point=input, **properties)
423
+ raise ValueError(f'A 1D geometry has no {kind.lower()}.')
424
+
425
+
426
+ def line_segment(parent: Node, /, start, end, *, name: str = None,
427
+ **properties) -> Node:
428
+ """Creates a straight line segment from `start` to `end`."""
429
+ _comsol.check_vector(parent, 'start', start)
430
+ _comsol.check_vector(parent, 'end', end)
431
+ return feature(parent, 'LineSegment', name=name, specify1='coord',
432
+ coord1=start, specify2='coord', coord2=end, **properties)
433
+
434
+
435
+ def interval(geom: Node, /, coord, *, name: str = None,
436
+ **properties) -> Node:
437
+ """
438
+ Creates an Interval in a 1D geometry through the points `coord`.
439
+
440
+ Consecutive points bound one domain each, e.g. `[0, 1, 3]` gives two.
441
+ """
442
+ if _comsol.parent_dim(geom) != 1:
443
+ raise ValueError('An interval needs a 1D geometry.')
444
+ if not isinstance(coord, (list, tuple, numpy.ndarray)) or len(coord) < 2:
445
+ raise ValueError(f'coord needs at least two points, not {coord!r}.')
446
+ return feature(geom, 'Interval', name=name, coord=coord, **properties)
447
+
448
+
449
+ ###############
450
+ # Work planes #
451
+ ###############
452
+
453
+ def workplane(geom: Node, /, *, name: str = None, **properties) -> Node:
454
+ """
455
+ Creates a Work Plane, for example `quickz=5`.
456
+
457
+ With `unite=True` the plane's 2D objects are imprinted into the 3D
458
+ geometry, e.g. to create an evaluation surface. Add 2D features with
459
+ `square(wp, ...)`, `rectangle(wp, ...)`, `circle(wp, ...)` or
460
+ `polygon(wp, ...)`.
461
+ """
462
+ return feature(geom, 'WorkPlane', name=name, **properties)
463
+
464
+
465
+ def square(parent: Node, /, size=None, pos=None, *, name: str = None,
466
+ **properties) -> Node:
467
+ """Creates a Square in a 2D geometry or a work plane."""
468
+ return feature(parent, 'Square', name=name, size=size, pos=pos,
469
+ **properties)
470
+
471
+
472
+ def rectangle(parent: Node, /, size=None, pos=None, *, name: str = None,
473
+ **properties) -> Node:
474
+ """Creates a Rectangle `size=(width, height)` in 2D or a work plane."""
475
+ return feature(parent, 'Rectangle', name=name, size=size, pos=pos,
476
+ **properties)
477
+
478
+
479
+ def circle(parent: Node, /, r=None, pos=None, *, name: str = None,
480
+ **properties) -> Node:
481
+ """Creates a Circle with radius `r` in 2D or a work plane."""
482
+ return feature(parent, 'Circle', name=name, r=r, pos=pos, **properties)
483
+
484
+
485
+ def polygon(parent: Node, /, x, y, *, name: str = None,
486
+ **properties) -> Node:
487
+ """Creates a closed Polygon through the points `x`, `y`."""
488
+ properties.setdefault('type', 'solid')
489
+ return feature(parent, 'Polygon', name=name, source='vectors', x=x, y=y,
490
+ **properties)
491
+
492
+
493
+ def extrude(geom: Node, /, input, distance, *, name: str = None,
494
+ **properties) -> Node:
495
+ """Extrudes `input` (e.g. a work plane) by `distance` (scalar or list)."""
496
+ if not isinstance(distance, (list, tuple)):
497
+ distance = [distance]
498
+ return feature(geom, 'Extrude', name=name, input=input,
499
+ distance=distance, **properties)
500
+
501
+
502
+ ##########
503
+ # Import #
504
+ ##########
505
+
506
+ def import_(geom: Node, file, /, *, type: str = None, name: str = None,
507
+ **properties) -> Node:
508
+ """
509
+ Imports geometry from `file`.
510
+
511
+ `type` defaults from the extension: `'native'` for `.mphbin`/`.mphtxt`,
512
+ otherwise `'cad'` (STEP, IGES, Parasolid, ...; needs a CAD-capable
513
+ license). The path is stored as an absolute path.
514
+ """
515
+ from pathlib import Path
516
+ from .errors import LicenseError
517
+ file = Path(file).resolve()
518
+ if not file.exists():
519
+ raise FileNotFoundError(f'File "{file}" does not exist.')
520
+ if type is None:
521
+ type = 'native' if file.suffix.lower() in ('.mphbin', '.mphtxt') \
522
+ else 'cad'
523
+ node = feature(geom, 'Import', name=name, type=type, filename=str(file),
524
+ **properties)
525
+ try:
526
+ # MPh's `Node.import_()` calls `discardData()`, which geometry
527
+ # imports lack; `importData()` reads the file right away.
528
+ node.java.importData()
529
+ except Exception as error:
530
+ node.remove()
531
+ if 'license' in str(error).lower():
532
+ raise LicenseError(
533
+ f'Importing "{file.name}" needs a license for CAD import '
534
+ '(CAD Import Module, Design Module or a LiveLink).'
535
+ ) from error
536
+ raise
537
+ return node
mphkit/py.typed ADDED
File without changes
mphkit/sel.py ADDED
@@ -0,0 +1,37 @@
1
+ """
2
+ Geometry-based selections: select entities by location, not by number.
3
+
4
+ Every helper takes the geometry node first and returns an MPh node that
5
+ physics, materials, mesh and other features can `select()`. The queries
6
+ `entities` and `find` return entity numbers instead and leave nothing in
7
+ the model.
8
+
9
+ With `where='geometry'`, the level can also be `'object'`: whole geometry
10
+ objects, as COMSOL's "Object" level. Such a selection is only an input for
11
+ geometry operations (e.g. deleting or uniting the unnamed copies of an
12
+ array in a region), not something physics can use, so it is returned as
13
+ the selection feature in the geometry sequence rather than a
14
+ `selections/` node.
15
+
16
+ `where='component'` (the default for a geometry) creates a selection in
17
+ the component. It is evaluated on the finished geometry.
18
+ `where='geometry'` creates it inside the geometry sequence instead. Then
19
+ it can also be the input of later geometry operations, but it only sees
20
+ objects created before it.
21
+
22
+ A work plane can take the place of the geometry in `box`, `ball`, `disk`,
23
+ `all` and the set operations and `adjacent`, e.g. to fillet single
24
+ corners: `mk.fillet(plane, mk.sel.box(plane, 'point', x=1, y=1), 0.3)`.
25
+ Coordinates are the plane's own. Such selections live in the plane's
26
+ sequence (the default there), only see objects created before them, and
27
+ are inputs of operations in that plane only: COMSOL derives no
28
+ model-level selection from them, and physics refuses them ("Unknown
29
+ selection"). They are returned as the selection feature in the plane.
30
+ """
31
+ from ._sel import adjacent, all_ as all, ball, box, complement, \
32
+ cumulative, cylinder, difference, disk, entities, find, intersection, \
33
+ layer, result, union
34
+
35
+ __all__ = ['adjacent', 'all', 'ball', 'box', 'complement', 'cumulative',
36
+ 'cylinder', 'difference', 'disk', 'entities', 'find',
37
+ 'intersection', 'layer', 'result', 'union']