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)