![]() |
0.1.33
|
File input and output is handled using .xml files. See the Wikipedia page for xml for more information on xml files: en.wikipedia.org/wiki/XML.
Within Helios, xml files can be used to execute any Context commands that would be issued within a program, for example, adding geometry, adding timeseries, or adding primitive data.
All xml files start with a header indicating the version to be used, and the tag "<helios>...</helios>" should encapsulate the whole file:
Comments are given in xml files by:
Primitives are added by giving a tag with the primitive type, followed by elements that specify necessary inputs. The table below gives examples of adding each primitive type in an .xml file. Note that the parameters used to specify the primitives are the same as when adding them to the Context via 'add[*]' commands (e.g., addPatch()).
| Primitive Type | Code Sample |
|---|---|
| Patch | |
| Triangle | |
| Disk | |
| Voxel | |
Data timeseries are specified by the tag 'timeseries' with the attribute giving the label for the timeseries (e.g., temperature). The 'datapoint' tag is used to specify data points, with elements specifying the time, date, and value. The sample below gives an example of creating a timeseries in an xml file.
Note that the date can alternatively be specified as a Julian day of year (1-366) using the <dateJulian> tag:
If timeseries/weather data is available in a tabular ASCII text file, this can be read directly into the Context to create a timeseries using the loadTabularTimeseriesData() method. The general format for these files is that each column should correspond to each variable (e.g., time, date, temperature, wind speed) and each row should be a different time point. There should be an equal number of columns on every line.
At a minimum, there should be a column specifying the date, which could either be a date string (e.g., 03/10/2023) or Julian day and the year, as well as columns specifying the hour and at least one corresponding data value. The date string can separate values by the '/' or '-' characters (03/10/2023 or 03-10-2023, respectively), and can have different ordering (e.g., YYYYMMDD) which is specified based on an argument to the loadTabularTimeseriesData() method.
There are two ways of specifying the ordering and labels of columns. The first is to specify them in the file header (first line). The second is to specify them manually as an argument to the loadTabularTimeseriesData() method. This can be useful if you want to customize the names of data values without modifying the actual text file.
There are certain column labels that are keywords used by Helios to specify required values such as the date. These are given in the table below. Any other label not contained in the table below will be treated as a variable, whose label in the timeseries will be the given column label.
| Column Label | Description | Required? |
|---|---|---|
| year | Year (YYYY) | year+DOY -OR- date |
| DOY or Jul | Day of year starting at Jan. 1 (DOY=1) | year+DOY -OR- date |
| date | Date string delimited by '/' or '-' character (default format YYYY-MM-DD) | year+DOY -OR- date |
| hour | Hour of the day. Can be specified either as 13 or 1300 (for 1PM example). | yes |
| minute | Minute of the hour. If 'hour' above is specified with minute included (e.g., 1315), the minute field will be automatically added. | no |
| second | Second of the minute. | no |
Arguments to the loadTabularTimeseriesData() method:
[]), the labels given in the first line of the file will be used. Otherwise, the length of the list must match the number of columns in the file.There is a special shortcut for reading CIMIS weather station files. To read these files, simply specify the second argument as a list containing the string "CIMIS" (e.g., ["CIMIS"]), which will automatically set column labels as: ["", "", "", "date", "hour", "DOY", "ETo", "", "precipitation", "", "net_radiation", "", "vapor_pressure", "", "air_temperature", "", "air_humidity", "", "dew_point", "", "wind_speed", "", "wind_direction", "", "soil_temperature", ""]. You can specify any delimiter in this case - it will be overridden to be comma delimited.
A code example for generic weather data is given below.
The above example would read the comma-delimited file "../input/weatherfile.csv". Timeseries data would be added to the Context for values of "temperature", which could be queried based on this label.
XML files can be read by the Context via the function loadXML(). This function parses the XML file and adds all specified structures (see above) to the Context. Below is an example of how to load an XML file into the Context.
The Context can automatically import Stanford .ply files en.wikipedia.org/wiki/PLY_(file_format). This is accomplished via the loadPLY() command, as illustrated below. There are several overloaded versions of this function, which allow for various modifications to the PLY model.
The nx, ny and nz vertex properties are read as per-vertex normals, and are given the same up-axis permutation as the vertex coordinates.
There are other forms of the loadPLY function that allow for translation, rotation, and scaling of the entire PLY model.
Different applications may utilize different coordinate axes in .ply files. In computer graphics applications, it is common to define the y-axis as the up direction. Helios uses a z-up coordinate system. By default loadPLY() assumes that the coordinate system used when creating the .ply file is y-up. There is an optional argument loadPLY() that can allow you to easily define the up-axis.
The Blender software package (www.blender.org) can easily modify and convert most polygon file formats to .ply format.
Helios can read Wavefront (.obj) files and associated material (.mtl) files. Not all features of .obj files are applicable to Helios, and thus some features are not supported. Supported features are:
v, v/vt, v//vn, v/vt/vn), including negative (relative) indicesWavefront files are loaded via the loadOBJ() function, which takes the name of an .obj file. If the file defines materials and lists an associated material (.mtl) file, the code looks in one of two places for the file. First, if an absolute or relative path is given for the .mtl file name, the file is simply loaded from that location. If only a file name is given for the .mtl file, the directory where the original .obj file was found is searched for the .mtl file.
All aspects of material files are not supported. Helios simply searches for the "Kd" texture map, and uses that to color the primitives.
The loadOBJ() function requires inputs that scale the model to achieve a certain size of the model, and apply a rotation to the model. There are multiple overloaded versions of this function (listed below) that allow specification of these aspects differently.
| Function | Description |
|---|---|
| loadOBJ(filename, origin, height, rotation, default_color, silent=False) | Load the model and translate it to the location 'origin', scale to have a height of 'height' (set height=0 to apply no scaling), and apply spherical rotation of 'rotation'. |
| loadOBJ(filename, origin, height, rotation, default_color, upaxis, silent=False) | Same as above, except specify the 'up axis' of the model ('XUP', 'YUP', or 'ZUP'). Some 3D modeling software uses a different default axis corresponding to the up direction (e.g., computer graphics software commonly uses 'y' as the up direction). |
| loadOBJ(filename, origin, scale, rotation, default_color, upaxis, silent=False) | Same as above, except a scaling factor is applied to the model in the x-, y-, and z-directions based on the value of 'scale' (if scale.x=scale.y=scale.z=0, no scaling is applied). |
The last optional argument to the loadOBJ() function allows the user to disable any messages output from the function while loading by setting silent=True.
Example code is given below to load an OBJ model at the origin with no scaling (height=0) or rotation (rotation=SphericalCoord(1, 0, 0)) applied. Note that upaxis defaults to ‘'YUP’; passupaxis='ZUP'‘ explicitly if your model already uses Helios’ z-up convention.
The Context has analogous files for writing PLY files based on the geometry currently loaded in the Context. This is accomplished via the writePLY() command, as illustrated below.
Geometry belonging to a Polymesh with retained connectivity (see Mesh Topology) is written against the mesh's shared vertices rather than three independent vertices per triangle, and nx, ny and nz vertex properties are emitted when every vertex in the file carries a normal. This is what allows a closed solid to reload as a closed solid whose volume can still be computed. Output for geometry that is not part of such a mesh is unchanged.
Writing Wavefront OBJ files is similar to writing a PLY file, except that it will produce both a .obj file containing the geometry, and a .mtl file defining materials (colors, texture masks). For this reason, only the base file name with no extension is provided to the function.
As with PLY, geometry belonging to a Polymesh with retained connectivity is written against the mesh's shared vertices, emitting each vertex once and referencing it by index from every face that uses it. With write_normals=True one true per-vertex normal is written per shared vertex, rather than one flattened face normal per primitive, so a smooth mesh survives a load–write–load round trip instead of degrading to a faceted one.
Triangles read from an OBJ or PLY file are grouped into a Polymesh compound object that retains the connectivity of the source file: a deduplicated vertex list, a face table, and optional per-vertex normals and texture coordinates. The vector of UUIDs returned by loadOBJ() and loadPLY() is unchanged; the object ID is obtained with getPrimitiveParentObjectID().
Retained connectivity is what makes a loaded mesh a surface rather than a triangle soup. It is used to compute an enclosed volume, to render the mesh smoothly, and to write it back out against shared vertices so a smooth mesh survives a load–write–load round trip.
| Function | Description |
|---|---|
| getPolymeshObjectVertices(objID) | Shared vertex positions |
| getPolymeshObjectFaces(objID) | Vertex index triples defining each face |
| getPolymeshObjectVertexNormals(objID) | Per-vertex normals, or an empty list if the mesh has none |
| getPolymeshObjectVertexUV(objID) | Per-vertex texture coordinates |
| getPolymeshObjectVertexCount(objID) | Number of shared vertices |
| getPolymeshObjectFaceCount(objID) | Number of faces |
| doesPolymeshObjectHaveVertexNormals(objID) | Whether the mesh carries vertex normals |
| getPolymeshObjectVertexNormalSource(objID) | Where those normals came from |
| getPolymeshObjectFaceIndexForPrimitive(objID, uuid) | Face index of a member primitive |
| getPolymeshObjectPrimitiveUUIDForFace(objID, face_index) | UUID of the primitive making up a face |
| setPolymeshObjectTopology(objID, vertices, faces, face_UUIDs, ...) | Attach a face set to a mesh built programmatically |
A mesh assembled with addPolymeshObject() has no topology until one is attached: its face count is zero, it cannot report a volume, and it is written out as independent per-triangle vertices.
A mesh loaded by loadOBJ() or loadPLY() always carries vertex normals. Normals authored in the file are kept as they are; a file that supplies none (most exporters omit them unless asked) has them generated from the mesh connectivity at load time (helios-core 1.3.85+), so a curved imported surface shades smoothly without any further call. The generated normals are blended across every edge, which leaves the shared-vertex topology intact, so isPolymeshObjectClosed() and getPolymeshObjectVolume() are unaffected.
Only a mesh assembled programmatically through setPolymeshObjectTopology() without normals has none. computePolymeshObjectVertexNormals() generates them for that case, or regenerates them at a different crease angle or after the mesh has been deformed. It averages face normals weighted by area, and splits vertices across edges whose dihedral angle exceeds crease_angle_degrees so that hard edges stay hard.
getPolymeshObjectVertexNormalSource() reports the provenance as a VertexNormalSource: AUTHORED (read from the file), COMPUTED (generated, by the loader or by computePolymeshObjectVertexNormals()) or NONE (a programmatic mesh with no normals).
| Function | Description |
|---|---|
| isPolymeshObjectClosed(objID) | Whether the mesh is watertight |
| getPolymeshObjectBoundaryEdges(objID) | Vertex index pairs for edges referenced by only one face |
| getPolymeshObjectConnectedComponents(objID) | Face indices grouped into disjoint pieces |
| getPolymeshObjectSurfaceArea(objID) | Total area of every face |
| getPolymeshObjectVolume(objID) | Enclosed volume, summed over the closed pieces |
Only a closed mesh has a well-defined enclosed volume. getPolymeshObjectVolume() separates a mesh into its connected pieces and sums the volume of those that are closed, so a solid shape modelled with an open stalk reports the shape's volume; an error naming the number of unmatched edges is raised only when no piece is closed. A mesh carrying no face table has its closure checked by matching facets on coincident corners. Use getPolymeshObjectSurfaceArea() for a wholly open mesh.
Sphere, Tube and Cone objects can report the true surface normal of the curved shape they approximate, rather than the flat normal of the triangle that approximates it. doesObjectHaveAnalyticVertexNormals() reports which object types can, and getObjectPrimitiveVertexNormals() returns the normals at each vertex of a member primitive. Pass a list of UUIDs instead of one to evaluate many primitives at once, which prepares the per-object quantities only once.
Normals are evaluated from each shape's own definition rather than stored, so they account for taper and remain correct after the object is transformed or its nodes and radii are changed. Object types built from genuinely flat faces, such as a tile or box, report that they have no analytic normals and return an empty list.
A compound object can report which of its member primitives meet at each mesh vertex. This is what lets a per-face quantity — an outgoing flux, a colour, a scalar field — be averaged onto the vertices that neighbouring faces have in common, and then interpolated back across each face, so a tessellated curve reads as a curve rather than as a set of flat panels.
doesObjectHaveSharedVertexTopology() reports whether an object exposes this. Tile, AdaptiveTile, Sphere, Tube and Cone objects always do; a Polymesh does when it carries a face table (see Mesh Topology). A Box or Disk object, and a polymesh assembled from loose primitives by addPolymeshObject(), does not.
getObjectSharedVertexCount() gives the number of distinct shared vertices — one greater than the largest index the accessors can return. getObjectPrimitiveSharedVertexIndices() returns, for one primitive, the shared vertex each of its corners belongs to, in the same order as getPrimitiveVertices(). Two primitives meeting at a corner report the same index there.
Both take a VertexWeldMode, which sets the granularity at which coincident vertices count as the same shared vertex:
| Value | Meaning |
|---|---|
VertexWeldMode.WELD_FULL | Treat every coincident vertex of the object as one shared vertex |
VertexWeldMode.WELD_CROSS_SECTION_ONLY | Weld only within a cross-section, keeping vertices at the same cross-sectional position on different segments distinct |
The distinction matters only for object types with a distinguished axis. For a Tube, welding fully smooths along the axis as well as around it, which erases detail the tessellation actually captured; welding only around the cross-section removes the faceting without touching the axial direction. A Polymesh or Tile has only one sensible answer and reports the same topology for either mode.
Prefer getObjectPrimitiveSharedVertexIndicesMulti() when walking a whole object: the per-primitive form locates the primitive in the object's list on every call, so a full walk of a Sphere, Tube or Cone is quadratic. getPrimitiveSharedVertexIndices() is a convenience form that resolves the primitive's parent object itself, and returns an empty list for a primitive that belongs to no object.
setPolymeshObjectVertices() moves every shared vertex of a polymesh in one call and pushes the new positions out to the member primitives, so faces that meet at a vertex stay welded. This is the supported way to deform a mesh: transforming the member primitives individually leaves each shared vertex wherever the last facet processed put it, tearing the surface apart.
The topology is unchanged, so the list must be parallel to and the same length as getPolymeshObjectVertices(). Texture coordinates are not touched, and neither is the solid fraction of the member primitives — which is a function of the (u,v) coordinates rather than the vertex positions — so deforming a textured mesh does not re-rasterize its alpha mask.
All geometry and global/primitive data loaded into the Context can be written to an XML file using the writeXML() function, which can be later read back in using the loadXML(). This functionality can be used to save progress during a simulation run, or to ensure that consistent geometry is always used across simulation runs, among other things.
An XML file can be written as follows:
and later read back into a new simulation:
To export only a subset of primitives, pass a list of UUIDs to writeXML():
To export a list of compound objects (and the primitives they contain), use writeXML_byobject():
The quiet flag (defaults to False) suppresses informational console output during export.