Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Beacon Offset scan compensation #37

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a4457c3
PLUGIN: gantry twist mesh creation
HelgeKeck Dec 1, 2024
752cbee
fix beacon scan compensation calculation
HelgeKeck Dec 2, 2024
eac5055
BEACON: final compensation mesh creation
HelgeKeck Dec 9, 2024
4b9127a
BEACON: default compensation mesh name changed
HelgeKeck Dec 9, 2024
0799711
BEACON: debug output added to scan compensation
HelgeKeck Dec 9, 2024
6d4715a
BEACON: better variable names
HelgeKeck Dec 9, 2024
a9a7837
BEACON: better variable name
HelgeKeck Dec 9, 2024
75856f9
MACRO: default profile changed for BEACON_CREATE_SCAN_COMPENSATION_MESH
HelgeKeck Dec 9, 2024
ee8a1d9
MACRO: add hotend heat soke time to BEACON_CREATE_SCAN_COMPENSATION_MESH
HelgeKeck Dec 9, 2024
c536ebc
MACRO: commetn changed in BEACON_CREATE_SCAN_COMPENSATION_MESH
HelgeKeck Dec 9, 2024
971307e
PLUGIN: unused variables removed
HelgeKeck Dec 9, 2024
f500825
Merge pull request #4 from Rat-OS/development
HelgeKeck Dec 9, 2024
0f66c59
BEACON: offset calculation inverted
HelgeKeck Dec 9, 2024
a30525b
BEACON: fix compensation calculation
HelgeKeck Dec 10, 2024
1cfe949
BEACON: debug output added to create_compensation_mesh
HelgeKeck Dec 10, 2024
cf3453b
BEACON: make beacon calibration idex aware
HelgeKeck Dec 10, 2024
05739d6
Merge pull request #5 from Rat-OS/development
HelgeKeck Dec 10, 2024
f2ccbc3
BEACON: moving beacon compensation into beacon_mesh.py
HelgeKeck Dec 10, 2024
3e11758
MACRO: add beacon_mesh printer object to beacon.cfg
HelgeKeck Dec 10, 2024
ec3b159
Merge pull request #6 from Rat-OS/development
HelgeKeck Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
177 changes: 177 additions & 0 deletions configuration/klippy/beacon_mesh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
import logging, collections
from . import bed_mesh as BedMesh

#####
# Beacon Mesh
#####

class BeaconMesh:

#####
# Initialize
#####
def __init__(self, config):
self.config = config
self.printer = config.get_printer()
self.name = config.get_name()
self.gcode = self.printer.lookup_object('gcode')
self.reactor = self.printer.get_reactor()

self.offset_mesh = None
self.offset_mesh_points = [[]]
self.pmgr = BedMeshProfileManager(self.config, self)

self.register_commands()
self.register_handler()

#####
# Handler
#####
def register_handler(self):
self.printer.register_event_handler("klippy:connect", self._connect)

def _connect(self):
if self.config.has_section("ratos"):
self.ratos = self.printer.lookup_object('ratos')
if self.config.has_section("bed_mesh"):
self.bed_mesh = self.printer.lookup_object('bed_mesh')

#####
# Gcode commands
#####
def register_commands(self):
self.gcode.register_command('BEACON_APPLY_SCAN_COMPENSATION', self.cmd_BEACON_APPLY_SCAN_COMPENSATION, desc=(self.desc_BEACON_APPLY_SCAN_COMPENSATION))
self.gcode.register_command('CREATE_BEACON_COMPENSATION_MESH', self.cmd_CREATE_BEACON_COMPENSATION_MESH, desc=(self.desc_CREATE_BEACON_COMPENSATION_MESH))

desc_BEACON_APPLY_SCAN_COMPENSATION = "Compensates a beacon scan mesh with the beacon compensation mesh."
def cmd_BEACON_APPLY_SCAN_COMPENSATION(self, gcmd):
profile = gcmd.get('PROFILE', "Offset")
if not profile.strip():
raise gcmd.error("Value for parameter 'PROFILE' must be specified")
if profile not in self.pmgr.get_profiles():
raise self.printer.command_error("Profile " + str(profile) + " not found for Beacon scan compensation")
self.offset_mesh = self.pmgr.load_profile(profile)
if not self.offset_mesh:
raise self.printer.command_error("Could not load profile " + str(profile) + " for Beacon scan compensation")
self.compensate_beacon_scan(profile)

desc_CREATE_BEACON_COMPENSATION_MESH = "Creates the beacon compensation mesh by joining a contact and a scan mesh."
def cmd_CREATE_BEACON_COMPENSATION_MESH(self, gcmd):
profile = gcmd.get('PROFILE', "Offset")
if not profile.strip():
raise gcmd.error("Value for parameter 'PROFILE' must be specified")
self.create_compensation_mesh(profile)

#####
# Beacon Scan Compensation
#####
def compensate_beacon_scan(self, profile):
systime = self.reactor.monotonic()
try:
if self.bed_mesh.z_mesh:
profile_name = self.bed_mesh.z_mesh.get_profile_name()
if profile_name != profile:
points = self.bed_mesh.get_status(systime)["profiles"][profile_name]["points"]
params = self.bed_mesh.z_mesh.get_mesh_params()
x_step = ((params["max_x"] - params["min_x"]) / (len(points[0]) - 1))
y_step = ((params["max_y"] - params["min_y"]) / (len(points) - 1))
new_points = []
for y in range(len(points)):
new_points.append([])
for x in range(len(points[0])):
x_pos = params["min_x"] + x * x_step
y_pos = params["min_y"] + y * y_step
scan_z = points[y][x]
offset_z = self.offset_mesh.calc_z(x_pos, y_pos)
new_z = scan_z + offset_z
self.ratos.debug_echo("Beacon scan compensation", "scan: %0.4f offset: %0.4f new: %0.4f" % (scan_z, offset_z, new_z))
new_points[y].append(new_z)
self.bed_mesh.z_mesh.build_mesh(new_points)
self.bed_mesh.save_profile(profile_name)
self.bed_mesh.set_mesh(self.bed_mesh.z_mesh)
self.ratos.console_echo("Beacon scan compensation", "debug", "Mesh scan profile %s compensated with contact profile %s" % (str(profile_name), str(profile)))
except BedMesh.BedMeshError as e:
self.ratos.console_echo("Beacon scan compensation error", "error", str(e))

def create_compensation_mesh(self, profile):
systime = self.reactor.monotonic()
if self.bed_mesh.z_mesh:
self.gcode.run_script_from_command("BED_MESH_PROFILE LOAD='RatOSTempOffsetScan'")
scan_mesh_points = self.bed_mesh.get_status(systime)["profiles"]["RatOSTempOffsetScan"]["points"]
self.gcode.run_script_from_command("BED_MESH_PROFILE LOAD='%s'" % profile)
try:
points = self.bed_mesh.get_status(systime)["profiles"][profile]["points"]
new_points = []
for y in range(len(points)):
new_points.append([])
for x in range(len(points[0])):
contact_z = points[y][x]
scan_z = scan_mesh_points[y][x]
offset_z = contact_z - scan_z
self.ratos.debug_echo("Create compensation mesh", "scan: %0.4f contact: %0.4f offset: %0.4f" % (scan_z, contact_z, offset_z))
new_points[y].append(offset_z)
self.bed_mesh.z_mesh.build_mesh(new_points)
self.bed_mesh.save_profile(profile)
self.bed_mesh.set_mesh(self.bed_mesh.z_mesh)
self.ratos.console_echo("Create compensation mesh", "debug", "Compensation Mesh %s created" % (str(profile)))
except BedMesh.BedMeshError as e:
self.ratos.console_echo("Create compensation mesh error", "error", str(e))

#####
# Bed Mesh Profile Manager
#####
PROFILE_VERSION = 1
class BedMeshProfileManager:
def __init__(self, config, bedmesh):
self.name = "bed_mesh"
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.bedmesh = bedmesh
self.profiles = {}
self.incompatible_profiles = []
# Fetch stored profiles from Config
stored_profs = config.get_prefix_sections(self.name)
stored_profs = [s for s in stored_profs
if s.get_name() != self.name]
for profile in stored_profs:
name = profile.get_name().split(' ', 1)[1]
version = profile.getint('version', 0)
if version != BedMesh.PROFILE_VERSION:
logging.info(
"bed_mesh: Profile [%s] not compatible with this version\n"
"of bed_mesh. Profile Version: %d Current Version: %d "
% (name, version, BedMesh.PROFILE_VERSION))
self.incompatible_profiles.append(name)
continue
self.profiles[name] = {}
zvals = profile.getlists('points', seps=(',', '\n'), parser=float)
self.profiles[name]['points'] = zvals
self.profiles[name]['mesh_params'] = params = \
collections.OrderedDict()
for key, t in BedMesh.PROFILE_OPTIONS.items():
if t is int:
params[key] = profile.getint(key)
elif t is float:
params[key] = profile.getfloat(key)
elif t is str:
params[key] = profile.get(key)
def get_profiles(self):
return self.profiles
def load_profile(self, prof_name):
profile = self.profiles.get(prof_name, None)
if profile is None:
return None
probed_matrix = profile['points']
mesh_params = profile['mesh_params']
z_mesh = BedMesh.ZMesh(mesh_params, prof_name)
try:
z_mesh.build_mesh(probed_matrix)
except BedMesh.BedMeshError as e:
raise self.gcode.error(str(e))
return z_mesh

#####
# Loader
#####
def load_config(config):
return BeaconMesh(config)
115 changes: 4 additions & 111 deletions configuration/klippy/ratos.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
import os, logging, re, glob
import logging, collections, pathlib
import json, asyncio, subprocess
from . import bed_mesh as BedMesh
import os, logging, glob
import json, subprocess, pathlib

#####
# RatOS
#####

PRUSA_SLICER = "prusaslicer"
SUPER_SLICER = "superslicer"
ORCA_SLICER = "orcaslicer"
UNKNOWN_SLICER = "unknown"

CHANGED_BY_POST_PROCESSOR = " ; Changed by RatOS post processor: "
REMOVED_BY_POST_PROCESSOR = "; Removed by RatOS post processor: "

class RatOS:

#####
Expand All @@ -32,8 +22,6 @@ def __init__(self, config):
self.reactor = self.printer.get_reactor()

self.old_is_graph_files = []
self.contact_mesh = None
self.pmgr = BedMeshProfileManager(self.config, self)

self.load_settings()
self.register_commands()
Expand All @@ -48,8 +36,6 @@ def register_handler(self):
def _connect(self):
self.v_sd = self.printer.lookup_object('virtual_sdcard', None)
self.sdcard_dirname = self.v_sd.sdcard_dirname
if self.config.has_section("bed_mesh"):
self.bed_mesh = self.printer.lookup_object('bed_mesh')
self.dual_carriage = None
if self.config.has_section("dual_carriage"):
self.dual_carriage = self.printer.lookup_object("dual_carriage", None)
Expand All @@ -75,7 +61,6 @@ def register_commands(self):
self.gcode.register_command('CONSOLE_ECHO', self.cmd_CONSOLE_ECHO, desc=(self.desc_CONSOLE_ECHO))
self.gcode.register_command('RATOS_LOG', self.cmd_RATOS_LOG, desc=(self.desc_RATOS_LOG))
self.gcode.register_command('PROCESS_GCODE_FILE', self.cmd_PROCESS_GCODE_FILE, desc=(self.desc_PROCESS_GCODE_FILE))
self.gcode.register_command('BEACON_APPLY_SCAN_COMPENSATION', self.cmd_BEACON_APPLY_SCAN_COMPENSATION, desc=(self.desc_BEACON_APPLY_SCAN_COMPENSATION))
self.gcode.register_command('TEST_PROCESS_GCODE_FILE', self.cmd_TEST_PROCESS_GCODE_FILE, desc=(self.desc_TEST_PROCESS_GCODE_FILE))

desc_TEST_PROCESS_GCODE_FILE = "Test the G-code post-processor for IDEX and RMMU, only for debugging purposes"
Expand Down Expand Up @@ -156,48 +141,9 @@ def cmd_PROCESS_GCODE_FILE(self, gcmd):
if self.process_gcode_file(filename, True):
self.v_sd.cmd_SDCARD_PRINT_FILE(gcmd)

desc_BEACON_APPLY_SCAN_COMPENSATION = "Compensates magnetic inaccuracies for beacon scan meshes."
def cmd_BEACON_APPLY_SCAN_COMPENSATION(self, gcmd):
profile = gcmd.get('PROFILE', "Contact")
if not profile.strip():
raise gcmd.error("Value for parameter 'PROFILE' must be specified")
if profile not in self.pmgr.get_profiles():
raise self.printer.command_error("Profile " + str(profile) + " not found for Beacon scan compensation")
self.contact_mesh = self.pmgr.load_profile(profile)
if not self.contact_mesh:
raise self.printer.command_error("Could not load profile " + str(profile) + " for Beacon scan compensation")
self.compensate_beacon_scan(profile)

#####
# Beacon Scan Compensation
# Gcode Post Processor
#####
def compensate_beacon_scan(self, profile):
systime = self.reactor.monotonic()
try:
if self.bed_mesh.z_mesh:
profile_name = self.bed_mesh.z_mesh.get_profile_name()
if profile_name != profile:
points = self.bed_mesh.get_status(systime)["profiles"][profile_name]["points"]
params = self.bed_mesh.z_mesh.get_mesh_params()
x_step = ((params["max_x"] - params["min_x"]) / (len(points[0]) - 1))
y_step = ((params["max_y"] - params["min_y"]) / (len(points) - 1))
new_points = []
for y in range(len(points)):
new_points.append([])
for x in range(len(points[0])):
x_pos = params["min_x"] + x * x_step
y_pos = params["min_y"] + y * y_step
z_val = points[y][x]
contact_z = self.contact_mesh.calc_z(x_pos, y_pos)
new_z = z_val - (z_val - contact_z)
new_points[y].append(new_z)
self.bed_mesh.z_mesh.build_mesh(new_points)
self.bed_mesh.save_profile(profile_name)
self.bed_mesh.set_mesh(self.bed_mesh.z_mesh)
self.console_echo("Beacon scan compensation", "debug", "Mesh scan profile %s compensated with contact profile %s" % (str(profile_name), str(profile)))
except BedMesh.BedMeshError as e:
self.console_echo("Beacon scan compensation error", "error", str(e))

def process_gcode_file(self, filename, enable_post_processing):
try:
[path, size] = self.get_gcode_file_info(filename)
Expand Down Expand Up @@ -341,7 +287,6 @@ def _process_output(eventtime):
raise
return False


def get_gcode_file_info(self, filename):
files = self.v_sd.get_file_list(True)
flist = [f[0] for f in files]
Expand Down Expand Up @@ -420,60 +365,8 @@ def get_ratos_version(self):
def get_status(self, eventtime):
return {'name': self.name, 'last_processed_file_result': self.last_processed_file_result}

#####
# Bed Mesh Profile Manager
#####
class BedMeshProfileManager:
def __init__(self, config, bedmesh):
self.name = "bed_mesh"
self.printer = config.get_printer()
self.gcode = self.printer.lookup_object('gcode')
self.bedmesh = bedmesh
self.profiles = {}
self.incompatible_profiles = []
# Fetch stored profiles from Config
stored_profs = config.get_prefix_sections(self.name)
stored_profs = [s for s in stored_profs
if s.get_name() != self.name]
for profile in stored_profs:
name = profile.get_name().split(' ', 1)[1]
version = profile.getint('version', 0)
if version != BedMesh.PROFILE_VERSION:
logging.info(
"bed_mesh: Profile [%s] not compatible with this version\n"
"of bed_mesh. Profile Version: %d Current Version: %d "
% (name, version, BedMesh.PROFILE_VERSION))
self.incompatible_profiles.append(name)
continue
self.profiles[name] = {}
zvals = profile.getlists('points', seps=(',', '\n'), parser=float)
self.profiles[name]['points'] = zvals
self.profiles[name]['mesh_params'] = params = \
collections.OrderedDict()
for key, t in BedMesh.PROFILE_OPTIONS.items():
if t is int:
params[key] = profile.getint(key)
elif t is float:
params[key] = profile.getfloat(key)
elif t is str:
params[key] = profile.get(key)
def get_profiles(self):
return self.profiles
def load_profile(self, prof_name):
profile = self.profiles.get(prof_name, None)
if profile is None:
return None
probed_matrix = profile['points']
mesh_params = profile['mesh_params']
z_mesh = BedMesh.ZMesh(mesh_params, prof_name)
try:
z_mesh.build_mesh(probed_matrix)
except BedMesh.BedMeshError as e:
raise self.gcode.error(str(e))
return z_mesh

#####
# Loader
#####
def load_config(config):
return RatOS(config)
return RatOS(config)
1 change: 1 addition & 0 deletions configuration/scripts/ratos-common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ verify_registered_extensions()
["z_offset_probe_extension"]=$(realpath "${RATOS_PRINTER_DATA_DIR}/config/RatOS/klippy/z_offset_probe.py")
["resonance_generator_extension"]=$(realpath "${RATOS_PRINTER_DATA_DIR}/config/RatOS/klippy/resonance_generator.py")
["ratos_extension"]=$(realpath "${RATOS_PRINTER_DATA_DIR}/config/RatOS/klippy/ratos.py")
["beacon_mesh_extension"]=$(realpath "${RATOS_PRINTER_DATA_DIR}/config/RatOS/klippy/beacon_mesh.py")
)

declare -A kinematics_extensions=(
Expand Down
Loading
Loading