Revisions

What changed between one release of Exudyn and the next, in prose, for somebody who has models written against the previous one. Every single resolved issue is in the changelog, and the issues themselves are in the issue tracker; this chapter is the short version, and a new section is added on top of it at each release.

This chapter is deliberately short. The revision behind it is recorded step by step in a plan and a log, which are working documents of the project rather than user documentation: there are two of them, revision2026 - finished, and what version 1.12 is - and revision2026b, which carries what it did not finish. Both are linked from the developer documentation, together with the standing information document that holds the measured facts and the decisions.

Version 1.13

A revision of the whole project rather than a feature release: the layout of the repository, the build, the tests, the documentation and the development tools. Most of it is invisible from a model script. The parts that are not are first.

Note

The individual issues of this revision carry 1.11.x version numbers, because the micro version counts closed issues continuously and does not restart; 1.12.0 is the point at which the revision was declared complete. 1.12 itself was not published: 1.13.0 is the first release after 1.11 and carries the revision together with what followed it.

What can break a script

ObjectANCFThinPlate computes other values. Its curvatures use the material measure - for a strip the curvature of ObjectANCFCable2D -, its integration rules changed (useReducedOrderIntegration 0: Gauss 5 x 5; 1 and 2: Lobatto 3 x 3 for the membrane, Gauss 2 x 2 for the bending), and a variable thickness gives the stiffness from the local thickness. Results change slightly; the outputs change in form: StrainLocal has 6 components through the thickness instead of the 3 membrane strains, CurvatureLocal and TorqueLocal are in the order \(xx,\, yy,\, xy\) with one sign - positive where the surface bends towards its normal -, and StressLocal has the opposite sign of its bending part (#2857, #2864).

bodyOrNodeList and bodyList of the Create functions are not accepted: the items are given in itemNumbers; a script that passes either gets a TypeError that says so, and exudev scripts --fix renames them (#2863).

Visualization parameters of items that draw nothing are gone: show of Node1D, the generic nodes, ObjectGenericODE1, the coordinate markers, LoadCoordinate and SensorUserFunction, show and color of ObjectConnectorCoordinateVector, and drawSize of ObjectConnectorGravity. A script that gives them, e.g. visualization=VCoordinateVectorConstraint(show=False), leaves them out; exudev scripts finds them (#2843).

exudyn.AccessFunctionType.SuperElementAlternativeRotationMode is gone: it was a mode of MarkerSuperElementRigid (useAlternativeApproach), not an access type, and is passed as an argument now (#2744).

Star imports export only what a module defines. from exudyn.utilities import * used to drag in everything that module had imported itself — np, sin, cos, sqrt, graphics and more. Every module now declares __all__, so a script that relied on those names arriving through Exudyn raises NameError. The fix is one line at the top of the script:

import numpy as np
from math import sin, cos, sqrt
import exudyn.graphics as graphics

from exudyn.utilities import * provides less (#2756): no longer the beam generators, the MainSystem extensions and the old graphics helpers, which each have their own place. A script that used them without importing them gets a NameError, and the fix is one line:

name

write instead

GenerateStraightLineANCFCable2D, GenerateSlidingJoint, GenerateAleSlidingJoint, GenerateStraightBeam

from exudyn.beams import GenerateStraightBeam (the one used)

CreateDistanceSensorGeometry, CreateDistanceSensor, DrawSystemGraph

mbs.CreateDistanceSensor(...), mbs.DrawSystemGraph(...)

color4red, color4steelblue, …

graphics.color.red, graphics.color.steelblue, …

GraphicsDataRectangle(x0, y0, x1, y1, color)

graphics.Lines([[x0,y0,0], [x1,y0,0], [x1,y1,0], [x0,y1,0], [x0,y0,0]], color=color)

GraphicsDataOrthoCubeLines(x0, y0, z0, x1, y1, z1, color)

graphics.BrickXYZ(x0, y0, z0, x1, y1, z1, addFaces=False, addEdges=True, edgeColor=color)

RefineMesh, ShrinkMeshNormalToSurface, … - the mesh functions of exudyn.graphicsDataUtilities

from exudyn.graphicsDataUtilities import RefineMesh (the one used)

GraphicsDataRectangle, GraphicsDataOrthoCubeLines and the color4... names are deprecated; the scripts of the repository use exudyn.graphics for them. exudev scripts <folder> names every such line of a script.

Names that are gone. Eleven small vector helpers were removed from exudyn.basicUtilities — numpy does all of them, faster and in one call; what they did is in the basicUtilities.py of Exudyn 1.11.0. They returned lists, numpy returns arrays:

removed

use

NormL2(v), VSum(v)

np.linalg.norm(v), np.sum(v)

VAdd(a, b), VSub(a, b), ScalarMult(s, v)

np.array(a) + b, np.array(a) - b, s*np.array(v)

VMult(a, b) (the scalar product)

np.dot(a, b)

Vec2Tilde(v), Tilde2Vec(m)

Skew(v), Skew2Vec(m) of exudyn.rigidBodyUtilities

DiagonalMatrix(n, value), eye2D, eye3D

value*np.eye(n), np.eye(2), np.eye(3)

The 23 deprecated GraphicsData... aliases in exudyn.utilities are gone as well; the current names are in exudyn.graphics - mostly the old name without the prefix, GraphicsDataSphere is graphics.Sphere. The exceptions: GraphicsDataOrthoCubePoint is graphics.Brick, GraphicsDataCube graphics.Cuboid, GraphicsDataOrthoCube graphics.BrickXYZ, GraphicsDataLine graphics.Lines, GraphicsDataFromSTLfileTxt graphics.FromSTLfileASCII, GraphicsData2PointsAndTrigs graphics.ToPointsAndTrigs, ExportGraphicsData2STL graphics.ExportSTL and MergeGraphicsDataTriangleList graphics.MergeTriangleLists (the aliases of 1.11.0).

Functions that moved inside the package: from exudyn.utilities into basicUtilities, advancedUtilities and mainSystemExtensions. A script that imports from exudyn.utilities in the usual way notices nothing; one that imported such a function by its full module path imports it from exudyn.utilities instead.

The files a run writes by default are in solution/: the solution file is solution/coordinatesSolution.txt, and the solver information and the restart file are there as well, so that a run writes nothing beside the script. A script that reads the solution file back as 'coordinatesSolution.txt' reads the new name 'solution/coordinatesSolution.txt' instead; a script that sets its own file names keeps them. exudev scripts <folder> finds both, and every file a script names without a directory.

InitializeFromRestartFile is gone from exudyn.utilities: it raised “not fully implemented” in every version. The restart file is still written (simulationSettings.solution.restart); how a simulation continues from it is #2850.

Item dictionaries are more forgiving, not less: a parameter that is left out keeps its default or its current value, where it used to raise KeyError.

A settings file changes what a script does, if you make one. ~/.exudyn/config.json is read at import into exudyn.special.overrideSettings and can override exudyn.config, any plain visualizationSettings — in every such structure that is created, not only in a SystemContainer — the size and position of a dialog, and the settings of the results monitor, which has no file of its own (User settings that persist between runs). Nothing writes it by itself, import exudyn prints one note naming what came from it, and EXUDYN_NO_USER_SETTINGS=1 ignores it — which is what a bug report needs. The test suites set that variable for themselves.

ObjectContactConvexRoll.rBoundingSphere is read-only. It is computed from coefficientsHull, and setting it never had an effect: the value was recomputed whenever the parameters changed. It is no longer an argument of the item and can no longer be set, so a script that passed it says so instead of being quietly ignored - delete the argument. Reading it — and pContact, the current potential contact point — works as before, with mbs.GetObjectParameter(objectNumber, 'pContact').

OpenVR is removed. The --openvr build flag, the settings under visualizationSettings.interactive.openVR, the openVR entry of the render state and the example openVRengine.py are gone. It could only be used with a head mounted display or an emulator, it was never part of a released wheel - it had to be compiled in - and it stood in the way of the coming rendering work. A script that only sets openVR settings runs once those lines are deleted; one that needs a head mounted display stays on Exudyn 1.11.

The text export of an image is removed. exportImages.saveImageFormat = 'TXT', the four exportImages.saveImageAsText... settings and exudyn.plot.LoadImage are gone. SC.renderer.GetGraphicsData() gives the same drawing elements, and more of them, as numpy arrays, without a render window; exudyn.plot.PlotImage draws what it returns, so a vector figure of a model is PlotImage(SC.renderer.GetGraphicsData(), fileName='model.pdf').

GraphicsData comes as rows. The functions of exudyn.graphics return points, normals, colors, triangles and edges as 2D numpy arrays - one row per point, color or triangle - and so does mbs.GetObject(..., addGraphicsData=True); they had been flat lists. Given to Exudyn, both forms are read. A script that indexes a returned list as flat, g['points'][3*i+1], reshapes it first: np.array(g['points']).reshape(-1,3)[i,1] works for both. graphics.Sphere returns the new type Spheres for a whole sphere instead of a TriangleList; the functions of exudyn.graphics that need triangles convert it (#2709).

Round primitives are curved. graphics.Cylinder, SolidOfRevolution (and with it Arrow, Basis, Frame, RigidLink, BallBearingRings) and Torus return 6-node triangles (triangles6) and curved rims (edges3) instead of flat triangles. nTiles keeps its meaning, the number of flat segments around: half as many curved elements carry them, and the renderer draws at least those segments. A script that reads g['triangles'] of such a shape gets the flat ones with graphics.Triangles6ToTriangles(g); graphics.ToPointsAndTrigs does this itself, so a contact mesh made from a cylinder with an even nTiles has the same facets as before, on more triangles (#2709).

The rotation of an ANCF slope node is one frame. NodePointSlope23 and the cross section of ObjectANCFBeam rotate with the orthonormal frame of their slopes (\( v_z\) normalized, \( v_y\) orthogonalized against it), and their angular velocity and rotation Jacobian are now its derivatives; they were a least-squares fit of both slopes, which differs where the cross section deforms. Joints and torques on these nodes converge as they should; the angular velocity output of a deformed cross section, and results with joints on slope nodes, change slightly (#2763).

The time integration uses the modified Newton method by default. timeIntegration.newton.useModifiedNewton is True: the Jacobian is kept over the steps and computed again only when Newton converges badly, which is much faster for most models. Results change within the tolerances of Newton - in most models from the eighth digit on, with contact and friction earlier; set it to False for the full Newton method. staticSolver.newton keeps the full Newton method (#2815).

The robotics classes compute with exudyn.HT. Robot.LinkHT, JointHT, COMHT, StdDH2HT, ModDHKK2HT, InverseKinematicsNumerical.GetCurrentRobotHT and dictJointType2HT return exu.HT instead of 4x4 numpy arrays, and RobotBase.HT, RobotTool.HT, RobotLink.localHT and preHT are stored as exu.HT, given as exu.HT or 4x4 matrix as before; the kinematics is about twice as fast. Scripts written for numpy arrays keep working: an exu.HT is indexed as its 4x4 matrix (H[0:3,3], also for writing, except the last row), H1 @ H2 is the product and H @ x what numpy computes, copy.deepcopy copies it, and np.array(H) gives the matrix. What no longer works is numpy arithmetic on it, such as H1 - H2: use np.array(H1) - np.array(H2) (#2821).

rigidBodyUtilities.HT is gone; its name is exudyn.HT now. The shortcut HT of the function HomogeneousTransformation(A, r), which from exudyn.utilities import * brought into a script, is removed, so that HT means one thing - the class exu.HT. A script that called HT(A, r) for a 4x4 array writes HomogeneousTransformation(A, r) (#2781).

What is new to use

Optional packages by name. pip install "exudyn[basic]" installs scipy and matplotlib with Exudyn, which most models need; [common] adds what frequently used features need, [tests], [all] and [rl] the larger sets, see Optional packages via the Exudyn extras.

Renamed settings keep working. A simulation setting that is renamed answers to its old name with a DeprecationWarning that names the new one, as the visualization settings do: parallel.multithreadedLLimitLoads, ...Residuals, ...Jacobians and ...MassMatrices are parallel.multithreadedLowerLimitLoads and so on, until 2031 (#2588, #2800). The same holds for item parameters: a renamed one is still taken under its old name - in the item class, in a dictionary and by mbs.GetObjectParameter/SetObjectParameter - with a warning naming the new one; the page of the item lists it (#2589).

mbs.CreateLinearSpringDamper creates a linear spring-damper along an axis between two rigid bodies or rigid markers, with the arguments of the joints - itemNumbers, position, axis, useGlobalFrame -, e.g. inside a prismatic joint (#1953).

A singular Jacobian with adaptive steps reduces the step. With adaptiveStep (the default) or automatic step size, the static and the implicit dynamic solvers take a singular system Jacobian as a failed step and reduce the step size, instead of stopping at once; a singularity of the model still ends the run, at the minimum step size, with the causing coordinate in the solver message and file (#1337). SC.GetSystem(i) keeps its SystemContainer alive, as SC.AddSystem() does, so that a system returned from a function works after the container went out of scope (#2851). ObjectContactFrictionCircleCable2D without a friction model - frictionStiffness and frictionVelocityPenalty zero - has no tangential force, also when its data node starts in slip (#1290).

A dynamic simulation continues from its restart file. With solution.restart.write the solvers write the state every solution.restart.writePeriod and at the end, and with solution.restart.continueIfAvailable the same script, started again, continues from it - appending to its solution and sensor files -, for a job that is stopped and resubmitted; see the user manual, Continuing a simulation from its restart file (#2850).

The Create functions take bodies, nodes or markers in itemNumbers. CreateSpringDamper, CreateCartesianSpringDamper, CreateRigidBodySpringDamper and CreateDistanceConstraint take a body at its local position, a node or a marker for each side; the joints and CreateTorsionalSpringDamper a body or a rigid marker; CreateCoordinateConstraint a body - its coordinates counted over its nodes -, a node, or None for the ground; the sphere contacts the sphere on a body, node or marker and the triangle or quad on a body; CreateForce and CreateTorque one body, node or marker in itemNumber. bodyNumbers and bodyNumber are the deprecated names until 2031, which exudev scripts --fix rewrites; the rolling discs keep bodyNumbers, as they take bodies only (#2863, #2337).

More markers on more bodies. ObjectRotationalMass1D takes forces and connectors anywhere on its table, not only on its axis; ObjectANCFBeam takes MarkerBodyRigid, so torques and joints with rotations act on its cross sections (#2775).

ANCF thin plates: damping, thickness and surface maps. ObjectANCFThinPlate has Kelvin-Voigt damping of the membrane and the bending stiffness (stiffnessProportionalDamping, bendingStiffnessProportionalDamping), which does not damp a rigid motion, and takes 12 thickness values - a thickness and its two gradients per node, which ShellMesh computes from a thicknessFunction. ShellMesh(surfaceMap=...) maps the mesh onto a surface, with the maps of exudyn.shells.SurfaceMap - SurfaceMap.Cylinder, ConeFrustum, HemisphericalShell, ToroidalPanel, … - or a map of one’s own; exudyn.shells has functions for constraints, edge loads and hinges of plate nodes (AddNodeConstraints, AddClampToGround, ApplyEdgeLoad, AddEdgeSpringDamper, …). visualizationSettings.bodies.shells draws the contour at a thickness coordinate (contourZeta), interpolated from the corners of the elements (reducedInterpolation), and along the normal (drawNormal) (#2857, #2858, #2859, #2864).

GeneralContact can keep a contact while the bodies penetrate. With keepContactWhilePenetrating = True a sphere-sphere or sphere-triangle contact acts as long as the bodies penetrate, and its damping may pull at the end of the contact, as ObjectContactSphereSphere and ObjectContactSphereTriangle do; by default it acts only while its force presses (#2848).

What an item computes, from Python. mbs.ComputeItem(item, what) computes, at the current state, what the solver computes for one object, node or marker - the position and rotation Jacobians of a body at a local position, its mass matrix and right-hand side, the forces of a connector, the equations, constraint Jacobian and reaction forces of a joint, the kinematics of a marker -, and mbs.ComputeItem(item) lists what applies (exu.ComputeItemType). exudyn.advancedUtilities.NumericalJacobian gives the numerical derivative to compare with, for testing a model or an item of one’s own (#2779).

exudyn.HT, the homogeneous transformation of the C++ core. exu.HT(rotation=A, translation=p) composes with * (H1*H2, and H*v for a point), inverts (Inverse()), and converts to and from the 4x4 matrix (HT44(), exu.HT(T44)) - faster than the 4x4 numpy arrays of exudyn.rigidBodyUtilities, whose function HomogeneousTransformation stays (#2780). It converts the rotation from and to Euler parameters, Tait-Bryan angles and the rotation vector, and the reference coordinates of the three rigid body nodes (GetCoordinatesEP(), SetCoordinatesRxyz(q), …), gives the frame of one HT seen from another (Relative), interpolates (InterpolateSO3, InterpolateSE3) and the angle and axis of its rotation; its Set functions return it, so that exu.HT().SetRotationAxis(axis, angle) is one line (#2810). LogSE3() and SetExpSE3(v) give and take the motion vector of a screw motion, LogR3xSO3() and SetExpR3xSO3(v) the translation and the rotation vector (#2819). The example homogeneousTransformationInterpolation.py drives rigid bodies along both interpolations; exu.HT(H) copies an HT (#2824).

The output variable HomogeneousTransformation. Every node, body point, marker and connector that gives Position and RotationMatrix also gives HomogeneousTransformation, the 4x4 matrix [A p; 0 1]: GetNodeOutput, GetObjectOutputBody and GetMarkerOutput return it as an exu.HT, a sensor stores its 16 values row by row, from which exu.HT(values) makes the HT (#2780, #2789, #2792).

A frame as one parameter. ObjectGround takes its frame as referencePosition and referenceRotation or at once as referenceHT - a 4x4 matrix, its 16 values or an exu.HT; a parameter left None is not given, and an HT given with one of its parts must agree with it. CreateGround and CreateRigidBody take referenceHT, and CreateRigidBody also initialHT for initialRotationMatrix and initialDisplacement (#2793, #2794). The rigid markers take localHT, a frame with a rotation in the body, link or node: MarkerBodyRigid and MarkerKinematicTreeRigid with localPosition as its translation, MarkerSuperElementRigid with offset, MarkerNodeRigid a rotation only - so a joint can take its axis from its markers instead of rotationMarker0/1 (#2795). ObjectKinematicTree takes its joint transformations and offsets as one list jointHTs, and numpy reads an exu.HT as its 4x4 matrix (np.array(H)), so the HT functions of exudyn.rigidBodyUtilities and the robotics classes take an exu.HT wherever they take a 4x4 array (#2798, #2799).

The rotation of a joint is its markers’. rotationMarker0/1 of ObjectJointGeneric, ObjectJointRevoluteZ, ObjectJointPrismaticX, ObjectConnectorRigidBodySpringDamper and ObjectConnectorTorsionalSpringDamper are deprecated and removed in 2031: the rotation is given to the markers as localHT, e.g. MarkerBodyRigid(bodyNumber=b, localHT=HomogeneousTransformation(A, p)); setting one other than the unit matrix gives a DeprecationWarning at the line that adds the item. The Create... functions, AddRevoluteJoint, AddPrismaticJoint, GetJointArgs and the robotics classes put the rotation into the markers they create (#2745). ObjectConnectorRigidBodySpringDamper applies rotationMarker0 after the frame of marker 0, as the joints do and as its page says; a model with a rotationMarker0 other than the unit matrix moves differently than before (#2801). ObjectContactCurveCircles has no rotationMarker0 any more: it was used only for drawing, and Assemble() refused any value other than the unit matrix; the curve lies in the frame of marker 0 (#2803).

Deprecations: once, counted, and found. A deprecated setting, item parameter or function warns once per session and name; exu.special.deprecations.warnOnce = False shows every use, to find all of them in a script. Every use is counted in exu.sys['deprecationUse'], by source (simulationSettings, visualizationSettings, items, functions) and name, so a test sees whether a model used anything deprecated (recordUse). exudev scripts also reports deprecated and removed item parameters, as keyword of the item class and as key of an item dictionary (#2804, #2805, #2806).

The deprecated functions of the Python library warn as well. AddRigidBody, AddRevoluteJoint, AddPrismaticJoint, graphics.BrickXYZ, GenerateStraightLineANCFCable2D and the others marked DEPRECATED in their description, and the arguments bodyList of the Create... functions and numberOfChildren/survivingIndividuals of GeneticOptimization, give a DeprecationWarning at the line of the script, once per session, and are counted in exu.sys['deprecationUse']['library']; each says what to use instead. They, and the deprecated functions of the C++ module, are removed in 2029; exudev scripts reports them in a script (#2807).

The simulation settings, reordered. No substructure ends in Settings any more, everything of the solution file is in solution.file, of the sensors in solution.sensors, of the restart file in solution.restart, and the solver of mbs.SolveDynamic is a setting. Every old name still works until 2031, with a DeprecationWarning naming the new one, and exudev scripts <folder> --fix rewrites a script to the new names (#2813):

old

new

solutionSettings

solution

solutionSettings.writeSolutionToFile, coordinatesSolutionFileName, solutionWritePeriod

solution.file.write, .name, .writePeriod

solutionSettings.appendToFile, binarySolutionFile, writeFileHeader, writeFileFooter, writeInitialValues

solution.file.append, .binary, .writeHeader, .writeFooter, .writeInitialValues

solutionSettings.solutionInformation, flushFilesDOF

solution.file.information, .flushAboveCoordinates

solutionSettings.exportVelocities, exportAccelerations, …

solution.file.export.velocities, .accelerations, …

solutionSettings.sensorsStoreAndWriteFiles, sensorsWritePeriod, sensorsAppendToFile, sensorsWriteFileHeader, sensorsWriteFileFooter

solution.sensors.active, .writePeriod, .append, .writeHeader, .writeFooter

solutionSettings.writeRestartFile, restartFileName, restartWritePeriod

solution.restart.write, .name, .writePeriod

solutionSettings.outputPrecision (files)

solution.precision

outputPrecision (console)

consolePrecision

linearSolverSettings, linearSolverType

linearSolver, linearSolver.solverType

displayComputationTime, displayGlobalTimers, displayStatistics

show.computationTime, show.globalTimers, show.statistics

timeIntegration.simulateInRealtime, realtimeFactor, realtimeWaitMicroseconds

timeIntegration.realtime.active, .factor, .waitMicroseconds

timeIntegration.explicitIntegration

timeIntegration.explicit

timeIntegration.explicitIntegration.dynamicSolverType

timeIntegration.solverType

newton.newtonResidualMode, newton.useNewtonSolver

newton.residualMode, newton.active

numericalDifferentiation.forODE2connectors, staticSolver.constrainODE1coordinates

forODE2Connectors, constrainODE1Coordinates

timeIntegration.solverType (default GeneralizedAlpha) is the solver mbs.SolveDynamic uses; a solverType given to SolveDynamic is used for that run and the setting is set back afterwards - SolveDynamic no longer leaves the explicit solver type written into the settings. An exudyn.MainSolverExplicit used directly takes the solver from timeIntegration.solverType and refuses an implicit one with a message naming the setting.

The binary solution file (solution.file.binary) stores its numbers as float if solution.precision is below 8 and as double otherwise (#2816).

Item parameters without the prefix physics, and a few more renamed. The parameters of the bodies and finite elements lost their prefix - physicsMass is mass, physicsInertia is inertia, physicsCenterOfMass is centerOfMass, physicsLength, physicsMassPerLength, physicsAxialStiffness, physicsBendingDamping and the others of the beams and the plate likewise; radiusSphere is sphereRadius (ContactSphereTorus, ContactSphereTriangle), the friction of ObjectConnectorCoordinateSpringDamperExt is dynamicFrictionForce, staticFrictionOffsetForce and viscousFrictionFactor (N s/m), factorValue1 of ObjectConnectorCoordinate is factor1, rollingFrictionViscous of ObjectConnectorRollingDiscPenalty is rollingViscousFriction (s/m), and intrinsicFormulation and classicalFormulation are useIntrinsicFormulation and useClassicalFormulation. The Create... functions take the new names. Every old name still works until 2031 with a DeprecationWarning - in the item class, in a dictionary, in GetObjectParameter/SetObjectParameter and as argument of a Create... function - and exudev scripts --fix rewrites it (#2814).

A system without coordinates - only ground, sensors and user functions - is solved by every solver: time advances, the user functions are called and the sensors record (#2790).

The frame of a rigid marker is drawn with visualizationSettings.markers.showBasis and basisSize: three lines in red, green and blue, or three arrows with short heads (#2791).

Curved shapes in GraphicsData. 6-node (quadratic) triangles - the key triangles6 of a TriangleList - quadratic lines (Lines with shape 'quadratic') and quadratic edges (edges3) are drawn curved: the renderers split them when they draw, as fine as visualizationSettings.openGL.advanced.curvedTriangleTilingAngle asks, and a change of it shows at once; each edge is split by its own curvature, so a surface curved in one direction is not split along the other. The type Spheres draws many spheres at once, and the raytracer intersects them exactly. The round shapes of exudyn.graphics - Cylinder, Sphere (also partial and hollow), Torus, SolidOfRevolution, Tube, LinkedCylinders and the shapes built from them - consist of 6-node triangles, as do the surfaces of quadratic NGsolve meshes and of Tet10 meshes (FEMinterface.VolumeToSurfaceElements) in the FFRF objects. python/Examples/graphicsCurvedShapes.py shows them all (#2709).

A command line for the installed package: python -m exudyn info prints the version, where the package is installed, which compiled module is loaded and which optional packages are present — the first thing to put into a bug report. monitor, plot and demo are the other three commands; see The command line: python -m exudyn.

Errors say what kind of problem they are. Everything Exudyn raises derives from exudyn.ExudynError and from the matching Python built-in, so except ValueError still works and except exudyn.SolverError is now possible — see Errors: what Exudyn raises.

Two environment variables keep a run quiet, which matters for parameter studies and for anything that runs unattended: EXUDYN_SUPPRESS_UI_WINDOW_OPEN=1 opens no renderer, viewer, plot or dialog window, and EXUDYN_OUTPUTDIRECTORY=<path> puts the files a model writes where you want them.

The windows can remember where you put them. The settings dialog has a store settings and a store positions button, the render window has a size and a position among its settings (view0.window.renderWindowSize and renderWindowPosition), and exudyn.plot.StorePlotWindowGeometry() keeps the plot windows of PlotSensor where they are arranged. Everything goes into ~/.exudyn/config.json, after showing what will be written (User settings that persist between runs).

The scene as data: SC.renderer.GetGraphicsData() returns every line, sphere, circle, text and triangle the renderer would draw, each with the item that drew it, and needs no window - for a test that checks what a model looks like, or for a figure drawn with matplotlib.

Stopping a simulation from a script: SC.renderer.StopSimulation() does what closing the render window does - the running simulation ends after its step, a later one before its first, and neither is reported as a solver failure - from a user function, another thread or a test; mbs.SetRenderEngineStopFlag(False) lets the next one run again.

exudyn.types answers questions about items from Python: which markers an object accepts, which item types exist.

mbs.Inspect(itemIndex, what) asks an existing item what it provides and requests - its output variables, its type flags, the node and marker types it requests, the access functions of a body - as lists of the exported enumerations; what is a member of exu.InspectType, or None for all that apply. A node marker answers the node types its node must provide as requirements, each a list of alternatives (#2817). The test model inspectTest.py shows it.

Tutorials as notebooks. python/Notebooks/ holds the tutorials as Jupyter notebooks, stored with the outputs of their last run; the documentation shows them with these outputs, and python/Examples/notebooks/ holds the same code as scripts. exudev notebooks runs them and stores the outputs. exudyn.interactive.ShowImage(SC) shows the scene in a notebook - an image of the raytracer, without a window (#2831). The examples of the Python-C++ interface chapter are notebooks of python/Notebooks/reference/, those of the user manual’s pages on graphics, solving and the renderer notebooks of python/Notebooks/snippets/; the tests run them, so each example on these pages shows what it printed.

Kinetic and potential energy as output variables (OutputVariableType.KineticEnergy, PotentialEnergy) of the bodies, beams, plates, superelements and spring-dampers, and exudyn.advancedUtilities.SystemEnergy for the energy of a whole system with its loads; an item that cannot compute its energy - with a user function defining its force - says so, and mbs.Inspect does not list it. The test models energiesTest.py and energiesFlexibleBodiesTest.py show them.

Springs and ropes drawn as tubes. visualizationSettings.connectors.curveTiling is the number of segments of a full turn of a spring winding or of the arc of a rope on a sheave, and connectors.drawSimplified = False draws the windings of the springs as a tube instead of lines and ObjectConnectorDistance as a rod of its drawSize; the rope of ObjectConnectorReevingSystemSprings is one closed tube along its spans and arcs (#2839, #2843). A 3D beam without section geometry is drawn as a line, and its contour colors follow it (#2837, #2843). ObjectConnectorGravity, if shown, is a line between its markers, and SensorLoad is drawn at the marker of its load (#2843).

What is new to read

The documentation is Markdown and is built with Sphinx for every release; the hand-written chapters are docs/manual/, and everything under docs/generated/ — the reference manual of all items, the Python utilities, the examples and the test models — is written by the generators from the definitions and the docstrings.

Until this release the documentation was one PDF, theDoc.pdf — a name that still appears in older issues, in scripts and in printed notes. It is the HTML documentation now: the same content, searchable, and one place per subject instead of a chapter and a PDF section saying it twice.

Installing and building are told once each. The installation instructions come before the first example, and building from source - which used to be described in three places that disagreed - is one page of the developer documentation, together with a page that takes a new developer from cloning the repository to a passing test run and one on the git workflow (#2646).

CHANGELOG.md is new: every resolved issue of the current release, newest first, with a table of every release above it. The version number says where an issue landed — the micro version is the count of issues closed in a release, so 1.10.160 is the 160th issue closed in release 1.10. The earlier releases are on the issue tracker page, which carries more about each issue than the changelog does, and the two no longer print the same list twice (#2599).

What changed behind the scenes

The wheels for Windows, Linux and macOS are built by continuous integration on every tagged release, and the documentation is built the same way. Everything else a maintainer does — build, test, documentation, issues, release — runs through one driver, exudev, which replaced sixteen batch files. None of that changes anything for a user of a wheel; it changes how quickly a fix reaches one.