/* ======================================================================== * Copyright (c) 2005-2026 The OPC Foundation, Inc. All rights reserved. * * OPC Foundation MIT License 1.00 * * Permission is hereby granted, free of charge, to any person * obtaining a copy of this software and associated documentation * files (the "Software"), to deal in the Software without * restriction, including without limitation the rights to use, * copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following * conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * The complete license agreement can be found here: * http://opcfoundation.org/License/MIT/1.00/ * ======================================================================*/ using System; using Opc.Ua; namespace Robotics.IntentEnabledRobot.Simulation { /// /// An axis-aligned solid a part can come to rest on: the bench, a fixture plate, a /// locating peg, or another part. /// /// /// Identifies the solid, so a caller can tell what a part ended up standing on. /// /// /// Centre of the footprint along X, in the world frame. /// /// /// Centre of the footprint along Y, in the world frame. /// /// /// Full extent of the footprint along X. /// /// /// Full extent of the footprint along Y. /// /// /// World height of the upper face, which is what something resting on it stands on. /// public readonly record struct SimulatedSupportSolid( string Name, double CentreX, double CentreY, double SizeX, double SizeY, double Top); /// /// Works out what a part comes to rest on, so a simulated cell can put a released part /// where gravity would leave it instead of where the tool happened to let go. /// /// /// /// This is a resting model, not a physics engine: it answers "what is the highest /// solid under this footprint" and nothing else. There is no toppling, no friction and /// no sliding, because none of those change the answer a pick-and-place cell needs - /// which is that a part released over a bench ends up on the bench, a part released /// over another part ends up on top of it, and neither ends up inside the other. /// /// /// A part released without such a model stays wherever the tool centre point was, which /// in this cell left it a measured 165 mm in the air. /// /// public sealed class SimulatedSupportModel { /// /// Initializes the model with the solids that never move. /// /// /// Benches, plates, pegs and anything else a part can stand on but that a robot /// does not carry. /// /// /// The height a part falls to when nothing at all is under it, which stops a /// footprint off the edge of every solid returning negative infinity. /// /// /// is null. /// public SimulatedSupportModel(ArrayOf fixtures, double groundLevel) { m_fixtures = fixtures; GroundLevel = groundLevel; } /// /// Gets the height a part falls to when nothing is under it. /// public double GroundLevel { get; } /// /// Gets the height of the highest solid under a footprint, which is the surface /// something placed there comes to rest on. /// /// /// Centre of the footprint along X, in the world frame. /// /// /// Centre of the footprint along Y, in the world frame. /// /// /// Full extent of the footprint along X. /// /// /// Full extent of the footprint along Y. /// /// /// Solids that move, typically the other parts. Anything the caller wants ignored - /// the part being placed, or a part currently in the gripper - is simply left out. /// /// /// The world height of the supporting surface. /// public double SupportHeight( double centreX, double centreY, double sizeX, double sizeY, ArrayOf movable) { double top = GroundLevel; top = Highest(top, m_fixtures, centreX, centreY, sizeX, sizeY); top = Highest(top, movable, centreX, centreY, sizeX, sizeY); return top; } /// /// Gets the height a part's centre settles at when released over a footprint, which /// is the supporting surface plus half the part's own height. /// /// /// Centre of the part along X, in the world frame. /// /// /// Centre of the part along Y, in the world frame. /// /// /// Full extent of the part along X. /// /// /// Full extent of the part along Y. /// /// /// Full height of the part. /// /// /// The other parts, excluding the one being placed. /// /// /// The world height of the part's centre once it is resting. /// public double RestingCentreHeight( double centreX, double centreY, double sizeX, double sizeY, double sizeZ, ArrayOf movable) { return SupportHeight(centreX, centreY, sizeX, sizeY, movable) + (sizeZ * 0.5); } /// /// Gets the height a part's centre settles at, never allowing it to end up lower /// than its support even when a caller asks for that. /// /// /// A caller that has its own idea of where the part should go - the height the tool /// let go at, say - passes it as . A request /// above the resting height is honoured, because a part can be held higher than it /// would fall to; a request below it is not, because that is a part inside a solid. /// /// /// Centre of the part along X, in the world frame. /// /// /// Centre of the part along Y, in the world frame. /// /// /// Full extent of the part along X. /// /// /// Full extent of the part along Y. /// /// /// Full height of the part. /// /// /// The height the caller would otherwise use. /// /// /// The other parts, excluding the one being placed. /// /// /// The requested height, or the resting height when the request is below it. /// public double ClampAboveSupport( double centreX, double centreY, double sizeX, double sizeY, double sizeZ, double requestedCentreZ, ArrayOf movable) { double resting = RestingCentreHeight(centreX, centreY, sizeX, sizeY, sizeZ, movable); return requestedCentreZ < resting ? resting : requestedCentreZ; } /// /// Gets a value indicating whether two footprints overlap, which is what decides /// whether one solid can hold another up. /// /// /// Centre of the first footprint along X. /// /// /// Centre of the first footprint along Y. /// /// /// Full extent of the first footprint along X. /// /// /// Full extent of the first footprint along Y. /// /// /// The second footprint. /// /// /// true when the footprints overlap. /// public static bool FootprintsOverlap( double aCentreX, double aCentreY, double aSizeX, double aSizeY, in SimulatedSupportSolid b) { return Math.Abs(aCentreX - b.CentreX) < ((aSizeX + b.SizeX) * 0.5) && Math.Abs(aCentreY - b.CentreY) < ((aSizeY + b.SizeY) * 0.5); } private static double Highest( double top, ArrayOf solids, double centreX, double centreY, double sizeX, double sizeY) { ReadOnlySpan span = solids.Span; for (int ii = 0; ii < span.Length; ii++) { if (span[ii].Top > top && FootprintsOverlap(centreX, centreY, sizeX, sizeY, span[ii])) { top = span[ii].Top; } } return top; } private readonly ArrayOf m_fixtures; } }