Breaking API changes¶
Changes that break existing Python code, newest first, each with what replaces it. 0.35.0.9
removes IfoptQPProblem and IfoptProblem: build a TrajOptQPProblem instead, and
expect different results wherever a cost went in without a penalty type. Its convex evaluators
take only the QP solution vector. The changelog lists every change; the
upgrade guide covers the tesseract upgrades in full.
| Release | What breaks | Use instead |
|---|---|---|
| Unreleased | CollisionCoeffData.setPairCollisionCoeff / getPairCollisionCoeff removed |
The C++ names |
| Unreleased | CollisionCoeffData, TrajOptCollisionConfig, SQPParameters compare by value and are unhashable |
A stable key, or id() |
| Unreleased | A collision constraint on a Var outside a NodesVariables raises VarNotInVariableSetError (a ValueError) at construction instead of segfaulting on its first update() |
Hand the Node over first |
| Unreleased | Node.getParent raises NodeParentUnavailableError for planner-owned nodes, including inside create overrides |
Keep the planner's Nodes, not their set |
| Unreleased | OSQPEigenSolver.update* raise QPSizeError on a wrong-length vector |
Size by init() |
| Unreleased | 16 command-language waypoint, instruction and Poly types compare by value and are unhashable | A stable key, or id() |
| Unreleased | TOTGCompositeProfile, ISPCompositeProfile, ISPMoveProfile, ConstantTCPSpeedCompositeProfile compare by value and are unhashable |
A stable key, or id() |
| Unreleased | The seven simple-planner move profiles compare by value and are unhashable | A stable key, or id() |
| Unreleased | OMPLSolverConfig.getNumPlanners() removed |
len(cfg.planners) |
| Unreleased | OMPL, TrajOpt and task-composer planning value types compare by value and are unhashable | A stable key, or id() |
| Unreleased | create_ompl_planner_configurators raises ValueError on a keyword none of the requested planners has |
Drop the keyword |
| Unreleased | 7 TrajOpt-Ifopt and Descartes profiles and waypoint configs compare by value and are unhashable | A stable key, or id() |
| Unreleased | Overriding a hook (generate, create, create*) on a Python subclass of a concrete profile raises ProfileOverrideNotDispatchedError at class definition; the override was silently never called |
Subclass the abstract base and hold the concrete profile |
| 0.35.0.9 | ChangeCollisionMarginsCommand(pair_margin_data) merges the pair margins (MODIFY) instead of replacing them |
Pass REPLACE |
| 0.35.0.9 | MotionProgram / get_manipulator_info require tcp_frame and working_frame; fk / ik require tip_link; to_composite_instruction has no tcp_frame |
Name the frames |
| 0.35.0.9 | tesseract_geometry.PointCloudPoint renamed |
PointCloud.Point |
| 0.35.0.9 | tesseract_scene_graph.JointType_* constants removed |
The JointType members |
| 0.35.0.9 | 37 collision, common and environment value types compare by value and are unhashable: hash(), set members and dict keys raise TypeError |
A stable key, or id() |
| 0.35.0.9 | TaskComposerPluginFactory.createTaskComposerNode / createTaskComposerExecutor no longer return None |
except TaskComposerPluginError |
| 0.35.0.9 | margin_data_override_type, set{Default,Pair}CollisionMarginData, CollisionMarginOverrideType, get/setPairCollisionMargin removed |
The tesseract names |
| 0.35.0.9 | ContactTestType_*, Events_*, ModifyAllowedCollisionsType_* and module-level CONSOLE_BRIDGE_LOG_* constants removed |
The enum members |
| 0.35.0.9 | tesseract_environment.AnyPoly_wrap_EnvironmentConst, tesseract_command_language.AnyPoly_* and _HAS_TASK_COMPOSER removed |
tesseract_task_composer.AnyPoly_* |
| 0.35.0.9 | FilesystemPath, _FilesystemPath, TransformMap, Environment.initFromUrdf / initFromUrdfSrdf removed; Environment.init(str, …) is content |
pathlib.Path |
| 0.35.0.9 | evaluateConvexCosts, evaluateTotalConvexCost, evaluateConvexConstraintViolations raise ValueError on a var_vals that is not getNumQPVars() long |
The QP solution vector |
| 0.35.0.9 | trajopt_sqp.IfoptQPProblem and trajopt_sqp.IfoptProblem removed |
TrajOptQPProblem |
| 0.35.0.1 | planning.Transform removed; Pose is an Isometry3d |
Pose |
| 0.35.0.1 | tesseract 0.35: package://tesseract_support/ resource URIs |
package://tesseract/support/ |
| 0.34.1.0 | tesseract 0.34: ifopt module, JointPosition, CartPosInfo, CollisionCache, … |
0.33 → 0.34 guide |
Unreleased¶
setPairCollisionCoeff and getPairCollisionCoeff removed¶
trajopt_ifopt.CollisionCoeffData bound two copies of its setter and getter under names C++ does
not have (#298). Call the C++ names:
| before | after |
|---|---|
data.setPairCollisionCoeff(a, b, c) |
data.setCollisionCoeff(a, b, c) |
data.getPairCollisionCoeff(a, b) |
data.getCollisionCoeff(a, b) |
trajopt value types compare by value and are unhashable¶
trajopt_ifopt.CollisionCoeffData, trajopt_ifopt.TrajOptCollisionConfig and
trajopt_sqp.SQPParameters bind __eq__/__ne__ from their C++ operator== and set
__hash__ = None (#261), as the 0.35.0.9 collision, common and environment types did.
CollisionCoeffData() == CollisionCoeffData() was False. hash(obj), {obj} and d[obj] = …
now raise TypeError: unhashable type:
| before | after |
|---|---|
seen = {config} |
seen = {id(config)} |
cache[params] = result |
key on the fields you vary, e.g. cache[params.max_iterations] = result |
Collision constraints need a Var in a variable set¶
trajopt's collision constraints key their contact cache on the variable set's hash, which they
read through var.getParent().getParent() without a check: a Var outside a NodesVariables
dereferenced null on the first update(). DiscreteCollisionConstraint,
DiscreteCollisionConstraintD, DiscreteCollisionNumericalConstraint,
ContinuousCollisionConstraint, ContinuousCollisionConstraintD and
ContinuousCollisionNumericalConstraint now raise VarNotInVariableSetError (a ValueError) at
construction (#297). A Var from createNodesVariables always qualifies; one from Node.addVar
qualifies once its Node is handed to NodesVariables:
| before | after |
|---|---|
var = node.addVar(...); DiscreteCollisionConstraint(evaluator, var); NodesVariables("t", [node]) |
var = node.addVar(...); NodesVariables("t", [node]); DiscreteCollisionConstraint(evaluator, var) |
Node.getParent raises for planner-owned nodes¶
A Python TrajOptIfoptCompositeProfile.create receives the planner's Nodes, whose variable set
the planner builds in C++ and frees when its solve returns. Node.getParent() on such a Node
returned a view of that set, which read freed memory once the solve was over (#297). It now raises
NodeParentUnavailableError (a RuntimeError) for every Node of a set C++ owns, during create
too. getParent() still returns the set for Nodes of a set built from Python:
| before | after |
|---|---|
nodes[0].getParent().getRows() in create |
read what create needs from the Nodes themselves: sum(node.size() for node in nodes) |
OSQPEigenSolver updates check sizes¶
updateGradient, updateLowerBound, updateUpperBound and updateBounds now sit on QPSolver
and raise QPSizeError (a ValueError) unless the vector has init()'s num_vars (gradient)
or num_cnts (bounds) entries (#283). trajopt checked with assert only and read past a short
vector. Call init(num_vars, num_cnts) with the QP's size before the updates.
Command-language types compare by value and are unhashable¶
The command-language waypoints, instructions and Polys compared by identity: two
JointWaypoint(["a"], [1.0]) were unequal. They now bind __eq__/__ne__ from the C++
operators and set __hash__ = None, as the collision, common and environment types did in
0.35.0.9 (#261): CartesianWaypoint, JointWaypoint, StateWaypoint, WaypointPoly,
CartesianWaypointPoly, JointWaypointPoly, StateWaypointPoly, InstructionPoly,
MoveInstructionPoly, MoveInstruction, CompositeInstruction, WaitInstruction,
TimerInstruction, SetAnalogInstruction, SetDigitalInstruction, SetToolInstruction.
hash(obj), {obj} and d[obj] = … now raise TypeError: unhashable type. Every instruction
has a UUID, which is the natural key; MoveInstruction equality also compares it, so two moves
built separately are unequal until the UUIDs match:
| before | after |
|---|---|
seen = {move} |
seen = {move.getUUID()} |
cache[waypoint] = result |
cache[id(waypoint)] = result (keep waypoint alive) |
move in done meant identity |
any(m is move for m in done); in now compares values |
Time-parameterization profiles compare by value and are unhashable¶
The four time-parameterization profiles were bound without their C++ operator==, so ==
compared identity: TOTGCompositeProfile() == TOTGCompositeProfile() was False. They now bind
__eq__/__ne__ and set __hash__ = None (#261), as the collision, common and environment value
types did in 0.35.0.9. The new RuckigTrajectorySmoothingCompositeProfile behaves the same way.
| before | after |
|---|---|
{TOTGCompositeProfile(): …}, set_of_profiles.add(p) |
key by name, e.g. {"DEFAULT": p}, or by id(p) |
p == q (identity) |
p is q |
Simple-planner move profiles compare by value and are unhashable¶
SimplePlannerFixedSize{,Assign,AssignNoIK}MoveProfile and
SimplePlannerLVS{,NoIK,Assign,AssignNoIK}MoveProfile bind the C++ operator== (#261): two
profiles with the same fields are ==, and hash(), set members and dict keys raise
TypeError, as for the value types in 0.35.0.9.
| before | after |
|---|---|
{profile: name} |
{id(profile): name}, or key by the profile name |
profile in seen_set (identity) |
any(p is profile for p in seen) |
OMPLSolverConfig.getNumPlanners() removed¶
OMPLSolverConfig.planners is bound (#302), so the count is len(cfg.planners). The list is
copied on access; its configurators are shared with the config. cfg.planners.append(x)
changes the copy, but cfg.planners[0].range = 0.5 edits the config. Assign a new list, or
use addPlanner / clearPlanners, which change it in place.
| before | after |
|---|---|
cfg.getNumPlanners() |
len(cfg.planners) |
Planner value types compare by value and are unhashable¶
As for collision, common and environment in 0.35.0.9, these classes bind the C++ operator==
and set __hash__ = None (#261). hash(), set members and dict keys raise TypeError:
| module | classes |
|---|---|
tesseract_motion_planners_ompl |
OMPLSolverConfig, OMPLRealVectorMoveProfile |
tesseract_motion_planners_trajopt |
TrajOptCartesianWaypointConfig, TrajOptJointWaypointConfig, TrajOptDefaultMoveProfile, TrajOptDefaultCompositeProfile, TrajOptOSQPSolverProfile, BasicTrustRegionSQPParameters (and the new OSQPSettings) |
tesseract_task_composer_planning |
ContactCheckProfile, FixStateBoundsProfile, FixStateCollisionProfile, KinematicLimitsCheckProfile, MinLengthProfile, ProfileSwitchProfile, UpsampleTrajectoryProfile |
Key on a field, or on id(obj) where identity is meant, as in
the 0.35.0.9 entry.
OMPLSolverConfig compares planners by count only (upstream ompl_solver_config.cpp), so two
configs holding different configurator types of the same number are equal.
create_ompl_planner_configurators rejects unused keywords¶
The helper accepts every OMPLPlannerType name (#301) and sets each keyword on the requested
configurators that have that field. It used to ignore a keyword no configurator used; now it
raises ValueError naming the keyword. A keyword that fits at least one requested planner is
still accepted (goal_bias with ["RRTConnect", "RRTstar"] sets it on the RRTstar ones).
| before | after |
|---|---|
create_ompl_planner_configurators(["RRTConnect"], goal_bias=0.1) |
create_ompl_planner_configurators(["RRTConnect"]) |
TrajOpt-Ifopt and Descartes profiles compare by value and are unhashable¶
These classes bind __eq__/__ne__ from their C++ operator== and set __hash__ = None, as the
0.35.0.9 value types do (#261):
| tesseract_motion_planners_trajopt_ifopt | TrajOptIfoptCartesianWaypointConfig, TrajOptIfoptJointWaypointConfig, TrajOptIfoptDefaultMoveProfile, TrajOptIfoptDefaultCompositeProfile, TrajOptIfoptOSQPSolverProfile |
| tesseract_motion_planners_descartes | DescartesDefaultMoveProfileD, DescartesLadderGraphSolverProfileD |
hash(profile), {profile} and d[profile] = … raise TypeError: unhashable type. Key on the
profile's name in the ProfileDictionary, or on id(profile) where identity is what you mean:
| seen = {profile} | seen = {profile_name} |
| cache[profile] = result | cache[id(profile)] = result (keep profile alive) |
Overriding a hook on a concrete profile raises¶
Only the abstract profile bases forward the planner's calls to Python. A Python subclass of a
concrete profile (the seven simple-planner move profiles, TrajOptIfoptDefaultMoveProfile,
TrajOptIfoptDefaultCompositeProfile, TrajOptIfoptOSQPSolverProfile,
DescartesDefaultMoveProfileD, DescartesLadderGraphSolverProfileD) that defined generate,
create or a create* hook planned with the C++ hook and never ran the override. Such a class
statement now raises ProfileOverrideNotDispatchedError, a TypeError (#340):
| before | after |
|---|---|
class P(SimplePlannerLVSMoveProfile): def generate(...) |
class P(SimplePlannerMoveProfile), holding a SimplePlannerLVSMoveProfile and calling its generate |
class P(TrajOptIfoptDefaultCompositeProfile): def create(...) |
class P(TrajOptIfoptCompositeProfile), holding a TrajOptIfoptDefaultCompositeProfile and calling its create |
class P(TrajOptIfoptOSQPSolverProfile): def create(...) |
configure the solver through the profile's fields and callbacks |
A subclass that defines no hook (to add fields or helpers) is unaffected.
0.35.0.9¶
ChangeCollisionMarginsCommand(pair_margin_data) defaults to MODIFY¶
The binding defaulted override_type to REPLACE, so leaving it out replaced every pair margin
in the environment, where the same C++ call merges the given pairs into the existing ones (#247).
Both overloads that take pair_margin_data now default to MODIFY, as upstream. Code that left
override_type out and relied on the replace passes it explicitly:
| before | after |
|---|---|
ChangeCollisionMarginsCommand(pairs) |
ChangeCollisionMarginsCommand(pairs, CollisionMarginPairOverrideType.REPLACE) |
CollisionMarginPairOverrideType is in tesseract_common. The new
ChangeCollisionMarginsCommand(default_margin, pairs, override_type) overload defaults to
MODIFY too.
The planning API no longer picks a TCP or working frame¶
tesseract_robotics.planning used to fill in frames it could not know (#30): the TCP as the last
of getActiveLinkNames(), which is only the tip for a serial chain, and the working frame as the
literal "base_link". get_manipulator_info also cached the guess and returned one shared,
mutable ManipulatorInfo per key. Now the caller names every frame: a missing one raises
TypeError at the call.
| before | after |
|---|---|
MotionProgram("manipulator") |
MotionProgram("manipulator", tcp_frame="tool0", working_frame="base_link") |
MotionProgram("manipulator", tcp_frame="tool0") |
MotionProgram("manipulator", tcp_frame="tool0", working_frame="base_link") |
robot.get_manipulator_info("manipulator") |
robot.get_manipulator_info("manipulator", "tool0", "base_link") |
robot.fk("manipulator", q) |
robot.fk("manipulator", q, "tool0") |
robot.ik("manipulator", pose, seed=q) |
robot.ik("manipulator", pose, "tool0", seed=q) |
robot.ik("manipulator", pose, q, "tool0") (positional seed, tip_link) |
robot.ik("manipulator", pose, "tool0", q) |
program.to_composite_instruction(joint_names, "tool0") |
program.to_composite_instruction(joint_names) |
For the bundled abb_irb2400 and lbr_iiwa_14_r820 the old guesses were "tool0" and
"base_link", so the right-hand column behaves exactly as before. For another robot, pass the
link your tool is mounted on and the frame your Cartesian targets are expressed in.
New: MotionProgram and get_manipulator_info take a keyword-only tcp_offset, a named TCP
(SRDF group_tcps, #133) or an Isometry3d, and the planner puts tcp_frame * tcp_offset on
each Cartesian target. See Frames and the TCP.
PointCloudPoint renamed to PointCloud.Point¶
The point type of PointCloud is nested in C++ (tesseract::geometry::PointCloud::Point) and is
now bound nested (#209). The module-level PointCloudPoint is gone: importing it raises
ImportError, reading it as an attribute raises AttributeError. addPoint(x, y, z) is
unchanged.
| before | after |
|---|---|
PointCloudPoint(), PointCloudPoint(x, y, z) |
PointCloud.Point(), PointCloud.Point(x, y, z) |
from tesseract_robotics.tesseract_geometry import PointCloudPoint |
from tesseract_robotics.tesseract_geometry import PointCloud, then PointCloud.Point |
SWIG-era JointType_* constants removed¶
tesseract_scene_graph no longer exports a module constant per JointType value. The seven
constants were copies kept from the SWIG bindings; C++ has none of them, and JointType is bound
as an enum whose members are the same objects (#214, finishing #176). Importing one now raises
ImportError: cannot import name 'JointType_FIXED' from 'tesseract_robotics.tesseract_scene_graph';
reading one as an attribute raises AttributeError.
| before | after |
|---|---|
JointType_FIXED (also _UNKNOWN, _REVOLUTE, _CONTINUOUS, _PRISMATIC, _FLOATING, _PLANAR) |
JointType.FIXED (.UNKNOWN, .REVOLUTE, …) |
from tesseract_robotics.tesseract_scene_graph import JointType_FIXED |
from tesseract_robotics.tesseract_scene_graph import JointType |
Value types compare by value and are unhashable¶
Every collision, common and environment class whose C++ header declares operator== was bound
without it, so == compared identity: ContactRequest() == ContactRequest() was False, and so
was env == env.clone(). These classes now bind __eq__/__ne__ from the C++ operators and set
__hash__ = None (#169):
| module | classes |
|---|---|
tesseract_collision |
ContactResult, ContactResultMap, ContactRequest, ContactManagerConfig, CollisionCheckConfig |
tesseract_common |
AllowedCollisionMatrix, BytesResource, CollisionMarginData, CollisionMarginPairData, GeneralResourceLocator, JointState, KinematicLimits, KinematicsPluginInfo, ManipulatorInfo, PluginInfo, PluginInfoContainer, SimpleLocatedResource |
tesseract_environment |
Environment, Command, AddKinematicsInformationCommand, AddLinkCommand, AddSceneGraphCommand, ChangeCollisionMarginsCommand, ChangeJointAccelerationLimitsCommand, ChangeJointOriginCommand, ChangeJointPositionLimitsCommand, ChangeJointVelocityLimitsCommand, ChangeLinkCollisionEnabledCommand, ChangeLinkOriginCommand, ChangeLinkVisibilityCommand, ModifyAllowedCollisionsCommand, MoveJointCommand, MoveLinkCommand, RemoveAllowedCollisionLinkCommand, RemoveJointCommand, RemoveLinkCommand, ReplaceJointCommand |
They join the classes that already compared by value (JointTrajectory, the plugin-info structs,
AddTrajectoryLinkCommand, AddContactManagersPluginInfoCommand and the two
SetActive*ContactManagerCommands).
What breaks is hashing. These objects are mutable, and an object that compares by value must not
hash by identity, or two equal objects land in different set buckets. So hash(obj), {obj}
and d[obj] = … now raise TypeError: unhashable type. Key on a field that identifies the
object, or on id(obj) where identity is what you mean:
| before | after |
|---|---|
seen = {cmd} |
seen = {(cmd.getType(), cmd.getLinkName())} (whatever names the command) |
cache[env] = result |
cache[env.getName()] = result, or cache[id(env)] (keep env alive) |
cmd in history meant identity |
any(c is cmd for c in history); in now compares values |
The equality is the C++ one, unchanged, so it inherits upstream's choices:
- Floating-point fields compare within a tolerance, not exactly:
JointStatevectors withisApprox(1e-5)after an exact size check (joint_state.cpp),ContactResultandCollisionCheckConfigwithalmostEqualRelativeAndAbs, the command origins withisApprox(1e-5). ContactRequest.is_validcompares the validator object: two requests are equal only while they hold the same validator (types.cpp).BytesResourceandSimpleLocatedResourcecompare their parent locators only for presence:ResourceLocator::operator==returnstrue(resource_locator.cpp).- A geometry compares by type and its per-instance uuid (geometry.cpp), so commands
holding geometry are equal only when they share it: the same URDF parsed twice gives unequal
AddSceneGraphCommands. Environmentcompares its revisions, the types of its commands, its current state and both timestamps (environment.cpp).env == env.clone()holds, becauseclone()copies the timestamps (L442–L444); two environments initialised separately from the same URDF are unequal. Its__eq__releases the GIL, because the C++ operator takes the environment's lock.
Two classes keep identity equality: ResourceLocator and Resource. Their C++ operator==
returns true for any pair (L46, L206), so binding it would make every two
Python locators equal. A Python locator subclass stays hashable.
KinematicLimits compares the shapes of its four limit matrices before calling the C++ operator,
which calls isApprox without checking them and reads out of bounds when they differ
(kinematic_limits.cpp). Limits of different shapes are unequal.
createTaskComposerNode and createTaskComposerExecutor raise¶
Upstream's TaskComposerPluginFactory returns null when it cannot create a node or executor:
the name is not configured, the plugin symbol fails to load, or the plugin factory throws. It
only logs a console_bridge warning. Python got None, against a stub that promised the object.
Both methods now raise TaskComposerPluginError, a RuntimeError (#203). For a name that is
not configured, the message lists the configured names.
| before | after |
|---|---|
node = factory.createTaskComposerNode(name)if node is None: … |
try: node = factory.createTaskComposerNode(name)except TaskComposerPluginError: … |
executor = factory.createTaskComposerExecutor(name)if executor is None: … |
try: executor = factory.createTaskComposerExecutor(name)except TaskComposerPluginError: … |
TaskComposer.plan still returns a failed PlanningResult for an unknown pipeline; its message
is now the factory's error.
Pre-0.33 collision-margin aliases removed¶
tesseract 0.33 renamed the collision-margin members; the bindings kept the old names as aliases of the new ones. The eight aliases are gone (#177); each replacement takes the same arguments and does the same thing.
| before | after |
|---|---|
config.margin_data_override_type |
config.pair_margin_override_type |
manager.setDefaultCollisionMarginData(m) |
manager.setDefaultCollisionMargin(m) |
manager.setPairCollisionMarginData(a, b, m) |
manager.setCollisionMarginPair(a, b, m) |
CollisionMarginOverrideType |
CollisionMarginPairOverrideType |
margins.getPairCollisionMargin(a, b) |
margins.getCollisionMargin(a, b) |
margins.setPairCollisionMargin(a, b, m) |
margins.setCollisionMargin(a, b, m) |
manager is a DiscreteContactManager or ContinuousContactManager, config a
ContactManagerConfig, margins a CollisionMarginData. A ContactManagerConfig has no
margin_data member (the collision API page used one; that snippet never ran): set its
default_margin, pair_margin_data and pair_margin_override_type, then pass it to
manager.applyContactManagerConfig(config).
SWIG-era enum constants removed¶
tesseract_collision, tesseract_environment and tesseract_common no longer export a module
constant per enum value. The 14 constants were copies kept from the SWIG bindings; C++ has
none of them, and every enum is bound as a class whose members are the same objects (#176).
Importing one now raises
ImportError: cannot import name 'ContactTestType_ALL' from 'tesseract_robotics.tesseract_collision';
reading one as an attribute raises AttributeError.
| before | after |
|---|---|
ContactTestType_ALL (also _FIRST, _CLOSEST, _LIMITED) |
ContactTestType.ALL (.FIRST, .CLOSEST, .LIMITED) |
Events_COMMAND_APPLIED, Events_SCENE_STATE_CHANGED |
Events.COMMAND_APPLIED, Events.SCENE_STATE_CHANGED |
ModifyAllowedCollisionsType_ADD (also _REMOVE, _REPLACE) |
ModifyAllowedCollisionsType.ADD (.REMOVE, .REPLACE) |
CONSOLE_BRIDGE_LOG_DEBUG (also _INFO, _WARN, _ERROR, _NONE) |
LogLevel.CONSOLE_BRIDGE_LOG_DEBUG (…) |
# before
from tesseract_robotics.tesseract_collision import ContactRequest, ContactTestType_ALL
from tesseract_robotics.tesseract_common import CONSOLE_BRIDGE_LOG_WARN, setLogLevel
request = ContactRequest(ContactTestType_ALL)
setLogLevel(CONSOLE_BRIDGE_LOG_WARN)
# after
from tesseract_robotics.tesseract_collision import ContactRequest, ContactTestType
from tesseract_robotics.tesseract_common import LogLevel, setLogLevel
request = ContactRequest(ContactTestType.ALL)
setLogLevel(LogLevel.CONSOLE_BRIDGE_LOG_WARN)
The CONSOLE_BRIDGE_LOG_* names keep their prefix because they are the C++ enumerator names of
console_bridge's unscoped LogLevel; only the module-level copies are gone.
AnyPoly helpers only in tesseract_task_composer¶
tesseract_environment re-exported AnyPoly_wrap_EnvironmentConst, and
tesseract_command_language re-exported AnyPoly_wrap_CompositeInstruction,
AnyPoly_wrap_ProfileDictionary and AnyPoly_as_CompositeInstruction, from
tesseract_task_composer inside a try: … except ImportError: pass. An install whose
task-composer extension failed to load lost the names without an error, and importing either
package loaded the task composer. The re-exports and tesseract_command_language._HAS_TASK_COMPOSER
are removed (#192); the functions are defined in, and imported from, tesseract_task_composer.
Code that imports them from the old place now fails with ImportError: cannot import name ….
| before | after |
|---|---|
from tesseract_robotics.tesseract_environment import AnyPoly_wrap_EnvironmentConst |
from tesseract_robotics.tesseract_task_composer import AnyPoly_wrap_EnvironmentConst |
from tesseract_robotics.tesseract_command_language import AnyPoly_wrap_CompositeInstruction (also AnyPoly_wrap_ProfileDictionary, AnyPoly_as_CompositeInstruction) |
from tesseract_robotics.tesseract_task_composer import AnyPoly_wrap_CompositeInstruction (…) |
A str is content, pathlib.Path is a file¶
tesseract_common.FilesystemPath was a str subclass, so Environment.init had to guess
whether a string was a path or URDF content, and init(str, str, locator) loaded files where
the C++ std::string overload parses content. std::filesystem::path now converts with
nanobind's caster (str | os.PathLike in, pathlib.Path out), and Environment.init binds the
four native overloads (#165):
| call | meaning |
|---|---|
init(urdf_xml, locator), init(urdf_xml, srdf_xml, locator) |
parse URDF/SRDF content |
init(Path(urdf), locator), init(Path(urdf), Path(srdf), locator) |
load files |
init(str(urdf_path), …) |
parsed as XML: returns False |
init(urdf_xml, Path(srdf), locator) (either order) |
TypeError |
The path overloads of Environment.init and the ContactManagersPluginFactory /
KinematicsPluginFactory config constructors accept os.PathLike only, never str, because
each sits beside a str content overload. Every other std::filesystem::path parameter takes
str | os.PathLike.
| before | after |
|---|---|
env.init(FilesystemPath(u), FilesystemPath(s), loc) |
env.init(Path(u), Path(s), loc) |
env.init(path_str, loc) (path) |
env.init(Path(path_str), loc) |
env.initFromUrdf(xml, loc) |
env.init(xml, loc) |
env.initFromUrdfSrdf(u_xml, s_xml, loc) |
env.init(u_xml, s_xml, loc) |
KinematicsPluginFactory(_FilesystemPath(p), loc) (also ContactManagersPluginFactory) |
KinematicsPluginFactory(Path(p), loc) |
TaskComposerPluginFactory(FilesystemPath(p), loc) |
TaskComposerPluginFactory(str(p), loc) |
TransformMap() |
{} |
TaskComposerPluginFactory still takes its config path as str; its path/content pair is not
bound yet.
The convex evaluators take the QP solution vector¶
QPProblem.evaluateConvexCosts, evaluateTotalConvexCost and
evaluateConvexConstraintViolations read var_vals in the layout of the QP that the last
convexify() built: getNumQPVars() entries, the NLP variables followed by the slack variables
(L28). trajopt documents that size (L59, L67) but does not check it. The
binding now raises ValueError for any other size, and before the first convexify(), while
getNumQPVars() is still 0 (L99, L831):
ValueError: evaluateConvexCosts: var_vals has 3 entries; it must be the QP solution vector of getNumQPVars() = 5 entries, the NLP variables followed by the slack variables
ValueError: evaluateConvexCosts: the problem has no convex model yet; call convexify() first
What used to work and now raises: an NLP-sized var_vals on a problem with constraint sets
and no hinge or absolute cost. Each constraint row adds one or two slack variables, and trajopt
0.35.0, which every tesseract-robotics-nanobind 0.35.0.x wheel bundles, reads only the NLP block
there, so the call returned the right values. Append zeros for the slack variables:
problem.convexify()
n_slack = problem.getNumQPVars() - problem.getNumNLPVars()
model_costs = problem.evaluateConvexCosts(np.concatenate([x, np.zeros(n_slack)]))
A QP solution, such as SQPResults.new_var_vals in a callback, already has the right size.
SQPResults.best_var_vals needs it too: on trajopt 0.35.0 it is NLP-sized until the first
accepted step and QP-sized after it (always NLP-sized in a wheel built on a trajopt that contains
trajopt#592), so take its first getNumNLPVars() entries and pad them as
above. A problem with no constraint sets and only squared costs has no slack variables, so its
NLP point is a QP point and nothing changes.
Why the binding checks: with a hinge or absolute cost, trajopt 0.35.0 multiplies the cost's full
QP rows, slack columns included, into var_vals (L187–L188), so an NLP-sized vector was
read past its end. On a one-joint problem whose absolute cost is 0.5, a view of the first three
entries of a longer buffer read 3.5 or 0.5, depending on the two entries after the view, and a
fresh three-entry array read 14.6. Before the first convexify(), an empty or NLP-sized vector
segfaulted on a squared cost. The rule covers all three evaluators, although trajopt 0.35.0 reads
only the NLP block in two of them: one contract, the one trajopt documents for all three once
trajopt#592 is in.
IfoptQPProblem and IfoptProblem removed¶
tesseract_robotics.trajopt_sqp no longer binds IfoptQPProblem or IfoptProblem. trajopt is
removing IfoptQPProblem (trajopt#595), and IfoptProblem was bound only to
construct it. Code that uses either now fails with
AttributeError: module 'tesseract_robotics.trajopt_sqp' has no attribute 'IfoptQPProblem'
(or 'IfoptProblem'). Build a TrajOptQPProblem over the same variables, and add every set to
it, each cost with a penalty type (#148):
# before
nlp = tsqp.IfoptProblem(nodes_variables)
nlp.addConstraintSet(joint_constraint)
nlp.addCostSet(vel_cost) # no penalty type
problem = tsqp.IfoptQPProblem(nlp)
problem.addConstraintSet(collision_constraint)
problem.setup()
# after
problem = tsqp.TrajOptQPProblem(nodes_variables)
problem.addConstraintSet(joint_constraint)
problem.addConstraintSet(collision_constraint)
problem.addCostSet(vel_cost, tsqp.CostPenaltyType.SQUARED)
problem.setup()
It is not a rename. In trajopt 0.35.0, which every tesseract-robotics-nanobind 0.35.0.x wheel
bundles (TrajOptQPProblem first ships in 0.35.0.9; earlier releases bind IfoptQPProblem),
the two problems differ in five ways, and the third and fourth change results:
IfoptQPProblem (removed) |
TrajOptQPProblem |
|
|---|---|---|
| 1. Per set, not per row | One merit coefficient, name and violation per constraint row; names are <set>_<row> (L93, L100). |
One per constraint set (L668, L675, L1021–L1043). Code that indexes names, violations or merit coefficients by row breaks, and the solver inflates a whole set's merit coefficient at once. |
2. addCostSet checks bounds |
Checks nothing; rejects HINGE (L50–L78). |
SQUARED and ABSOLUTE need equality bounds on every row, HINGE one-sided bounds; anything else, a range row included, raises (L416–L476). |
| 3. No raw-cost path | A cost added with IfoptProblem.addCostSet bypasses IfoptQPProblem::addCostSet (L59): it gets no gradient and no Hessian in the QP (L190, L263), while the merit still reads its raw, signed rows (L525–L531). |
Every cost goes in with addCostSet(set, penalty_type). A squared cost that was inert now shapes the steps; for ABSOLUTE and HINGE costs on trajopt 0.35.0, see the fourth difference. |
| 4. The merit matches the model for squared costs | Weights the model but not the merit, and counts the model once per cost term, so the solver can reject every step and stop at the seed (trajopt#595). | The merit weights and counts squared costs as the model does, so steps the old ratio rejected can be accepted. trajopt 0.35.0 has two exceptions, both fixed by trajopt#592 (merged 2026-09-30; the fix arrives only with a wheel built on a newer trajopt, none yet). Constraint coefficients weight a set's slack in the QP (L798) but not its violation in the merit (L1021–L1043). The model reads an ABSOLUTE or HINGE cost as 0 at every QP solution (L166–L196) while its exact cost ignores its coefficient (L1015), so the model predicts the whole cost away and the solver rejects a step that removes less than improve_ratio_threshold of it; a violation the trust box cannot cut by that much stops at its seed (the QP problem). |
| 5. Dynamic sets | Throws on isDynamic() sets (L42–L48). |
Accepts them (L404–L414). |
How much the third difference moves a result: this package's online SQP example added its velocity
cost with IfoptProblem.addCostSet, so its QP had no cost term and OSQP solved with an empty
Hessian. As a squared cost on TrajOptQPProblem, the global solve's summed squared joint steps
fell from 23.2 to 3.9, against 3.6 for the straight-line seed, and getTotalExactCost() now reports
that sum instead of the first velocity row (0.5).
Evidence
The five differences are read from the trajopt 0.35.0 sources linked above. The example's
numbers are measured on this repository's online_planning_sqp_example.py, and
tests/examples/test_online_planning_sqp_example.py pins its problem class, its cost as the
squared joint steps, and a global solve that reaches the target from the pinned start. How
much results move in other code depends on its costs; nothing here measures that. The
ABSOLUTE/HINGE exception is also measured: TestTrajOptQPProblemPenaltyCosts in
tests/trajopt_sqp/test_trajopt_sqp_bindings.py pins it.
0.35.0.1¶
Transform removed; Pose is an Isometry3d¶
tesseract_robotics.planning.Pose now subclasses Isometry3d. Its rotation math runs in Eigen,
its numpy-property facade is gone, its stored data are properties, and its factories collapse to
one name each that accepts both scalar-positional and array-like arguments. The Transform alias
is removed: use Pose (#76).
Resource URIs moved¶
tesseract 0.35 folds its support data into the tesseract package, so
package://tesseract_support/... is now package://tesseract/support/... in every URDF, SRDF and
locateResource call. The rest of that upgrade leaves the Python API unchanged; see the
0.34 → 0.35 guide.
0.34.1.0¶
The tesseract 0.34 upgrade removed the ifopt module (its types moved to trajopt_ifopt) and
replaced JointPosition with Var / Node / NodesVariables. It also removed CartPosInfo and
CollisionCache, dropped getStaticKey() from profiles, and renamed the cost and constraint
evaluation methods. The 0.33 → 0.34 guide has each change with its
replacement.