CoppeliaSim: screws.coppelia¶
The CoppeliaSim bridge. Requires screws[coppelia]; import screws never loads it.
SimulatorNotRunning ¶
Bases: RuntimeError
CoppeliaSim did not answer on the remote API port.
Source code in screws/coppelia/_sim.py
27 28 | |
Arm ¶
A serial chain in the scene: its joints ordered base to tip, and its tip object.
Joints are every joint under path, ordered by depth, or the ones named in joints
(aliases or paths, in order) when a gripper or other tool adds joints of its own. The
model's embedded scripts are removed (disable_scripts=False keeps them); removed_scripts
names what went. The tip is the first dummy
under path whose alias is "tip" or "ee" (or contains "tip"), else the last joint.
Source code in screws/coppelia/arm.py
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | |
theta ¶
theta()
Joint positions (rad or m).
Source code in screws/coppelia/arm.py
106 107 108 | |
dtheta ¶
dtheta()
Joint velocities.
Source code in screws/coppelia/arm.py
110 111 112 | |
tau ¶
tau()
Measured joint forces or torques; NaN for a joint that reports none (not dynamic).
Source code in screws/coppelia/arm.py
114 115 116 117 118 119 120 121 122 | |
tip_frame ¶
tip_frame()
The tip's 4x4 configuration in the world frame.
Source code in screws/coppelia/arm.py
124 125 126 | |
joint_frames ¶
joint_frames()
Each joint's 4x4 frame in the world frame; the joint axis is the local z axis.
Source code in screws/coppelia/arm.py
128 129 130 | |
joint_types ¶
joint_types()
"revolute" or "prismatic" for each joint, base to tip.
Source code in screws/coppelia/arm.py
132 133 134 135 136 137 | |
joint_limits ¶
joint_limits()
nx2 limits from the joints' intervals, or None if any joint is cyclic.
Source code in screws/coppelia/arm.py
139 140 141 142 143 144 145 146 147 | |
mode ¶
mode(kind)
Set how the arm is driven.
"position", "velocity" and "torque" are the dynamic control modes: the physics engine moves the joints toward the commanded targets with the model's motors, so a command is tracked with some lag. "kinematic" switches the joints to kinematic mode and makes the arm's shapes static: a command is the position itself, followed exactly, and the arm becomes a rigid mover that pushes dynamic objects (the right setting for a tool that must strike something repeatably). Leaving kinematic mode restores the shapes' flags and the dynamic joint mode.
Source code in screws/coppelia/arm.py
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
restore_dynamics ¶
restore_dynamics()
Leave kinematic mode: put the shapes' static flags and the joints' dynamic mode back. Called by mode(...) and by the Scene on exit, so a run never leaves the model frozen.
Source code in screws/coppelia/arm.py
186 187 188 189 190 191 192 193 194 195 196 197 | |
command ¶
command(u)
Dispatch on the current mode: positions, velocities or torques.
Source code in screws/coppelia/arm.py
205 206 207 208 209 210 211 212 213 214 215 | |
command_positions ¶
command_positions(theta)
Position mode: set each joint's target position.
Source code in screws/coppelia/arm.py
223 224 225 226 227 | |
command_velocities ¶
command_velocities(dtheta)
Velocity mode: set each joint's target velocity.
Source code in screws/coppelia/arm.py
229 230 231 232 233 | |
command_torques ¶
command_torques(tau)
Force mode: the signed joint force or torque is applied directly (sim.setJointTargetForce with signedValue, CoppeliaSim 4.3+).
Source code in screws/coppelia/arm.py
235 236 237 238 239 240 | |
teleport ¶
teleport(theta)
Set joint positions directly, without physics: for animating IK iterates.
Source code in screws/coppelia/arm.py
242 243 244 245 | |
link_shapes ¶
link_shapes()
The dynamic (non-static) shapes of each moving link, grouped by nearest joint ancestor.
Static shapes are visual shells in the stock models and carry meaningless masses.
Source code in screws/coppelia/arm.py
259 260 261 262 263 264 265 266 267 268 269 270 271 | |
robot ¶
robot(*, inertias=False, relative_to='world')
A screws.Robot read from the scene at the zero position.
M is the tip frame; joint i's screw axis has omega = its frame's z axis and
q = its origin (notes 4.1: v = -omega x q). relative_to="world" (default) makes {s}
CoppeliaSim's world frame; relative_to="base" makes {s} the frame of the object at
path, so the result is independent of where the model stands in the scene.
inertias=True also reads every dynamic shape's mass and inertia (MR 8.3 convention:
frame {i} at the link's centre of mass with joint i's orientation). The arm is
teleported to zero for the reading and put back afterwards, so call this before
starting the simulation or accept a jump.
Source code in screws/coppelia/arm.py
309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 | |
Log ¶
Per-step records of simulated time, joint state, command and end-effector frame.
Arrays: t (N,), theta (N, n), dtheta (N, n), tau (N, n), command (N, n) and T_sb (N, 4, 4). Empty arrays before the first record.
Source code in screws/coppelia/log.py
13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
record ¶
record(t, theta, dtheta, tau, command, T_sb)
Append one step; Scene.run calls this after every step.
Source code in screws/coppelia/log.py
28 29 30 31 32 33 34 35 36 37 38 | |
to_csv ¶
to_csv(path)
Write t, theta_i, dtheta_i, tau_i, command_i and the 12 entries of T_sb per row.
Source code in screws/coppelia/log.py
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
to_mr_csv ¶
to_mr_csv(path)
Write joint angles only, one row per step, no header: the MR wiki scenes' format.
Source code in screws/coppelia/log.py
87 88 89 | |
plot ¶
plot(*, show=True)
Four stacked time-series axes, one line per joint: theta, dtheta, tau and command
against simulated time. Shows the window unless show=False; returns the figure either
way (for fig.savefig). Needs matplotlib (screws[plot]).
Source code in screws/coppelia/log.py
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 | |
Scene ¶
A CoppeliaSim scene driven from Python one step at a time.
with Scene() as scene: connects (or takes an injected sim), switches on
stepping, and on exit stops the simulation if this object started it. Time only
advances when you call step().
In a notebook, call release() when a cell has finished stepping. A stepping client that sits idle blocks CoppeliaSim's window: it stops redrawing, and after a few seconds it shows a "ZMQ remote API server ... abort execution" banner, which, clicked, kills the remote API and strands the notebook. Released, the simulation runs on its own between cells (the joints hold their targets) and the window stays live; the next step() takes the clock back.
Source code in screws/coppelia/scene.py
20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
start ¶
start()
Start the simulation. A simulation left running by a crashed client is stopped first, since in stepping mode it would wait forever for that client's next step().
Source code in screws/coppelia/scene.py
86 87 88 89 90 91 92 | |
step ¶
step()
Advance one simulation step; active recorders grab a frame first. After release(), the clock is taken back first.
Source code in screws/coppelia/scene.py
94 95 96 97 98 99 100 101 102 | |
release ¶
release()
Hand the clock back to CoppeliaSim until the next step(): the simulation runs on its own, at its own pace, and CoppeliaSim's window keeps redrawing. Call it at the end of a notebook cell that stepped; idle in stepping mode, the window locks up.
Source code in screws/coppelia/scene.py
104 105 106 107 108 109 110 | |
stop ¶
stop()
Stop the simulation and wait until it has actually stopped.
stopSimulation is asynchronous; objects created while the simulation is still winding down are deleted when the stop completes, so nothing should be built until it has.
Source code in screws/coppelia/scene.py
112 113 114 115 116 117 118 119 120 121 122 | |
set_time_step ¶
set_time_step(dt)
Set the simulation (control) time step in seconds, while stopped; the physics engine keeps its own smaller step. The previous value is restored when the Scene exits.
Source code in screws/coppelia/scene.py
124 125 126 127 128 129 130 131 | |
frame ¶
frame(obj)
The 4x4 configuration of an object (path or handle) in the world frame.
Source code in screws/coppelia/scene.py
151 152 153 154 155 | |
set_frame ¶
set_frame(obj, T)
Place an object (path or handle) at T in the world frame.
Source code in screws/coppelia/scene.py
157 158 159 | |
show_frame ¶
show_frame(T, name='frame', size=0.1)
Draw a triad at T: red x, green y, blue z. Drawing the same name again moves it.
Source code in screws/coppelia/scene.py
161 162 163 164 165 166 167 168 169 170 171 172 | |
clear_frames ¶
clear_frames()
Remove every triad drawn by show_frame.
Source code in screws/coppelia/scene.py
174 175 176 177 | |
track ¶
track(*handles)
Remove these objects when the Scene exits, however the run ends.
Source code in screws/coppelia/scene.py
183 184 185 | |
untrack ¶
untrack(*handles)
Stop tracking these objects, so the Scene leaves them when it exits.
Source code in screws/coppelia/scene.py
187 188 189 190 191 | |
camera ¶
camera(
path=None,
*,
position=(1.5, -1.5, 1.0),
look_at=(0.0, 0.0, 0.4),
resolution=(640, 480),
fov_deg=60.0,
)
A vision sensor to record from: the one at path, or a new perspective sensor
placed at position looking at look_at with the image's up along world z. A
created sensor is removed when the Scene exits.
Source code in screws/coppelia/scene.py
193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 | |
record_video ¶
record_video(
path, *, camera=None, every=1, **camera_kwargs
)
A Recorder context: with scene.record_video("run.mp4") as rec: scene.run(...).
camera is a sensor handle or path; None creates one with camera_kwargs
(position, look_at, resolution, fov_deg). Frames are grabbed every every steps
and the movie plays at simulated time. Needs screws[video] to save.
Source code in screws/coppelia/scene.py
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 | |
arm ¶
arm(path, joints=None, *, disable_scripts=True)
The Arm under path; joints names a subset (aliases or paths) when a tool adds its
own; the model's embedded demo scripts are removed unless disable_scripts=False.
Source code in screws/coppelia/scene.py
251 252 253 254 255 256 | |
record ¶
record(arm, command=None, *, theta=None, dtheta=None)
Append one row to the log; pass theta/dtheta already read this step to avoid re-reading.
Source code in screws/coppelia/scene.py
260 261 262 263 264 265 | |
run ¶
run(controller, *, duration, arm, log=True)
Loop controller(t, theta, dtheta) -> command over the simulation for duration seconds.
Starts the simulation if needed, records before each step, returns the Log.
Source code in screws/coppelia/scene.py
267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 | |
Recorder ¶
Grabs a vision sensor's image before every step and writes an MP4 or GIF on exit.
Use through scene.record_video(path, ...). frames is (N, H, W, 3) uint8, upright;
fps is 1 / (every * dt), so the movie plays at simulated time however slowly the
controller ran. capture() grabs a frame by hand; Scene.run grabs one every
every steps.
Source code in screws/coppelia/video.py
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
capture ¶
capture()
Grab the camera's current image (upright, RGB uint8) and keep it.
Source code in screws/coppelia/video.py
41 42 43 44 45 46 47 48 49 50 51 | |
tick ¶
tick()
Called by Scene.run before each step: capture every every-th step.
Source code in screws/coppelia/video.py
53 54 55 56 57 | |
save ¶
save(path=None)
Encode the frames to path (default: the path given at creation). Needs screws[video].
Source code in screws/coppelia/video.py
73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 | |
connect ¶
connect(host='localhost', port=23000, *, timeout_s=5.0)
Connect to CoppeliaSim's ZMQ remote API and return its sim object.
Raises SimulatorNotRunning, with the sentence a student needs, if nothing answers.
Source code in screws/coppelia/_sim.py
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | |
The putting example: screws.coppelia.golf¶
A putting green for the UR5: a ball, a hole, a kinematic putter on the flange, and a putt.
The ball is the only dynamic body. The putter is a static, respondable tool rigidly attached to the arm's last link, so it moves exactly where the joint trajectory puts it and pushes the ball through contact. Everything else is kinematics from the rest of screws: the face pose that addresses the ball, a straight stroke along the target line, IK for every waypoint.
Green
dataclass
¶
The turf, the cup cut through it, the pin, and the ball.
Source code in screws/coppelia/golf.py
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 | |
ball_position ¶
ball_position()
The ball's centre in the world frame.
Source code in screws/coppelia/golf.py
86 87 88 | |
ball_speed ¶
ball_speed()
The ball's linear speed.
Source code in screws/coppelia/golf.py
90 91 92 93 | |
distance_to_hole ¶
distance_to_hole()
Horizontal distance from the ball's centre to the cup's centre.
Source code in screws/coppelia/golf.py
95 96 97 | |
holed ¶
holed()
True once the ball has dropped into the cup: inside the rim and below the surface.
Source code in screws/coppelia/golf.py
99 100 101 102 | |
handles ¶
handles()
Every object the green added to the scene.
Source code in screws/coppelia/golf.py
104 105 106 107 | |
remove ¶
remove()
Take the green out of the scene.
Source code in screws/coppelia/golf.py
109 110 111 112 | |
Putter
dataclass
¶
A putter on the arm: a shaft along the tool's +z and a face whose outward normal is the tool's +x, both static shapes parented to the last link (drive the arm in kinematic mode). T_tip_face is the face frame in the tip (last link) frame.
Source code in screws/coppelia/golf.py
506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 | |
robot ¶
robot(robot)
The arm's Robot with M moved from the flange to the putter face.
Source code in screws/coppelia/golf.py
518 519 520 | |
remove ¶
remove()
Take the putter off the arm and out of the scene.
Source code in screws/coppelia/golf.py
522 523 524 525 | |
PuttResult
dataclass
¶
How a putt went: whether it dropped, where the ball stopped, and the paths of the ball and the putter face.
Source code in screws/coppelia/golf.py
636 637 638 639 640 641 642 643 644 645 | |
camera_yaw ¶
camera_yaw(position, look_at)
The yaw (about z) whose x direction is a camera's horizontal right: what to pass as
build_green's yaw so text laid on the green reads correctly from that camera.
Source code in screws/coppelia/golf.py
142 143 144 145 146 | |
camera_forward ¶
camera_forward(position, look_at)
The horizontal unit vector pointing away from a camera: "up" in its image on the green.
Source code in screws/coppelia/golf.py
149 150 151 152 | |
text_image ¶
text_image(
path,
text,
*,
fg=(255, 255, 255),
bg=SMU_RED_8BIT,
aspect=14 / 9,
size=512,
)
Write a square PNG of bold text on a solid background (defaults: white on SMU red) for a flag plane of the given width:height aspect: the text is drawn at that aspect and the image stretched to a square, so it reads correctly once mapped onto the plane (a non-square texture would render dark).
Source code in screws/coppelia/golf.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 | |
seal_image ¶
seal_image(
path,
artwork,
*,
disc=(255, 255, 255),
background=TURF_COLOUR_8BIT,
size=512,
)
Write a PNG of a logo (a PNG with transparency, or any image) on a white disc that blends into the turf outside the disc, for a flat plane laid on the green. The artwork's own colours are kept, as the brand guide asks. size must be a power of two.
Source code in screws/coppelia/golf.py
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
logo_image ¶
logo_image(path, artwork, *, plate=None, size=TEXTURE_SIZE)
Write a square PNG of a logo (a PNG with transparency) for a plane laid flat on the green, and return (path, aspect): the artwork is stretched to fill the square (non-square textures render dark) and reads correctly on a plane of width:height = aspect, which is what build_green's logo_aspect takes. With plate=None the transparent parts take the turf colour, so a white or coloured logo looks painted on the grass; plate=(255, 255, 255) puts it on a white sign. Colours are kept.
Source code in screws/coppelia/golf.py
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 | |
surface_mesh ¶
surface_mesh(
*,
size,
thickness,
center,
hole,
hole_radius=HOLE_RADIUS,
n=96,
)
Vertices (V, 3) and triangles (F, 3) of a rectangular slab with a round hole through it: the turf as one smooth visual surface (the physics uses green_pieces underneath).
size is (sx, sy) and center (cx, cy) of the slab, whose bottom is at z = 0 and top at z = thickness; hole is (hx, hy). Outward normals, closed manifold, so it serves as a static respondable mesh.
Source code in screws/coppelia/golf.py
244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 | |
green_pieces ¶
green_pieces(
*,
size,
thickness,
center,
hole,
hole_radius=HOLE_RADIUS,
n=32,
)
The turf as convex pieces: four cuboid slabs and, in a square insert around the cup, n wedge prisms whose inner edges form the round hole.
Returns {"slabs": [(cx, cy, sx, sy), ...], "wedges": [(V, F), ...]}. Slabs have their bottom at z = 0 and top at z = thickness; wedge tops sit 0.3 mm lower so a ball rolling off a slab never catches their edge. Convex pieces keep the physics engine happy (a non-convex mesh is flagged by CoppeliaSim and kicks a rolling ball at its internal edges).
Source code in screws/coppelia/golf.py
345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | |
build_green ¶
build_green(
scene,
*,
ball_position,
hole_position,
size=(3.0, 3.0),
thickness=TURF_THICKNESS,
friction=0.8,
damping=0.6,
restitution=0.2,
flag_image=None,
seal_image=None,
seal_position=None,
seal_size=0.35,
logo_image=None,
logo_position=None,
logo_width=0.6,
logo_aspect=None,
yaw=0.0,
)
Lay turf with a real cup at (x, y), a pin in it, and a ball at (x, y) on the surface.
The turf is a static respondable slab of the given size (centred between the ball and the hole) and thickness, with the cup cut through it and a cup floor below; see green_pieces for why it is built from convex pieces. The ball's material (friction, Bullet's linear and angular damping, restitution) defaults roll about 0.3 m from a 0.3 m/s face on a dynamic putter.
flag_image (a PNG, e.g. from text_image) textures the flag; seal_image (e.g. from seal_image) is laid flat on the turf as a seal_size square at seal_position (x, y), default: beside the line, on the camera side; logo_image (e.g. from logo_image) is laid flat logo_width wide at logo_position, default: just past the seal along the yawed direction, with height logo_width / logo_aspect (default: the file's own aspect; pass the aspect logo_image returns, since it writes a square). yaw (radians about z) turns both so their text reads along a camera's horizontal: pass the camera's right-hand direction, e.g. golf.camera_yaw(position, look_at). Everything built is removed when the Scene exits, or by Green.remove().
Source code in screws/coppelia/golf.py
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 | |
attach_putter ¶
attach_putter(
scene,
arm,
*,
shaft_length=0.3,
shaft_radius=0.006,
face_size=(0.02, 0.1, 0.02),
)
Build a putter on the arm's tip: static shapes parented to the last link.
The face is a box of face_size (thickness along tool x, width along y, height along z) centred shaft_length along the tool's +z axis; its striking face is the +x side and it is respondable. The shaft is a visual only. Drive the arm with arm.mode("kinematic") for a putt: the tree is then static and the face is a rigid mover that strikes the ball exactly where the trajectory says (a respondable static shape on a dynamic tree is flagged by CoppeliaSim as unrealistic, and a dynamic face on a force sensor wobbles and lags).
Source code in screws/coppelia/golf.py
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 | |
address_pose ¶
address_pose(ball, hole, *, back=0.06)
The face frame that addresses the ball: back metres behind it on the target line, at ball-centre height, face normal (tool x) along the line, shaft (tool z) pointing down.
Source code in screws/coppelia/golf.py
571 572 573 574 575 576 577 578 | |
stroke_path ¶
stroke_path(
ball,
hole,
*,
back=0.06,
through=0.04,
speed=0.5,
dt=0.05,
)
Face poses, one per control step, for a straight stroke at constant speed from back metres behind the ball to through metres past its centre. The address pose itself is not included; the first pose is one step into the stroke.
Source code in screws/coppelia/golf.py
581 582 583 584 585 586 587 588 589 590 | |
swing_path ¶
swing_path(
ball,
hole,
*,
shaft_length,
back_angle=0.25,
through_angle=0.2,
speed=0.3,
dt=0.01,
gap=0.01,
face_thickness=0.02,
back_time=1.0,
return_time=1.0,
)
A putting stroke as a pendulum swing of the putter about the wrist.
The address pose puts the face gap metres behind the ball's surface. The pivot is the top of the shaft; the face swings in the vertical plane of the target line: back and up by back_angle (quintic timing over back_time), then down through the ball at a constant angular rate speed / shaft_length (so the face meets the ball at that speed), on to through_angle, and finally back to the address pose over return_time.
Returns {"backswing": [...], "downswing": [...], "back": [...]} of face poses, one per dt.
Source code in screws/coppelia/golf.py
593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 | |
putt ¶
putt(
scene,
arm,
robot_face,
green,
*,
speed=0.3,
back_angle=0.25,
through_angle=0.2,
gap=0.01,
lift=0.12,
approach_time=2.0,
settle_time=5.0,
seed=None,
shaft_length=0.3,
face_thickness=0.02,
)
Address the ball, swing the putter through it toward the hole, and watch it roll.
robot_face is the arm's Robot with M at the putter face (Putter.robot). Put the arm in kinematic mode (arm.mode("kinematic")) and start the simulation first. The approach goes in joint space to a pose lift metres above the address pose and descends straight onto it; the stroke is swing_path's pendulum: backswing, a pause, a downswing meeting the ball at speed, follow-through, and a return to the address pose, where the arm waits until the ball stops or settle_time has passed.
Use a 10 ms control step (scene.set_time_step(0.01)): at the default 50 ms the face jumps 15 mm per step and the strike is no longer clean. 0.3 m/s rolls the ball about 0.3 m on the default green.
Source code in screws/coppelia/golf.py
661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 | |