Newer
Older
Hardware / Plotter / plotterStepperMakerBackend.py
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)