Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

Version 1 Next »

Goal: Learn how to control a crane to pickup and dropoff material

Video Tutorial: https://www.youtube.com/watch?v=6iCuejggoRI

Tip: Watch the video on YouTube in FullScreen mode and use HD quality (settings) to read the script clearly!

https://www.youtube.com/watch?v=6iCuejggoRI

In this tutorial you will learn how to use a Crane to pickup a box and drop it somewhere else by combining learnings from the basic tutorials.

Sequence

The material handling steps is described in the swimlane diagram below. A spawner spawns a box on the InputSpline. When the box hits the Pickup-cue at the end of the spline, the statemachine sends the crane to the Pickup-cue. The box is parented to the crane and moved to the Dropoff-cue where it is unparented on the Dropoff-cue and given a MotionTensor. At the end of the OutputSpline the box is destroyed. The three Crane splines forms a MotionWeb that is created using the ActorMotionPathGraph tool. You will learn how to create this MotionWeb in another tutorial.

Scene

Fig.1 Scene diagram showing the Splines and Cues involved in the simulation.

State Diagram

Fig.2 State Diagram showing the flow from the Spawner on the InputSpline, via the states in the Crane state machine to the OutputSpline and Destroyer.

Swimlane Diagram

Fig.3 Swimlane Diagram showing the resources used and events triggered during one material handling cycle.

Scene

The scene hierarchy consists of the following objects:

  • DES Controller: Controls the simulation and must be the parent of all simulation objects

  • InputSpline: Contains the Spawner and the cues for spawning and pickup (parenting).

  • Crane: Consists of a MotionWeb with the splines and cues for the base rotation, vertical movement and horizontal movement, and a CraneInstructor controlling the motion between the cues.

  • OutputSpline: Contains the cues for dropoff (unparenting) and destroying and the Destroyer.

  • BoxActor: Is the box (DESActor) that will be picked up.

Scripts

CraneInstructor Script

The CraneInstructor script detects boxes hitting the pickup cue on the InputSpline, controls the crane to pickup the box, rotates the crane to the dropoff position, drops the box and gives the box speed on the OutputSpline. Details are described below the code snippet.

Script 1 The CraneInstructor script that controls the actors in the MotionWeb

using System;
using System.Collections.Generic;
using u040.prespective.prescripted.des.activities.motiontensor;
using u040.prespective.prescripted.des.motionweb;
using u040.prespective.prescripted.des.participant.actor;
using u040.prespective.prescripted.des.participant.cue;
using u040.prespective.prescripted.des.support;
using u040.prespective.prescripted.des.viewmodel;
using UnityEngine;

namespace u040.prespective.demos.cranePickupDropoff
{
    //1) Class definition
    public class CraneInstructor : MotionWebInstructor
    {
        //2) MotionTensor of the box after dropoff on the OutputSpline (define tensor in the Inspector)
        public MotionTensorViewModel OutputSplineTensor = new MotionTensorViewModel();

        //3) Assignment buffer (stores boxes at the pickup point when the crane is busy)
        public AssignmentBuffer CraneAssignmentBuffer = new AssignmentBuffer();


        //===============================================================================================================
        // APPLY INSTRUCTION
        //===============================================================================================================

        //4) ApplyInstruction is called by the MotionWebInstructor when any DESActor crosses a DESCue (BoxActor/RotatorActor/VerticalActor/HorizontalActor)
        public override bool ApplyInstruction(ADESActor _actor, ADESCue _cue, ActorCueIntersectionEvent _intersectionEvent)
        {
            //5) Update the baseclass before calling the statemachine, otherwise the sequence will stop
            base.ApplyInstruction(_actor, _cue, _intersectionEvent);

            //6) Only accept center cue triggers (neglect enter and exit triggers)
            if (_intersectionEvent.IntersectionType != ActorCueIntersectionEvent.CueIntersectionType.Center)
            {
                //Return false, because not a breaking event. Exit ApplyInstruction.
                return false;
            }

            //7) Only accept BoxActor triggers (neglect Crane Horizontal/Vertical/Rotator actors)
            if (!(_actor is BoxActor))
            {
                //Return false, because not a breaking event. Exit ApplyInstruction.
                return false;
            }

            Debug.Log("Crane, go and pickup material..");

            //8) Call the RequestPickupByCrane method
            RequestPickupByCrane(_actor, _intersectionEvent.EventTime,
            new Func<double, MotionWebInstructionSequence, bool>((double _frameTimeAfterComplete, MotionWebInstructionSequence _justFinished) =>
            {
                //Return false, because not a breaking event, because the Crane has already finished it's sequence. Exit ApplyInstruction.
                return false;
            }));

            //Return false, because not a breaking event. Exit ApplyInstruction.
            return false;

        } //ApplyInstruction


        //===============================================================================================================
        // REQUEST PICKUP BY CRANE
        //===============================================================================================================

        //9) RequestPickupByCrane is called by ApplyInstruction when a box hits the pickup cue
        bool RequestPickupByCrane(ADESActor _thingToPickup, double _frameTimePassed, Func<double, MotionWebInstructionSequence, bool> _onDoneWithTransport)
        {
            //10) AssignmentBuffer (stores boxes at the Pickup cue if crane is busy)
            if (!CraneAssignmentBuffer.SetOrAddAssignment(_frameTimePassed,
                new BufferedAssignment()
                {
                    AssignmentDescription = "Pickup_Actor_" + _thingToPickup.ParticipantID,
                    AssignmentPriority = 99,
                    RequestingActor = _thingToPickup,
                    NumberOfRetries = -1,
                    TryStartAssignment = new Func<double, bool>((double _passedTimeAtRetry) => {
                        return RequestPickupByCrane(_thingToPickup, _passedTimeAtRetry, _onDoneWithTransport);
                    })
                }))
            {
                return true;
            }


            //11) Look up actors
            CraneRotatorMotionWebActor _rotator       = this.GetMotionWebActorsByType<CraneRotatorMotionWebActor>()[0];
            CraneVerticalMotionWebActor _vertical     = this.GetMotionWebActorsByType<CraneVerticalMotionWebActor>()[0];
            CraneHorizontalMotionWebActor _horizontal = this.GetMotionWebActorsByType<CraneHorizontalMotionWebActor>()[0];


            //PICKUP
            //12) OnArrivedAtPickup is called by the Statemachine
            Func<double, MotionWebInstructionStep, bool> OnArrivedAtPickupLocation = new Func<double, MotionWebInstructionStep, bool>(
                (double _passedFrameTimeAfterStep, MotionWebInstructionStep _stepJustCompleted) => 
                {
                    //Destroy the tensor on the box to be able to parent it to the Crane
                    List<MotionTensor> CurrentMotions = _thingToPickup.GetActivitiesByType<MotionTensor>();
                    for (int i = 0; i < CurrentMotions.Count; i++)
                    {
                        CurrentMotions[i].Destroy(_passedFrameTimeAfterStep, SimulationController);
                    }

                    //Parent box to the Crane (HorizontalActor)
                    _thingToPickup.ChangeTransformParent(_passedFrameTimeAfterStep, SimulationController.FrameTime, _horizontal);

                    //Return true, because parenting is a breaking event. Go to the next step in the statemachine.
                    return true;
                });


            //DROPOFF
            //13) OnArrivedAtDropOffLocation is called by the Statemachine
            Func<double, MotionWebInstructionStep, bool> OnArrivedAtDropOffLocation = new Func<double, MotionWebInstructionStep, bool>(
               (double _passedFrameTimeAfterStep, MotionWebInstructionStep _stepJustCompleted) => 
               {
                   //Unparent the box from the Crane ("null" means no new parent)
                   _thingToPickup.ChangeTransformParent(_passedFrameTimeAfterStep, SimulationController.FrameTime, null);

                   //Add a MotionTensor to the dropped box (speed etc. specified in the Inspector of the CraneInstructor)
                   if (OutputSplineTensor.TryGenerateMotionTensor(_thingToPickup, out MotionTensor _result))
                   {
                       SimulationController.AddActivityMidFrame(_passedFrameTimeAfterStep, SimulationController.FrameTime, _thingToPickup, _result);
                   }

                   //Return true, because unparenting is a breaking event. Go to the next step in the statemachine.
                   return true;
               });


            //FINISHED
            //14) DoOnDoneWithAssignement is called by the statemachine when it's finished
            Func<double, MotionWebInstructionSequence, bool> DoOnDoneWithAssignment = new Func<double, MotionWebInstructionSequence, bool>(
              (double _passedFrameTimeAfterAssignment, MotionWebInstructionSequence _completedSequence) =>
              {
                  //Pickup the next box in the AssignmentBuffer
                  CraneAssignmentBuffer.OnReadyForNextAssignment(_passedFrameTimeAfterAssignment);

                  //Return true to be sure event is picked up by the AssignmentBuffer
                  return true;
              });


            //15) STATEMACHINE
            MotionWebInstructionSequence _sequence = new MotionWebInstructionSequence()
            {
                SequenceID = "MoveCraneSequence",
                OnSequenceComplete = DoOnDoneWithAssignment,
                Verbose = false,
                InstructionSteps = new List<MotionWebInstructionStep>()
                {
                    //1: To VerticalTop
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "1_VerticalTop",                                         //Name
                        .1d,                                                     //Velocity
                        _vertical,                                               //Actor
                        "CUE_VerticalTop"                                        //Target cue
                        ),

                    //2: To HorizontalIn
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "2_HorizontalIn",                                         //Name
                        .1d,                                                      //Velocity
                        _horizontal,                                              //Actor
                        "CUE_HorizontalIn",                                       //Target cue
                        new List<string>(){ "1_VerticalTop" }                     //Previous step
                        ),

                    //3: To RotationPickup
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "3_RotateToPickup",                                        //Name
                        .5d,                                                       //Velocity
                        _rotator,                                                  //Actor
                        "CUE_RotationPickup",                                      //Target cue
                        new List<string>(){ "2_HorizontalIn" }                     //Previous step
                        ),

                    //4: To HorizontalOut
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "4_HorizontalOut",                                         //Name
                        .1d,                                                       //Velocity
                        _horizontal,                                               //Actor
                        "CUE_HorizontalOut",                                       //Target cue
                        new List<string>(){ "3_RotateToPickup" }                   //Previous step
                        ),
                      
                    //5: To VerticalBottom (PICKUP)
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "5_VerticalBottom",                                        //Name
                        .1d,                                                       //Velocity
                        _vertical,                                                 //Actor
                        "CUE_VerticalBottom",                                      //Target cue
                        new List<string>(){ "4_HorizontalOut" },                   //Previous step
                        null,                                                      //Following step
                        null,                                                      //Midroute cue action
                        OnArrivedAtPickupLocation                                  //Run at TargetCue (pickup location)
                        ),

                    //6: To VerticalTop
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "6_VerticalTop",                                           //Name
                        .1d,                                                       //Velocity
                        _vertical,                                                 //Actor
                        "CUE_VerticalTop",                                         //Target cue
                        new List<string>(){ "5_VerticalBottom" }                   //Previous step
                        ),

                    //7: To HorizontalIn
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "7_HorizontalIn",                                          //Name
                        .1d,                                                       //Velocity
                        _horizontal,                                               //Actor
                        "CUE_HorizontalIn",                                        //Target cue
                        new List<string>(){ "6_VerticalTop" }                      //Previous step
                        ),

                    //8: To RotationDropoff
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "8_RotateToDropoff",                                       //Name
                        .5d,                                                       //Velocity
                        _rotator,                                                  //Actor
                        "CUE_RotationDropoff",                                     //Target cue (one exit)
                        new List<string>(){ "7_HorizontalIn" }                     //Previous step
                        ),

                    //9: To HorizontalOut
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "9_HorizontalOut",                                          //Name
                        .1d,                                                        //Velocity
                        _horizontal,                                                //Actor
                        "CUE_HorizontalOut",                                        //Target cue
                        new List<string>(){ "8_RotateToDropoff" }                   //Previous step
                        ),

                    //10: To VerticalBottom (DROPOFF)
                    MotionWebInstructionStep.GetStepWithMotionVelocity(  
                        "10_VerticalBottom",                                        //Name
                        .1d,                                                        //Velocity
                        _vertical,                                                  //Actor
                        "CUE_VerticalBottom",                                       //Target cue
                        new List<string>(){ "9_HorizontalOut" },                    //Previous step
                        null,                                                       //Following step
                        null,                                                       //Midroute cue action
                        OnArrivedAtDropOffLocation                                  //Run at dropoff location
                        ),

                    //11: To VerticalTop
                    MotionWebInstructionStep.GetStepWithMotionVelocity(
                        "11_VerticalTop",                                           //Name
                        .1d,                                                        //Velocity
                        _vertical,                                                  //Actor
                        "CUE_VerticalTop",                                          //Target cue
                        new List<string>(){ "10_VerticalBottom" }                   //Previous step
                        ),

                    //12: To HorizontalIn
                    MotionWebInstructionStep.GetStepWithMotionVelocity(
                        "12_HorizontalIn",                                          //Name
                        .1d,                                                        //Velocity
                        _horizontal,                                                //Actor
                        "CUE_HorizontalIn",                                         //Target cue
                        new List<string>(){ "11_VerticalTop" }                      //Previous step
                        ),

                    //13: To RotationPickup
                    MotionWebInstructionStep.GetStepWithMotionVelocity(
                        "13_RotateToPickup",                                       //Name
                        .5d,                                                       //Velocity
                        _rotator,                                                  //Actor
                        "CUE_RotationPickup",                                      //Target cue
                        new List<string>(){ "12_HorizontalIn" }                    //Previous step
                        )
                } //InstructionSteps
            }; //Sequence

            //16) Register the sequence
            InstructionSequences.Add(_sequence);

            //17) Start Statemachine/Sequence
            return _sequence.StartSequence(this, _frameTimePassed);
        
        } //RequestPickupByCrane
    } //class
} //namespace

CraneInstructor script Explanation

Line 14: public class CraneInstructor : MotionWebInstructor

The CraneInstructor is a MotionWebInstructor that makes it possible to use a Statemachine (Sequence) to control the Crane.

Line 17: public MotionTensorViewModel OutputSplineTensor = new MotionTensorViewModel();

The MotionTensorViewModel defines the MotionTensor for the box on the OutputSpline. The speed, MotionMode and MotionType is specified in the Inspector.

Line 20: public AssignmentBuffer CraneAssignmentBuffer = new AssignmentBuffer();

The AssignmentBuffer stores boxes arriving at the pickup point when the crane is busy.

Line 28: public override bool ApplyInstruction(ADESActor _actor, ADESCue _cue, ActorCueIntersectionEvent _intersectionEvent)

The ApplyInstruction method is mandatory for MotionWebInstructors and it is called whenever any of our DESActor's (BoxActor/RotatorActor/VerticalActor/HorizontalActor) crosses a DESCue connected to this CraneInstructor.

Line 31: base.ApplyInstruction(_actor, _cue, _intersectionEvent);

The base.ApplyInstruction calls and updates the baseclass to enable correct statemachine operation.

Line 34: if (_intersectionEvent.IntersectionType != ActorCueIntersectionEvent.CueIntersectionType.Center)

Only center cue hits are used (neglect enter and exit hits)

Line 41: if (!(_actor is BoxActor))

Only BoxActor hits are used (neglect Crane Horizontal/Vertical/Rotator actors hitting a cue)

Line 50: RequestPickupByCrane(_actor, _intersectionEvent.EventTime, new Func<...

Call the RequestPickupByCrane method defined next.

Line 68: bool RequestPickupByCrane(ADESActor _thingToPickup, double _frameTimePassed, Func<...

The RequestPickupByCrane method is called by ApplyInstruction when a box hits the pickup cue.

Line 71: if (!CraneAssignmentBuffer.SetOrAddAssignment(_frameTimePassed,
new BufferedAssignment()

An AssignmentBuffer is created that stores boxes hitting the Pickup cue while the crane is busy.

Line 88: CraneRotatorMotionWebActor _rotator = this.GetMotionWebActorsByType<CraneRotatorMotionWebActor>()[0];

The CraneRotatorMotionWebActor (3x) looks up the three actors in the MotionWeb and use those in the Statemachine sequence.

Line 95: Func<double, MotionWebInstructionStep, bool> OnArrivedAtPickupLocation = new Func<...

The OnArrivedAtPickupLocation function is called from the Statemachine when the crane arrives at the Pickup cue. The box tensor is destroyed and the box parented to the crane (horizontal actor). We return true because parenting is a breaking event.

Line 115: Func<double, MotionWebInstructionStep, bool> OnArrivedAtDropOffLocation = new Func<...

The OnArrivedAtDropOffLocation function is called from the Statemachine when the crane arrives with the box at the Dropoff cue. The box is unparented from the crane (parented to "null") and given a MotionTensor on the OutputSpline. We return true because parenting is a breaking event.

Line 134: Func<double, MotionWebInstructionSequence, bool> DoOnDoneWithAssignment = new Func<...

The DoOnDoneWithAssignment function is called from the Statemachine when it has finished all steps. A new assignment is requested from the AssignmentBuffer. We return true because starting a new assignment is a breaking event.

Line 146: MotionWebInstructionSequence _sequence = new MotionWebInstructionSequence()

The Statemachine is a sequence limited by the three splines and cues in the Crane MotionWeb. OnSequenceComplete specifies that the DoOnDoneWithAssignment function is called when the sequence is finished. Verbose enables detailed debug info in the Console. Each MotionWebInstructionStep defines the name of the target position, the velocity of the actor, the actor that will move, the name of the TargetCue, the name of the previous step and the function to run at the TargetCue.

Line 278: InstructionSequences.Add(_sequence);

The Add(_sequence) registers the statemachine.

Line 281: return _sequence.StartSequence(this, _frameTimePassed);

Finally the StartSequence starts the statemachine

BoxActor Script

Script 2 The BoxActor script defines this actor to be a DES Actor. The core of the script is empty.

using u040.prespective.prescripted.des.participant.actor;
using UnityEngine;

namespace u040.prespective.demos.cranePickupDropoff
{
    public class BoxActor : DESActor
    {

    }
}

CraneRotatorMotionWebActor Script

Script 3 The CraneRotatorMotionWebActor script defines this actor to be a MotionWebActor so it can move around in the MotionWeb. The core of the script is empty.

using u040.prespective.prescripted.des.motionweb;
using UnityEngine;

namespace u040.prespective.demos.cranePickupDropoff
{
    public class CraneRotatorMotionWebActor : MotionWebActor
    {

    }
}

CraneVerticalMotionWebActor Script

Script 4 The CraneVerticalMotionWebActor script defines this actor to be a MotionWebActor so it can move around in the MotionWeb. The core of the script is empty.

using u040.prespective.prescripted.des.motionweb;
using UnityEngine;

namespace u040.prespective.demos.cranePickupDropoff
{
    public class CraneVerticalMotionWebActor : MotionWebActor
    {

    }
}

CraneHorizontalMotionWebActor Script

The CraneHorizontalMotionWebActor script is also similar to the CraneRotatorMotionWebActor script.

Script 5 The CraneHorizontalMotionWebActor script that this actor to be a MotionWebActor so it can move around in the MotionWeb. The core of the script is empty.

using u040.prespective.prescripted.des.motionweb;
using UnityEngine;

namespace u040.prespective.demos.cranePickupDropoff
{
    public class CraneHorizontalMotionWebActor : MotionWebActor
    {

    }
}

Playmode

Watch the video above to see what happens when we press play: The spawner spawns boxes on the InputSpline. When the first box hits the Pickup-cue at the end of the spline, the statemachine sends the crane to the Pickup-cue. The box is parented to the crane and moved to the Dropoff-cue via all the steps in the Statemachine. At the Dropoff-cue the box is unparented and given a MotionTensor on the OutputSpline. At the end of the OutputSpline, the box is destroyed.

  • No labels