Using Selections
Using Selections for Editing Geometry Objects
The following method generates a plate with an array of cylinders. The cylinders may be used, for example, in a difference operation to create an array of holes in the plate.
GeomSequence geom = model.component("comp1").geom("geom1");
geom.create("blk1", "Block");
geom.feature("blk1").set("size", new int[]{10, 10, 1});
geom.create("start_cyl1", "Cylinder");
geom.feature("start_cyl1").set("pos", new double[]{2.5, 2.5, 0});
geom.create("arr1", "Array");
geom.feature("arr1").selection("input").set("start_cyl1");
geom.feature("arr1").set("fullsize", new int[]{2, 2, 1});
geom.feature("arr1").set("displ", new int[]{5, 5, 0});
geom.run("arr1");
The resulting geometry is shown in the figure below.
Assume now that the resulting geometry, from the previous step, corresponds to an imported geometry object and that you would like to replace the cylinders with larger cylinders before subtracting and generating the holes. Furthermore, assume that the cylinder objects are generated by an external software in such a way that, although you know these objects are cylinders, they are represented as generic geometry objects with no information on radius, height, or position. The following method finds the array of cylinders, extracts coordinate information for each cylinder, deletes the cylinders, creates a new array of wider cylinders, and finally subtracts the cylinders from the plate. The resulting geometry is shown in the figure below.
The method exemplifies retrieval of geometry object names, coordinate information, and the use of selections. To start from another geometry object (which is also necessarily a plate with cylinders), you can replace the string tag in the variable plateAndCylinders accordingly.
double selTol = 1e-2; // Selection tolerance
double newR = 1.25; // New cylinder radius
double newH = 1; // New cylinder height
double plateThickness = 1; // Plate thickness
String plateAndCylinders = "arr1";
 
// Update and split geometry
model.component("comp1").geom("geom1").run("fin");
GeomSequence geom = model.component("comp1").geom("geom1");
geom.run(plateAndCylinders);
geom.create("spl1", "Split");
geom.feature("spl1").selection("input").set(plateAndCylinders);
geom.run("spl1");
 
// Find extents of geometry in x,y,z directions
double[] bBox = geom.getBoundingBox();
double MinX = bBox[0];
double MaxX = bBox[1];
double MinY = bBox[2];
double MaxY = bBox[3];
double MinZ = bBox[4];
double MaxZ = bBox[5];
 
// Define scaled coordinate tolerance
double scale = Math.max(Math.max(MaxX-MinX, MaxY-MinY), MaxZ-MinZ);
double scaleSelTol = scale*selTol;
 
// Create box selection based on geometry extents
geom.create("boxsel1", "BoxSelection");
with(geom.feature("boxsel1"));
  // Select boundaries inside box in X-Y direction using tolerance
  set("xmin", MinX+scaleSelTol);
  set("xmax", MaxX-scaleSelTol);
  set("ymin", MinY+scaleSelTol);
  set("ymax", MaxY-scaleSelTol);
  set("zmin", MinZ-scaleSelTol);
  set("zmax", MaxZ+scaleSelTol);
  set("condition", "inside");
  set("entitydim", -1); // Select objects
endwith();
geom.run("boxsel1");
 
// Get object names
String[] so = geom.selection("boxsel1").objects();
int nso = so.length;
 
// Extract cylinder parameters
double[] MinXC = new double[nso];
double[] MaxXC = new double[nso];
double[] MinYC = new double[nso];
double[] MaxYC = new double[nso];
double[] MinZC = new double[nso];
double[] MaxZC = new double[nso];
double[] bBoxC = new double[6];
for (int i = 0; i < nso; i++) {
  bBoxC = geom.obj(so[i]).getBoundingBox();
  MinXC[i] = bBoxC[0];
  MaxXC[i] = bBoxC[1];
  MinYC[i] = bBoxC[2];
  MaxYC[i] = bBoxC[3];
  MinZC[i] = bBoxC[4];
  MaxZC[i] = bBoxC[5];
}
 
double[] radius = new double[nso];
double[] xc = new double[nso];
double[] yc = new double[nso];
double[] zc = new double[nso];
double[] hc = new double[nso];
 
double ry;
double tol = scale*1e-6; // Tolerance check for skew cylinders, optional consistency check
for (int i = 0; i < nso; i++) {
  xc[i] = (MaxXC[i]+MinXC[i])/2;
  yc[i] = (MaxYC[i]+MinYC[i])/2;
  zc[i] = (MaxZC[i]+MinZC[i])/2;
  hc[i] = MaxZC[i]-MinZC[i];
  radius[i] = (MaxXC[i]-MinXC[i])/2;
  ry = (MaxYC[i]-MinYC[i])/2;
  if (Math.abs(radius[i]-ry) > tol)
    error("Object is not a circular cylinder.");
}
 
// Delete all cylinder objects
geom.create("del1", "Delete");
geom.feature("del1").selection("input").init();
geom.feature("del1").selection("input").set(so);
geom.run("del1");
 
// Add new cylinders
String[] cylname = new String[nso];
for (int i = 0; i < nso; i++) {
  cylname[i] = "cyl"+toString(i+1);
  geom.create(cylname[i], "Cylinder");
  with(geom.feature(cylname[i]));
    set("r", newR);
    set("h", newH+2*scaleSelTol);
    set("pos", new double[]{xc[i], yc[i], plateThickness-2*zc[i]-scaleSelTol});
  endwith();
}
 
// Combine all cylinders into one object
geom.create("uni1", "Union");
geom.feature("uni1").selection("input").set(cylname);
geom.run("uni1");
 
// Difference between all objects and cylinders
geom.create("dif1", "Difference");
String[] objs = geom.objectNames();
geom.feature("dif1").selection("input").set(objs);
geom.feature("dif1").selection("input2").set("uni1");
geom.run("dif1");
Comments
The method assumes that the input geometry objects have the same structure as the plate with cylinders example above. Note that to clear the geometry sequence, you can create a method with the line
model.component("comp1").geom("geom1").feature().clear();
This can be useful if you are running the main method from above repeatedly, since you need to clear the geometry sequence before each run.
The first of the lines
geom.feature("del1").selection("input").init();
geom.feature("del1").selection("input").set(so);
initializes the selection to be empty of type object. The second line then selects all objects with names in the array so. In general, a call to init() without input argument means that the selection is for objects and init(n), where n=0,1,2, or 3, means that the selection is for points, edges, faces, and domains, respectively. Note that in some cases n=-1 is used to denote the object level (instead of an empty input argument); see the Programming Reference Manual.
The figure below shows an example based on a larger array of cylinders.
This example is part of a collection available for download:
www.comsol.com/model/application-programming-guide-examples-140771
The relevant files for this example are:
plate_with_four_cylinders.mph
plate_with_four_hundred_cylinders.mph
Selecting and Partitioning Edges for a Cylinder Object
The following two examples show how to make selections for a geometry feature that partitions the edges in a geometry. In this case the geometry is a cylinder object but the method is applicable to any geometry object as shown in the subsequent section.
The first example generates a cylinder and then inserts a PartitionEdges feature ("pare1") to subdivide four specific cylinder edges at parametric positions 0.25, 0.50, and 0.75 along their lengths, runs the partition, and then selects the partition feature in the Model Builder for further operations. You can, for example, use such a partition, under the Mesh node, to select edges for mesh refinement.
clearModel(model);
model.component().create("comp1", true);
ModelNode comp1 = model.component("comp1");
comp1.geom().create("geom1", 3);
GeomSequence geom1 = comp1.geom("geom1");
geom1.create("cyl1", "Cylinder");
geom1.feature("cyl1").set("r", 0.5);
geom1.feature("cyl1").set("h", 2);
geom1.feature("cyl1").set("pos", new double[]{0, 0, -1});
geom1.run("cyl1");
 
geom1.create("pare1", "PartitionEdges");
geom1.feature("pare1").selection("edge").set("cyl1", 4, 5, 8, 11);
geom1.feature("pare1").setIndex("param", 0.25, 0);
geom1.feature("pare1").setIndex("param", 0.5, 1);
geom1.feature("pare1").setIndex("param", 0.75, 2);
geom1.run("pare1");
selectNode(geom1.feature("pare1"));
The second example is identical apart from the selection part, which is now replaced by a call to the method all, which selects all available edges.
clearModel(model);
model.component().create("comp1", true);
ModelNode comp1 = model.component("comp1");
comp1.geom().create("geom1", 3);
GeomSequence geom1 = comp1.geom("geom1");
geom1.create("cyl1", "Cylinder");
geom1.feature("cyl1").set("r", 0.5);
geom1.feature("cyl1").set("h", 2);
geom1.feature("cyl1").set("pos", new double[]{0, 0, -1});
geom1.run("cyl1");
 
geom1.create("pare1", "PartitionEdges");
geom1.feature("pare1").selection("edge").all();
geom1.feature("pare1").setIndex("param", 0.25, 0);
geom1.feature("pare1").setIndex("param", 0.5, 1);
geom1.feature("pare1").setIndex("param", 0.75, 2);
geom1.run("pare1");
selectNode(geom1.feature("pare1"));
The all method can be used for most geometry features for selecting all geometric entities (domains, boundaries, edges, points). The following picture shows the results of the second example, zoomed in on one of the edges.
To view all available selection methods, type
geom1.feature("pare1").selection("edge")
then press Ctrl + Space. The code-completion popup below will list the options, as shown in the figure below:
This example is part of a collection available for download:
www.comsol.com/model/application-programming-guide-examples-140771
The relevant file for this example is:
cylinder_edge_partition_and_selections.mph
Selecting and Partitioning Edges for General Objects
The following examples are similar to the ones in the previous section but show how to make selections for a geometry feature that partitions the edges in one of several geometry objects. This example features a cone and a block object.
The first example shows how to select and partition all edges of all objects.
clearModel(model);
 
// Create a 3D component and geometry
model.component().create("comp1", true);
ModelNode comp1 = model.component("comp1");
comp1.geom().create("geom1", 3);
GeomSequence geom1 = comp1.geom("geom1");
 
// Create a cylinder object
geom1.create("cyl1", "Cylinder");
geom1.feature("cyl1").set("r", 0.5);
geom1.feature("cyl1").set("h", 2);
geom1.feature("cyl1").set("pos", new double[]{0, 0, -1});
geom1.run("cyl1");
 
// Create a block object
geom1.create("blk1", "Block");
geom1.run("blk1");
 
// Get the names of all objects
String[] objs = model.component("comp1").geom("geom1").objectNames();
debugLog(objs); // Display all object names in the Debug Log window
 
// Create an edge partition feature
geom1.create("pare1", "PartitionEdges");
geom1.feature("pare1").selection("edge").all(objs);
 
// Note, also the following syntax would work:
// geom1.feature("pare1").selection("edge").all();
 
geom1.feature("pare1").setIndex("param", 0.25, 0);
geom1.feature("pare1").setIndex("param", 0.5, 1);
geom1.feature("pare1").setIndex("param", 0.75, 2);
geom1.run("pare1");
 
selectNode(geom1.feature("pare1"));
The call to:
geom1.feature("pare1").selection("edge").all(objs);
is very similar to using Ctrl+A in the user interface for selecting all edges.
To instead select all edges of just the second object (the block), use:
geom1.feature("pare1").selection("edge").all(objs[1]);
Finally, to partition just 4 of the second object’s edges, use:
int[] edges = new int[]{1, 2, 3, 4};
geom1.feature("pare1").selection("edge").set(objs[1], edges);
This example is part of a collection available for download:
www.comsol.com/model/application-programming-guide-examples-140771
The relevant file for this example is:
geom_edge_partition_and_selections.mph
Using Selections to Measure Geometric Objects
The following example creates a cone of base radius 1 and height 1 and measures its various geometric properties. This code uses selections based on geometric entity numbers, however, the selection techniques from the previous section can also be used.
// Clear the model
clearModel(model);
 
// Create a 3D component and geometry
model.modelNode().create("comp1");
model.geom().create("geom", 3);
 
// Add a cone to the geometry
ModelNode comp1 = model.component("comp1");
GeomSequence geom = comp1.geom("geom");
geom.create("cone1", "Cone");
geom.feature("cone1").set("specifytop", "radius");
geom.feature("cone1").set("rtop", 0);
geom.run("cone1");
 
// Build the geometry
model.geom("geom").run();
 
// Measure the properties of the geometry
 
// Select the volumetric domain of the cone object
geom.measure().selection().init(3);
geom.measure().selection().set("cone1", 1);
 
// Measure properties from domain selection
double vol = geom.measure().getVolume(); // The volume of the cone
double volArea = geom.measure().getBoundaryArea(); // The total boundary area of the cone based on the domain selection
 
// Select all of the boundaries of cone object
geom.measure().selection().init(2);
geom.measure().selection().all();
 
// Measure properties from boundary selection (all)
double bndArea = geom.measure().getArea(); // The total boundary area of the cone based on selecting all boundaries
 
// Select one of the boundaries of the cone object (the bottom surface of the cone)
geom.measure().selection().init(2);
geom.measure().selection().set("cone1", 2);
 
// Measure properties from boundary selection (boundary 2)
double boundaryPerimeterLength = geom.measure().getBoundaryVolume();
 
// Select the edges of the bottom surface of the cone
geom.measure().selection().init(1);
int[] edges = new int[]{1, 3, 5, 8}; // The list of edges stored in an array (vector)
geom.measure().selection().set("cone1", edges);
// geom.measure().selection().set("cone1", 1, 3, 5, 8); // Alternative syntax
 
// Measure properties from edge selection (edges 1,3,5,8)
double edgePerimeterLength = geom.measure().getLength(); // The total boundary area of the cone based on selecting all boundaries
 
// Select two of the vertices: the apex and one of the bottom surface vertices
geom.measure().selection().init(0);
int[] vertices = new int[]{1, 3}; // The list of vertices stored in an array (vector)
geom.measure().selection().set("cone1", vertices);
 
// Measure properties from vertex selection (vertices 1,3)
double[] dist = geom.measure().getVtxDistance(); // d[0] is the distance, and d[i] is the distance in the ith coordinate (i = 1, 2, 3).
 
// Select the apex vertex
geom.measure().selection().set("cone1", 3);
 
// Get the coordinates for the apex vertex (3)
double[] coord = geom.measure().getVtxCoord();
 
// Display the results in the Debug Log window
debugLog("Cone volume = "+vol);
debugLog("Cone area, from domain selection = "+volArea);
debugLog("Cone area, from boundary selection = "+bndArea);
debugLog("Cone bottom surface perimeter length from boundary selection = "+boundaryPerimeterLength);
debugLog("Cone bottom surface perimeter length from edge selection = "+edgePerimeterLength);
debugLog("Distance between two points = "+dist[0]);
debugLog("X-distance between two points = "+dist[1]);
debugLog("Y-distance between two points = "+dist[2]);
debugLog("Z-distance between two points = "+dist[3]);
debugLog("X-coordinate of apex vertex = "+coord[0]);
debugLog("Y-coordinate of apex vertex = "+coord[1]);
debugLog("Z-coordinate of apex vertex = "+coord[2]);
 
// Note that you can also display the results in the Messages window, for example:
message("Cone volume = "+vol);
The syntax:
geom.measure().selection().init(0);
geom.measure().selection().init(1);
geom.measure().selection().init(2);
geom.measure().selection().init(3);
is used to change the selection mode to vertices, edges, boundaries, and domains, respectively.
The syntax:
geom.measure().selection().set("cone1", 1, 3, 5, 8);
is equivalent to:
int[] edges = new int[]{1, 3, 5, 8};
geom.measure().selection().set("cone1", edges);
and follows the general pattern:
geom.measure().selection().set(objectName, entityNumbers);
where objectName is the name of the geometry object and entityNumbers is an integer vector of geometric entity numbers (vertices, edges, boundaries, or domains).
This example is part of a collection available for download:
www.comsol.com/model/application-programming-guide-examples-140771
The relevant file for this example is:
measure_cone.mph
For examples of measuring similar quantities for the entire model using Results tools, see Measuring Model Quantities.
Using Selections on the Finalized Geometry
Selections applied to the finalized geometry differ from those applied to individual geometry objects within the geometry sequence under the Geometry node. Finalized geometry selections are used by Model Builder nodes such as Materials, Mesh, and physics interfaces like Heat Transfer in Solids. They are also used under a model component, such as Component 1, for defining Variables, Nonlocal Couplings, and other features that operate on the finalized geometry.
The following example sets up and runs a 3D stationary heat transfer simulation, using selections defined by direct reference to geometric entity numbers.
clearModel(model);
model.component().create("comp1", true);
model.component("comp1").geom().create("geom1", 3);
model.component("comp1").geom("geom1").geomRep("comsol");
model.component("comp1").mesh().create("mesh1");
 
model.component("comp1").geom("geom1").create("cyl1", "Cylinder");
model.component("comp1").geom("geom1").feature("cyl1").set("h", 0.1);
model.component("comp1").geom("geom1").feature("cyl1").set("r", 0.005);
model.component("comp1").geom("geom1").run();
 
model.component("comp1").geom("geom1").create("arr1", "Array");
model.component("comp1").geom("geom1").feature("arr1").selection("input").set("cyl1");
model.component("comp1").geom("geom1").feature("arr1").set("fullsize", new int[]{3, 3, 1});
model.component("comp1").geom("geom1").feature("arr1").set("displ", new double[]{0.02, 0.02, 0});
model.component("comp1").geom("geom1").run();
 
model.component("comp1").physics().create("ht", "HeatTransfer", "geom1");
 
model.component("comp1").material().create("mat1", "Common");
model.component("comp1").material("mat1").label("Steel AISI 4340");
model.component("comp1").material("mat1").propertyGroup("def").set("density", "7850[kg/m^3]");
model.component("comp1").material("mat1").propertyGroup("def").set("heatcapacity", "475[J/(kg*K)]");
model.component("comp1").material("mat1").propertyGroup("def")
.set("thermalconductivity", new String[]{"44.5[W/(m*K)]", "0", "0", "0", "44.5[W/(m*K)]", "0", "0", "0", "44.5[W/(m*K)]"});
 
model.func().create("rn1", "Random");
model.func("rn1").set("type", "uniform");
model.func("rn1").set("nargs", 3);
model.func("rn1").set("mean", 0.5);
 
model.component("comp1").physics("ht").create("temp1", "TemperatureBoundary", 2);
model.component("comp1").physics("ht").feature("temp1").set("T0", "300[K]");
int[] bnds = new int[]{3, 7, 11, 21, 25, 29, 39, 43, 47};
model.component("comp1").physics("ht").feature("temp1").selection().set(bnds);
 
model.component("comp1").physics("ht").create("hs1", "HeatSource", 3);
int[] doms = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
model.component("comp1").physics("ht").feature("hs1").selection().set(doms);
model.component("comp1").physics("ht").feature("hs1").set("Q0", "1e6*(1+rn1(x,y,z))");
 
model.study().create("std1");
model.study("std1").create("stat", "Stationary");
model.study("std1").feature("stat").setSolveFor("/physics/ht", true);
model.study("std1").createAutoSequences("all");
model.sol("sol1").runAll();
 
model.result().create("pg1", "PlotGroup3D");
model.result("pg1").label("Temperature (ht)");
model.result("pg1").feature().create("vol1", "Volume");
model.result("pg1").feature("vol1").set("colortable", "HeatCameraLight");
model.result("pg1").run();
The following lines illustrate how selections are applied to boundary surfaces in a physics interface:
model.component("comp1").physics("ht").create("temp1", "TemperatureBoundary", 2);
model.component("comp1").physics("ht").feature("temp1").set("T0", "300[K]");
int[] bnds = new int[]{3, 7, 11, 21, 25, 29, 39, 43, 47};
model.component("comp1").physics("ht").feature("temp1").selection().set(bnds);
In the first line, the final argument 2 specifies that the selection targets entities of dimension 2, that is, boundary surfaces. Aside from this dimension specification, the syntax is similar to selections applied to individual geometry objects.
Similarly, selections for volumetric domains are specified as follows:
model.component("comp1").physics("ht").create("hs1", "HeatSource", 3);
int[] doms = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
model.component("comp1").physics("ht").feature("hs1").selection().set(doms);
model.component("comp1").physics("ht").feature("hs1").set("Q0", "1e6*(1+rn1(x,y,z))");
Here, the argument 3 in the first line refers to entities of dimension 3, corresponding to domains (volumes) in the geometry.
To use named explicit selections instead of directly referencing entity numbers in physics features, replace the previous code blocks with the following:
model.component("comp1").selection().create("sel1", "Explicit");
model.component("comp1").selection("sel1").geom(2);
model.component("comp1").selection("sel1").label("Temperature Boundaries");
int[] bnds = new int[]{3, 7, 11, 21, 25, 29, 39, 43, 47};
model.component("comp1").selection("sel1").set(bnds);
 
model.component("comp1").physics("ht").create("temp1", "TemperatureBoundary", 2);
model.component("comp1").physics("ht").feature("temp1").set("T0", "300[K]");
model.component("comp1").physics("ht").feature("temp1").selection().named("sel1");
 
model.component("comp1").selection().create("sel2", "Explicit");
model.component("comp1").selection("sel2").geom(3);
int[] doms = new int[]{1, 2, 3, 4, 5, 6, 7, 8, 9};
model.component("comp1").selection("sel2").label("Heat Source Domains");
model.component("comp1").selection("sel2").set(doms);
 
model.component("comp1").physics("ht").create("hs1", "HeatSource", 3);
model.component("comp1").physics("ht").feature("hs1").selection().named("sel2");
model.component("comp1").physics("ht").feature("hs1").set("Q0", "1e6*(1+rn1(x,y,z))");
Selections created with model.component("comp1").selection() can be placed anywhere earlier in the code, as long as they are defined before the finalized geometry is created. This approach is generally preferred, as named explicit selections promote reuse and improve the clarity of the model structure.
To retrieve the geometric entities associated with physics feature selections, you can use the inputEntities() method. This should be done after either of the selection creation methods, whether by direct entity numbers or named explicit selections, has been called. The following code demonstrates this:
// Retrieve the geometric entities for the Temperature Boundary selection
int[] entitiesT = model.component("comp1").physics("ht").feature("temp1").selection().inputEntities();
debugLog("Geometric entities for the temperature boundary selection:");
debugLog(entitiesT);
 
// Retrieve the geometric entities for the Heat Source selection
int[] entitiesHS = model.component("comp1").physics("ht").feature("hs1").selection().inputEntities();
debugLog("Geometric entities for the heat source domain selection:");
debugLog(entitiesHS);
This example is part of a collection available for download:
www.comsol.com/model/application-programming-guide-examples-140771
The relevant file for this example is:
heat_transfer_in_cylindrical_rod_array_selections.mph