In this material, we will explore the creation of a digital twin of a
robotic technological complex (RTC) using an industrial framework. We
will create a simple application for the robot to pick up a shaft using
a gripper on its end point.
Preparation
To begin, let's create the UI of the application. Add a scene view and
two buttons on top of it – for starting and resetting the RTC. The
workspace_scene should be displayed in this view. Add a prepare function
that will be executed during scene initialization.
To prevent the objects placed in the scene from falling down, let's add
a floor using the add_floor function, placing the call to it in prepare.
Next, let's create an instance of Workspace – the RTC space.
Workspace Content View
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import SceneKit | |
import IndustrialKit | |
let workspace_scene = SCNScene() | |
var workspace = Workspace() | |
struct ContentView: View | |
{ | |
var body: some View | |
{ | |
ObjectSceneView(scene: workspace_scene, on_init: { scene_view in prepare() }) | |
.frame(width: 512, height: 512) | |
.background | |
{ | |
Rectangle() | |
.fill(.thinMaterial) | |
.shadow(radius: 4) | |
} | |
.overlay(alignment: .bottomTrailing) | |
{ | |
HStack | |
{ | |
Button(action: reset) | |
{ | |
Text("Reset") | |
} | |
.buttonStyle(.bordered) | |
Button(action: perform) | |
{ | |
Label("Perform", systemImage: "play") | |
} | |
.buttonStyle(.borderedProminent) | |
} | |
.padding() | |
} | |
} | |
} | |
func prepare() | |
{ | |
// Add floor | |
add_floor() | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func add_floor() | |
{ | |
// Create floor node | |
let floor = SCNNode(geometry: SCNPlane(width: 1024, height: 1024)) | |
// Add material | |
if let material = floor.geometry?.firstMaterial | |
{ | |
material.diffuse.contents = UIColor.gray | |
material.lightingModel = .constant | |
material.reflective.intensity = 0 | |
material.isDoubleSided = true | |
} | |
// Set floor parameters | |
floor.eulerAngles.x = -.pi / 2 | |
floor.position.y = -0.05 | |
floor.physicsBody = .static() | |
floor.opacity = 0.4 | |
// Add to workspace | |
workspace_scene.rootNode.addChildNode(floor) | |
} |
Adding Components
Our robotic complex implies the presence of a robot, a tool, and a part,
one instance of the corresponding models each. The modules required for
their creation are already built into the package. Therefore, to create
a production object, it will be sufficient to initialize it with a name
and module.
In the Application Playground, access to resources is organized in a
rather specific way, so the module resources are placed in a separate
resource folder, and their address strings in the modules contain only
the scene file name itself.
Add Robot
Let's start with the manipulator. Set the position of its local
coordinate system, offsetting it forward and upward and rotating it
downward. Next, specify that the robot is placed in the RTC workspace.
Be sure to initialize the update – in our case, it is only needed to
update the manipulator links via inverse kinematics. Finally, add our
robot to the workspace.
All the described creation functionality is placed in the prepare_robot
function, which is called in the prepare function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func prepare_robot() | |
{ | |
// Create robot by module | |
let arm = Robot(name: "6DOF", module: _6DOF_Module) | |
// Set robot position | |
arm.location = [0, 0, 0] | |
arm.origin_location = [200, 0, 100] | |
arm.origin_rotation = [90, 90, 0] | |
arm.is_placed = true | |
// Perform robot model update | |
arm.perform_update() | |
// Add robot in workspace | |
workspace.add_robot(arm) | |
} |
Note that continuous cyclic updating is also used (for both robots and
tools) to obtain statistical data and synchronize with the real device via
a connector.
Add Tool
Next, let's create a gripper from the module of the same name. Since the
gripper should be attached to the robot, specify that it is attached, as
well as the name of the robot on whose end point to attach it. We place
all the functionality in the prepare_tool function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func prepare_tool() | |
{ | |
// Create tool by module | |
let gripper = Tool(name: "Gripper", module: Gripper_Module) | |
// Set tool location | |
gripper.is_placed = true | |
gripper.is_attached = true | |
gripper.attached_to = "6DOF" | |
// Add tool to workspace | |
workspace.add_tool(gripper) | |
} |
Of course, the tool can also stand separately. If the tool is not
attached, it is placed in accordance with the specified position
parameters in space, which are not taken into account when attaching it to
the robot. Also, only one tool can be attached to one robot.
Add Part
It remains to add the shaft. Initialize it from the module, set the
position in space – it stands in front of the robot. Also specify the
physical body parameters of the part. In our case, it is dynamic.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func prepare_part() | |
{ | |
// Create part by module | |
let shaft = Part(name: "Shaft", module: Shaft_Module) | |
// Set part location | |
shaft.location = [200, 0, 30] | |
shaft.physics_type = .ph_dynamic | |
shaft.is_placed = true | |
// Add part to workspace | |
workspace.add_part(shaft) | |
} |
Deployment
To deploy the virtual model of the RTC in the scene, place a call to the
connect_scene function of our workspace in the prepare function. Pass
workspace_scene as the input argument.
If everything is done correctly, we will see our RTC in the scene view.
Deployment View
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
import SceneKit | |
import IndustrialKit | |
let workspace_scene = SCNScene() | |
var workspace = Workspace() | |
struct ContentView: View | |
{ | |
var body: some View | |
{ | |
ObjectSceneView(scene: workspace_scene, on_init: { scene_view in prepare() }) | |
.frame(width: 512, height: 512) | |
.background | |
{ | |
Rectangle() | |
.fill(.thinMaterial) | |
.shadow(radius: 4) | |
} | |
.overlay(alignment: .bottomTrailing) | |
{ | |
HStack | |
{ | |
Button(action: reset) | |
{ | |
Text("Reset") | |
} | |
.buttonStyle(.bordered) | |
Button(action: perform) | |
{ | |
Label("Perform", systemImage: "play") | |
} | |
.buttonStyle(.borderedProminent) | |
} | |
.padding() | |
} | |
} | |
} | |
func prepare() | |
{ | |
// Add floor | |
add_floor() | |
// Prepare components | |
prepare_robot() | |
prepare_tool() | |
prepare_part() | |
// Deploying | |
workspace.connect_scene(workspace_scene) | |
} |
Next, add a function to reset the scene – reset. In it, we sequentially
delete all the nested nodes of workspace_scene, re-add the floor, and then
deploy the digital twin of the RTC. When resetting, there is no need to
recreate the workspace.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func reset() | |
{ | |
workspace_scene.rootNode.remove_all_child_nodes() | |
add_floor() | |
workspace.connect_scene(workspace_scene) | |
} |
Handling
Now that everything is ready, let's move on to controlling our complex.
In this material, we will not use IMA and programs, it will be
sufficient for us to sequentially call single operations.
Just as the robot selects positions for execution, the workspace selects
devices for control. Let's select the robot and tool by their names in
the RTC. After that, let's start the gripper operation and manipulator
displacement operations on the selected devices.
We put all the functionality in the perform function.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func perform() | |
{ | |
workspace.select_robot(name: "6DOF") | |
workspace.select_tool(name: "Gripper") | |
workspace.selected_tool.perform(code: 1) | |
{ | |
workspace.selected_robot.move_to(point: PositionPoint(x: 150, y: 0, z: 0)) | |
{ | |
workspace.selected_tool.perform(code: 0) | |
{ | |
workspace.selected_robot.move_to(point: PositionPoint(x: 0, y: 0, z: 0)) | |
{ | |
print("Finished") | |
} | |
} | |
} | |
} | |
} |
It remains to assign calls to the reset and perform functions to the
corresponding buttons of our main view. The final version of the
application will look like this.
Conclusion
So, we have successfully mastered the construction of a robotic complex
using IndustrialKit. We learned about the principle of filling the
workspace with components and organizing them into a single production
system.
Almost any robotic production is a complex of interconnected controlled
devices, and the possibility of their mutual integration is a priority
task in industrial automation.