Step 2: Datamodel, forklift shape, pallet positions
1. Update the data model
1.1. Create new classes
Go to the '…datamodel' bundle and open the model/datamodel.ecore file:
Create a new 'Forklift' EClass
-
In the 'datamodel.ecore' file editor, right-click on the 'datamodel' item and select the 'New Child' → 'EClass' menu item. A new datamodel class is created.
-
In its properties, set its 'Name' to 'Forklift'.
-
Select 'Agent' as the only ESuper type.
Create a new 'Direction' EEnum
-
In the 'datamodel.ecore' file editor, right-click on the 'datamodel' item and select the 'New Child' → 'EEnum' menu item. A new datamodel enumeration is created.
-
In its properties, set its 'Name' to 'Direction'.
-
Right-click the 'Direction' enum and in its context menu choose 'New Child'→ 'EEnum Literal'. Set the literal’s 'Literal' and 'Name' properties to 'IN', and 'Value' property to 0 (zero).
-
In the same manner, add another literal to Direction, with 'Literal' and 'Name' equal to 'OUT' and 'Value' equal to 1.
-
Set Direction enum 'Default Value' property to 'IN (0)'.
Create a new 'DockArea' EClass
-
Create a new datamodel EClass with name='DockArea'
-
Add new fields to EClass 'DockArea':
-
new EReference, with name='scenario' and EType='Scenario';
-
new EAttribute, with name='id', EType='EString' and ID=true;
-
new EAttribute, with name='direction' and EType='Direction';
-
new EReference, with name='storagePlaces', EType='Node' and Upper Bound=-1.
-
Create new fields in 'Scenario' EClass
Add new fields to the Scenario EClass:
-
new EReference, with name='storagePlaces', EType='Node', and Upper Bound=-1;
-
new EReference, with name='dockAreas', EType='DockArea', Containment=true, Upper Bound=-1, and EOpposite='scenario';
Change the type of 'agents' field from 'Agent' to 'Forklift. Change the name of 'agents' field to 'forklifts'. Move the 'scenario' field from 'Agent' class into 'Forklift' class.
Save the modified 'datamodel.ecore' file.
Check if you have the data model similar to this one:
1.2. Generate datamodel Java classes
In the 'datamodel.genmodel' file (located next to the 'datamodel.ecore' file) editor, right-click the child Datamodel element and choose 'Generate Model Code'.
1.3. Fix compilation errors caused by the datamodel update
-
AgentPage: should extendAbstractPage<Forklift, Scenario>, callsuper(Forklift::getScenario);, increateControlsInternal()useSCENARIO__FORKLIFTS. -
ObjectsPart: should useSCENARIO__FORKLIFTS,getForklifts()instead ofgetAgents(). -
TreePart: should useSCENARIO__FORKLIFTS. -
TreeElementScenario: should callgetForklifts()instead ofgetAgents(). -
Model: should callgetForklifts()instead ofgetAgents()
1.4. Download template scenario
Here you can find the scenario that matches the up-to-date data model and contains some test data: https://github.com/amalgama-llc/warehouse-fast-track-tutorial/blob/main/scenario/Warehouse_lightweight.xlsx
2. Create Forklift class in the simulation bundle
-
Locate the
Agentclass already created in thesimulationbundle in thecom.company.warehouse.simulation.graph.agentspackage. -
Rename it to
Forklift. -
Add new fields and methods to the
Forkliftclass:Forklift.javaprivate boolean loaded; // User-provided callback to announce completion of the current action private Runnable onComplete; //... public boolean isLoaded() { return loaded; } private void resetAction(Runnable onComplete) { cancelMoving(); this.onComplete = onComplete; } private void finishAction() { if (onComplete != null) { var callback = onComplete; onComplete = null; callback.run(); } } public void moveTo(Node node, Runnable onComplete) { resetAction(onComplete); moveTo(node, currentVelocity); } -
Replace the
onDestinationReachedmethod:Forklift.java@Override public void onDestinationReached(GeometricGraphPosition<Node, Arc> destPosition) { super.onDestinationReached(destPosition); finishAction(); }
3. Update forklift shape
Add ForkliftShape class to the com.amalgamasimulation.warehouse.application.animation package:
package com.company.warehouse.application.animation;
import java.awt.Color;
import com.amalgamasimulation.animation.shapes.shapes2d.GroupShape;
import com.amalgamasimulation.animation.shapes.shapes2d.RectangleShape;
import com.amalgamasimulation.geometry.Point;
import com.amalgamasimulation.utils.Colors;
import com.company.warehouse.simulation.graph.agents.Forklift;
public class ForkliftShape extends GroupShape {
private Forklift forklift;
public ForkliftShape(Forklift forklift) {
super(() -> forklift.getCurrentAnimationPoint());
this.forklift = forklift;
withShape(new RectangleShape(() -> new Point(-5, -5), () -> 10.0, () -> 20.0)
.withFillColor(Colors.orange)
);
withShape(new RectangleShape(() -> new Point(-cargoSize(), 10 - cargoSize()), () -> cargoSize() * 2, () -> cargoSize() * 2)
.withFillColor(() -> cargoColor())
);
withRotationAngle(() -> - forklift.getCurrentAnimationHeading());
withFixedScale(1);
}
private double cargoSize() {
return forklift.isLoaded() ? 6 : 5;
}
private Color cargoColor() {
return forklift.isLoaded() ? Color.green : Color.black;
}
}
Update the SimulationPart.onShowModel() to use the new ForkliftShape shape instead of the AgentShape:
// ...
model.getAgents().forEach(agent -> animationView.addShape(new ForkliftShape(agent)));
// ...
Add the missing ForkliftShape import.
Remove the (not used anymore) AgentShape class.
In the Model.dispatchAgent() method, replace the line that starts with agent.moveTo… with the following to use the new moveTo() method of the Forklift class that depends on the onComplete callback:
// ...
agent.moveTo(possibleDestinationNodes.get(random.nextInt(possibleDestinationNodes.size())), () -> dispatchAgent(agent));
// ...
Run the simulation:
4. Show pallet positions
4.1. PalletPosition class
Add PalletPosition class to the com.company.warehouse.simulation package (the 'simulation' bundle):
package com.company.warehouse.simulation;
import com.company.warehouse.simulation.graph.Node;
/**
* A location in the main storage area or in a dock area that can contain at max one pallet.
*/
public class PalletPosition {
public enum Operation {
// Pallet is moved into the container
LOADING,
// Pallet is moved out of the container
UNLOADING}
private final Node node;
/**
* Whether a pallet is present here
*/
private boolean busy;
public PalletPosition(Node node) {
this.node = node;
}
public Node getNode() {
return node;
}
public boolean isBusy() {
return busy;
}
public boolean isAvailableFor(Operation operation) {
return (busy && operation == Operation.UNLOADING) || (!busy && operation == Operation.LOADING);
}
public void performPalletOperation(Operation operation) {
if (!isAvailableFor(operation)) {
throw new IllegalStateException("Cannot " + operation);
}
busy = !busy;
}
}
4.2. DockArea class
Add the new DockArea class to the com.company.warehouse.simulation package (the 'simulation' bundle):
package com.company.warehouse.simulation;
import java.util.ArrayList;
import java.util.List;
import com.company.warehouse.datamodel.Direction;
public class DockArea {
/**
* Whether this dock area is incoming (for unloading) or outgoing (for loading).
*/
private final Direction direction;
private List<PalletPosition> palletPositions = new ArrayList<>();
public DockArea(Direction direction) {
this.direction = direction;
}
public Direction getDirection() {
return direction;
}
public void addPalletPosition(PalletPosition palletPosition) {
palletPositions.add(palletPosition);
}
}
4.3. Initialize dock areas and pallet positions
In the Model class, add new fields and a new method:
// ...
protected List<PalletPosition> mainStoragePalletPositions = new ArrayList<>();
protected Map<Direction, List<DockArea>> dockAreas = new HashMap<>();
protected Map<Direction, List<PalletPosition>> palletPositionsAtDockAreas = new HashMap<>();
// ...
public List<PalletPosition> getAllPositions() {
return Utils.union(mainStoragePalletPositions, palletPositionsAtDockAreas.get(Direction.IN), palletPositionsAtDockAreas.get(Direction.OUT));
}
private PalletPosition newPalletPosition(com.company.warehouse.datamodel.Node scenarioNode, boolean busy) {
var node = mapping.nodesMap.get(scenarioNode);
var p = new PalletPosition(node);
if (busy) {
p.performPalletOperation(Operation.LOADING);
}
return p;
}
private void initializeMainStorage() {
scenario.getStoragePlaces()
.stream()
.forEach(scenarioNode -> mainStoragePalletPositions.add(newPalletPosition(scenarioNode, randomTrue(0.5))));
}
private void initializeDockAreas() {
for (var direction : Direction.values()) {
dockAreas.put(direction, new ArrayList<>());
palletPositionsAtDockAreas.put(direction, new ArrayList<>());
}
for (var scenarioDockArea : scenario.getDockAreas()) {
var dockArea = new DockArea(scenarioDockArea.getDirection());
var direction = scenarioDockArea.getDirection();
for (var scenarioNode : scenarioDockArea.getStoragePlaces()) {
var palletPosition = newPalletPosition(scenarioNode, randomTrue(0.5));
dockArea.addPalletPosition(palletPosition);
palletPositionsAtDockAreas.get(direction).add(palletPosition);
}
dockAreas.get(direction).add(dockArea);
}
}
private boolean randomTrue(double probability) {
return random.nextDouble() < probability;
}
Add missing import statements.
Append two new lines to the Model class constructor:
// ...
initializeMainStorage();
initializeDockAreas();
4.4. PalletPositionShape class
Add a new shape class to the com.company.warehouse.application.animation package:
package com.company.warehouse.application.animation;
import java.awt.Color;
import com.amalgamasimulation.animation.shapes.shapes2d.GroupShape;
import com.amalgamasimulation.animation.shapes.shapes2d.RectangleShape;
import com.amalgamasimulation.geometry.Point;
import com.company.warehouse.simulation.PalletPosition;
public class PalletPositionShape extends GroupShape {
public PalletPositionShape(PalletPosition p) {
super(p.getNode().getPoint());
withShape(new RectangleShape(() -> new Point(-5, -5), () -> 10.0, () -> 10.0)
.withLineColor(Color.gray)
.withFillColor(() -> p.isAvailableFor(PalletPosition.Operation.UNLOADING) ? Color.green : Color.gray)
);
}
}
In the SimulationPart.onShowModel() method, append this new line to the if (model != null) block body:
//...
model.getAllPositions().forEach(p -> animationView.addShape(new PalletPositionShape(p)));
//...
Add the missing import statement.
Run the application. Now the pallet position shapes should be used, colored green (with cargo) or gray (without cargo). At this stage, cargo is not moved around the warehouse yet, so the color of each pallet remains unchanged.