Python in next version is awesome. It is future of scripting. I'm just finishing documentation:
https://cnc.zone/sdk/python/python
Python in next version is awesome. It is future of scripting. I'm just finishing documentation:
https://cnc.zone/sdk/python/python
any date for a release?
Any day now.
I'm doing documentation and testing. I'm also consolidating some function names to be more compliant to standards. For example expression "openfn" is renamed to "open_file".
I also made "demo" profile to demonstrate lots of new features.
Any ideas for examples in demo project?
This one shows a lot of non obvious stuff but it isn't really useful for real CNC.
https://www.youtube.com/watch?v=eE8JQF5yMuQ
A practical use case would be welcome.
Why would one use python?
what exactly can be done with it?
There are several advantages:
- Python is much more flexible than "Expressions".
- It has virtually unlimited possibilities because there is a library for anything.
- AI knows Python better than most humans
I have script that uses OpenCV machine vision that moves machine exactly to object.
I have different g-code generators.
I'm create Modbus script to control external devices
I attached image of custom screen that runs several Python scripts.
This ChatGPT prompt:
"Write a Python script that will generate g-code for smiley shape"
generates this script:
and this generates this g-code:Code:#! /usr/bin/env python import math import planetcnc def generate_circle(center, radius, segments): """Generate points approximating a full circle.""" points = [] for i in range(segments + 1): # +1 to close the circle angle = 2 * math.pi * i / segments x = center[0] + radius * math.cos(angle) y = center[1] + radius * math.sin(angle) points.append((x, y)) return points def generate_arc(center, radius, start_angle_deg, end_angle_deg, segments): """Generate points along a circular arc. Angles are in degrees. The arc is drawn from start_angle_deg to end_angle_deg. (Angles may decrease if the start is larger than the end.) """ points = [] for i in range(segments + 1): angle_deg = start_angle_deg + (end_angle_deg - start_angle_deg) * i / segments angle = math.radians(angle_deg) x = center[0] + radius * math.cos(angle) y = center[1] + radius * math.sin(angle) points.append((x, y)) return points def format_point(pt): """Format a coordinate tuple for G-code (3 decimals).""" return f"X{pt[0]:.3f} Y{pt[1]:.3f}" def main(): # Parameters for the smiley face face_center = (0, 0) face_radius = 50 face_segments = 100 # More segments gives a smoother circle left_eye_center = (-20, 20) right_eye_center = (20, 20) eye_radius = 5 eye_segments = 36 # For the smile: # We want the arc to start at (-20, -10) and end at (20, -10). # A circle passing through these points with its center below the arc can be: # center: (0, -25) and radius: 25. # (Check: distance from (0,-25) to (20,-10) is sqrt(20^2+15^2)=25.) # Calculate the angles (relative to the smile circle's center): # For (-20,-10): dx = -20, dy = 15 => angle ? 143.13° # For (20,-10): dx = 20, dy = 15 => angle ? 36.87° # We generate the arc from the left endpoint to the right. smile_center = (0, -25) smile_radius = 25 smile_segments = 30 smile_start_deg = 143.13 # left endpoint angle smile_end_deg = 36.87 # right endpoint angle # Prepare PlanetCNC planetcnc.gcode_close() if not planetcnc.gcode_line_add_allowed(): planetcnc.msg("Adding g-code lines is not allowed!") return 1 # Header commands planetcnc.gcode_line_add("G21 ; Set units to mm") planetcnc.gcode_line_add("G90 ; Use absolute positioning") planetcnc.gcode_line_add("F100 ; Set feed rate") planetcnc.gcode_line_add("") # --- Draw face outline --- face_points = generate_circle(face_center, face_radius, face_segments) # Rapid move to the starting point of the face planetcnc.gcode_line_add(f"G0 {format_point(face_points[0])}") # Draw the circle with linear moves for pt in face_points[1:]: planetcnc.gcode_line_add(f"G1 {format_point(pt)}") planetcnc.gcode_line_add("") # --- Draw left eye --- left_eye_points = generate_circle(left_eye_center, eye_radius, eye_segments) planetcnc.gcode_line_add(f"G0 {format_point(left_eye_points[0])}") for pt in left_eye_points[1:]: planetcnc.gcode_line_add(f"G1 {format_point(pt)}") planetcnc.gcode_line_add("") # --- Draw right eye --- right_eye_points = generate_circle(right_eye_center, eye_radius, eye_segments) planetcnc.gcode_line_add(f"G0 {format_point(right_eye_points[0])}") for pt in right_eye_points[1:]: planetcnc.gcode_line_add(f"G1 {format_point(pt)}") planetcnc.gcode_line_add("") # --- Draw smile arc --- smile_points = generate_arc(smile_center, smile_radius, smile_start_deg, smile_end_deg, smile_segments) planetcnc.gcode_line_add(f"G0 {format_point(smile_points[0])}") for pt in smile_points[1:]: planetcnc.gcode_line_add(f"G1 {format_point(pt)}") planetcnc.gcode_line_add("") # Footer planetcnc.gcode_line_add("M30 ; End of program") planetcnc.gcode_open() if __name__ == '__main__': main()
Code:G21 ; Set units to mm G90 ; Use absolute positioning F100 ; Set feed rate G0 X50.000 Y0.000 G1 X49.901 Y3.140 G1 X49.606 Y6.267 G1 X49.114 Y9.369 G1 X48.429 Y12.434 G1 X47.553 Y15.451 G1 X46.489 Y18.406 G1 X45.241 Y21.289 G1 X43.815 Y24.088 G1 X42.216 Y26.791 G1 X40.451 Y29.389 G1 X38.526 Y31.871 G1 X36.448 Y34.227 G1 X34.227 Y36.448 G1 X31.871 Y38.526 G1 X29.389 Y40.451 G1 X26.791 Y42.216 G1 X24.088 Y43.815 G1 X21.289 Y45.241 G1 X18.406 Y46.489 G1 X15.451 Y47.553 G1 X12.434 Y48.429 G1 X9.369 Y49.114 G1 X6.267 Y49.606 G1 X3.140 Y49.901 G1 X0.000 Y50.000 G1 X-3.140 Y49.901 G1 X-6.267 Y49.606 G1 X-9.369 Y49.114 G1 X-12.434 Y48.429 G1 X-15.451 Y47.553 G1 X-18.406 Y46.489 G1 X-21.289 Y45.241 G1 X-24.088 Y43.815 G1 X-26.791 Y42.216 G1 X-29.389 Y40.451 G1 X-31.871 Y38.526 G1 X-34.227 Y36.448 G1 X-36.448 Y34.227 G1 X-38.526 Y31.871 G1 X-40.451 Y29.389 G1 X-42.216 Y26.791 G1 X-43.815 Y24.088 G1 X-45.241 Y21.289 G1 X-46.489 Y18.406 G1 X-47.553 Y15.451 G1 X-48.429 Y12.434 G1 X-49.114 Y9.369 G1 X-49.606 Y6.267 G1 X-49.901 Y3.140 G1 X-50.000 Y0.000 G1 X-49.901 Y-3.140 G1 X-49.606 Y-6.267 G1 X-49.114 Y-9.369 G1 X-48.429 Y-12.434 G1 X-47.553 Y-15.451 G1 X-46.489 Y-18.406 G1 X-45.241 Y-21.289 G1 X-43.815 Y-24.088 G1 X-42.216 Y-26.791 G1 X-40.451 Y-29.389 G1 X-38.526 Y-31.871 G1 X-36.448 Y-34.227 G1 X-34.227 Y-36.448 G1 X-31.871 Y-38.526 G1 X-29.389 Y-40.451 G1 X-26.791 Y-42.216 G1 X-24.088 Y-43.815 G1 X-21.289 Y-45.241 G1 X-18.406 Y-46.489 G1 X-15.451 Y-47.553 G1 X-12.434 Y-48.429 G1 X-9.369 Y-49.114 G1 X-6.267 Y-49.606 G1 X-3.140 Y-49.901 G1 X-0.000 Y-50.000 G1 X3.140 Y-49.901 G1 X6.267 Y-49.606 G1 X9.369 Y-49.114 G1 X12.434 Y-48.429 G1 X15.451 Y-47.553 G1 X18.406 Y-46.489 G1 X21.289 Y-45.241 G1 X24.088 Y-43.815 G1 X26.791 Y-42.216 G1 X29.389 Y-40.451 G1 X31.871 Y-38.526 G1 X34.227 Y-36.448 G1 X36.448 Y-34.227 G1 X38.526 Y-31.871 G1 X40.451 Y-29.389 G1 X42.216 Y-26.791 G1 X43.815 Y-24.088 G1 X45.241 Y-21.289 G1 X46.489 Y-18.406 G1 X47.553 Y-15.451 G1 X48.429 Y-12.434 G1 X49.114 Y-9.369 G1 X49.606 Y-6.267 G1 X49.901 Y-3.140 G1 X50.000 Y-0.000 G0 X-15.000 Y20.000 G1 X-15.076 Y20.868 G1 X-15.302 Y21.710 G1 X-15.670 Y22.500 G1 X-16.170 Y23.214 G1 X-16.786 Y23.830 G1 X-17.500 Y24.330 G1 X-18.290 Y24.698 G1 X-19.132 Y24.924 G1 X-20.000 Y25.000 G1 X-20.868 Y24.924 G1 X-21.710 Y24.698 G1 X-22.500 Y24.330 G1 X-23.214 Y23.830 G1 X-23.830 Y23.214 G1 X-24.330 Y22.500 G1 X-24.698 Y21.710 G1 X-24.924 Y20.868 G1 X-25.000 Y20.000 G1 X-24.924 Y19.132 G1 X-24.698 Y18.290 G1 X-24.330 Y17.500 G1 X-23.830 Y16.786 G1 X-23.214 Y16.170 G1 X-22.500 Y15.670 G1 X-21.710 Y15.302 G1 X-20.868 Y15.076 G1 X-20.000 Y15.000 G1 X-19.132 Y15.076 G1 X-18.290 Y15.302 G1 X-17.500 Y15.670 G1 X-16.786 Y16.170 G1 X-16.170 Y16.786 G1 X-15.670 Y17.500 G1 X-15.302 Y18.290 G1 X-15.076 Y19.132 G1 X-15.000 Y20.000 G0 X25.000 Y20.000 G1 X24.924 Y20.868 G1 X24.698 Y21.710 G1 X24.330 Y22.500 G1 X23.830 Y23.214 G1 X23.214 Y23.830 G1 X22.500 Y24.330 G1 X21.710 Y24.698 G1 X20.868 Y24.924 G1 X20.000 Y25.000 G1 X19.132 Y24.924 G1 X18.290 Y24.698 G1 X17.500 Y24.330 G1 X16.786 Y23.830 G1 X16.170 Y23.214 G1 X15.670 Y22.500 G1 X15.302 Y21.710 G1 X15.076 Y20.868 G1 X15.000 Y20.000 G1 X15.076 Y19.132 G1 X15.302 Y18.290 G1 X15.670 Y17.500 G1 X16.170 Y16.786 G1 X16.786 Y16.170 G1 X17.500 Y15.670 G1 X18.290 Y15.302 G1 X19.132 Y15.076 G1 X20.000 Y15.000 G1 X20.868 Y15.076 G1 X21.710 Y15.302 G1 X22.500 Y15.670 G1 X23.214 Y16.170 G1 X23.830 Y16.786 G1 X24.330 Y17.500 G1 X24.698 Y18.290 G1 X24.924 Y19.132 G1 X25.000 Y20.000 G0 X-20.000 Y-10.000 G1 X-19.035 Y-8.793 G1 X-17.997 Y-7.648 G1 X-16.891 Y-6.569 G1 X-15.720 Y-5.561 G1 X-14.489 Y-4.627 G1 X-13.203 Y-3.771 G1 X-11.866 Y-2.996 G1 X-10.484 Y-2.304 G1 X-9.062 Y-1.700 G1 X-7.605 Y-1.185 G1 X-6.119 Y-0.760 G1 X-4.610 Y-0.429 G1 X-3.083 Y-0.191 G1 X-1.545 Y-0.048 G1 X0.000 Y0.000 G1 X1.545 Y-0.048 G1 X3.083 Y-0.191 G1 X4.610 Y-0.429 G1 X6.119 Y-0.760 G1 X7.605 Y-1.185 G1 X9.062 Y-1.700 G1 X10.484 Y-2.304 G1 X11.866 Y-2.996 G1 X13.203 Y-3.771 G1 X14.489 Y-4.627 G1 X15.720 Y-5.561 G1 X16.891 Y-6.569 G1 X17.997 Y-7.648 G1 X19.035 Y-8.793 G1 X20.000 Y-10.000 M30 ; End of program
I'm currently looking for a few things in regards to handling tools.
The current implementation for the atc slots is clumsy to use. I need a one screen view where I can simply enter the tool numbers for each slot.
I would like to have check if all tools in a program are in a atc slot or if a tool is not available and needs to be loaded manually. And if yes which tools are missing
Would I be able to solve those questions with python and how would I do this.
You'll be able to solve this even without Python But with Python it is much more elegant.
I will make an example.
This feedback is very useful for me.
This was easier than I thought.
GCode:
Expr:Code:T2 M6 T5 M6 T22 M6 T3 M6
Output:Code:loop(_prog_tools, exec( print("Tool used: ", _prog_tools|(.arg1)); ));
Code:Tool used: 2 Tool used: 3 Tool used: 5 Tool used: 22
With Python you can make this fancy. Custom panel that shows all slots and auto assigns used tool numbers. You can edit it and then confirm so that slots table is correctly populated.
Maybe I'm going to be a bit more precise what I'm looking for and highlight the areas where I’m currently missing information.
What am I looking for:
A Check that is executed when starting a program (for example called in the onstart script).
Checks what tools are used in a program (can be taken from a comment that a post puts at the beginning of the file)
Checks which of those tools are currently available in an ATC Slot
Prints out all tool numbers that are currently not in any ATC Slot to the output window.
If a global parameter is set to true (which can be toggled with a button in the gui) a warning message box is displayed, listing the missing tools and offers the user the choice to either continue (I might have tools outside of the ATC on purpose) or to cancel.
With my currently knowledge a couple of questions come up.
First of some very general stuff:
- I’m currently using visual studio code to do all my python developments, I assume that I can use any IDE to develop for planetcnc and that the script can be placed in the filesystem and no planetcnc GUI interaction is required to add/remove/change any script?
- How could I get auto complete and intellisense working in vscode for the planetcnc library?
- Where do I put the phyton scripts to be able to use them with planetcnc?
- How to call a python script from gcode ?
- Where do normal python print outputs go?
- How can we best debug python code?
Some thoughts from the example explained above:
- How to call the script from the onstart gcode
- From you example I assume that I need to get the tools used in the program by parsing the program. How can I access the program gcode in python?
- How can I get the tool numbers that are currently configured in the ATC Slots?
- Comparing the two arrays is no big deal. Should be straight forward in python.
- Outputting something to the output windows seems to be possible with planetcnc.print(args)
- Accessing a parameter seems to be possible with planetcnc.param_get(name)
- Finally raising a message would be done with planetcnc.msg_show(....)
My main issues are:
- How to call a python script
- How to get the program gcode
- How to get the Tool numbers from the ATC Slots
It is much easier than you imagine.
- there is no need to check all this at program start. When g-code is loaded you get all program info you need. Check parameters that start with _prig
https://cnc.zone/tng/parameters/parameters
_prog_tools is a list of all tools that g-code uses
_slot_tool gets you tool number assigned to slot
Then you just need to compare
My python programs are simple and I never needed anything else than Notepad++;
I'll include your case in Demo project and you'll see how it is done
PS: Is dialog for assigning tools to slots still relevant? .
.
Ok with, with _prog_tools its a bit easier.
_slot_tool was the parameter I was missing all the time ;-)
However, its still unclear to me how to call a python script from gcode. or from a button
A Simplfied dialog for the Slot configuraiton is still requiered but easily implemented with just traditional gcode thanks to _slot_tool. However a python implementation could provide a nice usecase.
Here are some more:
Code:loop(_slot_count, exec( sn = _slot_get|(.arg1); print("Slot ", sn, " has tool ", _slot_tool|sn); ));
Code:loop(_tool_count, exec( tn = _tool_get|(.arg1); print("Tool ", tn, " has slot ", _tool_slot|tn); ));
How many slots you have and how many tools you usually use on job?
It will help me design better dialog knowing this.
My ATC has 16 Slots.
Typically my hobs have between 4 and 15 tools.
However the job tool count is not relevant for the dialog that I want.
I'm looking for something like I build for the old ATC Implementation which lookes like the attached screenshot.
Just a simple dialog, showing all the slots, beeing able to swtich with "tab" between the slots, entering the tool number, switch to the next and confirm everything at the end.
With the old gcode style dialog that was totally static, slots numbers were hardcoded in the script and some additional global parameters in parameters.txt were needed to get this to work but overall quiet straigt forward.
With the new slots implementation this should be much easier. And with python it can maybe dynamically adapt to the number of slots configured.
This video shows how I imagined your dialog. I still need to do automatic tool allocation.
That looks good.
Auto Allocation is nice but I think I will seldom change all the tools in the ATC at once.
With the old Dialog the biggest issue was that I couldn't just skip through the slots with tabs and half the time I forgott to press the update button ;-)
This is script:
Code:#! /usr/bin/env python import sys import time import math import planetcnc def SlotsNew(): global dlg_handle global slot_count global comp_tool global comp_toolname dlg_handle = planetcnc.dlg_new("Otganize tool slots") planetcnc.dlg_callback(dlg_handle, SlotsGen) planetcnc.dlg_keep_open(dlg_handle, False) planetcnc.dlg_set_modal(dlg_handle, True) planetcnc.dlg_set_size(dlg_handle, 0, 0) planetcnc.dlg_set_resizable(dlg_handle, False) planetcnc.dlg_set_btn(dlg_handle, False, True, True) planetcnc.dlg_add_option(dlg_handle, "Clear", ClearOpt, 0) planetcnc.dlg_add_option(dlg_handle, "Auto", AutoOpt, 0) comp = planetcnc.dlg_add_label(dlg_handle, "", "Slot Name") planetcnc.dlg_comp_color(comp, 0xffa500) planetcnc.dlg_comp_font(comp, 15, False) planetcnc.dlg_comp_pos(comp, 80, -1) comp = planetcnc.dlg_add_label(dlg_handle, "", "Tool Number") planetcnc.dlg_comp_color(comp, 0xffa500) planetcnc.dlg_comp_font(comp, 15, False) planetcnc.dlg_comp_pos(comp, 200, -1) slot_count = planetcnc.param_get("_slot_count") comp_tool = {} comp_toolname = {} for slot_idx in range(slot_count): slot_num = planetcnc.param_get("_slot_get", slot_idx) tool_num = planetcnc.param_get("_slot_tool", slot_num) #print(f"Slot {slot_num} has number{slot_num} and assigned tool {tool_num}.") # Slot Name comp_slot = planetcnc.dlg_add_label(dlg_handle, str(slot_num), planetcnc.param_get("_slot_name", slot_num)) planetcnc.dlg_comp_size(comp_slot, 200, 24) # Tool Number comp_tool[slot_idx] = planetcnc.dlg_add_num_input(dlg_handle, "", tool_num, 0, 1000, 0) planetcnc.dlg_comp_pos(comp_tool[slot_idx], 200, -1) planetcnc.dlg_comp_size(comp_tool[slot_idx], 90, 0) planetcnc.dlg_comp_callback(comp_tool[slot_idx], ToolNumChange, slot_idx) # Tool Name comp_toolname[slot_idx] = planetcnc.dlg_add_label(dlg_handle, "", planetcnc.param_get("_tool_name", tool_num)) planetcnc.dlg_comp_pos(comp_toolname[slot_idx], 300, -1) planetcnc.dlg_comp_size(comp_toolname[slot_idx], 120, 24) planetcnc.dlg_show(dlg_handle) return None def ToolNumChange(comp, slot_idx): tool_num = planetcnc.dlg_comp_value(comp) planetcnc.dlg_comp_value(comp_toolname[slot_idx], planetcnc.param_get("_tool_name", tool_num)) def ClearOpt(id): for slot_idx in range(slot_count): planetcnc.dlg_comp_value(comp_tool[slot_idx], 0) planetcnc.dlg_comp_value(comp_toolname[slot_idx], "") def AutoOpt(id): for slot_idx in range(slot_count): tool_idx = slot_idx tool_num = planetcnc.param_get("_prog_tools", tool_idx) planetcnc.dlg_comp_value(comp_tool[slot_idx], tool_num) planetcnc.dlg_comp_value(comp_toolname[slot_idx], planetcnc.param_get("_tool_name", tool_num)) def SlotsGen(): if (dlg_result != -2): print(" CLOSE") return None for slot_idx in range(slot_count): slot_num = planetcnc.param_get("_slot_get", slot_idx) tool_num = planetcnc.dlg_comp_value(comp_tool[slot_idx]) #print(f"Tool {tool_num} to slot {slot_num}") planetcnc.param_set("_slot_tool", slot_num, tool_num) return None if __name__ == '__main__': planetcnc.ready() # Open small example G-code planetcnc.gcode_close() planetcnc.gcode_line_add("T1 M6\nT2 M6\nT22 M6\n") planetcnc.gcode_open() SlotsNew() if (planetcnc.dlg_is_valid(dlg_handle)): while (planetcnc.dlg_run(dlg_handle)): pass