Skip to content

Commit b926b20

Browse files
committed
send extrusion speed per point
1 parent fcfff64 commit b926b20

4 files changed

Lines changed: 175 additions & 77 deletions

File tree

‎src/clay_3dp_print/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
from .print_frame import PrintFrame # noqa: F401
2+
from .print_layer import PrintLayer # noqa: F401

‎src/clay_3dp_print/__main__.py‎

Lines changed: 108 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,120 @@
11
import sys
2+
import typing
23

34
from compas import json_load
45
import compas.geometry
56
from compas_fab.backends import RosClient
7+
from compas_fab.backends.ros.messages import ROSmsg
68
import compas_rrc as rrc
9+
from clay_3dp_print import PrintLayer
710

811
# IP = "192.168.8.30" # cook
912
IP = "localhost"
1013

1114
WOBJ = "wobj_pallet_markers"
1215
TOOL = "t_erratic_t25"
1316

14-
DRY_RUN = False
15-
1617
SPEED = 75
1718
SPEED_PRINT = 50
1819

20+
EXTRUSION_FACTOR_DO = "ao_printPtSpd"
21+
22+
# negative means move towards the sky
23+
Z_ADJUSTMENT = -3
24+
1925

20-
def get_set_extruder(state: int):
21-
return rrc.SetDigital("do_extrudeRelSpd", state)
26+
def get_set_extruder(speed_factor: float):
27+
return rrc.SetAnalog(EXTRUSION_FACTOR_DO, speed_factor)
2228

2329

24-
def get_start_extrude():
25-
if not DRY_RUN:
26-
return get_set_extruder(1)
30+
def get_start_extrude(speed_factor=1):
31+
return get_set_extruder(speed_factor)
2732

2833

2934
def get_stop_extrude():
30-
if not DRY_RUN:
31-
return get_set_extruder(0)
35+
return get_set_extruder(0)
3236

3337

34-
def construct_cmds(frames, travel):
35-
last_was_t = True
38+
def construct_cmds(layers: list[PrintLayer]) -> typing.Generator[ROSmsg]:
39+
first_print_frame = layers[0].pop(0)
3640

37-
cmds = []
38-
for i, (lframes, ltravel) in enumerate(zip(frames, travel)):
39-
lcmds = []
41+
first_print_frame.translate_frame_in_local_Z(Z_ADJUSTMENT)
4042

41-
lcmds.append(rrc.PrintText(f"Layer {i}"))
43+
yield rrc.MoveToFrame(
44+
first_print_frame, SPEED, rrc.Zone.FINE, motion_type=rrc.Motion.JOINT
45+
)
4246

43-
x, y, z = lframes[0].point
47+
for i, layer in enumerate(layers):
48+
yield rrc.PrintText(f"Layer {i}")
49+
50+
x, y, z = layer[0].point
4451
first_pt_str = f"{x:.2f},{y:.2f},{z:.2f}"
4552

46-
lcmds.append(rrc.PrintText(f"First frame: {first_pt_str}"))
53+
yield rrc.PrintText(f"First frame: {first_pt_str}")
4754

48-
for f, t in zip(lframes, ltravel, strict=True):
49-
if not DRY_RUN:
50-
if not t and last_was_t:
51-
lcmds.append(get_start_extrude())
55+
for print_frame in layer:
56+
yield get_set_extruder(print_frame.extrusion_factor)
5257

53-
if t and not last_was_t:
54-
lcmds.append(get_stop_extrude())
58+
print_frame.translate_frame_in_local_Z(Z_ADJUSTMENT)
5559

56-
lcmds.append(
57-
rrc.MoveToFrame(
58-
f,
59-
SPEED if t else SPEED_PRINT,
60-
rrc.Zone.Z1,
61-
motion_type=rrc.Motion.LINEAR,
62-
)
63-
)
64-
last_was_t = t
60+
if print_frame.is_travel():
61+
# TODO: Add retraction
62+
pass
6563

66-
cmds.append(lcmds)
67-
return cmds
64+
yield rrc.MoveToFrame(
65+
print_frame,
66+
SPEED if print_frame.is_travel() else SPEED_PRINT,
67+
rrc.Zone.Z1,
68+
motion_type=rrc.Motion.LINEAR,
69+
feedback_level=rrc.FeedbackLevel.DONE,
70+
)
6871

6972

70-
def robot_program(
71-
frames: list[list[compas.geometry.Frame]], travel: list[list[bool]] | None
73+
def process_with_batches(
74+
abb: rrc.AbbClient, cmd_generator: typing.Generator[ROSmsg], batch_size: int = 100
7275
):
76+
futures = []
77+
commands_sent = 0
78+
79+
# Send the first batch
80+
for _ in range(batch_size):
81+
try:
82+
cmd = next(cmd_generator)
83+
84+
feedback = abb.send(cmd)
85+
86+
if feedback:
87+
futures.append(feedback)
88+
89+
commands_sent += 1
90+
except StopIteration:
91+
break
92+
93+
while futures:
94+
completed = []
95+
for f in futures:
96+
try:
97+
f.result(timeout=3)
98+
completed.append(f)
99+
except rrc.TimeoutException:
100+
continue
101+
102+
# Remove completed futures
103+
for f in completed:
104+
futures.remove(f)
105+
106+
# Add more commands to maintain the buffer
107+
for _ in range(len(completed)):
108+
try:
109+
cmd = next(cmd_generator)
110+
futures.append(abb.send(cmd))
111+
commands_sent += 1
112+
except StopIteration:
113+
break
114+
115+
116+
def robot_program(layers: list[PrintLayer]):
117+
breakpoint()
73118
# Define robot joints
74119
robot_joints_start_position = robot_joints_end_position = [100, 20, 25, 5, -40, -10]
75120

@@ -81,19 +126,7 @@ def robot_program(
81126
override = 100 # Unit [%]
82127
max_tcp = 250 # Unit [mm/s]
83128

84-
z_adjustment_mm = -3 # negative means raise up printbed
85-
86-
if z_adjustment_mm != 0:
87-
for layer in frames:
88-
for frame in layer:
89-
v = frame.normal.copy()
90-
v.scale(z_adjustment_mm)
91-
frame.point.translate(v)
92-
93-
first_frame = frames[0].pop(0)
94-
_ = travel[0].pop(0)
95-
96-
cmds = construct_cmds(frames, travel)
129+
cmd_generator = construct_cmds(layers)
97130

98131
with RosClient(host=IP, port=9090) as ros:
99132
abb = rrc.AbbClient(ros, "/rob1")
@@ -102,8 +135,7 @@ def robot_program(
102135
abb.send(rrc.SetAcceleration(acc, ramp))
103136
abb.send(rrc.SetMaxSpeed(override, max_tcp))
104137

105-
if not DRY_RUN:
106-
abb.send_and_wait(get_stop_extrude()) # reset signal
138+
abb.send(get_stop_extrude()) # reset signal
107139

108140
abb.send(rrc.SetTool(TOOL))
109141
abb.send(rrc.SetWorkObject(WOBJ))
@@ -117,21 +149,13 @@ def robot_program(
117149
abb.send(rrc.Stop())
118150

119151
# Move robot to start position
120-
abb.send_and_wait(
152+
abb.send(
121153
rrc.MoveToJoints(
122154
robot_joints_start_position, external_axis_dummy, SPEED, rrc.Zone.FINE
123155
)
124156
)
125157

126-
abb.send(rrc.MoveToFrame(first_frame, SPEED, rrc.Zone.FINE))
127-
128-
for lcmds in cmds:
129-
abb.send_and_wait(lcmds.pop(0))
130-
for c in lcmds:
131-
abb.send(c)
132-
133-
if not DRY_RUN:
134-
abb.send_and_wait((get_stop_extrude()))
158+
process_with_batches(abb, cmd_generator)
135159

136160
# Move robot to end position
137161
abb.send(
@@ -144,24 +168,31 @@ def robot_program(
144168
abb.send_and_wait(rrc.PrintText("Print finished."))
145169

146170

171+
def load_json_from_arg1():
172+
filepath = sys.argv[1]
173+
data = json_load(filepath)
174+
frames = data["frames"]
175+
extrusion_factors = data["travel"]
176+
177+
# put all in one "layer"
178+
if isinstance(frames[0], compas.geometry.Frame):
179+
if len(extrusion_factors) == len(frames) - 1:
180+
# TODO: Fix me
181+
extrusion_factors.append(True)
182+
183+
frames = [frames]
184+
extrusion_factors = [extrusion_factors]
185+
186+
return [
187+
PrintLayer.from_frames_and_factors(f, ef)
188+
for f, ef in zip(frames, extrusion_factors, strict=True)
189+
]
190+
191+
147192
def main():
148193
if len(sys.argv) > 1:
149-
filepath = sys.argv[1]
150-
data = json_load(filepath)
151-
frames = data["frames"]
152-
travel = data["travel"]
153-
154-
# put all in one "layer"
155-
if isinstance(frames[0], compas.geometry.Frame):
156-
frames = [frames]
157-
travel = [travel]
158-
159-
print(len(frames))
160-
print(len(travel))
161-
print(len(frames[0]))
162-
print(len(travel[0]))
163-
164-
robot_program(frames, travel)
194+
print_layers = load_json_from_arg1()
195+
robot_program(print_layers)
165196
else:
166197
print("Usage: clay_3dp_print <filepath>")
167198

‎src/clay_3dp_print/print_frame.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
from dataclasses import dataclass
2+
3+
import compas.geometry
4+
from compas.tolerance import TOL
5+
6+
7+
@dataclass
8+
class PrintFrame(compas.geometry.Frame):
9+
"""Frame and extrusion value"""
10+
11+
def __init__(self, frame: compas.geometry.Frame, extrusion_factor: float):
12+
super().__init__(
13+
frame.point, xaxis=frame.xaxis, yaxis=frame.yaxis, name=frame.name
14+
)
15+
self._extrusion_factor: float = extrusion_factor
16+
17+
DATASCHEMA = {
18+
"type": "object",
19+
"properties": {
20+
"point": compas.geometry.Point.DATASCHEMA,
21+
"xaxis": compas.geometry.Vector.DATASCHEMA,
22+
"yaxis": compas.geometry.Vector.DATASCHEMA,
23+
"extrusion_factor": "number",
24+
},
25+
"required": ["point", "xaxis", "yaxis", "extrusion_factor"],
26+
}
27+
28+
@property
29+
def __data__(self):
30+
return super().__data__ | {"extrusion_factor": self.extrusion_factor}
31+
32+
@property
33+
def extrusion_factor(self) -> float:
34+
return float(self._extrusion_factor)
35+
36+
@extrusion_factor.setter
37+
def extrusion_factor(self, value):
38+
self._extrusion_factor = value
39+
40+
def is_travel(self) -> bool:
41+
if not isinstance(self.extrusion_factor, bool):
42+
return TOL.is_close(self.extrusion_factor, 0)
43+
# TODO: Clean this up when I only set numbers not bools
44+
return bool(self.extrusion_factor)
45+
46+
def translate_frame_in_local_Z(self, signed_distance: float) -> None:
47+
"""Translates along local Z axis in place."""
48+
v = self.normal.copy()
49+
v.scale(signed_distance)
50+
self.point.translate(v)

‎src/clay_3dp_print/print_layer.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import typing
2+
from collections import UserList
3+
4+
from clay_3dp_print.print_frame import PrintFrame
5+
6+
7+
class PrintLayer(UserList):
8+
def __init__(self, print_frames: list[PrintFrame] | None = None):
9+
self.data = print_frames or []
10+
11+
@classmethod
12+
def from_frames_and_factors(cls, frames, extrusion_factors) -> typing.Self:
13+
return cls(
14+
[PrintFrame(f, ef) for f, ef in zip(frames, extrusion_factors, strict=True)]
15+
)

0 commit comments

Comments
 (0)