Last active
November 3, 2025 22:22
-
-
Save WilliamLivingood/ea025971df15116f4157c34e818f1213 to your computer and use it in GitHub Desktop.
python_part_of_3d_print
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| """ | |
| This file contains parameters, helpers, and setup to | |
| create a basic gcode generation algorithm from line segments. | |
| The main | |
| Inputs: | |
| lines: the line segments to be converted into gcode commands for extrusion | |
| nozzle_diameter: the diameter of the 3D printer's nozzle | |
| filament_diameter: the diameter of the 3d printing filament | |
| layer_height: the height of each layer in the print | |
| extrusion_width: the width of the extruded line from the printer | |
| travel_feed_rate: the speed at which the extruder moves in X and Y | |
| layer_change_feed_rate: the speed at which teh extruder moves when | |
| changing layers in the Z direction | |
| extrusion_feed_rate: the speed at which the extruder move when extruding | |
| Output: | |
| gcode_output: a string with gcode commands separate by new-lines""" | |
| __author__ = "mrivera-cu" | |
| import rhinoscriptsyntax as rs | |
| import math | |
| ########## CONSTANTS BELOW ############### | |
| # GCODE COMMANDS | |
| COMMAND_MOVE = "G1" | |
| # GCODE PARAM NAMES | |
| PARAM_X = "X" | |
| PARAM_Y = "Y" | |
| PARAM_Z = "Z" | |
| PARAM_E = "E" | |
| PARAM_F = "F" | |
| # Separates commands | |
| COMMAND_DELIMITER = "\n" | |
| # Precision for converting floats to strings | |
| E_VALUE_PRECISION = 5 | |
| XYZ_VALUE_PRECISION = 3 | |
| # Float equality precision | |
| FLOAT_EQUALITY_PRECISION = 5 | |
| # Converts a float (f) to a string with some precision of decimal places | |
| # For example: | |
| # Input: f=0.1234, precision=3 | |
| # Output: "0.123" | |
| def float_to_string(f, precision=XYZ_VALUE_PRECISION): | |
| f = float(f) | |
| str_format = "{value:." + str(precision) +"f}" | |
| return str_format.format(value=f) | |
| # Helper to convert the E value to the proper precision | |
| def e_value_to_string(e): | |
| return float_to_string(e, E_VALUE_PRECISION) | |
| # Helper to convert the XYZ value to the proper precision | |
| def xyz_value_to_string(e): | |
| return float_to_string(e, XYZ_VALUE_PRECISION) | |
| ######################################################################### | |
| # Helper function to compare floats in grasshopper/python due to float precision errors | |
| def are_floats_equal(f1, f2, epsilon=10**(-FLOAT_EQUALITY_PRECISION)): | |
| f1 *= 10**FLOAT_EQUALITY_PRECISION | |
| f2 *= 10**FLOAT_EQUALITY_PRECISION | |
| return math.fabs(f2 - f1) <= epsilon | |
| # Helper function to compare if two points are equal (have the same coordinates) | |
| # by handling float precision comparisons | |
| def is_same_pt(ptA, ptB): | |
| return are_floats_equal(ptA[0], ptB[0]) and are_floats_equal(ptA[1], ptB[1]) and are_floats_equal(ptA[2], ptB[2]) | |
| ######################################################################## | |
| # creates a string consisting of a G1 move command and | |
| # any associated parameters | |
| def gcode_move(current_pos, next_pos, feed_rate=None, should_extrude=False): | |
| # Start with "G1" as command | |
| move_command_str = COMMAND_MOVE | |
| # Compare X positions | |
| if (not are_floats_equal(current_pos[0],next_pos[0])): | |
| # we have a different x position so add it as a parameter to the command | |
| x_value = float_to_string(next_pos[0], precision=XYZ_VALUE_PRECISION) | |
| # Add X<x_value> to move string, e.g., X100.00 | |
| move_command_str += " " + PARAM_X + x_value | |
| # Compare Y positions | |
| if (not are_floats_equal(current_pos[1], next_pos[1])): | |
| # we have a different y position so add the new position as a parameter | |
| y_value = float_to_string(next_pos[1], precision=XYZ_VALUE_PRECISION) | |
| # Add Y<y_value> to move string, e.g., Y100.00 | |
| move_command_str += " " + PARAM_Y + y_value | |
| # Compare Z position | |
| if (not are_floats_equal(current_pos[2], next_pos[2])): | |
| # we have a different z position so add the new position as a parameter | |
| z_value = float_to_string(next_pos[2], precision=XYZ_VALUE_PRECISION) | |
| # Add Z<z_value> to move string, e.g., Z100.00 | |
| move_command_str += " " + PARAM_Z + z_value | |
| # [TODO]: handle "should_extrude" == true by determining the proper amount to | |
| # extrude using the capsule model, then append the E parameter and value | |
| # to the move command. | |
| # NOTE: YOUR FLOAT PRECISION MUST MATCH E_VALUE_PRECISION | |
| if should_extrude: | |
| dx = float(next_pos[0]) - float(current_pos[0]) | |
| dy = float(next_pos[1]) - float(current_pos[1]) | |
| dz = float(next_pos[2]) - float(current_pos[2]) | |
| dist = math.sqrt(dx*dx + dy*dy + dz*dz) | |
| w = max(float(extrusion_width), float(layer_height)) | |
| h = float(layer_height) | |
| A_capsule = h * (w - h) + (math.pi / 4.0) * (h ** 2) | |
| V_out = A_capsule * dist # total extruded volume in mm³ | |
| r_fil = float(filament_diameter) * 0.5 | |
| A_fil = math.pi * r_fil * r_fil | |
| if A_fil > 0 and V_out > 0: | |
| E_value = V_out / A_fil | |
| move_command_str += " " + PARAM_E + e_value_to_string(E_value) | |
| # See if we have a feedrate to use, and handle it differently than other | |
| # parameters as it is an integer value | |
| if (feed_rate is not None): | |
| # feed rate is an int | |
| feed_rate_value = round(feed_rate) | |
| # Add F<feed_rate_value> to move string, e.g., F2000 | |
| move_command_str += " " + PARAM_F + str(feed_rate_value) | |
| # Remove excess whitespace on ends | |
| move_command_str = move_command_str.strip(" ") | |
| return move_command_str | |
| ############################################ | |
| ############################################ | |
| ############################################ | |
| ''' Here's the main method of the script that uses the helper methods above ''' | |
| def generate_gcode(): | |
| # [TODO]: Implement the algorithm to generate gcode for each layer by | |
| # first to moveing to the layer height, then moving to each line segment. | |
| # Once at a line segment, you should move and extrude along it, | |
| # then move (travel) to the next line until there are no lines left | |
| # For each of these movements, you should append the command to | |
| # the list: `all_move_commands` | |
| current_position = [0, 0, 0] # start extruder at the origin | |
| all_move_commands = [] # list to hold for all the move commands | |
| for i in range(0, len(lines)): | |
| # Get pts of the line segment | |
| line = lines[i] | |
| line_start_position = line.From | |
| line_end_position = line.To | |
| # [TODO]: Handle moving to the next layer (Z Position) | |
| # NOTE- ALL Z MOVEMENTS SHOULD: | |
| # 1) BE INDEPENDENT MOVES(e.g., G1 Z# and not move with other positions like XYE) | |
| # 2) USE THE `layer_change_feedrate` | |
| # 3) BE IN INCREASING ORDER | |
| if not are_floats_equal(current_position[2], line_start_position[2]): | |
| # Z-only move: keep current X,Y; change only Z to the line's start Z | |
| z_only_target = [current_position[0], current_position[1], float(line_start_position[2])] | |
| z_move_cmd = gcode_move(current_position, z_only_target, feed_rate=layer_change_feed_rate, should_extrude=False) | |
| all_move_commands.append(z_move_cmd) | |
| current_position = z_only_target | |
| # Now if our current_position is not the start of our line segment | |
| # we need to move (travel) to the line segment's starting point | |
| if not is_same_pt(current_position, line_start_position): | |
| # [Move to the the line_start_position | |
| move_to_line_start_command = gcode_move(current_position, line_start_position, feed_rate=travel_feed_rate) | |
| # A ppend command | |
| all_move_commands.append(move_to_line_start_command) | |
| # [TODO]: Once our extruder is at the start of the line, create a | |
| # command to move AND extrude along | |
| # the line segment using `extrusion_feed_rate` | |
| extrude_move_command = gcode_move(line_start_position, line_end_position, feed_rate=extrusion_feed_rate, should_extrude=True) | |
| # [TODO]: Append the move command across the line segment | |
| all_move_commands.append(extrude_move_command) | |
| # [TODO]: Update the current position of our extruder to be at the end of the line | |
| current_position = [float(line_end_position[0]), float(line_end_position[1]), float(line_end_position[2])] | |
| # End of for-loop above -- now create the full set of commands | |
| # [TODO]: Once you have all of the move commands stored as a list in | |
| # `all_move_commands`, combine the `start_gcode_lines`, `all_move_commands`, and `end_gcode_lines` | |
| # into one list called `gcode_lines` | |
| gcode_lines = [] | |
| try: | |
| gcode_lines.extend(start_gcode_lines) | |
| except NameError: | |
| pass | |
| gcode_lines.extend(all_move_commands) | |
| try: | |
| gcode_lines.extend(end_gcode_lines) | |
| except NameError: | |
| pass | |
| # --- DO NOT EDIT BELOW ---- | |
| # The takes the combined gcode_lines list and creates a string containing each line | |
| # separated by a COMMAND_DELIMITER (the new-line character), and sets it | |
| # to the `gcode_output`variable of this component | |
| output = COMMAND_DELIMITER.join(gcode_lines) | |
| return output | |
| ''' RUN THE MAIN FUNCITON ABOVE - DO NOT EDIT ''' | |
| # this sets the gcode commands to be the the `gcode_output` variable of this grasshopper component | |
| gcode_output = generate_gcode() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment