From b648d3572156afbc4214bc87d249516d27fdce27 Mon Sep 17 00:00:00 2001 From: Philipp Molitor Date: Sat, 7 Dec 2024 20:58:03 +0100 Subject: [PATCH 01/32] docs: update crew members and contributors (#464) * add new table of crew members and contributors * make build happy * are you happy now? --- docs/Sponsors.md | 47 +++++++++++++++++++++++----------------- docs/Status_Reference.md | 2 +- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/docs/Sponsors.md b/docs/Sponsors.md index de6b1e5ee..ad6f775a4 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -1,28 +1,35 @@ # Sponsors -Kalico is Free Software. We depend on the generous support from +Kalico is free (as in "free beer") software. We depend on the generous support from sponsors. Please consider sponsoring Kalico or supporting our sponsors. -## Kalico Developers +## The People behind Kalico -### Rogerio Goncalves (@rogerlz) +### The Crew -Rogerio initiated the Kalico project, forking the awesome Klipper firmware originally as "Danger-Klipper" to add some bleeding edge spice to it. +| Name | Alias | GitHub | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------- | ------------------------------------------ | +| **Bea Nance**
Bea initiated the Kalico project together with Maël, forking the awesome Klipper firmware originally as "Danger-Klipper" to add some bleeding edge spice to it.
_"So I saw people wanted some community-driven features in klipper, so i was like "ehh, i'll just fork it and give it a go"_ | bwnance | [@bwnance](https://github.com/bwnance) | +| **Maël Kerbiriou**
Maël together with Bea brought Kalico to life. | piezoid | [@Piezoid](https://github.com/Piezoid) | +| **Rogerio Goncalves**
Roger chimed in early into the development of Kalico, and has since been a crucial contributor to the project. | rogerlz | [@rogerlz](https://github.com/rogerlz) | +| **Frank Tackitt**
Frank started his klipper hacking with exclude_object and just forgot to stop there | frank.af | [@kageurufu](https://github.com/kageurufu) | -Contact: [rogerlz@gmail.com](mailto:rogerlz@gmail.com) +### Contributors -### Lasse Dalegaard (@dalegaard) +| Name | Alias | GitHub | +| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------- | ---------------------------------------------------- | +| **Vinzenz Hassert**
_"Uhh dunno... I mean I did pid profiles, filament sensors and curve control mainly. And I am best at giving Roger grey hairs cause I do huge pull requests!"_ | LastZeanon | [@LastZeanon](https://github.com/LastZeanon) | +| **Yancey Arrington**
Yancey wrote the docs for all the bleeding edge features and model predictive control, otherwise you wouldn't know about all the fancy stuff Kalico brings to the table! He also helps other community members a lot with their questions. What a great guy! | Ray_Boy | [@YanceyA](https://github.com/YanceyA) | +| **Ryan Ghosh**
All Ryan cares about are the TradRack and Belay modules of Kalico. How selfish! (no, in fact, that is very generous. Thanks Ryan!) | RyanG | [@rsghosh](https://github.com/rsghosh) | +| **Lasse Dalegaard**
Our secret consultant for roasting everything. He also contributed a lot more than he is willing to admit. Silly Lasse. | dalegaard | [@dalegaard](https://github.com/dalegaard) | +| **Philipp Molitor**
Phil started hacking around with the docs because he does web stuff and 3D printers, and liked the Kalico Crew so much he just... stuck around.
_"Hehehe... I wrote this page!"_ | philm0 | [@PhilippMolitor](https://github.com/PhilippMolitor) | +## Other Kalico Contributors - -Contact: [dalegaard@gmail.com](mailto:dalegaard@gmail.com) - -### Bea Nance (@bwnance) - - - -Contact: [bwnance@gmail.com](mailto:bwnance@gmail.com) +Kalico is a community effort, and we have a lot more people helping us! +For a full list of people, have a look at the members of +the [GitHub Organization](https://github.com/orgs/KalicoCrew/people) and our [Contributors](https://github.com/KalicoCrew/kalico/graphs/contributors). ## Original Klipper Developers @@ -37,7 +44,7 @@ at: [https://ko-fi.com/koconnor](https://ko-fi.com/koconnor) or ### Eric Callahan Eric is the author of bed_mesh, spi_flash, and several other Klipper -modules. Eric has a donations page at: +modules. Eric has a donations page at: [https://ko-fi.com/arksine](https://ko-fi.com/arksine) ## Related Kalico Projects @@ -45,8 +52,8 @@ modules. Eric has a donations page at: Kalico is frequently used with other Free Software. Consider using or supporting these projects. -* [Moonraker](https://github.com/Arksine/moonraker) -* [Mainsail](https://github.com/mainsail-crew/mainsail) -* [Fluidd](https://github.com/fluidd-core/fluidd) -* [OctoPrint](https://octoprint.org/) -* [KlipperScreen](https://github.com/jordanruthe/KlipperScreen) +- [Moonraker](https://github.com/Arksine/moonraker) +- [Mainsail](https://github.com/mainsail-crew/mainsail) +- [Fluidd](https://github.com/fluidd-core/fluidd) +- [OctoPrint](https://octoprint.org/) +- [KlipperScreen](https://github.com/jordanruthe/KlipperScreen) diff --git a/docs/Status_Reference.md b/docs/Status_Reference.md index 0a77d6afe..e5a36cd98 100644 --- a/docs/Status_Reference.md +++ b/docs/Status_Reference.md @@ -571,7 +571,7 @@ on a cartesian, hybrid_corexy or hybrid_corexz robot ## tools_calibrate The following information is available in the -[tools_calibrate](Config_Reference.md#️-tools_calibrate) object: +[tools_calibrate](Config_Reference.md#tools_calibrate) object: - `sensor_location`: Once calibrated, the location of the sensor - `last_result`: The last tool calibration result - `calibration_probe_inactive`: Status of the calibration probe as of From a38d9eb1470c48321cf0373dd3d617f33a4224f5 Mon Sep 17 00:00:00 2001 From: Ryan Ghosh <56000109+rsghosh@users.noreply.github.com> Date: Sat, 7 Dec 2024 11:59:29 -0800 Subject: [PATCH 02/32] docs: update trad rack links and fix heading level for TR_DISCARD_BOWDEN_LENGTHS (#465) --- docs/Config_Reference.md | 2 +- docs/G-Codes.md | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index 85947b7d9..ddadd7b32 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -5714,7 +5714,7 @@ Trad Rack multimaterial system support. See the following documents from the TradRack repo for additional information: - [Tuning.md](https://github.com/Annex-Engineering/TradRack/blob/main/docs/Tuning.md): document referenced by some of the config options below. -- [Trad Rack config reference document](https://github.com/Annex-Engineering/TradRack/blob/main/docs/klipper/Config_Reference.md): contains info on additional config +- [Trad Rack config reference document](https://github.com/Annex-Engineering/TradRack/blob/main/docs/kalico/Config_Reference.md): contains info on additional config sections that are expected to be used alongside [trad_rack]. ``` diff --git a/docs/G-Codes.md b/docs/G-Codes.md index dd0442363..72c8416d2 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -1618,7 +1618,7 @@ specified and it is higher than the extruder's current temperature, then the extruder will be heated to at least `MIN_TEMP` before unloading/loading; the current extruder temperature target may be used instead if it is higher than `MIN_TEMP`, and if not then -[tr_last_heater_target](https://github.com/Annex-Engineering/TradRack/blob/main/docs/klipper/Save_Variables.md) +[tr_last_heater_target](https://github.com/Annex-Engineering/TradRack/blob/main/docs/kalico/Save_Variables.md) may be used. If `EXACT_TEMP` is specified, the extruder will be heated to `EXACT_TEMP` before unloading/loading, regardless of any other temperature setting. If any of the optional length parameters are @@ -1643,7 +1643,7 @@ the extruder's current temperature, then the extruder will be heated to at least `MIN_TEMP` before unloading; the current extruder temperature target may be used instead if it is higher than `MIN_TEMP`, and if not then -[tr_last_heater_target](https://github.com/Annex-Engineering/TradRack/blob/main/docs/klipper/Save_Variables.md) +[tr_last_heater_target](https://github.com/Annex-Engineering/TradRack/blob/main/docs/kalico/Save_Variables.md) may be used. If `EXACT_TEMP` is specified, the extruder will be heated to `EXACT_TEMP` before unloading/loading, regardless of any other temperature setting. @@ -1738,7 +1738,7 @@ hotend_load_length will be set to the value passed in. If the ADJUST parameter is used, the adjustment will be added to the current value of hotend_load_length. -### TR_DISCARD_BOWDEN_LENGTHS +#### TR_DISCARD_BOWDEN_LENGTHS `TR_DISCARD_BOWDEN_LENGTHS [MODE=[ALL|LOAD|UNLOAD]]`: Discards saved values for "bowden_load_length" and/or "bowden_unload_length" (see [bowden lengths](https://github.com/Annex-Engineering/TradRack/blob/main/docs/Tuning.md#bowden-lengths) @@ -1746,7 +1746,7 @@ for details on how these settings are used). These settings will each be reset to the value of `bowden_length` from the [trad_rack config section](Config_Reference.md#trad_rack), and empty dictionaries will be saved for -[tr_calib_bowden_load_length and tr_calib_bowden_unload_length](https://github.com/Annex-Engineering/TradRack/blob/main/docs/klipper/Save_Variables.md). +[tr_calib_bowden_load_length and tr_calib_bowden_unload_length](https://github.com/Annex-Engineering/TradRack/blob/main/docs/kalico/Save_Variables.md). "bowden_load_length" and tr_calib_bowden_load_length will be affected if MODE=LOAD is specified, "bowden_unload_length" and tr_calib_bowden_unload_length will be affected if MODE=UNLOAD is From 805e4db60e5c4bf6e1cc77449dafdc409cf14b89 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Tue, 10 Dec 2024 12:25:50 +0000 Subject: [PATCH 03/32] docs: Celsius typo (#468) --- docs/Z_Calibration.md | 4 ++-- klippy/extras/control_mpc.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/Z_Calibration.md b/docs/Z_Calibration.md index 38e8e78a7..7819216f7 100644 --- a/docs/Z_Calibration.md +++ b/docs/Z_Calibration.md @@ -72,7 +72,7 @@ But, there are some requirements to use it: Temperature or humindity changes are not a big deal since the switch is not affected much by them and all values are probed in a small time period and only the releations to each other are used. The nozzle height in step 2 can be -determined some time later and even many celsius higher in the printer's +determined some time later and even many Celsius higher in the printer's chamber, compared to the homing in step 1. That is why the nozzle is probed again and can vary a little to the first homing position. @@ -286,4 +286,4 @@ at 250, you can preheat the nozzle to 180, and run this script before finishing temperature. This may have varying effects depending on temperatures used. Also consider picking up your probe prior to your nozzle wipe, to allow this script to probe the -nozzle immediately after cleaning it. \ No newline at end of file +nozzle immediately after cleaning it. diff --git a/klippy/extras/control_mpc.py b/klippy/extras/control_mpc.py index 0064defe5..1f7c5673e 100644 --- a/klippy/extras/control_mpc.py +++ b/klippy/extras/control_mpc.py @@ -546,7 +546,7 @@ def process(eventtime): ret = temp > target if ret and not reported[0]: gcmd.respond_info( - f"Waiting for heater to drop below {target} degrees celcius" + f"Waiting for heater to drop below {target} degrees Celsius" ) reported[0] = True return ret From 94fd9728071862bfc7f231be015d6c8e4b175229 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Tue, 10 Dec 2024 14:36:46 +0000 Subject: [PATCH 04/32] docs: update donation details (#469) --- .github/FUNDING.yml | 4 ++-- docs/Sponsors.md | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 996dfcaa3..f33ca3ba6 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -ko_fi: koconnor -custom: https://www.klipper3d.org/Sponsors.html#klipper-developers +ko_fi: kalicocrew +custom: https://docs.kalico.gg/Sponsors.html#the-crew diff --git a/docs/Sponsors.md b/docs/Sponsors.md index ad6f775a4..3a2c7948b 100644 --- a/docs/Sponsors.md +++ b/docs/Sponsors.md @@ -1,8 +1,11 @@ # Sponsors -Kalico is free (as in "free beer") software. We depend on the generous support from -sponsors. Please consider sponsoring Kalico or supporting our -sponsors. +Kalico is a community-driven, open-source project that is free to use (as in "free beer"). +While we don't have official sponsors yet, we are deeply grateful for the generous support +from our community. Every contribution helps us continue improving Kalico for everyone. + +If you'd like to support Kalico, donations to our Ko-Fi are more than welcome: +👉 [https://ko-fi.com/kalicocrew](https://ko-fi.com/kalicocrew) ## The People behind Kalico From b0e18b371e6f613e4032ee47b44f251a3c7a9142 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Tue, 10 Dec 2024 19:18:11 +0000 Subject: [PATCH 05/32] toolhead: Fixed junction deviation calculation for straight segments (#6747) (#471) * toolhead: Use delta_v2 when calculating centripetal force As a minor math optimization, it's possible to calculate: .5 * self.move_d * self.accel * tan_theta_d2 using: self.delta_v2 * .25 * tan_theta_d2 because self.delta_v2 is "2. * self.move_d * self.accel". Signed-off-by: Dmitry Butyugin Signed-off-by: Kevin O'Connor * toolhead: Remove arbitrary constants controlling junction deviation When calculating the junction speed between two moves the code checked for angles greater than 0.999999 or less than -0.999999 to avoid math issues (sqrt of a negative number and/or divide by zero). However, these arbitrary constants could unnecessarily pessimize junction speeds when angles are close to 180 or 0 degrees. Change the code to explicitly check for negative numbers during sqrt and to explicilty check for zero values prior to division. This simplifies the code and avoids unnecessarily reducing some junction speeds. Signed-off-by: Dmitry Butyugin Signed-off-by: Kevin O'Connor --------- Signed-off-by: Dmitry Butyugin Signed-off-by: Kevin O'Connor Co-authored-by: Kevin O'Connor --- klippy/toolhead.py | 55 ++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 26 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index d065ea4df..1cf5cba8c 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -20,6 +20,7 @@ def __init__(self, toolhead, start_pos, end_pos, speed): self.start_pos = tuple(start_pos) self.end_pos = tuple(end_pos) self.accel = toolhead.max_accel + self.junction_deviation = toolhead.junction_deviation self.equilateral_corner_v2 = toolhead.equilateral_corner_v2 self.timing_callbacks = [] velocity = min(speed, toolhead.max_velocity) @@ -74,6 +75,12 @@ def calc_junction(self, prev_move): return # Allow extruder to calculate its maximum junction extruder_v2 = self.toolhead.extruder.calc_junction(prev_move, self) + max_start_v2 = min( + extruder_v2, + self.max_cruise_v2, + prev_move.max_cruise_v2, + prev_move.max_start_v2 + prev_move.delta_v2, + ) # Find max velocity using "approximated centripetal velocity" axes_r = self.axes_r prev_axes_r = prev_move.axes_r @@ -82,33 +89,29 @@ def calc_junction(self, prev_move): + axes_r[1] * prev_axes_r[1] + axes_r[2] * prev_axes_r[2] ) - if junction_cos_theta > 0.999999: - return - junction_cos_theta = max(junction_cos_theta, -0.999999) - sin_theta_d2 = math.sqrt(0.5 * (1.0 - junction_cos_theta)) - R_jd = sin_theta_d2 / (1.0 - sin_theta_d2) - # Approximated circle must contact moves no further away than mid-move - tan_theta_d2 = sin_theta_d2 / math.sqrt( - 0.5 * (1.0 + junction_cos_theta) - ) - move_centripetal_v2 = 0.5 * self.move_d * tan_theta_d2 * self.accel - prev_move_centripetal_v2 = ( - 0.5 * prev_move.move_d * tan_theta_d2 * prev_move.accel - ) + sin_theta_d2 = math.sqrt(max(0.5 * (1.0 - junction_cos_theta), 0.0)) + cos_theta_d2 = math.sqrt(max(0.5 * (1.0 + junction_cos_theta), 0.0)) + one_minus_sin_theta_d2 = 1.0 - sin_theta_d2 + if one_minus_sin_theta_d2 > 0.0 and cos_theta_d2 > 0.0: + R_jd = sin_theta_d2 / one_minus_sin_theta_d2 + move_jd_v2 = R_jd * self.junction_deviation * self.accel + pmove_jd_v2 = R_jd * prev_move.junction_deviation * prev_move.accel + # Approximated circle must contact moves no further than mid-move + # centripetal_v2 = .5 * self.move_d * self.accel * tan_theta_d2 + quarter_tan_theta_d2 = 0.25 * sin_theta_d2 / cos_theta_d2 + move_centripetal_v2 = self.delta_v2 * quarter_tan_theta_d2 + pmove_centripetal_v2 = prev_move.delta_v2 * quarter_tan_theta_d2 + max_start_v2 = min( + max_start_v2, + move_jd_v2, + pmove_jd_v2, + move_centripetal_v2, + pmove_centripetal_v2, + ) # Apply limits - self.max_start_v2 = min( - R_jd * self.equilateral_corner_v2, - R_jd * prev_move.equilateral_corner_v2, - move_centripetal_v2, - prev_move_centripetal_v2, - extruder_v2, - self.max_cruise_v2, - prev_move.max_cruise_v2, - prev_move.max_start_v2 + prev_move.delta_v2, - ) + self.max_start_v2 = max_start_v2 self.max_smoothed_v2 = min( - self.max_start_v2, - prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2, + max_start_v2, prev_move.max_smoothed_v2 + prev_move.smooth_delta_v2 ) def set_junction(self, start_v2, cruise_v2, end_v2): @@ -277,7 +280,7 @@ def __init__(self, config): "square_corner_velocity", 5.0, minval=0.0 ) self.equilateral_corner_v2 = 0.0 - self.max_accel_to_decel = 0.0 + self.junction_deviation = self.max_accel_to_decel = 0 self._calc_junction_deviation() # Input stall detection self.check_stall_time = 0.0 From 8b673f589ec13a39a9d32036ecd88b6b88fefa10 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Tue, 10 Dec 2024 19:55:49 +0000 Subject: [PATCH 06/32] docs: z_tilt_ng description (#470) --- docs/Config_Reference.md | 7 +++++++ docs/G-Codes.md | 16 +++++++++++++--- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index ddadd7b32..b19bf6f2c 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -1423,6 +1423,13 @@ extended [G-Code command](G-Codes.md#z_tilt) becomes available. #### [z_tilt_ng] +z_tilt's next generation, adding the Z_TILT_CALIBRATE and Z_TILT_AUTODETECT +extended [G-Code commands](G-Codes.md#z_tilt_ng). Z_TILT_CALIBRATE performs multiple +probing runs to calculate z_offsets, enabling accurate tilt adjustment with fewer +probe points. Z_TILT_AUTODETECT automatically determines pivot positions for each +Z stepper through iterative probing. When this section is present, these extended +commands become available, enhancing bed leveling accuracy and calibration efficiency. + ``` [z_tilt_ng] #z_positions: diff --git a/docs/G-Codes.md b/docs/G-Codes.md index 72c8416d2..111916952 100644 --- a/docs/G-Codes.md +++ b/docs/G-Codes.md @@ -313,7 +313,7 @@ from executing. ### [delta_calibrate] The following commands are available when the -[delta_calibrate] config section is enabled (also see the +[delta_calibrate] config section is enabled (also see the [delta calibrate guide](Delta_Calibrate.md)). #### DELTA_CALIBRATE @@ -1876,6 +1876,18 @@ and detach a mag-probe for these commands! The following commands are available when the [z_tilt config section](Config_Reference.md#z_tilt) is enabled. +#### Z_TILT_ADJUST +`Z_TILT_ADJUST [HORIZONTAL_MOVE_Z=] [=]`: This +command will probe the points specified in the config and then make independent +adjustments to each Z stepper to compensate for tilt. See the PROBE command for +details on the optional probe parameters. The optional `HORIZONTAL_MOVE_Z` +value overrides the `horizontal_move_z` option specified in the config file. + +### [z_tilt_ng] + +The following commands are available when the +[z_tilt_ng config section](Config_Reference.md#z_tilt_ng) is enabled. + #### Z_TILT_ADJUST `Z_TILT_ADJUST [HORIZONTAL_MOVE_Z=] [=] [INCREASING_THRESHOLD=]`: This @@ -1900,5 +1912,3 @@ configured in the z_tilt_ng section: small misalgnments of the steppers. The amount of misalignment can be configured with the DELTA paramter. It iterates until the calculated positions cannot be improved any further. This is can be lengthy procedure. -IMPORTANT: For the Z_TILT_CALIBRATE and Z_TILT_AUTODETECT commands to work -the numpy package has to be installed via ~/klippy-env/bin/pip install -v numpy. From 9ca47e4c3b437b4847b596b891f1e585e0f2cb22 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Tue, 10 Dec 2024 12:57:50 -0700 Subject: [PATCH 07/32] Parse floats, but error on NaN (#463) * Disallow NaN values when float parsing * handle inf too --- klippy/configfile.py | 6 ++++-- klippy/gcode.py | 5 ++++- klippy/mathutil.py | 7 +++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/klippy/configfile.py b/klippy/configfile.py index 36cad0b77..e157c9bd8 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -3,7 +3,7 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, glob, re, time, logging, configparser, io +import sys, os, glob, re, time, logging, configparser, io, mathutil from extras.danger_options import get_danger_options error = configparser.Error @@ -49,6 +49,8 @@ def _get_wrapper( "Option '%s' in section '%s' must be specified" % (option, self.section) ) + if parser is float: + parser = mathutil.safe_float try: v = parser(self.section, option) except self.error as e: @@ -209,7 +211,7 @@ def getfloatlist( default, seps=(sep,), count=count, - parser=float, + parser=mathutil.safe_float, note_valid=note_valid, ) diff --git a/klippy/gcode.py b/klippy/gcode.py index a8a9da78b..183eb01d6 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -4,6 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, re, logging, collections, shlex +import mathutil class CommandError(Exception): @@ -73,6 +74,8 @@ def get( above=None, below=None, ): + if parser is float: + parser = mathutil.safe_float value = self._params.get(name) if value is None: if default is self.sentinel: @@ -123,7 +126,7 @@ def get_float( return self.get( name, default, - parser=float, + parser=mathutil.safe_float, minval=minval, maxval=maxval, above=above, diff --git a/klippy/mathutil.py b/klippy/mathutil.py index a53ad6d79..f892f25ac 100644 --- a/klippy/mathutil.py +++ b/klippy/mathutil.py @@ -7,6 +7,13 @@ import queuelogger +def safe_float(v: str) -> float: + f = float(v) + if math.isnan(f) or math.isinf(f): + raise ValueError(f"{v} is not a valid float") + return f + + ###################################################################### # Coordinate descent ###################################################################### From aa13acd7425183aa8f0e80c11fe3cd03add928c8 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Wed, 11 Dec 2024 02:59:38 -0700 Subject: [PATCH 08/32] Collect os-release information without platform.freedesktop_os_release (#475) * Collect os-release information without platform.freedesktop_os_release * telemetry: do not enforce home and add ci tests --------- Co-authored-by: Rogerio Goncalves --- klippy/extras/telemetry.py | 50 ++++++++++++++++++++++++++++---------- test/klippy/commands.test | 4 +++ 2 files changed, 41 insertions(+), 13 deletions(-) diff --git a/klippy/extras/telemetry.py b/klippy/extras/telemetry.py index f5e730df7..ee32b88f4 100644 --- a/klippy/extras/telemetry.py +++ b/klippy/extras/telemetry.py @@ -10,6 +10,7 @@ import pathlib import platform import sys +import shlex import threading import urllib.request import uuid @@ -129,9 +130,7 @@ def cmd_TELEMETRY_EXAMPLE(self, gcmd): with filename.open("w", encoding="utf-8") as fp: json.dump(data, fp, indent=2) - gcmd.respond_info( - f"Example telemetry saved to {filename.relative_to(pathlib.Path.home())}" - ) + gcmd.respond_info(f"Example telemetry saved to {filename}") def _get_machine_id(self): """ @@ -198,26 +197,51 @@ def _collect_platform(self): { "machine": "x86_64", - "os_release": { - "NAME": "Debian GNU/Linux", - "ID": "debian", - "PRETTY_NAME": "Debian GNU/Linux trixie/sid", - "VERSION_CODENAME": "trixie", - "HOME_URL": "https://www.debian.org/", - "SUPPORT_URL": "https://www.debian.org/support", - "BUG_REPORT_URL": "https://bugs.debian.org/" - }, + "os_release": { ... }, "version": "#1 SMP PREEMPT_DYNAMIC Debian 6.12~rc6-1~exp1 (2024-11-10)", "python": "3.12.7 (main, Nov 8 2024, 17:55:36) [GCC 14.2.0]" } """ return { "machine": platform.machine(), - "os_release": platform.freedesktop_os_release(), + "os_release": self._collect_os_release(), "version": platform.version(), "python": sys.version, } + def _collect_os_release(self): + """ + Collect the freedesktop OS-RELEASE information. + See also `platform.freedesktop_os_release()` (available in Python 3.10+) + + { + "NAME": "Debian GNU/Linux", + "ID": "debian", + "PRETTY_NAME": "Debian GNU/Linux trixie/sid", + "VERSION_CODENAME": "trixie", + "HOME_URL": "https://www.debian.org/", + "SUPPORT_URL": "https://www.debian.org/support", + "BUG_REPORT_URL": "https://bugs.debian.org/" + } + """ + paths = [ + pathlib.Path("/etc/os-release"), + pathlib.Path("/usr/lib/os-release"), + ] + path = next(filter(pathlib.Path.exists, paths), None) + if not path: + return + + result = {} + with path.open("r") as fp: + for line in fp: + if "=" not in line: + continue + key, value = line.split("=", maxsplit=1) + result[key] = shlex.split(value)[0] + + return result + def _collect_printer_objects(self): """ Collect a list of all enabled objects in the current Kalico runtime diff --git a/test/klippy/commands.test b/test/klippy/commands.test index 50e71ab3c..e838ae25f 100644 --- a/test/klippy/commands.test +++ b/test/klippy/commands.test @@ -42,5 +42,9 @@ M204 S500 SET_PRESSURE_ADVANCE EXTRUDER=extruder ADVANCE=.001 SET_PRESSURE_ADVANCE ADVANCE=.002 ADVANCE_LOOKAHEAD_TIME=.001 +# test telemetry +TELEMETRY_ENABLE +TELEMETRY_EXAMPLE + # Restart command (must be last in test) RESTART From bec96836d133126bc888b4192486f750afca6942 Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Wed, 11 Dec 2024 15:54:33 +0000 Subject: [PATCH 09/32] bugfix: toolhead stuttering after commit b0e18b3 (#478) --- klippy/toolhead.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 1cf5cba8c..8c8977edc 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -21,7 +21,6 @@ def __init__(self, toolhead, start_pos, end_pos, speed): self.end_pos = tuple(end_pos) self.accel = toolhead.max_accel self.junction_deviation = toolhead.junction_deviation - self.equilateral_corner_v2 = toolhead.equilateral_corner_v2 self.timing_callbacks = [] velocity = min(speed, toolhead.max_velocity) self.is_kinematic_move = True @@ -55,6 +54,7 @@ def __init__(self, toolhead, start_pos, end_pos, speed): self.delta_v2 = 2.0 * move_d * self.accel self.max_smoothed_v2 = 0.0 self.smooth_delta_v2 = 2.0 * move_d * toolhead.max_accel_to_decel + self.next_junction_v2 = 999999999.9 def limit_speed(self, speed, accel): speed2 = speed**2 @@ -65,6 +65,9 @@ def limit_speed(self, speed, accel): self.delta_v2 = 2.0 * self.move_d * self.accel self.smooth_delta_v2 = min(self.smooth_delta_v2, self.delta_v2) + def limit_next_junction_speed(self, speed): + self.next_junction_v2 = min(self.next_junction_v2, speed**2) + def move_error(self, msg="Move out of range"): ep = self.end_pos m = "%s: %.3f %.3f %.3f [%.3f]" % (msg, ep[0], ep[1], ep[2], ep[3]) @@ -79,6 +82,7 @@ def calc_junction(self, prev_move): extruder_v2, self.max_cruise_v2, prev_move.max_cruise_v2, + prev_move.next_junction_v2, prev_move.max_start_v2 + prev_move.delta_v2, ) # Find max velocity using "approximated centripetal velocity" @@ -279,7 +283,6 @@ def __init__(self, config): self.square_corner_velocity = config.getfloat( "square_corner_velocity", 5.0, minval=0.0 ) - self.equilateral_corner_v2 = 0.0 self.junction_deviation = self.max_accel_to_decel = 0 self._calc_junction_deviation() # Input stall detection @@ -577,6 +580,11 @@ def set_position(self, newpos, homing_axes=()): self.kin.set_position(newpos, homing_axes) self.printer.send_event("toolhead:set_position") + def limit_next_junction_speed(self, speed): + last_move = self.lookahead.get_last() + if last_move is not None: + last_move.limit_next_junction_speed(speed) + def move(self, newpos, speed): move = Move(self, self.commanded_pos, newpos, speed) if not move.move_d: @@ -751,7 +759,7 @@ def get_max_velocity(self): def _calc_junction_deviation(self): scv2 = self.square_corner_velocity**2 - self.equilateral_corner_v2 = scv2 * (math.sqrt(2.0) - 1.0) + self.junction_deviation = scv2 * (math.sqrt(2.0) - 1.0) / self.max_accel self.max_accel_to_decel = self.max_accel * (1.0 - self.min_cruise_ratio) def cmd_G4(self, gcmd): From b9a62e19e1aa8a4b6a1de303d0b90da19ca9627e Mon Sep 17 00:00:00 2001 From: Rogerio Goncalves Date: Wed, 11 Dec 2024 17:34:12 +0000 Subject: [PATCH 10/32] gcode: update M115 app name (#476) --- klippy/gcode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/klippy/gcode.py b/klippy/gcode.py index 183eb01d6..0848dfbad 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -441,7 +441,7 @@ def cmd_M112(self, gcmd): def cmd_M115(self, gcmd): # Get Firmware Version and Capabilities software_version = self.printer.get_start_args().get("software_version") - kw = {"FIRMWARE_NAME": "Klipper", "FIRMWARE_VERSION": software_version} + kw = {"FIRMWARE_NAME": "Kalico", "FIRMWARE_VERSION": software_version} msg = " ".join(["%s:%s" % (k, v) for k, v in kw.items()]) did_ack = gcmd.ack(msg) if not did_ack: From 136149bab11fb2241d29ce78a9428d90942271c2 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Wed, 11 Dec 2024 12:05:15 -0700 Subject: [PATCH 11/32] Convert klippy to a python package (#371) * Convert klippy to a python package 1. Fix all imports within klippy to be relative, or absolute klippy.mod 2. For compatibility with external `extras` that will probably still use the old import names (e.g. a bare `import stepper` instead of `from klippy import stepper`), klippy/compat.py hot patches `sys.modules` before starting the Printer reactor. Any module currently in `sys.modules` under the `klippy.` package namespace are inserted a second time with the leading namespace stripped. * To keep compatibility with `klippy.xxx` being used, `klippy/__init__.py` does `from klippy.printer import *`, ensuring all legacy values exist 3. For compatibility with running `python klippy/klippy.py` (every current printer's systemd service, as well as any KIAUH install), a shim `klippy/klippy.py` is in place that patches `sys.path` before importing and calling `klippy.printer.main()` * Fix ruff * Fix some path manipulation * Fix import test, cleanup two more extras * fix last few imports --------- Co-authored-by: Rogerio Goncalves --- klippy/__init__.py | 3 + klippy/__main__.py | 3 + klippy/compat.py | 20 + klippy/configfile.py | 6 +- klippy/extras/adc_temperature.py | 2 +- klippy/extras/aht10.py | 2 +- klippy/extras/bed_mesh.py | 2 +- klippy/extras/bed_tilt.py | 2 +- klippy/extras/bltouch.py | 2 +- klippy/extras/bme280.py | 2 +- klippy/extras/bus.py | 2 +- klippy/extras/delta_calibrate.py | 2 +- klippy/extras/ds18b20.py | 5 +- klippy/extras/endstop_phase.py | 2 +- klippy/extras/extruder_stepper.py | 2 +- klippy/extras/force_move.py | 2 +- klippy/extras/gcode_macro.py | 2 +- klippy/extras/homing.py | 2 +- klippy/extras/htu21d.py | 2 +- klippy/extras/input_shaper.py | 2 +- klippy/extras/lm75.py | 2 +- klippy/extras/manual_stepper.py | 2 +- klippy/extras/motion_report.py | 2 +- klippy/extras/probe.py | 2 +- klippy/extras/probe_eddy_current.py | 2 +- klippy/extras/pwm_tool.py | 2 +- klippy/extras/replicape.py | 2 +- klippy/extras/shaper_calibrate.py | 2 +- klippy/extras/sht3x.py | 2 +- klippy/extras/spi_temperature.py | 2 +- klippy/extras/statistics.py | 2 +- klippy/extras/sx1509.py | 2 +- klippy/extras/temperature_combined.py | 2 +- klippy/extras/temperature_host.py | 2 +- klippy/extras/temperature_mcu.py | 2 +- klippy/extras/tmc.py | 2 +- klippy/extras/tools_calibrate.py | 5 +- klippy/extras/trad_rack.py | 11 +- klippy/extras/z_calibration.py | 2 +- klippy/extras/z_tilt.py | 2 +- klippy/extras/z_tilt_ng.py | 2 +- klippy/gcode.py | 2 +- klippy/kinematics/cartesian.py | 2 +- klippy/kinematics/corexy.py | 2 +- klippy/kinematics/corexz.py | 2 +- klippy/kinematics/delta.py | 2 +- klippy/kinematics/deltesian.py | 2 +- klippy/kinematics/extruder.py | 2 +- klippy/kinematics/hybrid_corexy.py | 2 +- klippy/kinematics/hybrid_corexz.py | 2 +- klippy/kinematics/idex_modes.py | 2 +- klippy/kinematics/polar.py | 2 +- klippy/kinematics/rotary_delta.py | 2 +- klippy/kinematics/winch.py | 2 +- klippy/klippy.py | 601 +------------------------- klippy/mathutil.py | 2 +- klippy/mcu.py | 4 +- klippy/parsedump.py | 2 +- klippy/printer.py | 601 ++++++++++++++++++++++++++ klippy/reactor.py | 2 +- klippy/serialhdl.py | 4 +- klippy/stepper.py | 2 +- klippy/toolhead.py | 12 +- klippy/webhooks.py | 6 +- scripts/buildcommands.py | 9 +- scripts/calibrate_shaper.py | 9 +- scripts/ci-build.sh | 1 + {klippy => scripts}/console.py | 6 +- scripts/dump_mcu.py | 10 +- scripts/graph_accelerometer.py | 10 +- scripts/make_version.py | 6 +- scripts/parsecandump.py | 11 +- scripts/spi_flash/spi_flash.py | 5 +- scripts/test_klippy.py | 3 +- 74 files changed, 746 insertions(+), 707 deletions(-) create mode 100644 klippy/__init__.py create mode 100644 klippy/__main__.py create mode 100644 klippy/compat.py create mode 100644 klippy/printer.py rename {klippy => scripts}/console.py (98%) diff --git a/klippy/__init__.py b/klippy/__init__.py new file mode 100644 index 000000000..eb6b9c2d1 --- /dev/null +++ b/klippy/__init__.py @@ -0,0 +1,3 @@ +APP_NAME = "Kalico" + +from .printer import * # noqa: E402, F403 diff --git a/klippy/__main__.py b/klippy/__main__.py new file mode 100644 index 000000000..4d96bc05e --- /dev/null +++ b/klippy/__main__.py @@ -0,0 +1,3 @@ +from .printer import main + +main() diff --git a/klippy/compat.py b/klippy/compat.py new file mode 100644 index 000000000..ea6022c57 --- /dev/null +++ b/klippy/compat.py @@ -0,0 +1,20 @@ +import sys + + +def hotpatch_modules(): + """ + This is a compatibility shim for legacy external modules + to fix + Redirect legacy `import x` to `import klippy.x` + + """ + + for module_name, module in list(sys.modules.items()): + if not module_name.startswith("klippy."): + continue + + hotpatched_name = module_name.removeprefix("klippy.") + if hotpatched_name in sys.modules: + continue + + sys.modules[hotpatched_name] = module diff --git a/klippy/configfile.py b/klippy/configfile.py index e157c9bd8..a87c81113 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -3,8 +3,10 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, glob, re, time, logging, configparser, io, mathutil -from extras.danger_options import get_danger_options +import sys, os, glob, re, time, logging, configparser, io +from .extras.danger_options import get_danger_options +from . import mathutil + error = configparser.Error diff --git a/klippy/extras/adc_temperature.py b/klippy/extras/adc_temperature.py index 2637c9d85..456e09bc2 100644 --- a/klippy/extras/adc_temperature.py +++ b/klippy/extras/adc_temperature.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, bisect -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Interface between MCU adc and heater temperature callbacks diff --git a/klippy/extras/aht10.py b/klippy/extras/aht10.py index 16d17a343..9b0241884 100644 --- a/klippy/extras/aht10.py +++ b/klippy/extras/aht10.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Compatible Sensors: diff --git a/klippy/extras/bed_mesh.py b/klippy/extras/bed_mesh.py index 38b3233b8..362d6ebd2 100644 --- a/klippy/extras/bed_mesh.py +++ b/klippy/extras/bed_mesh.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, json, collections from . import probe -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options PROFILE_VERSION = 1 PROFILE_OPTIONS = { diff --git a/klippy/extras/bed_tilt.py b/klippy/extras/bed_tilt.py index 2b09cb35a..556cc3657 100644 --- a/klippy/extras/bed_tilt.py +++ b/klippy/extras/bed_tilt.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil +from klippy import mathutil from . import probe diff --git a/klippy/extras/bltouch.py b/klippy/extras/bltouch.py index b765feba5..0888739ac 100644 --- a/klippy/extras/bltouch.py +++ b/klippy/extras/bltouch.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging from . import probe -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options SIGNAL_PERIOD = 0.020 MIN_CMD_TIME = 5 * SIGNAL_PERIOD diff --git a/klippy/extras/bme280.py b/klippy/extras/bme280.py index ebd99d1ff..0f57c7c39 100644 --- a/klippy/extras/bme280.py +++ b/klippy/extras/bme280.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options REPORT_TIME = 0.8 BME280_CHIP_ADDR = 0x76 diff --git a/klippy/extras/bus.py b/klippy/extras/bus.py index cb19776cf..f0797a324 100644 --- a/klippy/extras/bus.py +++ b/klippy/extras/bus.py @@ -3,7 +3,7 @@ # Copyright (C) 2018,2019 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import mcu +from klippy import mcu def resolve_bus_name(mcu, param, bus): diff --git a/klippy/extras/delta_calibrate.py b/klippy/extras/delta_calibrate.py index 4eb4b587e..d43c76c6b 100644 --- a/klippy/extras/delta_calibrate.py +++ b/klippy/extras/delta_calibrate.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging import math -import mathutil +from klippy import mathutil from . import probe # A "stable position" is a 3-tuple containing the number of steps diff --git a/klippy/extras/ds18b20.py b/klippy/extras/ds18b20.py index 64b002e2d..ab111ab9f 100644 --- a/klippy/extras/ds18b20.py +++ b/klippy/extras/ds18b20.py @@ -3,9 +3,10 @@ # Copyright (C) 2020 Alan Lord # # This file may be distributed under the terms of the GNU GPLv3 license. -import logging, mcu +import logging -from extras.danger_options import get_danger_options +from klippy import mcu +from .danger_options import get_danger_options DS18_REPORT_TIME = 3.0 # Temperature can be sampled at any time but conversion time is ~750ms, so diff --git a/klippy/extras/endstop_phase.py b/klippy/extras/endstop_phase.py index 221b9b99e..a7bf5fbc1 100644 --- a/klippy/extras/endstop_phase.py +++ b/klippy/extras/endstop_phase.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper +from klippy import stepper TRINAMIC_DRIVERS = [ "tmc2130", diff --git a/klippy/extras/extruder_stepper.py b/klippy/extras/extruder_stepper.py index d78028ad9..6bcec529b 100644 --- a/klippy/extras/extruder_stepper.py +++ b/klippy/extras/extruder_stepper.py @@ -3,7 +3,7 @@ # Copyright (C) 2019 Simo Apell # # This file may be distributed under the terms of the GNU GPLv3 license. -from kinematics import extruder +from klippy.kinematics import extruder class PrinterExtruderStepper: diff --git a/klippy/extras/force_move.py b/klippy/extras/force_move.py index 9adee13b4..d224df556 100644 --- a/klippy/extras/force_move.py +++ b/klippy/extras/force_move.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import chelper +from klippy import chelper BUZZ_DISTANCE = 1.0 BUZZ_VELOCITY = BUZZ_DISTANCE / 0.250 diff --git a/klippy/extras/gcode_macro.py b/klippy/extras/gcode_macro.py index f3ccd24e8..700443203 100644 --- a/klippy/extras/gcode_macro.py +++ b/klippy/extras/gcode_macro.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import traceback, logging, ast, copy, json, threading import jinja2, math -import configfile +from klippy import configfile PYTHON_SCRIPT_PREFIX = "!" diff --git a/klippy/extras/homing.py b/klippy/extras/homing.py index d25158bb1..a32341929 100644 --- a/klippy/extras/homing.py +++ b/klippy/extras/homing.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import math import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options # HOMING_START_DELAY = 0.001 # ENDSTOP_SAMPLE_TIME = 0.000015 diff --git a/klippy/extras/htu21d.py b/klippy/extras/htu21d.py index 33252ce80..c378be2d4 100644 --- a/klippy/extras/htu21d.py +++ b/klippy/extras/htu21d.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # NOTE: The implementation requires write support of length 0 diff --git a/klippy/extras/input_shaper.py b/klippy/extras/input_shaper.py index 99179895e..9672bf7ea 100644 --- a/klippy/extras/input_shaper.py +++ b/klippy/extras/input_shaper.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import collections -import chelper +from klippy import chelper from . import shaper_defs diff --git a/klippy/extras/lm75.py b/klippy/extras/lm75.py index de0eab70f..2e98c9749 100644 --- a/klippy/extras/lm75.py +++ b/klippy/extras/lm75.py @@ -6,7 +6,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options LM75_CHIP_ADDR = 0x48 LM75_I2C_SPEED = 100000 diff --git a/klippy/extras/manual_stepper.py b/klippy/extras/manual_stepper.py index c667ecf9c..821020e2f 100644 --- a/klippy/extras/manual_stepper.py +++ b/klippy/extras/manual_stepper.py @@ -3,7 +3,7 @@ # Copyright (C) 2019-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper, chelper +from klippy import stepper, chelper from . import force_move diff --git a/klippy/extras/motion_report.py b/klippy/extras/motion_report.py index 38a9c3169..e6080b5cf 100644 --- a/klippy/extras/motion_report.py +++ b/klippy/extras/motion_report.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import chelper +from klippy import chelper from . import bulk_sensor diff --git a/klippy/extras/probe.py b/klippy/extras/probe.py index fc4bceeb9..e25ae1177 100644 --- a/klippy/extras/probe.py +++ b/klippy/extras/probe.py @@ -4,8 +4,8 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import pins import math +from klippy import pins from . import manual_probe HINT_TIMEOUT = """ diff --git a/klippy/extras/probe_eddy_current.py b/klippy/extras/probe_eddy_current.py index 2e9bed3c9..5d78bea0d 100644 --- a/klippy/extras/probe_eddy_current.py +++ b/klippy/extras/probe_eddy_current.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, bisect -import mcu +from klippy import mcu from . import ldc1612, probe, manual_probe diff --git a/klippy/extras/pwm_tool.py b/klippy/extras/pwm_tool.py index c9eb69d48..c813b773d 100644 --- a/klippy/extras/pwm_tool.py +++ b/klippy/extras/pwm_tool.py @@ -3,7 +3,7 @@ # Copyright (C) 2017-2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import chelper +from klippy import chelper MAX_SCHEDULE_TIME = 5.0 diff --git a/klippy/extras/replicape.py b/klippy/extras/replicape.py index 6d0ba4e59..0cccf07e5 100644 --- a/klippy/extras/replicape.py +++ b/klippy/extras/replicape.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, os -import pins, mcu +from klippy import pins, mcu from . import bus REPLICAPE_MAX_CURRENT = 3.84 diff --git a/klippy/extras/shaper_calibrate.py b/klippy/extras/shaper_calibrate.py index 9e23692a6..e2f66b737 100644 --- a/klippy/extras/shaper_calibrate.py +++ b/klippy/extras/shaper_calibrate.py @@ -9,7 +9,7 @@ import multiprocessing import traceback -shaper_defs = importlib.import_module(".shaper_defs", "extras") +from . import shaper_defs MIN_FREQ = 5.0 MAX_FREQ = 200.0 diff --git a/klippy/extras/sht3x.py b/klippy/extras/sht3x.py index 69b1120f6..a15db8713 100644 --- a/klippy/extras/sht3x.py +++ b/klippy/extras/sht3x.py @@ -7,7 +7,7 @@ import logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # Compatible Sensors: diff --git a/klippy/extras/spi_temperature.py b/klippy/extras/spi_temperature.py index 53d0fc934..6ef75a555 100644 --- a/klippy/extras/spi_temperature.py +++ b/klippy/extras/spi_temperature.py @@ -7,7 +7,7 @@ import math, logging from . import bus -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options ###################################################################### # SensorBase diff --git a/klippy/extras/statistics.py b/klippy/extras/statistics.py index 4d7bb9d69..6f704b78b 100644 --- a/klippy/extras/statistics.py +++ b/klippy/extras/statistics.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, time, logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options class PrinterSysStats: diff --git a/klippy/extras/sx1509.py b/klippy/extras/sx1509.py index 4bd52fc37..9bd16c29b 100644 --- a/klippy/extras/sx1509.py +++ b/klippy/extras/sx1509.py @@ -3,7 +3,7 @@ # Copyright (C) 2018 Florian Heilmann # # This file may be distributed under the terms of the GNU GPLv3 license. -import pins +from klippy import pins from . import bus # Word registers diff --git a/klippy/extras/temperature_combined.py b/klippy/extras/temperature_combined.py index 3d9549751..97417b5d1 100644 --- a/klippy/extras/temperature_combined.py +++ b/klippy/extras/temperature_combined.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options REPORT_TIME = 0.300 diff --git a/klippy/extras/temperature_host.py b/klippy/extras/temperature_host.py index 93c3359eb..764a8cec1 100644 --- a/klippy/extras/temperature_host.py +++ b/klippy/extras/temperature_host.py @@ -6,7 +6,7 @@ import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options HOST_REPORT_TIME = 1.0 RPI_PROC_TEMP_FILE = "/sys/class/thermal/thermal_zone0/temp" diff --git a/klippy/extras/temperature_mcu.py b/klippy/extras/temperature_mcu.py index 32c3c7853..9167ff79d 100644 --- a/klippy/extras/temperature_mcu.py +++ b/klippy/extras/temperature_mcu.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from extras.danger_options import get_danger_options +from .danger_options import get_danger_options SAMPLE_TIME = 0.001 SAMPLE_COUNT = 8 diff --git a/klippy/extras/tmc.py b/klippy/extras/tmc.py index 266e6e1e7..ff699060b 100644 --- a/klippy/extras/tmc.py +++ b/klippy/extras/tmc.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging, collections -import stepper +from klippy import stepper ###################################################################### diff --git a/klippy/extras/tools_calibrate.py b/klippy/extras/tools_calibrate.py index b2bea4bc9..490b9ba6b 100644 --- a/klippy/extras/tools_calibrate.py +++ b/klippy/extras/tools_calibrate.py @@ -5,9 +5,10 @@ # Sourced from https://github.com/viesturz/klipper-toolchanger/blob/main/klipper/extras/tools_calibrate.py import collections -import logging -import pins import enum +import logging + +from klippy import pins class Axis(enum.IntEnum): diff --git a/klippy/extras/trad_rack.py b/klippy/extras/trad_rack.py index c11bbd3e3..1f4426bd4 100644 --- a/klippy/extras/trad_rack.py +++ b/klippy/extras/trad_rack.py @@ -6,10 +6,11 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import logging, math, os, time from collections import deque -from extras.homing import Homing, HomingMove -from gcode import CommandError -from stepper import LookupMultiRail -import chelper, toolhead, kinematics.extruder +from .homing import Homing, HomingMove +from .. import chelper, toolhead +from ..gcode import CommandError +from ..stepper import LookupMultiRail +from ..kinematics import extruder SERVO_NAME = "servo tr_servo" SELECTOR_STEPPER_NAME = "stepper_tr_selector" @@ -2389,7 +2390,7 @@ def __init__(self, config, buffer_pull_speed, is_extruder_synced): # Create kinematic class gcode = self.printer.lookup_object("gcode") self.Coord = gcode.Coord - self.extruder = kinematics.extruder.DummyExtruder(self.printer) + self.extruder = extruder.DummyExtruder(self.printer) try: self.kin = TradRackKinematics(self, config, is_extruder_synced) except config.error as e: diff --git a/klippy/extras/z_calibration.py b/klippy/extras/z_calibration.py index f6f838bef..d9bbaa0fa 100644 --- a/klippy/extras/z_calibration.py +++ b/klippy/extras/z_calibration.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -from mcu import MCU_endstop +from klippy.mcu import MCU_endstop class ZCalibrationHelper: diff --git a/klippy/extras/z_tilt.py b/klippy/extras/z_tilt.py index f63de211b..f07af9c9e 100644 --- a/klippy/extras/z_tilt.py +++ b/klippy/extras/z_tilt.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil +from klippy import mathutil from . import probe diff --git a/klippy/extras/z_tilt_ng.py b/klippy/extras/z_tilt_ng.py index d806a738b..5bbd9422b 100644 --- a/klippy/extras/z_tilt_ng.py +++ b/klippy/extras/z_tilt_ng.py @@ -4,8 +4,8 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import logging -import mathutil import numpy as np +from klippy import mathutil from . import probe diff --git a/klippy/gcode.py b/klippy/gcode.py index 0848dfbad..f6c84f213 100644 --- a/klippy/gcode.py +++ b/klippy/gcode.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, re, logging, collections, shlex -import mathutil +from . import mathutil class CommandError(Exception): diff --git a/klippy/kinematics/cartesian.py b/klippy/kinematics/cartesian.py index 24577ab9f..b08add5ef 100644 --- a/klippy/kinematics/cartesian.py +++ b/klippy/kinematics/cartesian.py @@ -3,7 +3,7 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/corexy.py b/klippy/kinematics/corexy.py index 25f1e7b20..4841d481f 100644 --- a/klippy/kinematics/corexy.py +++ b/klippy/kinematics/corexy.py @@ -3,7 +3,7 @@ # Copyright (C) 2017-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper class CoreXYKinematics: diff --git a/klippy/kinematics/corexz.py b/klippy/kinematics/corexz.py index e67d7b354..5fb383146 100644 --- a/klippy/kinematics/corexz.py +++ b/klippy/kinematics/corexz.py @@ -3,7 +3,7 @@ # Copyright (C) 2020 Maks Zolin # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper class CoreXZKinematics: diff --git a/klippy/kinematics/delta.py b/klippy/kinematics/delta.py index e28fd0a16..0d6f0602b 100644 --- a/klippy/kinematics/delta.py +++ b/klippy/kinematics/delta.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, mathutil +from klippy import stepper, mathutil # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO SLOW_RATIO = 3.0 diff --git a/klippy/kinematics/deltesian.py b/klippy/kinematics/deltesian.py index 7a6c5feb7..e51c11239 100644 --- a/klippy/kinematics/deltesian.py +++ b/klippy/kinematics/deltesian.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper +from klippy import stepper # Slow moves once the ratio of tower to XY movement exceeds SLOW_RATIO SLOW_RATIO = 3.0 diff --git a/klippy/kinematics/extruder.py b/klippy/kinematics/extruder.py index 9445a77dd..ab955aa01 100644 --- a/klippy/kinematics/extruder.py +++ b/klippy/kinematics/extruder.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, chelper +from klippy import stepper, chelper class ExtruderStepper: diff --git a/klippy/kinematics/hybrid_corexy.py b/klippy/kinematics/hybrid_corexy.py index 2b927f002..64da79c68 100644 --- a/klippy/kinematics/hybrid_corexy.py +++ b/klippy/kinematics/hybrid_corexy.py @@ -3,7 +3,7 @@ # Copyright (C) 2021 Fabrice Gallet # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/hybrid_corexz.py b/klippy/kinematics/hybrid_corexz.py index b9993f545..819cfee6e 100644 --- a/klippy/kinematics/hybrid_corexz.py +++ b/klippy/kinematics/hybrid_corexz.py @@ -3,7 +3,7 @@ # Copyright (C) 2021 Fabrice Gallet # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper +from klippy import stepper from . import idex_modes diff --git a/klippy/kinematics/idex_modes.py b/klippy/kinematics/idex_modes.py index ab317e4cb..1cdf34037 100644 --- a/klippy/kinematics/idex_modes.py +++ b/klippy/kinematics/idex_modes.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math -import chelper +from klippy import chelper INACTIVE = "INACTIVE" PRIMARY = "PRIMARY" diff --git a/klippy/kinematics/polar.py b/klippy/kinematics/polar.py index c5fd7af55..27e55e076 100644 --- a/klippy/kinematics/polar.py +++ b/klippy/kinematics/polar.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math -import stepper +from klippy import stepper class PolarKinematics: diff --git a/klippy/kinematics/rotary_delta.py b/klippy/kinematics/rotary_delta.py index 0e8da2411..950b3377a 100644 --- a/klippy/kinematics/rotary_delta.py +++ b/klippy/kinematics/rotary_delta.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging -import stepper, mathutil, chelper +from klippy import stepper, mathutil, chelper class RotaryDeltaKinematics: diff --git a/klippy/kinematics/winch.py b/klippy/kinematics/winch.py index 75a9f82db..fe3202d9a 100644 --- a/klippy/kinematics/winch.py +++ b/klippy/kinematics/winch.py @@ -3,7 +3,7 @@ # Copyright (C) 2018-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import stepper, mathutil +from klippy import stepper, mathutil class WinchKinematics: diff --git a/klippy/klippy.py b/klippy/klippy.py index be027f679..70b6f9811 100644 --- a/klippy/klippy.py +++ b/klippy/klippy.py @@ -1,601 +1,10 @@ -#!/usr/bin/env python2 -# Main code for host side printer firmware -# -# Copyright (C) 2016-2020 Kevin O'Connor -# -# This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, gc, optparse, logging, time, collections, importlib, importlib.util -import util, reactor, queuelogger, msgproto -import gcode, configfile, pins, mcu, toolhead, webhooks -from extras.danger_options import get_danger_options +#!/usr/bin/env python3 -APP_NAME = "Kalico" +import sys, pathlib -message_ready = "Printer is ready" - -message_startup = """ -Printer is not ready -The klippy host software is attempting to connect. Please -retry in a few moments. -""" - -message_restart = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -Printer is halted -""" - -message_protocol_error = """MCU Protocol error""" - -message_protocol_error1 = """ -This is frequently caused by running an older version of the -firmware on the MCU(s). Fix by recompiling and flashing the -firmware. -""" - -message_protocol_error2 = """ -Once the underlying issue is corrected, use the "RESTART" -command to reload the config and restart the host software. -""" - -message_mcu_connect_error = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Error configuring printer -""" - -message_shutdown = """ -Once the underlying issue is corrected, use the -"FIRMWARE_RESTART" command to reset the firmware, reload the -config, and restart the host software. -Printer is shutdown -""" - - -class WaitInterruption(gcode.CommandError): - pass - - -class Printer: - config_error = configfile.error - command_error = gcode.CommandError - - def __init__(self, main_reactor, bglogger, start_args): - if sys.version_info[0] < 3: - logging.error("Kalico requires Python 3") - sys.exit(1) - - self.bglogger = bglogger - self.start_args = start_args - self.reactor = main_reactor - self.reactor.register_callback(self._connect) - self.state_message = message_startup - self.in_shutdown_state = False - self.run_result = None - self.event_handlers = {} - self.objects = collections.OrderedDict() - # Init printer components that must be setup prior to config - for m in [gcode, webhooks]: - m.add_early_printer_objects(self) - - def get_start_args(self): - return self.start_args - - def get_reactor(self): - return self.reactor - - def get_state_message(self): - if self.state_message == message_ready: - category = "ready" - elif self.state_message == message_startup: - category = "startup" - elif self.in_shutdown_state: - category = "shutdown" - else: - category = "error" - return self.state_message, category - - def is_shutdown(self): - return self.in_shutdown_state - - def _set_state(self, msg): - if self.state_message in (message_ready, message_startup): - self.state_message = msg - if ( - msg != message_ready - and self.start_args.get("debuginput") is not None - ): - self.request_exit("error_exit") - - def add_object(self, name, obj): - if name in self.objects: - raise self.config_error( - "Printer object '%s' already created" % (name,) - ) - self.objects[name] = obj - - def lookup_object(self, name, default=configfile.sentinel): - if name in self.objects: - return self.objects[name] - if default is configfile.sentinel: - raise self.config_error("Unknown config object '%s'" % (name,)) - return default - - def lookup_objects(self, module=None): - if module is None: - return list(self.objects.items()) - prefix = module + " " - objs = [ - (n, self.objects[n]) for n in self.objects if n.startswith(prefix) - ] - if module in self.objects: - return [(module, self.objects[module])] + objs - return objs - - def load_object(self, config, section, default=configfile.sentinel): - if section in self.objects: - return self.objects[section] - module_parts = section.split() - module_name = module_parts[0] - extras_py_name = os.path.join( - os.path.dirname(__file__), "extras", module_name + ".py" - ) - extras_py_dirname = os.path.join( - os.path.dirname(__file__), "extras", module_name, "__init__.py" - ) - - plugins_py_dirname = os.path.join( - os.path.dirname(__file__), "plugins", module_name, "__init__.py" - ) - plugins_py_name = os.path.join( - os.path.dirname(__file__), "plugins", module_name + ".py" - ) - - found_in_extras = os.path.exists(extras_py_name) or os.path.exists( - extras_py_dirname - ) - found_in_plugins = os.path.exists(plugins_py_name) - found_in_plugins_dir = os.path.exists(plugins_py_dirname) - - if not any([found_in_extras, found_in_plugins, found_in_plugins_dir]): - if default is not configfile.sentinel: - return default - raise self.config_error("Unable to load module '%s'" % (section,)) - - if ( - found_in_extras - and (found_in_plugins or found_in_plugins_dir) - and not get_danger_options().allow_plugin_override - ): - raise self.config_error( - "Module '%s' found in both extras and plugins!" % (section,) - ) - - if found_in_plugins: - mod_spec = importlib.util.spec_from_file_location( - "extras." + module_name, plugins_py_name - ) - mod = importlib.util.module_from_spec(mod_spec) - mod_spec.loader.exec_module(mod) - elif found_in_plugins_dir: - mod_spec = importlib.util.spec_from_file_location( - "plugins." + module_name, plugins_py_dirname - ) - mod = importlib.util.module_from_spec(mod_spec) - mod_spec.loader.exec_module(mod) - else: - mod = importlib.import_module("extras." + module_name) - - init_func = "load_config" - if len(module_parts) > 1: - init_func = "load_config_prefix" - init_func = getattr(mod, init_func, None) - if init_func is None: - if default is not configfile.sentinel: - return default - raise self.config_error("Unable to load module '%s'" % (section,)) - self.objects[section] = init_func(config.getsection(section)) - return self.objects[section] - - def _read_config(self): - self.objects["configfile"] = pconfig = configfile.PrinterConfig(self) - config = pconfig.read_main_config() - self.load_object(config, "danger_options", None) - if ( - self.bglogger is not None - and get_danger_options().log_config_file_at_startup - ): - pconfig.log_config(config) - # Create printer components - for m in [pins, mcu]: - m.add_printer_objects(config) - for section_config in config.get_prefix_sections(""): - self.load_object(config, section_config.get_name(), None) - # kalico on-by-default extras - for section_config in [ - "force_move", - "respond", - "exclude_object", - "telemetry", - ]: - self.load_object(config, section_config, None) - for m in [toolhead]: - m.add_printer_objects(config) - # Validate that there are no undefined parameters in the config file - error_on_unused = get_danger_options().error_on_unused_config_options - pconfig.check_unused_options(config, error_on_unused) - - def _build_protocol_error_message(self, e): - host_version = self.start_args["software_version"] - - msg_update = [] - msg_updated = [] - - for mcu_name, mcu_obj in self.lookup_objects("mcu"): - try: - mcu_version = mcu_obj.get_status()["mcu_version"] - except: - logging.exception("Unable to retrieve mcu_version from mcu_obj") - continue - - if mcu_version != host_version: - msg_update.append( - "%s: Current version %s" - % ( - mcu_name.split()[-1], - mcu_obj.get_status()["mcu_version"], - ) - ) - else: - msg_updated.append( - "%s: Current version %s" - % ( - mcu_name.split()[-1], - mcu_obj.get_status()["mcu_version"], - ) - ) - - if not len(msg_updated): - msg_updated.append("") - - version_msg = [ - "\nYour Kalico version is: %s\n" % host_version, - "MCU(s) which should be updated:", - "\n%s\n" % "\n".join(msg_update), - "Up-to-date MCU(s):", - "\n%s\n" % "\n".join(msg_updated), - ] - - msg = [ - message_protocol_error, - "", - " ".join(message_protocol_error1.splitlines())[1:], - "\n".join(version_msg), - " ".join(message_protocol_error2.splitlines())[1:], - "", - str(e), - ] - - return "\n".join(msg) - - def _connect(self, eventtime): - try: - self._read_config() - self.send_event("klippy:mcu_identify") - for cb in self.event_handlers.get("klippy:connect", []): - if self.state_message is not message_startup: - return - cb() - except (self.config_error, pins.error) as e: - logging.exception("Config error") - self._set_state("%s\n%s" % (str(e), message_restart)) - return - except msgproto.error as e: - logging.exception("Protocol error") - self._set_state(self._build_protocol_error_message(e)) - util.dump_mcu_build() - return - except mcu.error as e: - logging.exception("MCU error during connect") - self._set_state("%s%s" % (str(e), message_mcu_connect_error)) - util.dump_mcu_build() - return - except Exception as e: - logging.exception("Unhandled exception during connect") - self._set_state( - "Internal error during connect: %s\n%s" - % ( - str(e), - message_restart, - ) - ) - return - try: - self._set_state(message_ready) - for cb in self.event_handlers.get("klippy:ready", []): - if self.state_message is not message_ready: - return - cb() - except Exception as e: - logging.exception("Unhandled exception during ready callback") - self.invoke_shutdown( - "Internal error during ready callback: %s" % (str(e),) - ) - - def run(self): - systime = time.time() - monotime = self.reactor.monotonic() - logging.info( - "Start printer at %s (%.1f %.1f)", - time.asctime(time.localtime(systime)), - systime, - monotime, - ) - # Enter main reactor loop - try: - self.reactor.run() - except: - msg = "Unhandled exception during run" - logging.exception(msg) - # Exception from a reactor callback - try to shutdown - try: - self.reactor.register_callback( - (lambda e: self.invoke_shutdown(msg)) - ) - self.reactor.run() - except: - logging.exception("Repeat unhandled exception during run") - # Another exception - try to exit - self.run_result = "error_exit" - # Check restart flags - run_result = self.run_result - try: - if run_result == "firmware_restart": - self.send_event("klippy:firmware_restart") - self.send_event("klippy:disconnect") - except: - logging.exception("Unhandled exception during post run") - return run_result - - def set_rollover_info(self, name, info, log=True): - if log: - logging.info(info) - if self.bglogger is not None: - self.bglogger.set_rollover_info(name, info) - - def invoke_shutdown(self, msg): - if self.in_shutdown_state: - return - logging.error("Transition to shutdown state: %s", msg) - self.in_shutdown_state = True - self._set_state("%s%s" % (msg, message_shutdown)) - for cb in self.event_handlers.get("klippy:shutdown", []): - try: - cb() - except: - logging.exception("Exception during shutdown handler") - logging.info( - "Reactor garbage collection: %s", self.reactor.get_gc_stats() - ) - - def invoke_async_shutdown(self, msg): - self.reactor.register_async_callback( - (lambda e: self.invoke_shutdown(msg)) - ) - - def register_event_handler(self, event, callback): - self.event_handlers.setdefault(event, []).append(callback) - - def send_event(self, event, *params): - return [cb(*params) for cb in self.event_handlers.get(event, [])] - - def request_exit(self, result): - if self.run_result is None: - self.run_result = result - self.reactor.end() - - wait_interrupted = WaitInterruption - - def wait_while(self, condition_cb, error_on_cancel=True, interval=1.0): - """ - receives a callback - waits until callback returns False - (or is interrupted, or printer shuts down) - """ - gcode = self.lookup_object("gcode") - counter = gcode.get_interrupt_counter() - eventtime = self.reactor.monotonic() - while condition_cb(eventtime): - if self.is_shutdown() or counter != gcode.get_interrupt_counter(): - if error_on_cancel: - raise WaitInterruption("Command interrupted") - else: - return - eventtime = self.reactor.pause(eventtime + interval) - - -###################################################################### -# Startup -###################################################################### - - -def import_test(): - # Import all optional modules (used as a build test) - from extras import danger_options - from unittest import mock - - danger_options.DANGER_OPTIONS = mock.Mock() - dname = os.path.dirname(__file__) - for mname in ["extras", "kinematics"]: - for fname in os.listdir(os.path.join(dname, mname)): - if fname.endswith(".py") and fname != "__init__.py": - module_name = fname[:-3] - else: - iname = os.path.join(dname, mname, fname, "__init__.py") - if not os.path.exists(iname): - continue - module_name = fname - importlib.import_module(mname + "." + module_name) - sys.exit(0) - - -def arg_dictionary(option, opt_str, value, parser): - key, fname = "dictionary", value - if "=" in value: - mcu_name, fname = value.split("=", 1) - key = "dictionary_" + mcu_name - if parser.values.dictionary is None: - parser.values.dictionary = {} - parser.values.dictionary[key] = fname - - -def main(): - usage = "%prog [options] " - opts = optparse.OptionParser(usage) - opts.add_option( - "-i", - "--debuginput", - dest="debuginput", - help="read commands from file instead of from tty port", - ) - opts.add_option( - "-I", - "--input-tty", - dest="inputtty", - default="/tmp/printer", - help="input tty name (default is /tmp/printer)", - ) - opts.add_option( - "-a", - "--api-server", - dest="apiserver", - help="api server unix domain socket filename", - ) - opts.add_option( - "-l", - "--logfile", - dest="logfile", - help="write log to file instead of stderr", - ) - opts.add_option( - "--rotate-log-at-restart", - action="store_true", - help="rotate the log file at every restart", - ) - opts.add_option( - "-v", action="store_true", dest="verbose", help="enable debug messages" - ) - opts.add_option( - "-o", - "--debugoutput", - dest="debugoutput", - help="write output to file instead of to serial port", - ) - opts.add_option( - "-d", - "--dictionary", - dest="dictionary", - type="string", - action="callback", - callback=arg_dictionary, - help="file to read for mcu protocol dictionary", - ) - opts.add_option( - "--import-test", - action="store_true", - help="perform an import module test", - ) - options, args = opts.parse_args() - if options.import_test: - import_test() - if len(args) != 1: - opts.error("Incorrect number of arguments") - start_args = { - "config_file": args[0], - "apiserver": options.apiserver, - "start_reason": "startup", - } - - debuglevel = logging.INFO - if options.verbose: - debuglevel = logging.DEBUG - if options.debuginput: - start_args["debuginput"] = options.debuginput - debuginput = open(options.debuginput, "rb") - start_args["gcode_fd"] = debuginput.fileno() - else: - start_args["gcode_fd"] = util.create_pty(options.inputtty) - if options.debugoutput: - start_args["debugoutput"] = options.debugoutput - start_args.update(options.dictionary) - bglogger = None - if options.logfile: - start_args["log_file"] = options.logfile - bglogger = queuelogger.setup_bg_logging( - filename=options.logfile, - debuglevel=debuglevel, - rotate_log_at_restart=options.rotate_log_at_restart, - ) - if options.rotate_log_at_restart: - bglogger.doRollover() - else: - logging.getLogger().setLevel(debuglevel) - logging.info("=======================") - logging.info("Starting Klippy...") - git_info = util.get_git_version() - git_vers = git_info["version"] - - extra_git_desc = "" - extra_git_desc += "\nBranch: %s" % (git_info["branch"]) - extra_git_desc += "\nRemote: %s" % (git_info["remote"]) - extra_git_desc += "\nTracked URL: %s" % (git_info["url"]) - start_args["software_version"] = git_vers - start_args["git_branch"] = git_info["branch"] - start_args["git_remote"] = git_info["remote"] - start_args["cpu_info"] = util.get_cpu_info() - if bglogger is not None: - versions = "\n".join( - [ - f"Args: {sys.argv}", - f"App Name: {APP_NAME}", - f"Git version: {repr(start_args['software_version'])}{extra_git_desc}", - f"CPU: {start_args['cpu_info']}", - f"Python: {repr(sys.version)}", - ] - ) - logging.info(versions) - elif not options.debugoutput: - logging.warning( - "No log file specified!" " Severe timing issues may result!" - ) - gc.disable() - - # Start Printer() class - while True: - if bglogger is not None: - bglogger.clear_rollover_info() - bglogger.set_rollover_info("versions", versions) - gc.collect() - main_reactor = reactor.Reactor(gc_checking=True) - printer = Printer(main_reactor, bglogger, start_args) - res = printer.run() - if res in ["exit", "error_exit"]: - break - time.sleep(1.0) - main_reactor.finalize() - main_reactor = printer = None - logging.info("Restarting printer") - start_args["start_reason"] = res - if options.rotate_log_at_restart and bglogger is not None: - bglogger.doRollover() - - if bglogger is not None: - bglogger.stop() - - if res == "error_exit": - sys.exit(-1) +if __name__ == "__main__": + sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + from klippy.printer import main -if __name__ == "__main__": main() diff --git a/klippy/mathutil.py b/klippy/mathutil.py index f892f25ac..b3d974538 100644 --- a/klippy/mathutil.py +++ b/klippy/mathutil.py @@ -4,7 +4,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, multiprocessing, traceback -import queuelogger +from . import queuelogger def safe_float(v: str) -> float: diff --git a/klippy/mcu.py b/klippy/mcu.py index 9c0327520..5a80697cf 100644 --- a/klippy/mcu.py +++ b/klippy/mcu.py @@ -7,8 +7,8 @@ import math import os import zlib -import serialhdl, msgproto, pins, chelper, clocksync -from extras.danger_options import get_danger_options +from . import serialhdl, msgproto, pins, chelper, clocksync +from .extras.danger_options import get_danger_options class error(Exception): diff --git a/klippy/parsedump.py b/klippy/parsedump.py index 93d8d14e2..3b752cbf5 100755 --- a/klippy/parsedump.py +++ b/klippy/parsedump.py @@ -5,7 +5,7 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import os, sys, logging -import msgproto +from . import msgproto def read_dictionary(filename): diff --git a/klippy/printer.py b/klippy/printer.py new file mode 100644 index 000000000..b78a0e678 --- /dev/null +++ b/klippy/printer.py @@ -0,0 +1,601 @@ +# Main code for host side printer firmware +# +# Copyright (C) 2016-2020 Kevin O'Connor +# +# This file may be distributed under the terms of the GNU GPLv3 license. + +import sys, os, gc, optparse, logging, time, collections, importlib, importlib.util + +from . import compat +from . import util, reactor, queuelogger, msgproto +from . import gcode, configfile, pins, mcu, toolhead, webhooks +from .extras.danger_options import get_danger_options +from . import APP_NAME + +message_ready = "Printer is ready" + +message_startup = """ +Printer is not ready +The klippy host software is attempting to connect. Please +retry in a few moments. +""" + +message_restart = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +Printer is halted +""" + +message_protocol_error = """MCU Protocol error""" + +message_protocol_error1 = """ +This is frequently caused by running an older version of the +firmware on the MCU(s). Fix by recompiling and flashing the +firmware. +""" + +message_protocol_error2 = """ +Once the underlying issue is corrected, use the "RESTART" +command to reload the config and restart the host software. +""" + +message_mcu_connect_error = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Error configuring printer +""" + +message_shutdown = """ +Once the underlying issue is corrected, use the +"FIRMWARE_RESTART" command to reset the firmware, reload the +config, and restart the host software. +Printer is shutdown +""" + + +class WaitInterruption(gcode.CommandError): + pass + + +class Printer: + config_error = configfile.error + command_error = gcode.CommandError + + def __init__(self, main_reactor, bglogger, start_args): + if sys.version_info[0] < 3: + logging.error("Kalico requires Python 3") + sys.exit(1) + + self.bglogger = bglogger + self.start_args = start_args + self.reactor = main_reactor + self.reactor.register_callback(self._connect) + self.state_message = message_startup + self.in_shutdown_state = False + self.run_result = None + self.event_handlers = {} + self.objects = collections.OrderedDict() + # Init printer components that must be setup prior to config + for m in [gcode, webhooks]: + m.add_early_printer_objects(self) + + def get_start_args(self): + return self.start_args + + def get_reactor(self): + return self.reactor + + def get_state_message(self): + if self.state_message == message_ready: + category = "ready" + elif self.state_message == message_startup: + category = "startup" + elif self.in_shutdown_state: + category = "shutdown" + else: + category = "error" + return self.state_message, category + + def is_shutdown(self): + return self.in_shutdown_state + + def _set_state(self, msg): + if self.state_message in (message_ready, message_startup): + self.state_message = msg + if ( + msg != message_ready + and self.start_args.get("debuginput") is not None + ): + self.request_exit("error_exit") + + def add_object(self, name, obj): + if name in self.objects: + raise self.config_error( + "Printer object '%s' already created" % (name,) + ) + self.objects[name] = obj + + def lookup_object(self, name, default=configfile.sentinel): + if name in self.objects: + return self.objects[name] + if default is configfile.sentinel: + raise self.config_error("Unknown config object '%s'" % (name,)) + return default + + def lookup_objects(self, module=None): + if module is None: + return list(self.objects.items()) + prefix = module + " " + objs = [ + (n, self.objects[n]) for n in self.objects if n.startswith(prefix) + ] + if module in self.objects: + return [(module, self.objects[module])] + objs + return objs + + def load_object(self, config, section, default=configfile.sentinel): + if section in self.objects: + return self.objects[section] + module_parts = section.split() + module_name = module_parts[0] + extras_py_name = os.path.join( + os.path.dirname(__file__), "extras", module_name + ".py" + ) + extras_py_dirname = os.path.join( + os.path.dirname(__file__), "extras", module_name, "__init__.py" + ) + + plugins_py_dirname = os.path.join( + os.path.dirname(__file__), "plugins", module_name, "__init__.py" + ) + plugins_py_name = os.path.join( + os.path.dirname(__file__), "plugins", module_name + ".py" + ) + + found_in_extras = os.path.exists(extras_py_name) or os.path.exists( + extras_py_dirname + ) + found_in_plugins = os.path.exists(plugins_py_name) + found_in_plugins_dir = os.path.exists(plugins_py_dirname) + + if not any([found_in_extras, found_in_plugins, found_in_plugins_dir]): + if default is not configfile.sentinel: + return default + raise self.config_error("Unable to load module '%s'" % (section,)) + + if ( + found_in_extras + and (found_in_plugins or found_in_plugins_dir) + and not get_danger_options().allow_plugin_override + ): + raise self.config_error( + "Module '%s' found in both extras and plugins!" % (section,) + ) + + if found_in_plugins: + mod_spec = importlib.util.spec_from_file_location( + "klippy.extras." + module_name, plugins_py_name + ) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + elif found_in_plugins_dir: + mod_spec = importlib.util.spec_from_file_location( + "klippy.plugins." + module_name, plugins_py_dirname + ) + mod = importlib.util.module_from_spec(mod_spec) + mod_spec.loader.exec_module(mod) + else: + mod = importlib.import_module("klippy.extras." + module_name) + + init_func = "load_config" + if len(module_parts) > 1: + init_func = "load_config_prefix" + init_func = getattr(mod, init_func, None) + if init_func is None: + if default is not configfile.sentinel: + return default + raise self.config_error("Unable to load module '%s'" % (section,)) + self.objects[section] = init_func(config.getsection(section)) + return self.objects[section] + + def _read_config(self): + self.objects["configfile"] = pconfig = configfile.PrinterConfig(self) + config = pconfig.read_main_config() + self.load_object(config, "danger_options", None) + if ( + self.bglogger is not None + and get_danger_options().log_config_file_at_startup + ): + pconfig.log_config(config) + # Create printer components + for m in [pins, mcu]: + m.add_printer_objects(config) + for section_config in config.get_prefix_sections(""): + self.load_object(config, section_config.get_name(), None) + # Kalico on-by-default extras + for section_config in [ + "force_move", + "respond", + "exclude_object", + "telemetry", + ]: + self.load_object(config, section_config, None) + for m in [toolhead]: + m.add_printer_objects(config) + # Validate that there are no undefined parameters in the config file + error_on_unused = get_danger_options().error_on_unused_config_options + pconfig.check_unused_options(config, error_on_unused) + + def _build_protocol_error_message(self, e): + host_version = self.start_args["software_version"] + + msg_update = [] + msg_updated = [] + + for mcu_name, mcu_obj in self.lookup_objects("mcu"): + try: + mcu_version = mcu_obj.get_status()["mcu_version"] + except: + logging.exception("Unable to retrieve mcu_version from mcu_obj") + continue + + if mcu_version != host_version: + msg_update.append( + "%s: Current version %s" + % ( + mcu_name.split()[-1], + mcu_obj.get_status()["mcu_version"], + ) + ) + else: + msg_updated.append( + "%s: Current version %s" + % ( + mcu_name.split()[-1], + mcu_obj.get_status()["mcu_version"], + ) + ) + + if not len(msg_updated): + msg_updated.append("") + + version_msg = [ + "\nYour Kalico version is: %s\n" % host_version, + "MCU(s) which should be updated:", + "\n%s\n" % "\n".join(msg_update), + "Up-to-date MCU(s):", + "\n%s\n" % "\n".join(msg_updated), + ] + + msg = [ + message_protocol_error, + "", + " ".join(message_protocol_error1.splitlines())[1:], + "\n".join(version_msg), + " ".join(message_protocol_error2.splitlines())[1:], + "", + str(e), + ] + + return "\n".join(msg) + + def _connect(self, eventtime): + try: + self._read_config() + self.send_event("klippy:mcu_identify") + for cb in self.event_handlers.get("klippy:connect", []): + if self.state_message is not message_startup: + return + cb() + except (self.config_error, pins.error) as e: + logging.exception("Config error") + self._set_state("%s\n%s" % (str(e), message_restart)) + return + except msgproto.error as e: + logging.exception("Protocol error") + self._set_state(self._build_protocol_error_message(e)) + util.dump_mcu_build() + return + except mcu.error as e: + logging.exception("MCU error during connect") + self._set_state("%s%s" % (str(e), message_mcu_connect_error)) + util.dump_mcu_build() + return + except Exception as e: + logging.exception("Unhandled exception during connect") + self._set_state( + "Internal error during connect: %s\n%s" + % ( + str(e), + message_restart, + ) + ) + return + try: + self._set_state(message_ready) + for cb in self.event_handlers.get("klippy:ready", []): + if self.state_message is not message_ready: + return + cb() + except Exception as e: + logging.exception("Unhandled exception during ready callback") + self.invoke_shutdown( + "Internal error during ready callback: %s" % (str(e),) + ) + + def run(self): + systime = time.time() + monotime = self.reactor.monotonic() + logging.info( + "Start printer at %s (%.1f %.1f)", + time.asctime(time.localtime(systime)), + systime, + monotime, + ) + # Enter main reactor loop + try: + self.reactor.run() + except: + msg = "Unhandled exception during run" + logging.exception(msg) + # Exception from a reactor callback - try to shutdown + try: + self.reactor.register_callback( + (lambda e: self.invoke_shutdown(msg)) + ) + self.reactor.run() + except: + logging.exception("Repeat unhandled exception during run") + # Another exception - try to exit + self.run_result = "error_exit" + # Check restart flags + run_result = self.run_result + try: + if run_result == "firmware_restart": + self.send_event("klippy:firmware_restart") + self.send_event("klippy:disconnect") + except: + logging.exception("Unhandled exception during post run") + return run_result + + def set_rollover_info(self, name, info, log=True): + if log: + logging.info(info) + if self.bglogger is not None: + self.bglogger.set_rollover_info(name, info) + + def invoke_shutdown(self, msg): + if self.in_shutdown_state: + return + logging.error("Transition to shutdown state: %s", msg) + self.in_shutdown_state = True + self._set_state("%s%s" % (msg, message_shutdown)) + for cb in self.event_handlers.get("klippy:shutdown", []): + try: + cb() + except: + logging.exception("Exception during shutdown handler") + logging.info( + "Reactor garbage collection: %s", self.reactor.get_gc_stats() + ) + + def invoke_async_shutdown(self, msg): + self.reactor.register_async_callback( + (lambda e: self.invoke_shutdown(msg)) + ) + + def register_event_handler(self, event, callback): + self.event_handlers.setdefault(event, []).append(callback) + + def send_event(self, event, *params): + return [cb(*params) for cb in self.event_handlers.get(event, [])] + + def request_exit(self, result): + if self.run_result is None: + self.run_result = result + self.reactor.end() + + wait_interrupted = WaitInterruption + + def wait_while(self, condition_cb, error_on_cancel=True, interval=1.0): + """ + receives a callback + waits until callback returns False + (or is interrupted, or printer shuts down) + """ + gcode = self.lookup_object("gcode") + counter = gcode.get_interrupt_counter() + eventtime = self.reactor.monotonic() + while condition_cb(eventtime): + if self.is_shutdown() or counter != gcode.get_interrupt_counter(): + if error_on_cancel: + raise WaitInterruption("Command interrupted") + else: + return + eventtime = self.reactor.pause(eventtime + interval) + + +###################################################################### +# Startup +###################################################################### + + +def import_test(): + # Import all optional modules (used as a build test) + from .extras import danger_options + from unittest import mock + + danger_options.DANGER_OPTIONS = mock.Mock() + dname = os.path.dirname(__file__) + for mname in ["extras", "kinematics"]: + for fname in os.listdir(os.path.join(dname, mname)): + if fname.endswith(".py") and fname != "__init__.py": + module_name = fname[:-3] + else: + iname = os.path.join(dname, mname, fname, "__init__.py") + if not os.path.exists(iname): + continue + module_name = fname + importlib.import_module("klippy." + mname + "." + module_name) + sys.exit(0) + + +def arg_dictionary(option, opt_str, value, parser): + key, fname = "dictionary", value + if "=" in value: + mcu_name, fname = value.split("=", 1) + key = "dictionary_" + mcu_name + if parser.values.dictionary is None: + parser.values.dictionary = {} + parser.values.dictionary[key] = fname + + +def main(): + usage = "%prog [options] " + opts = optparse.OptionParser(usage, prog="klippy") + opts.add_option( + "-i", + "--debuginput", + dest="debuginput", + help="read commands from file instead of from tty port", + ) + opts.add_option( + "-I", + "--input-tty", + dest="inputtty", + default="/tmp/printer", + help="input tty name (default is /tmp/printer)", + ) + opts.add_option( + "-a", + "--api-server", + dest="apiserver", + help="api server unix domain socket filename", + ) + opts.add_option( + "-l", + "--logfile", + dest="logfile", + help="write log to file instead of stderr", + ) + opts.add_option( + "--rotate-log-at-restart", + action="store_true", + help="rotate the log file at every restart", + ) + opts.add_option( + "-v", action="store_true", dest="verbose", help="enable debug messages" + ) + opts.add_option( + "-o", + "--debugoutput", + dest="debugoutput", + help="write output to file instead of to serial port", + ) + opts.add_option( + "-d", + "--dictionary", + dest="dictionary", + type="string", + action="callback", + callback=arg_dictionary, + help="file to read for mcu protocol dictionary", + ) + opts.add_option( + "--import-test", + action="store_true", + help="perform an import module test", + ) + options, args = opts.parse_args() + if options.import_test: + import_test() + if len(args) != 1: + opts.error("Incorrect number of arguments") + start_args = { + "config_file": args[0], + "apiserver": options.apiserver, + "start_reason": "startup", + } + + debuglevel = logging.INFO + if options.verbose: + debuglevel = logging.DEBUG + if options.debuginput: + start_args["debuginput"] = options.debuginput + debuginput = open(options.debuginput, "rb") + start_args["gcode_fd"] = debuginput.fileno() + else: + start_args["gcode_fd"] = util.create_pty(options.inputtty) + if options.debugoutput: + start_args["debugoutput"] = options.debugoutput + start_args.update(options.dictionary) + bglogger = None + if options.logfile: + start_args["log_file"] = options.logfile + bglogger = queuelogger.setup_bg_logging( + filename=options.logfile, + debuglevel=debuglevel, + rotate_log_at_restart=options.rotate_log_at_restart, + ) + if options.rotate_log_at_restart: + bglogger.doRollover() + else: + logging.getLogger().setLevel(debuglevel) + logging.info("=======================") + logging.info("Starting Klippy...") + git_info = util.get_git_version() + git_vers = git_info["version"] + + extra_git_desc = "" + extra_git_desc += "\nBranch: %s" % (git_info["branch"]) + extra_git_desc += "\nRemote: %s" % (git_info["remote"]) + extra_git_desc += "\nTracked URL: %s" % (git_info["url"]) + start_args["software_version"] = git_vers + start_args["git_branch"] = git_info["branch"] + start_args["git_remote"] = git_info["remote"] + start_args["cpu_info"] = util.get_cpu_info() + if bglogger is not None: + versions = "\n".join( + [ + f"Args: {sys.argv}", + f"App Name: {APP_NAME}", + f"Git version: {repr(start_args['software_version'])}{extra_git_desc}", + f"CPU: {start_args['cpu_info']}", + f"Python: {repr(sys.version)}", + ] + ) + logging.info(versions) + elif not options.debugoutput: + logging.warning( + "No log file specified!" " Severe timing issues may result!" + ) + + compat.hotpatch_modules() + + gc.disable() + + # Start Printer() class + while True: + if bglogger is not None: + bglogger.clear_rollover_info() + bglogger.set_rollover_info("versions", versions) + gc.collect() + main_reactor = reactor.Reactor(gc_checking=True) + printer = Printer(main_reactor, bglogger, start_args) + res = printer.run() + if res in ["exit", "error_exit"]: + break + time.sleep(1.0) + main_reactor.finalize() + main_reactor = printer = None + logging.info("Restarting printer") + start_args["start_reason"] = res + if options.rotate_log_at_restart and bglogger is not None: + bglogger.doRollover() + + if bglogger is not None: + bglogger.stop() + + if res == "error_exit": + sys.exit(-1) diff --git a/klippy/reactor.py b/klippy/reactor.py index 970504e57..10caae5fe 100644 --- a/klippy/reactor.py +++ b/klippy/reactor.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import os, gc, select, math, time, logging, queue import greenlet -import chelper, util +from . import chelper, util _NOW = 0.0 _NEVER = 9999999999999999.0 diff --git a/klippy/serialhdl.py b/klippy/serialhdl.py index c055ddd92..a257fedc7 100644 --- a/klippy/serialhdl.py +++ b/klippy/serialhdl.py @@ -6,8 +6,8 @@ import logging, threading, os import serial -import msgproto, chelper, util -from extras.danger_options import get_danger_options +from . import msgproto, chelper, util +from .extras.danger_options import get_danger_options class error(Exception): diff --git a/klippy/stepper.py b/klippy/stepper.py index 66ac60812..453d81a65 100644 --- a/klippy/stepper.py +++ b/klippy/stepper.py @@ -5,7 +5,7 @@ # This file may be distributed under the terms of the GNU GPLv3 license. import collections import math -import chelper +from . import chelper class error(Exception): diff --git a/klippy/toolhead.py b/klippy/toolhead.py index 8c8977edc..408b30b71 100644 --- a/klippy/toolhead.py +++ b/klippy/toolhead.py @@ -4,9 +4,9 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. import math, logging, importlib -import chelper -import kinematics.extruder -from extras.danger_options import get_danger_options +from . import chelper +from .kinematics import extruder +from .extras.danger_options import get_danger_options # Common suffixes: _d is distance (in mm), _v is velocity (in # mm/second), _v2 is velocity squared (mm^2/s^2), _t is time (in @@ -317,10 +317,10 @@ def __init__(self, config): # Create kinematics class gcode = self.printer.lookup_object("gcode") self.Coord = gcode.Coord - self.extruder = kinematics.extruder.DummyExtruder(self.printer) + self.extruder = extruder.DummyExtruder(self.printer) kin_name = config.get("kinematics") try: - mod = importlib.import_module("kinematics." + kin_name) + mod = importlib.import_module("klippy.kinematics." + kin_name) self.kin = mod.load_kinematics(self, config) except config.error as e: raise @@ -843,4 +843,4 @@ def cmd_M204(self, gcmd): def add_printer_objects(config): config.get_printer().add_object("toolhead", ToolHead(config)) - kinematics.extruder.add_printer_objects(config) + extruder.add_printer_objects(config) diff --git a/klippy/webhooks.py b/klippy/webhooks.py index 676454501..d0079c534 100644 --- a/klippy/webhooks.py +++ b/klippy/webhooks.py @@ -4,9 +4,9 @@ # # This file may be distributed under the terms of the GNU GPLv3 license import logging, socket, os, sys, errno, json, collections -import gcode -from klippy import APP_NAME -from extras.danger_options import get_danger_options +from . import gcode +from . import APP_NAME +from .extras.danger_options import get_danger_options REQUEST_LOG_SIZE = 20 diff --git a/scripts/buildcommands.py b/scripts/buildcommands.py index 4c2b23fed..2c18281fa 100644 --- a/scripts/buildcommands.py +++ b/scripts/buildcommands.py @@ -4,11 +4,12 @@ # Copyright (C) 2016-2024 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, subprocess, optparse, logging, shlex, socket, time, traceback -import json, zlib +import sys, os, subprocess, optparse, logging, pathlib, shlex, socket, time +import traceback, json, zlib -sys.path.append("./klippy") -import msgproto # noqa: E402 +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy import msgproto FILEHEADER = """ /* DO NOT EDIT! This is an autogenerated file. See scripts/buildcommands.py. */ diff --git a/scripts/calibrate_shaper.py b/scripts/calibrate_shaper.py index 91833c590..93e48d43e 100755 --- a/scripts/calibrate_shaper.py +++ b/scripts/calibrate_shaper.py @@ -6,14 +6,13 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. from __future__ import print_function -import importlib, optparse, os, sys +import optparse, sys, pathlib from textwrap import wrap import numpy as np, matplotlib -sys.path.append( - os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "klippy") -) -shaper_calibrate = importlib.import_module(".shaper_calibrate", "extras") +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy.extras import shaper_calibrate MAX_TITLE_LENGTH = 65 diff --git a/scripts/ci-build.sh b/scripts/ci-build.sh index 555786814..7543d9860 100755 --- a/scripts/ci-build.sh +++ b/scripts/ci-build.sh @@ -63,6 +63,7 @@ fi ###################################################################### start_test klippy "Test klippy import (Python3)" +# I'm leaving this with klippy/klippy.py so we test that compatibility $PYTHON klippy/klippy.py --import-test finish_test klippy "Test klippy import (Python3)" diff --git a/klippy/console.py b/scripts/console.py similarity index 98% rename from klippy/console.py rename to scripts/console.py index b7b6e34f2..defd5f6de 100755 --- a/klippy/console.py +++ b/scripts/console.py @@ -4,8 +4,10 @@ # Copyright (C) 2016-2021 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, optparse, os, re, logging -import util, reactor, serialhdl, msgproto, clocksync +import sys, optparse, os, re, logging, pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) +from klippy import util, reactor, serialhdl, msgproto, clocksync help_txt = """ This is a debugging console for the Klipper micro-controller. diff --git a/scripts/dump_mcu.py b/scripts/dump_mcu.py index 24e7bea0d..35ec6aba0 100755 --- a/scripts/dump_mcu.py +++ b/scripts/dump_mcu.py @@ -5,17 +5,17 @@ # # This file may be distributed under the terms of the GNU GPLv3 license. -import clocksync -import serialhdl -import reactor import sys import argparse import os import traceback import logging +import pathlib + +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy import clocksync, serialhdl, reactor -KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) -sys.path.append(os.path.join(KLIPPER_DIR, "klippy")) ########################################################### # diff --git a/scripts/graph_accelerometer.py b/scripts/graph_accelerometer.py index b50e0f728..cf620d75e 100755 --- a/scripts/graph_accelerometer.py +++ b/scripts/graph_accelerometer.py @@ -5,14 +5,14 @@ # Copyright (C) 2020 Dmitry Butyugin # # This file may be distributed under the terms of the GNU GPLv3 license. -import importlib, optparse, os, sys +import optparse, os, pathlib, sys from textwrap import wrap import numpy as np, matplotlib -sys.path.append( - os.path.join(os.path.dirname(os.path.realpath(__file__)), "..", "klippy") -) -shaper_calibrate = importlib.import_module(".shaper_calibrate", "extras") +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) + +from klippy.extras import shaper_calibrate # noqa:E402 + MAX_TITLE_LENGTH = 65 diff --git a/scripts/make_version.py b/scripts/make_version.py index 6617be6be..dcdcafa3c 100644 --- a/scripts/make_version.py +++ b/scripts/make_version.py @@ -6,13 +6,15 @@ # This file may be distributed under the terms of the GNU GPLv3 license. from __future__ import print_function -import util import argparse import os import sys -sys.path.append(os.path.join(os.path.dirname(__file__), "../klippy")) +KLIPPER_DIR = os.path.abspath(os.path.join(os.path.dirname(__file__), "../")) +sys.path.append(KLIPPER_DIR) + +from klippy import util # noqa: E402 def main(argv): diff --git a/scripts/parsecandump.py b/scripts/parsecandump.py index e5d6c577c..953aa3893 100755 --- a/scripts/parsecandump.py +++ b/scripts/parsecandump.py @@ -4,15 +4,11 @@ # Copyright (C) 2023 Kevin O'Connor # # This file may be distributed under the terms of the GNU GPLv3 license. -import sys, os, optparse +import sys, pathlib, optparse +sys.path.insert(0, str(pathlib.Path(__file__).parent.parent)) -def import_msgproto(): - global msgproto - # Load msgproto.py module - kdir = os.path.join(os.path.dirname(__file__), "..", "klippy") - sys.path.append(kdir) - import msgproto +from klippy import msgproto def read_dictionary(filename): @@ -143,7 +139,6 @@ def main(): canfilename, canid, dictfilename = args canid = int(canid, 16) - import_msgproto() dictionary = read_dictionary(dictfilename) canfile = open(canfilename, "r") diff --git a/scripts/spi_flash/spi_flash.py b/scripts/spi_flash/spi_flash.py index 530c28122..5266e4363 100644 --- a/scripts/spi_flash/spi_flash.py +++ b/scripts/spi_flash/spi_flash.py @@ -17,10 +17,7 @@ import json import board_defs import fatfs_lib -import reactor -import serialhdl -import clocksync -import mcu +from klippy import reactor, serialhdl, clocksync, mcu ########################################################### # diff --git a/scripts/test_klippy.py b/scripts/test_klippy.py index 0b8a27e02..35b16ddee 100644 --- a/scripts/test_klippy.py +++ b/scripts/test_klippy.py @@ -113,7 +113,8 @@ def launch_test( ) args = [ sys.executable, - "./klippy/klippy.py", + "-m", + "klippy", config_fname, "-i", gcode_fname, From 86d1f55de0e1885836677389eea64f375eaf79c7 Mon Sep 17 00:00:00 2001 From: Frank Tackitt Date: Wed, 11 Dec 2024 12:50:20 -0700 Subject: [PATCH 12/32] Configuration reference interpolation (#448) * Configuration reference interpolation Constants are defined in a `[constants]` section, and unused constants will only warn instead of erroring. My intended usage looks like this: ``` [constants] run_current_ab: 1.9 [tmc5160 stepper_x] run_current: ${constants.run_current_ab} [tmc5160 stepper_y] run_current: ${constants.run_current_ab} ``` `${option}` references the current section `${section.option}` looks up anywhere interpolation occurs at most 10 times right now to avoid infinite loops * Add Danger_Features note --- docs/Config_Reference.md | 24 ++++++++++++++ docs/Danger_Features.md | 1 + klippy/configfile.py | 58 ++++++++++++++++++++++++++++------ test/klippy/danger_options.cfg | 4 ++- 4 files changed, 76 insertions(+), 11 deletions(-) diff --git a/docs/Config_Reference.md b/docs/Config_Reference.md index b19bf6f2c..fbb4d8446 100644 --- a/docs/Config_Reference.md +++ b/docs/Config_Reference.md @@ -145,6 +145,30 @@ A collection of Kalico-specific system options #log_webhook_method_register_messages: False ``` +## ⚠️ Configuration references + +In your configuration, you can reference other values to share +configuration between multiple sections. References take the form of +`${option}` to copy a value in the current section, or +`${section.option}` to look up a value elsewhere in your configuration. + +Optionally, a `[constants]` section may be used specifically to store +these values. Unlike the rest of your configuration, unused constants +will show a warning instead of causing an error. + +``` +[constants] +run_current_ab: 1.0 +i_am_not_used: True # Will show "Constant 'i_am_not_used' is unused" + +[tmc5160 stepper_x] +run_current: ${constants.run_current_ab} + +[tmc5160 stepper_y] +run_current: ${tmc5160 stepper_x.run_current} +# Nested references work, but are not advised +``` + ## Common kinematic settings ### [printer] diff --git a/docs/Danger_Features.md b/docs/Danger_Features.md index 3705b3d3c..0ab9e603e 100644 --- a/docs/Danger_Features.md +++ b/docs/Danger_Features.md @@ -14,6 +14,7 @@ - `--rotate-log-at-restart` can be added to your Kalico start script or service to force log rotation every restart. - [`[virtual_sdcard] with_subdirs`](./Config_Reference.md#virtual_sdcard) enables scanning of subdirectories for .gcode files, for the menu and M20/M23 commands - [`[firmware_retraction] z_hop_height`](./Config_Reference.md#firmware_retraction) adds an automatic z hop when using firmware retraction +- [`[constants]` and `${constants.value}`](./Config_Reference.md#configuration-references) allow re-using values in your configuration ## Enhanced behavior diff --git a/klippy/configfile.py b/klippy/configfile.py index a87c81113..211318bbe 100644 --- a/klippy/configfile.py +++ b/klippy/configfile.py @@ -15,6 +15,38 @@ class sentinel: pass +class SectionInterpolation(configparser.Interpolation): + """ + variable interpolation replacing ${[section.]option} + """ + + _KEYCRE = re.compile( + r"\$\{(?:(?P
[^.:${}]+)[.:])?(?P