import json
class PlotterBackend:
def __init__(self, plotter):
self.steps = {"x": 1.0, "y": 1.0, "z": 40.0}
self.step_count = 0
self.current_step = 0
self.plotter = plotter
self.point_a = None
self.point_b = None
# --- Plotter Actions ---
def move(self, x, y):
self.plotter.move(x=x, y=y)
def set_home(self):
self.plotter.set_home()
def set_z(self, z_value):
self.plotter.set_spindle(z_value)
def get_position(self):
return self.plotter.get_position()
# --- A/B Point Management ---
def set_a(self):
self.point_a = self.plotter.get_position()
return self.point_a
def go_a(self):
if not self.point_a:
return
cur = self.plotter.get_position()
self.plotter.move(
x=self.point_a["x"] - cur["x"],
y=self.point_a["y"] - cur["y"]
)
self.plotter.set_spindle(self.point_a["z"])
def set_b(self):
self.point_b = self.plotter.get_position()
return self.point_b
def go_b(self):
if not self.point_b:
return
cur = self.plotter.get_position()
self.plotter.move(
x=self.point_b["x"] - cur["x"],
y=self.point_b["y"] - cur["y"]
)
self.plotter.set_spindle(self.point_b["z"])
def go_one(self):
"""
Move the plotter to the first raster step (step 1) based on point A/B
and the step sizes.
"""
if not self.point_a or not self.point_b:
return None # Cannot move, points not defined
# Compute first raster step coordinates
x_min, x_max = sorted([self.point_a["x"], self.point_b["x"]])
y_min, y_max = sorted([self.point_a["y"], self.point_b["y"]])
z_min, z_max = sorted([self.point_a["z"], self.point_b["z"]])
first_coords = {
"x": x_min,
"y": y_min,
"z": z_min
}
# Move relative to current position
current = self.get_position()
dx = first_coords["x"] - current["x"]
dy = first_coords["y"] - current["y"]
dz = first_coords["z"] - current["z"]
self.move(x=dx, y=dy)
self.plotter.set_spindle(first_coords["z"])
return first_coords
# --- Step Management ---
def set_step_sizes(self, x, y, z):
self.steps["x"] = float(x)
self.steps["y"] = float(y)
self.steps["z"] = float(z)
def update_step_count(self):
if not self.point_a or not self.point_b:
return 0
sx = max(self.steps["x"], 0.0001)
sy = max(self.steps["y"], 0.0001)
sz = max(self.steps["z"], 0.0001)
dx = abs(self.point_b["x"] - self.point_a["x"])
dy = abs(self.point_b["y"] - self.point_a["y"])
dz = abs(self.point_b["z"] - self.point_a["z"])
nx = int(dx / sx) + 1
ny = int(dy / sy) + 1
nz = int(dz / sz) + 1
return nx * ny * nz
def next_raster_step(self, current_pos=None):
if self.point_a is None or self.point_b is None:
return None, None, None, None
sx, sy, sz = self.steps["x"], self.steps["y"], self.steps["z"]
if current_pos is None:
current_pos = self.plotter.get_position()
cx, cy, cz = current_pos["x"], current_pos["y"], current_pos["z"]
# Axis min/max
x_min, x_max = sorted([self.point_a["x"], self.point_b["x"]])
y_min, y_max = sorted([self.point_a["y"], self.point_b["y"]])
z_min, z_max = sorted([self.point_a["z"], self.point_b["z"]])
# Discrete positions
nx = max(1, int(round((x_max - x_min)/sx)) + 1)
ny = max(1, int(round((y_max - y_min)/sy)) + 1)
nz = max(1, int(round((z_max - z_min)/sz)) + 1)
total_steps = nx * ny * nz
x_list = [x_min + i*sx for i in range(nx)]
y_list = [y_min + i*sy for i in range(ny)]
z_list = [z_min + i*sz for i in range(nz)]
# Clamp current position to nearest grid index
ix = min(range(nx), key=lambda i: abs(x_list[i]-cx))
iy = min(range(ny), key=lambda i: abs(y_list[i]-cy))
iz = min(range(nz), key=lambda i: abs(z_list[i]-cz))
# Current step number
current_step = iz * (nx * ny) + iy * nx + ix + 1
# Compute next indices
next_ix = ix + 1
next_iy = iy
next_iz = iz
if next_ix >= nx:
next_ix = 0
next_iy += 1
if next_iy >= ny:
next_iy = 0
next_iz += 1
if next_iz >= nz:
next_iz = 0 # wrap to start
# Next step number
next_step_number = next_iz * (nx * ny) + next_iy * nx + next_ix + 1
next_coords = {
"x": x_list[next_ix],
"y": y_list[next_iy],
"z": z_list[next_iz]
}
return current_step, total_steps, next_step_number, next_coords
def get_step_number(self, pos):
if self.point_a is None or self.point_b is None:
return None
x_min, x_max = sorted([self.point_a["x"], self.point_b["x"]])
y_min, y_max = sorted([self.point_a["y"], self.point_b["y"]])
z_min, z_max = sorted([self.point_a["z"], self.point_b["z"]])
steps_x = int((x_max - x_min) / self.steps["x"]) + 1
steps_y = int((y_max - y_min) / self.steps["y"]) + 1
ix = int(round((pos["x"] - x_min) / self.steps["x"]))
iy = int(round((pos["y"] - y_min) / self.steps["y"]))
iz = int(round((pos["z"] - z_min) / self.steps["z"]))
return iz * (steps_x * steps_y) + iy * steps_x + ix + 1
def get_step_coords(self, step_no):
if self.point_a is None or self.point_b is None:
return None
x_min, x_max = sorted([self.point_a["x"], self.point_b["x"]])
y_min, y_max = sorted([self.point_a["y"], self.point_b["y"]])
z_min, z_max = sorted([self.point_a["z"], self.point_b["z"]])
steps_x = int((x_max - x_min) / self.steps["x"]) + 1
steps_y = int((y_max - y_min) / self.steps["y"]) + 1
steps_z = int((z_max - z_min) / self.steps["z"]) + 1
total_steps = steps_x * steps_y * steps_z
if step_no < 1 or step_no > total_steps:
return None
# Convert step number (1-based) to 3D indices
idx = step_no - 1
ix = idx % steps_x
iy = (idx // steps_x) % steps_y
iz = idx // (steps_x * steps_y)
x = x_min + ix * self.steps["x"]
y = y_min + iy * self.steps["y"]
z = z_min + iz * self.steps["z"]
return {"x": x, "y": y, "z": z}
def get_total_steps(self):
if self.point_a is None or self.point_b is None:
return 0
x_min, x_max = sorted([self.point_a["x"], self.point_b["x"]])
y_min, y_max = sorted([self.point_a["y"], self.point_b["y"]])
z_min, z_max = sorted([self.point_a["z"], self.point_b["z"]])
steps_x = int((x_max - x_min) / self.steps["x"]) + 1
steps_y = int((y_max - y_min) / self.steps["y"]) + 1
steps_z = int((z_max - z_min) / self.steps["z"]) + 1
return steps_x * steps_y * steps_z
def save_state(self):
return {
"point_a": self.point_a,
"point_b": self.point_b,
"steps": self.steps,
"step_count": self.step_count,
"current_step": self.current_step,
}
def load_state(self, state):
self.point_a = state.get("point_a")
self.point_b = state.get("point_b")
self.steps = state.get("steps", {"x": 1.0, "y": 1.0, "z": 40.0})
self.step_count = state.get("step_count", 0)
self.current_step = state.get("current_step", 0)