From 97693efde581dd7303bb4d4425a3c5a115b6d852 Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 13:59:24 +0200 Subject: [PATCH 1/7] Added Command Line parsing and corrected errors when referencing variables --- .gitignore | 2 ++ Transformation_STL.py | 40 +++++++++++++++++++++++++--------------- 2 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9ea7969 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.DS_Store +*.stl diff --git a/Transformation_STL.py b/Transformation_STL.py index 6a87589..d09cc97 100644 --- a/Transformation_STL.py +++ b/Transformation_STL.py @@ -1,8 +1,9 @@ import numpy as np from stl import mesh import time +import sys import os - +import argparse def refinement_one_triangle(triangle): """ @@ -95,8 +96,8 @@ def transformation_STL_file(path, output_dir, cone_type, nb_iterations): if not os.path.exists(output_dir): os.mkdir(output_dir) - file_name = file_path[file_path.rfind('/'):] - file_name = file_name.replace('.stl', '_' + transformation_type + '_transformed.stl') + file_name = path[path.rfind('/'):] + file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') output_path = output_dir + file_name my_mesh_transformed.save(output_path) end = time.time() @@ -105,18 +106,27 @@ def transformation_STL_file(path, output_dir, cone_type, nb_iterations): # ------------------------------------------------------------------------------- -# Apply the functions for a STL file +# Apply the functions to a STL file # ------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') + args = parser.parse_args() -# STL transformation function parameters -file_path = '/path/to/stl/file.stl' -dir_transformed = '/path/to/save/transformation/' -transformation_type = 'inward' # inward or outward -number_iterations = 4 # number iterations for triangulation refinement + try: + # STL transformation function call + transformation_STL_file(path=args.file_path, + output_dir=args.dir_transformed, + cone_type=args.transformation_type, + nb_iterations=args.number_iterations, + ) -# STL transformation function call -transformation_STL_file(path=file_path, - output_dir=dir_transformed, - cone_type=transformation_type, - nb_iterations=number_iterations, - ) + except KeyboardInterrupt: + print("Done.") + try: + sys.exit(0) + except SystemExit: + os._exit(0) \ No newline at end of file From 7108ac1aca03e9adfbb770fc3ece952ac19352f2 Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 14:26:42 +0200 Subject: [PATCH 2/7] Added Command Line parsing to Backtransformation_GCode.py --- .gitignore | 1 + Backtransformation_GCode.py | 1189 ++++++++++++++++++----------------- Transformation_STL.py | 263 ++++---- 3 files changed, 733 insertions(+), 720 deletions(-) diff --git a/.gitignore b/.gitignore index 9ea7969..529f5fd 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store *.stl +*.gcode diff --git a/Backtransformation_GCode.py b/Backtransformation_GCode.py index d125221..a111175 100644 --- a/Backtransformation_GCode.py +++ b/Backtransformation_GCode.py @@ -1,589 +1,600 @@ -import re -import numpy as np -import os -import time - - -def insert_Z(row, z_value): - """ - Insert or replace the z-value in a row. The new z-value must be given. - :param row: string - String containing the row, in which a z-value has to be inserted or replaced - :param z_value: float - New z-value, which should be inserted - :return: string - New string, containing the row with replaced z-value - """ - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - match_x = re.search(pattern_X, row) - match_y = re.search(pattern_Y, row) - match_z = re.search(pattern_Z, row) - - if match_z is not None: - row_new = re.sub(pattern_Z, ' Z' + str(round(z_value, 3)), row) - else: - if match_y is not None: - row_new = row[0:match_y.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_y.end(0):] - elif match_x is not None: - row_new = row[0:match_x.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_x.end(0):] - else: - row_new = 'Z' + str(round(z_value, 3)) + ' ' + row - return row_new - - -def replace_E(row, dist_old, dist_new, corr_value): - """ - Replace the amount of extruded filament in a row. The new amount is proportional to the old amount, where - the factor is obtained by the ratio of new distance to old distance. (Due to the transformation, the amount has to - be divided by sqrt(2). replace_E is accessed 2 times.) - :param row: string - String containing the row, of which the extruder value should be replaced - :param dist_old: float - Length of the distance before backtransformation - :param dist_new: float - Length of the distance after backtransformation - :param corr_value: float - additional correction value due to transformation # added to have additional possiblity to correct amount of - extruded material - :return: string - New string, containing the row with replaced extruder value - """ - pattern_E = r'E[-0-9]+[.]?[0-9]*' - match_e = re.search(pattern_E, row) - if match_e is None: - return row - e_val_old = float(match_e.group(0).replace('E', '')) - if dist_old == 0: - e_val_new = 0 - else: - e_val_new = round(e_val_old * dist_new * corr_value / dist_old, 6) - e_str_new = 'E' + str(e_val_new) - row_new = row[0:match_e.start(0)] + e_str_new + row[match_e.end(0):] - return row_new - - -def compute_angle_radial(x_new, y_new, inward_cone): - """ - Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. - (Note: the z-value is not considered for the orientation of the printing head.) The direction is given by the - direction of the new point by the arctan2 value according to the coordinates. - :param x_new: float - x-coordinate of the new point - :param y_new: float - y-coordinate of the new point - :param inward_cone: bool - Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to - the angle. - :return: float - Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. - """ - angle = np.arctan2(y_new, x_new) - if inward_cone: - angle = angle + np.pi - return angle - - -def compute_angle_tangential(x_old, y_old, x_new, y_new, inward_cone): - """ - Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. - (Note: the z-value is not considered for the orientation of the printing head.) The direction is normal to the - movement of direction, such that the printing head will point to the origin. - x-coordinate of the old point - :param x_old: float - x-coordinate of the old point - :param y_old: float - y-coordinate of the old point - :param x_new: float - x-coordinate of the new point - :param y_new: float - y-coordinate of the new point - :param inward_cone: bool - Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to - the angle. - :return: float - Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. - """ - direction_normal = np.array([-(y_new - y_old), x_new - x_old]) - len_normal = np.linalg.norm(direction_normal) - direction_point = np.array([x_new, y_new]) - len_point = np.linalg.norm(direction_point) - if len_normal * len_point == 0: - angle = np.arctan2(y_new, x_new) - else: - inner_prod = np.dot(direction_normal / len_normal, direction_point / len_point) - if np.isclose(inner_prod, 0, atol=0.01): - angle = np.arctan2(direction_normal[1], direction_normal[0]) - else: - printhead_direction = inner_prod * len_point / len_normal * direction_normal - angle = np.arctan2(printhead_direction[1], printhead_direction[0]) - - if inward_cone: - angle = angle + np.pi - - return angle - - -def compute_U_values(angle_array): - """ - Compute the U-values, which will be inserted, according to given angle values. The U-values are computed such that - there are no changes larger than 180. The range of the U-values is [-3600-180, 3600+180]. - :param angle_array: array - Array, which contains the angle values in radian - :return array - Array, which contains U-values in degrees - """ - angle_candidates = np.around(np.array([angle_array + k * 2 * np.pi for k in range(-10, 11)]).T, 4) - angle_insert = [angle_array[0]] - for i in range(1, len(angle_array)): - angle_prev = angle_insert[i - 1] - idx = np.argmin(np.absolute(angle_candidates[i] - angle_prev)) - angle_insert.append(angle_candidates[i, idx]) - - angle_insert = np.round(np.array(angle_insert) * 360 / (2 * np.pi), 2) - - return angle_insert - - -def insert_U(row, angle): - """ - Insert or replace the U-value in a row, where the U-values describes the orientation of the printing head. - :param row: string - String containing the row, in which a U-value has to be inserted or replaced - :param angle: float - Value of the angle, which is inserted or replaces the old U-value - :return: string - New string, containing the row with replaced U-value - """ - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - match_z = re.search(pattern_Z, row) - pattern_U = r'U[-0-9]+[.]?[0-9]*' - match_u = re.search(pattern_U, row) - - if match_u is None: - row_new = row[0:match_z.end(0)] + ' U' + str(angle) + row[match_z.end(0):] - else: - row_new = re.sub(pattern_U, 'U' + str(angle), row) - - return row_new - - -def backtransform_data_radial(data, cone_type, maximal_length): - """ - Backtransform G-Code, which is given in a list, each element describing a row. Rows which describe a movement - are detected, x-, y-, z-, E- and U-values are replaced accordingly to the transformation. If a original segment - is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed - using the function compute_angle_radial. (Added, that while travel moves, nozzle only rises 1 mm above highest - printed point and not along cone.) - :param data: list - List of strings, describing each line of the GCode, which is to be backtransformed - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param maximal_length: float - Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting - segments are shorter than maximal_length - :return: list - List of strings, which describe the new GCode. - """ - new_data = [] - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - pattern_E = r'E[-0-9]+[.]?[0-9]*' - pattern_G = r'\AG[01] ' - - x_old, y_old = 0, 0 - x_new, y_new = 0, 0 - z_layer = 0 - angle_old = 0 - z_max = 0 - update_x, update_y = False, False - if cone_type == 'outward': - c = -1 - inward_cone = False - elif cone_type == 'inward': - c = 1 - inward_cone = True - else: - raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) - - for row in data: - - g_match = re.search(pattern_G, row) - if g_match is None: - new_data.append(row) - - else: - x_match = re.search(pattern_X, row) - y_match = re.search(pattern_Y, row) - z_match = re.search(pattern_Z, row) - if x_match is None and y_match is None and z_match is None: - new_data.append(row) - - else: - if z_match is not None: - z_layer = float(z_match.group(0).replace('Z', '')) - if x_match is not None: - x_new = float(x_match.group(0).replace('X', '')) - update_x = True - if y_match is not None: - y_new = float(y_match.group(0).replace('Y', '')) - update_y = True - - # Compute new distance and angle according to new row - e_match = re.search(pattern_E, row) - x_old_bt, x_new_bt = x_old / np.sqrt(2), x_new / np.sqrt(2) - y_old_bt, y_new_bt = y_old / np.sqrt(2), y_new / np.sqrt(2) - dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) - - # Compute new values for backtransformation of row - num_segm = int(dist_transformed // maximal_length + 1) - x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) - y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) - if inward_cone and e_match is None and (update_x or update_y): - z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) - z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) - z_vals = np.linspace(z_start, z_end, num_segm + 1) - else: - z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) for x, y in zip(x_vals, y_vals)]) - if e_match and (np.max(z_vals) > z_max or z_max == 0): - z_max = np.max(z_vals) # save hightes point with material extruded - if e_match is None and np.max(z_vals) > z_max: - np.minimum(z_vals, (z_max + 1), - z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety - # das hier könnte noch verschönert werden, in dem dann eine alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden - - angle_new = compute_angle_radial(x_old_bt, y_old_bt, inward_cone) - - angle_vals = np.array( - [angle_old] + [compute_angle_radial(x_vals[k], y_vals[k], inward_cone) - for k in range(0, num_segm)]) - u_vals = compute_U_values(angle_vals) - distances_transformed = dist_transformed / num_segm * np.ones(num_segm) - distances_bt = np.array( - [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) - for i in range(1, num_segm + 1)]) - - # Replace new row with num_seg new rows for movements and possible command rows for the U value - row = insert_Z(row, z_vals[0]) - row = replace_E(row, num_segm, 1, 1 / np.sqrt(2)) - replacement_rows = '' - for j in range(0, num_segm): - single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) - single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) - single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) - single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) - if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: - single_row = insert_U(single_row, u_vals[j + 1]) - else: - single_row = 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' + single_row - replacement_rows = replacement_rows + single_row - if np.amax(np.absolute(u_vals)) > 3600: - angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) - replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' - angle_old = angle_new - else: - angle_old = u_vals[-1] * 2 * np.pi / 360 - row = replacement_rows - - if update_x: - x_old = x_new - update_x = False - if update_y: - y_old = y_new - update_y = False - new_data.append(row) - - return new_data - - -def backtransform_data_tangential(data, cone_type, maximal_length): - """ - Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement - are detected, x-, y-, z-, e- and U-values are replaced accordingly to the transformation. If a original segment - is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed - using the function compute_angle_tangential. - :param data: list - List of strings, describing each line of the GCode, which is to be backtransformed - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param maximal_length: float - Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting - segments are shorter than maximal_length - :return: list - List of strings, which describe the new GCode. - """ - new_data = [] - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - pattern_E = r'E[-0-9]+[.]?[0-9]*' - pattern_G = r'\AG[01] ' - - x_old, y_old = 0, 0 - x_new, y_new = 0, 0 - z_layer = 0 - angle_old = 0 - z_max = 0 - - update_x, update_y = False, False - if cone_type == 'outward': - c = -1 - inward_cone = False - elif cone_type == 'inward': - c = 1 - inward_cone = True - else: - raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) - - for row in data: - - g_match = re.search(pattern_G, row) - if g_match is None: - new_data.append(row) - - else: - x_match = re.search(pattern_X, row) - y_match = re.search(pattern_Y, row) - z_match = re.search(pattern_Z, row) - - if x_match is None and y_match is None and z_match is None: - new_data.append(row) - - else: - if z_match is not None: - z_layer = float(z_match.group(0).replace('Z', '')) - if x_match is not None: - x_new = float(x_match.group(0).replace('X', '')) - update_x = True - if y_match is not None: - y_new = float(y_match.group(0).replace('Y', '')) - update_y = True - - # Compute new values according to new row - e_match = re.search(pattern_E, row) - x_old_bt, y_old_bt = x_old / np.sqrt(2), y_old / np.sqrt(2) - x_new_bt, y_new_bt = x_new / np.sqrt(2), y_new / np.sqrt(2) - dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) - if update_x or update_y: - angle_new = compute_angle_tangential(x_old_bt, y_old_bt, x_new_bt, y_new_bt, inward_cone) - else: - angle_new = angle_old - - # Compute new values for backtransformation of row - num_segm = int(dist_transformed // maximal_length + 1) - x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) - y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) - if inward_cone and e_match is None and (update_x or update_y): - z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) - z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) - z_vals = np.linspace(z_start, z_end, num_segm + 1) - else: - z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) for x, y in zip(x_vals, y_vals)]) - if e_match and (np.max(z_vals) > z_max or z_max == 0): - z_max = np.max(z_vals) # save hightes point with material extruded - if e_match is None and np.max(z_vals) > z_max: - np.minimum(z_vals, (z_max + 1), - z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety - # das hier könnte noch verschönert werden, in dem dann alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden - angle_vals = np.array([angle_old] + [angle_new for k in range(0, num_segm)]) - u_vals = compute_U_values(angle_vals) - distances_transformed = dist_transformed / num_segm * np.ones(num_segm) - distances_bt = np.array( - [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) - for i in range(1, num_segm + 1)]) - - # Replace new row with num_seg new rows for movements and possible command rows for the U value - row = insert_Z(row, z_vals[0]) - row = replace_E(row, num_segm, 1, 1 / np.sqrt(2)) - replacement_rows = '' - for j in range(0, num_segm): - single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) - single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) - single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) - single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) - if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: - single_row = insert_U(single_row, u_vals[j + 1]) - else: - single_row = single_row + 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' - replacement_rows = replacement_rows + single_row - if np.amax(np.absolute(u_vals)) > 3600: - angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) - replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' - angle_old = angle_new - else: - angle_old = u_vals[-1] * 2 * np.pi / 360 - - row = replacement_rows - - if update_x: - x_old = x_new - update_x = False - if update_y: - y_old = y_new - update_y = False - new_data.append(row) - - return new_data - - -def translate_data(data, translate_x, translate_y, z_desired, e_parallel, e_perpendicular): - """ - Translate the GCode in x- and y-direction. Only the lines, which describe a movement will be translated. - Additionally, if z_translation is True, the z-values will be translated such that the minimal z-value is z_desired. - This happens by traversing the list of strings twice. If cone_type is 'inward', it is assured, that all moves - with no extrusion have at least a height of z_desired. - :param data: list - List of strings, containing the GCode - :param translate_x: float - Float, which describes the translation in x-direction - :param translate_y: float - Float, which describes the translation in y-direction - :param z_desired: float - Desired minimal z-value - :param e_parallel: float - Correction of extrusion error parallel to nozzle - :param e_perpendicular: float - Correction of extrusion error perpendicular to nozzle - :return: list - List of strings, which contains the translated GCode - """ - new_data = [] - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - pattern_E = r'E[-0-9]+[.]?[0-9]*' - pattern_U = r'U[-0-9]+[.]?[0-9]*' - pattern_G = r'\AG[01] ' - z_initialized = False - u_val = 0.0 - - for row in data: - g_match = re.search(pattern_G, row) - z_match = re.search(pattern_Z, row) - e_match = re.search(pattern_E, row) - if g_match is not None and z_match is not None and e_match is not None: - z_val = float(z_match.group(0).replace('Z', '')) - if not z_initialized: - z_min = z_val - z_initialized = True - if z_val < z_min: - z_min = z_val - z_translate = z_desired - z_min - - for row in data: - - x_match = re.search(pattern_X, row) - y_match = re.search(pattern_Y, row) - z_match = re.search(pattern_Z, row) - g_match = re.search(pattern_G, row) - u_match = re.search(pattern_U, row) - - if u_match is not None: - u_val = np.radians(float(u_match.group(0).replace('U', ''))) - - if g_match is None: - new_data.append(row) - - else: - if x_match is not None: - x_val = round(float(x_match.group(0).replace('X', '')) + translate_x - (e_parallel * np.cos(u_val)) + ( - e_perpendicular * np.sin(u_val)), 3) # added correction for misalignment of nozzle - row = re.sub(pattern_X, 'X' + str(x_val), row) - if y_match is not None: - y_val = round(float(y_match.group(0).replace('Y', '')) + translate_y - (e_parallel * np.sin(u_val)) - ( - e_perpendicular * np.cos(u_val)), 3) # added correction for misalignment of nozzle - row = re.sub(pattern_Y, 'Y' + str(y_val), row) - if z_match is not None: - z_val = max(round(float(z_match.group(0).replace('Z', '')) + z_translate, 3), z_desired) - row = re.sub(pattern_Z, 'Z' + str(z_val), row) - - new_data.append(row) - - return new_data - - -def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, x_shift, y_shift, z_desired, e_parallel, - e_perpendicular): - """ - Read GCode from file, backtransform, translate it and save backtransformed G-Code. - :param path: string - String with the path to the GCode-file - :param output_dir: string - path of directory, where transformed STL-file will be saved - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param maximal_length: float - Maximal length of a segment in the original GCode - :param angle_comp: string - String, which describes the way, the angle is computed; one of 'radial' or 'tangential' - :param x_shift: float - Float, which describes the translation in x-direction - :param y_shift: float - Float, which describes the translation in y-direction - :param z_desired: float - Desired minimal z-value - :param e_perpendicular: float - Correction of extrusion error parallel to nozzle - :param e_parallel: float - Correction of extrusion error perpendicular to nozzle - :return: None - """ - start = time.time() - if angle_comp == 'radial': - backtransform_data = backtransform_data_radial - elif angle_comp == 'tangential': - backtransform_data = backtransform_data_tangential - else: - raise ValueError('{} is not a admissible type for the angle computation'.format(angle_comp)) - - with open(path, 'r') as f_gcode: - data = f_gcode.readlines() - data_bt = backtransform_data(data, cone_type, maximal_length) - data_bt_string = ''.join(data_bt) - data_bt = [row + ' \n' for row in data_bt_string.split('\n')] - data_bt = translate_data(data_bt, x_shift, y_shift, z_desired, e_parallel, e_perpendicular) - data_bt_string = ''.join(data_bt) - - if not os.path.exists(output_dir): - os.mkdir(output_dir) - file_name = path[path.rfind('/'):] - file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') - output_path = output_dir + file_name - with open(output_path, 'w+') as f_gcode_bt: - f_gcode_bt.write(data_bt_string) - - end = time.time() - print('GCode generated in {:.1f}s, saved in {}'.format(end - start, output_path)) - return None - - -# ------------------------------------------------------------------------------- -# Apply the functions for a G-Code file -# ------------------------------------------------------------------------------- - -# G-Code backtransformation function parameters -file_path = '/path/to/gcode/file.gcode' -dir_backtransformed = '/path/to/save/backtransformation/' -transformation_type = 'inward' # inward or outward -angle_type = 'radial' # radial or tangential -max_length = 5 # maximal length of a segment in mm -delta_x = 0 # shift of code in x-direction -delta_y = 0 # shift of code in y-direction -z_height = 0.1 # desired height in z-direction -err_parallel = 0.25 # error in parallel direction -err_perpendicular = 0.65 # error in perpendicular direction - -# G-Code backtransformation function call -backtransform_file(path=file_path, - output_dir=dir_backtransformed, - cone_type=transformation_type, - maximal_length=max_length, - angle_comp=angle_type, - x_shift=delta_x, - y_shift=delta_y, - z_desired=z_height, - e_parallel=err_parallel, - e_perpendicular=err_perpendicular - ) +import re +import numpy as np +import sys +import os +import time +import argparse + +def insert_Z(row, z_value): + """ + Insert or replace the z-value in a row. The new z-value must be given. + :param row: string + String containing the row, in which a z-value has to be inserted or replaced + :param z_value: float + New z-value, which should be inserted + :return: string + New string, containing the row with replaced z-value + """ + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + match_x = re.search(pattern_X, row) + match_y = re.search(pattern_Y, row) + match_z = re.search(pattern_Z, row) + + if match_z is not None: + row_new = re.sub(pattern_Z, ' Z' + str(round(z_value, 3)), row) + else: + if match_y is not None: + row_new = row[0:match_y.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_y.end(0):] + elif match_x is not None: + row_new = row[0:match_x.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_x.end(0):] + else: + row_new = 'Z' + str(round(z_value, 3)) + ' ' + row + return row_new + + +def replace_E(row, dist_old, dist_new, corr_value): + """ + Replace the amount of extruded filament in a row. The new amount is proportional to the old amount, where + the factor is obtained by the ratio of new distance to old distance. (Due to the transformation, the amount has to + be divided by sqrt(2). replace_E is accessed 2 times.) + :param row: string + String containing the row, of which the extruder value should be replaced + :param dist_old: float + Length of the distance before backtransformation + :param dist_new: float + Length of the distance after backtransformation + :param corr_value: float + additional correction value due to transformation # added to have additional possiblity to correct amount of + extruded material + :return: string + New string, containing the row with replaced extruder value + """ + pattern_E = r'E[-0-9]+[.]?[0-9]*' + match_e = re.search(pattern_E, row) + if match_e is None: + return row + e_val_old = float(match_e.group(0).replace('E', '')) + if dist_old == 0: + e_val_new = 0 + else: + e_val_new = round(e_val_old * dist_new * corr_value / dist_old, 6) + e_str_new = 'E' + str(e_val_new) + row_new = row[0:match_e.start(0)] + e_str_new + row[match_e.end(0):] + return row_new + + +def compute_angle_radial(x_new, y_new, inward_cone): + """ + Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. + (Note: the z-value is not considered for the orientation of the printing head.) The direction is given by the + direction of the new point by the arctan2 value according to the coordinates. + :param x_new: float + x-coordinate of the new point + :param y_new: float + y-coordinate of the new point + :param inward_cone: bool + Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to + the angle. + :return: float + Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. + """ + angle = np.arctan2(y_new, x_new) + if inward_cone: + angle = angle + np.pi + return angle + + +def compute_angle_tangential(x_old, y_old, x_new, y_new, inward_cone): + """ + Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. + (Note: the z-value is not considered for the orientation of the printing head.) The direction is normal to the + movement of direction, such that the printing head will point to the origin. + x-coordinate of the old point + :param x_old: float + x-coordinate of the old point + :param y_old: float + y-coordinate of the old point + :param x_new: float + x-coordinate of the new point + :param y_new: float + y-coordinate of the new point + :param inward_cone: bool + Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to + the angle. + :return: float + Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. + """ + direction_normal = np.array([-(y_new - y_old), x_new - x_old]) + len_normal = np.linalg.norm(direction_normal) + direction_point = np.array([x_new, y_new]) + len_point = np.linalg.norm(direction_point) + if len_normal * len_point == 0: + angle = np.arctan2(y_new, x_new) + else: + inner_prod = np.dot(direction_normal / len_normal, direction_point / len_point) + if np.isclose(inner_prod, 0, atol=0.01): + angle = np.arctan2(direction_normal[1], direction_normal[0]) + else: + printhead_direction = inner_prod * len_point / len_normal * direction_normal + angle = np.arctan2(printhead_direction[1], printhead_direction[0]) + + if inward_cone: + angle = angle + np.pi + + return angle + + +def compute_U_values(angle_array): + """ + Compute the U-values, which will be inserted, according to given angle values. The U-values are computed such that + there are no changes larger than 180. The range of the U-values is [-3600-180, 3600+180]. + :param angle_array: array + Array, which contains the angle values in radian + :return array + Array, which contains U-values in degrees + """ + angle_candidates = np.around(np.array([angle_array + k * 2 * np.pi for k in range(-10, 11)]).T, 4) + angle_insert = [angle_array[0]] + for i in range(1, len(angle_array)): + angle_prev = angle_insert[i - 1] + idx = np.argmin(np.absolute(angle_candidates[i] - angle_prev)) + angle_insert.append(angle_candidates[i, idx]) + + angle_insert = np.round(np.array(angle_insert) * 360 / (2 * np.pi), 2) + + return angle_insert + + +def insert_U(row, angle): + """ + Insert or replace the U-value in a row, where the U-values describes the orientation of the printing head. + :param row: string + String containing the row, in which a U-value has to be inserted or replaced + :param angle: float + Value of the angle, which is inserted or replaces the old U-value + :return: string + New string, containing the row with replaced U-value + """ + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + match_z = re.search(pattern_Z, row) + pattern_U = r'U[-0-9]+[.]?[0-9]*' + match_u = re.search(pattern_U, row) + + if match_u is None: + row_new = row[0:match_z.end(0)] + ' U' + str(angle) + row[match_z.end(0):] + else: + row_new = re.sub(pattern_U, 'U' + str(angle), row) + + return row_new + + +def backtransform_data_radial(data, cone_type, maximal_length): + """ + Backtransform G-Code, which is given in a list, each element describing a row. Rows which describe a movement + are detected, x-, y-, z-, E- and U-values are replaced accordingly to the transformation. If a original segment + is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed + using the function compute_angle_radial. (Added, that while travel moves, nozzle only rises 1 mm above highest + printed point and not along cone.) + :param data: list + List of strings, describing each line of the GCode, which is to be backtransformed + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param maximal_length: float + Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting + segments are shorter than maximal_length + :return: list + List of strings, which describe the new GCode. + """ + new_data = [] + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + pattern_E = r'E[-0-9]+[.]?[0-9]*' + pattern_G = r'\AG[01] ' + + x_old, y_old = 0, 0 + x_new, y_new = 0, 0 + z_layer = 0 + angle_old = 0 + z_max = 0 + update_x, update_y = False, False + if cone_type == 'outward': + c = -1 + inward_cone = False + elif cone_type == 'inward': + c = 1 + inward_cone = True + else: + raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) + + for row in data: + + g_match = re.search(pattern_G, row) + if g_match is None: + new_data.append(row) + + else: + x_match = re.search(pattern_X, row) + y_match = re.search(pattern_Y, row) + z_match = re.search(pattern_Z, row) + if x_match is None and y_match is None and z_match is None: + new_data.append(row) + + else: + if z_match is not None: + z_layer = float(z_match.group(0).replace('Z', '')) + if x_match is not None: + x_new = float(x_match.group(0).replace('X', '')) + update_x = True + if y_match is not None: + y_new = float(y_match.group(0).replace('Y', '')) + update_y = True + + # Compute new distance and angle according to new row + e_match = re.search(pattern_E, row) + x_old_bt, x_new_bt = x_old / np.sqrt(2), x_new / np.sqrt(2) + y_old_bt, y_new_bt = y_old / np.sqrt(2), y_new / np.sqrt(2) + dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) + + # Compute new values for backtransformation of row + num_segm = int(dist_transformed // maximal_length + 1) + x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) + y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) + if inward_cone and e_match is None and (update_x or update_y): + z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) + z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) + z_vals = np.linspace(z_start, z_end, num_segm + 1) + else: + z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) for x, y in zip(x_vals, y_vals)]) + if e_match and (np.max(z_vals) > z_max or z_max == 0): + z_max = np.max(z_vals) # save hightes point with material extruded + if e_match is None and np.max(z_vals) > z_max: + np.minimum(z_vals, (z_max + 1), + z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety + # das hier könnte noch verschönert werden, in dem dann eine alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden + + angle_new = compute_angle_radial(x_old_bt, y_old_bt, inward_cone) + + angle_vals = np.array( + [angle_old] + [compute_angle_radial(x_vals[k], y_vals[k], inward_cone) + for k in range(0, num_segm)]) + u_vals = compute_U_values(angle_vals) + distances_transformed = dist_transformed / num_segm * np.ones(num_segm) + distances_bt = np.array( + [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) + for i in range(1, num_segm + 1)]) + + # Replace new row with num_seg new rows for movements and possible command rows for the U value + row = insert_Z(row, z_vals[0]) + row = replace_E(row, num_segm, 1, 1 / np.sqrt(2)) + replacement_rows = '' + for j in range(0, num_segm): + single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) + single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) + single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) + single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) + if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: + single_row = insert_U(single_row, u_vals[j + 1]) + else: + single_row = 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' + single_row + replacement_rows = replacement_rows + single_row + if np.amax(np.absolute(u_vals)) > 3600: + angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) + replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' + angle_old = angle_new + else: + angle_old = u_vals[-1] * 2 * np.pi / 360 + row = replacement_rows + + if update_x: + x_old = x_new + update_x = False + if update_y: + y_old = y_new + update_y = False + new_data.append(row) + + return new_data + + +def backtransform_data_tangential(data, cone_type, maximal_length): + """ + Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement + are detected, x-, y-, z-, e- and U-values are replaced accordingly to the transformation. If a original segment + is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed + using the function compute_angle_tangential. + :param data: list + List of strings, describing each line of the GCode, which is to be backtransformed + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param maximal_length: float + Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting + segments are shorter than maximal_length + :return: list + List of strings, which describe the new GCode. + """ + new_data = [] + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + pattern_E = r'E[-0-9]+[.]?[0-9]*' + pattern_G = r'\AG[01] ' + + x_old, y_old = 0, 0 + x_new, y_new = 0, 0 + z_layer = 0 + angle_old = 0 + z_max = 0 + + update_x, update_y = False, False + if cone_type == 'outward': + c = -1 + inward_cone = False + elif cone_type == 'inward': + c = 1 + inward_cone = True + else: + raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) + + for row in data: + + g_match = re.search(pattern_G, row) + if g_match is None: + new_data.append(row) + + else: + x_match = re.search(pattern_X, row) + y_match = re.search(pattern_Y, row) + z_match = re.search(pattern_Z, row) + + if x_match is None and y_match is None and z_match is None: + new_data.append(row) + + else: + if z_match is not None: + z_layer = float(z_match.group(0).replace('Z', '')) + if x_match is not None: + x_new = float(x_match.group(0).replace('X', '')) + update_x = True + if y_match is not None: + y_new = float(y_match.group(0).replace('Y', '')) + update_y = True + + # Compute new values according to new row + e_match = re.search(pattern_E, row) + x_old_bt, y_old_bt = x_old / np.sqrt(2), y_old / np.sqrt(2) + x_new_bt, y_new_bt = x_new / np.sqrt(2), y_new / np.sqrt(2) + dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) + if update_x or update_y: + angle_new = compute_angle_tangential(x_old_bt, y_old_bt, x_new_bt, y_new_bt, inward_cone) + else: + angle_new = angle_old + + # Compute new values for backtransformation of row + num_segm = int(dist_transformed // maximal_length + 1) + x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) + y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) + if inward_cone and e_match is None and (update_x or update_y): + z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) + z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) + z_vals = np.linspace(z_start, z_end, num_segm + 1) + else: + z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) for x, y in zip(x_vals, y_vals)]) + if e_match and (np.max(z_vals) > z_max or z_max == 0): + z_max = np.max(z_vals) # save hightes point with material extruded + if e_match is None and np.max(z_vals) > z_max: + np.minimum(z_vals, (z_max + 1), + z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety + # das hier könnte noch verschönert werden, in dem dann alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden + angle_vals = np.array([angle_old] + [angle_new for k in range(0, num_segm)]) + u_vals = compute_U_values(angle_vals) + distances_transformed = dist_transformed / num_segm * np.ones(num_segm) + distances_bt = np.array( + [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) + for i in range(1, num_segm + 1)]) + + # Replace new row with num_seg new rows for movements and possible command rows for the U value + row = insert_Z(row, z_vals[0]) + row = replace_E(row, num_segm, 1, 1 / np.sqrt(2)) + replacement_rows = '' + for j in range(0, num_segm): + single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) + single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) + single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) + single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) + if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: + single_row = insert_U(single_row, u_vals[j + 1]) + else: + single_row = single_row + 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' + replacement_rows = replacement_rows + single_row + if np.amax(np.absolute(u_vals)) > 3600: + angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) + replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' + angle_old = angle_new + else: + angle_old = u_vals[-1] * 2 * np.pi / 360 + + row = replacement_rows + + if update_x: + x_old = x_new + update_x = False + if update_y: + y_old = y_new + update_y = False + new_data.append(row) + + return new_data + + +def translate_data(data, translate_x, translate_y, z_desired, e_parallel, e_perpendicular): + """ + Translate the GCode in x- and y-direction. Only the lines, which describe a movement will be translated. + Additionally, if z_translation is True, the z-values will be translated such that the minimal z-value is z_desired. + This happens by traversing the list of strings twice. If cone_type is 'inward', it is assured, that all moves + with no extrusion have at least a height of z_desired. + :param data: list + List of strings, containing the GCode + :param translate_x: float + Float, which describes the translation in x-direction + :param translate_y: float + Float, which describes the translation in y-direction + :param z_desired: float + Desired minimal z-value + :param e_parallel: float + Correction of extrusion error parallel to nozzle + :param e_perpendicular: float + Correction of extrusion error perpendicular to nozzle + :return: list + List of strings, which contains the translated GCode + """ + new_data = [] + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + pattern_E = r'E[-0-9]+[.]?[0-9]*' + pattern_U = r'U[-0-9]+[.]?[0-9]*' + pattern_G = r'\AG[01] ' + z_initialized = False + u_val = 0.0 + + for row in data: + g_match = re.search(pattern_G, row) + z_match = re.search(pattern_Z, row) + e_match = re.search(pattern_E, row) + if g_match is not None and z_match is not None and e_match is not None: + z_val = float(z_match.group(0).replace('Z', '')) + if not z_initialized: + z_min = z_val + z_initialized = True + if z_val < z_min: + z_min = z_val + z_translate = z_desired - z_min + + for row in data: + + x_match = re.search(pattern_X, row) + y_match = re.search(pattern_Y, row) + z_match = re.search(pattern_Z, row) + g_match = re.search(pattern_G, row) + u_match = re.search(pattern_U, row) + + if u_match is not None: + u_val = np.radians(float(u_match.group(0).replace('U', ''))) + + if g_match is None: + new_data.append(row) + + else: + if x_match is not None: + x_val = round(float(x_match.group(0).replace('X', '')) + translate_x - (e_parallel * np.cos(u_val)) + ( + e_perpendicular * np.sin(u_val)), 3) # added correction for misalignment of nozzle + row = re.sub(pattern_X, 'X' + str(x_val), row) + if y_match is not None: + y_val = round(float(y_match.group(0).replace('Y', '')) + translate_y - (e_parallel * np.sin(u_val)) - ( + e_perpendicular * np.cos(u_val)), 3) # added correction for misalignment of nozzle + row = re.sub(pattern_Y, 'Y' + str(y_val), row) + if z_match is not None: + z_val = max(round(float(z_match.group(0).replace('Z', '')) + z_translate, 3), z_desired) + row = re.sub(pattern_Z, 'Z' + str(z_val), row) + + new_data.append(row) + + return new_data + + +def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, x_shift, y_shift, z_desired, e_parallel, + e_perpendicular): + """ + Read GCode from file, backtransform, translate it and save backtransformed G-Code. + :param path: string + String with the path to the GCode-file + :param output_dir: string + path of directory, where transformed STL-file will be saved + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param maximal_length: float + Maximal length of a segment in the original GCode + :param angle_comp: string + String, which describes the way, the angle is computed; one of 'radial' or 'tangential' + :param x_shift: float + Float, which describes the translation in x-direction + :param y_shift: float + Float, which describes the translation in y-direction + :param z_desired: float + Desired minimal z-value + :param e_perpendicular: float + Correction of extrusion error parallel to nozzle + :param e_parallel: float + Correction of extrusion error perpendicular to nozzle + :return: None + """ + start = time.time() + if angle_comp == 'radial': + backtransform_data = backtransform_data_radial + elif angle_comp == 'tangential': + backtransform_data = backtransform_data_tangential + else: + raise ValueError('{} is not a admissible type for the angle computation'.format(angle_comp)) + + with open(path, 'r') as f_gcode: + data = f_gcode.readlines() + data_bt = backtransform_data(data, cone_type, maximal_length) + data_bt_string = ''.join(data_bt) + data_bt = [row + ' \n' for row in data_bt_string.split('\n')] + data_bt = translate_data(data_bt, x_shift, y_shift, z_desired, e_parallel, e_perpendicular) + data_bt_string = ''.join(data_bt) + + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path[path.rfind('/'):] + file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') + print("output filename: {}".format(file_name)) + output_path = output_dir + file_name + with open(output_path, 'w+') as f_gcode_bt: + f_gcode_bt.write(data_bt_string) + + end = time.time() + print('GCode generated in {:.1f}s, saved in {}'.format(end - start, output_path)) + return None + + +# ------------------------------------------------------------------------------- +# Apply the functions for a G-Code file +# ------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='GCODE-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_backtransformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-a', '--angle-type', dest='angle_type', type=str, default='radial', help='The angle type: radial or tangential.') + parser.add_argument('-m', '--max-length', dest='max_length', type=int, default=5, help='Maximal length of a segment in mm.') + parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=0, help='Shift the code in x-direction.') + parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=0, help='Shift the code in y-direction.') + parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=0.1, help='Desired height in z-direction.') + parser.add_argument('-ep', '--err-parallel', dest='err_parallel', type=float, default=0.25, help='Error in parallel direction.') + parser.add_argument('-et', '--err-perpendicular', dest='err_perpendicular', type=float, default=0.65, help='Error in perpendicular direction.') + args = parser.parse_args() + + try: + # G-Code backtransformation function call + backtransform_file(path=args.file_path, + output_dir=args.dir_backtransformed, + cone_type=args.transformation_type, + maximal_length=args.max_length, + angle_comp=args.angle_type, + x_shift=args.delta_x, + y_shift=args.delta_y, + z_desired=args.z_height, + e_parallel=args.err_parallel, + e_perpendicular=args.err_perpendicular + ) + + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: + os._exit(0) \ No newline at end of file diff --git a/Transformation_STL.py b/Transformation_STL.py index d09cc97..975fdd4 100644 --- a/Transformation_STL.py +++ b/Transformation_STL.py @@ -1,132 +1,133 @@ -import numpy as np -from stl import mesh -import time -import sys -import os -import argparse - -def refinement_one_triangle(triangle): - """ - Compute a refinement of one triangle. On every side, the midpoint is added. The three corner points and three - midpoints result in four smaller triangles. - :param triangle: array - array of three points of shape (3, 3) (one triangle) - :return: array - array of shape (4, 3, 3) of four triangles - """ - point1 = triangle[0] - point2 = triangle[1] - point3 = triangle[2] - midpoint12 = (point1 + point2) / 2 - midpoint23 = (point2 + point3) / 2 - midpoint31 = (point3 + point1) / 2 - triangle1 = np.array([point1, midpoint12, midpoint31]) - triangle2 = np.array([point2, midpoint23, midpoint12]) - triangle3 = np.array([point3, midpoint31, midpoint23]) - triangle4 = np.array([midpoint12, midpoint23, midpoint31]) - return np.array([triangle1, triangle2, triangle3, triangle4]) - - -def refinement_triangulation(triangle_array, num_iterations): - """ - Compute a refinement of a triangulation using the refinement_four_triangles function. - The number of iteration defines, how often the triangulation has to be refined; n iterations lead to - 4^n times many triangles. - :param triangle_array: array - array of shape (num_triangles, 3, 3) of triangles - :param num_iterations: int - :return: array - array of shape (num_triangles*4^num_iterations, 3, 3) of triangles - """ - refined_array = triangle_array - for i in range(0, num_iterations): - n_triangles = refined_array.shape[0] * 4 - refined_array = np.array(list(map(refinement_one_triangle, refined_array))) - refined_array = np.reshape(refined_array, (n_triangles, 3, 3)) - return refined_array - - -def transformation_cone(points, cone_type): - """ - Compute the cone-transformation (x', y', z') = (\sqrt{2}x, \sqrt{2}y, z + \sqrt{x^{2} + y^{2}}) ('outward') or - (x', y', z') = (\sqrt{2}x, \sqrt{2}y, z - \sqrt{x^{2} + y^{2}}) ('inward') for a list of points - :param points: array - array of points of shape ( , 3) - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :return: array - array of transformed points, of same shape as input array - """ - if cone_type == 'outward': - c = 1 - elif cone_type == 'inward': - c = -1 - else: - raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) - T = (lambda x, y, z: np.array([np.sqrt(2) * x, np.sqrt(2) * y, z + c * np.sqrt(x ** 2 + y ** 2)])) - points_transformed = list(map(T, points[:, 0], points[:, 1], points[:, 2])) - return np.array(points_transformed) - - -def transformation_STL_file(path, output_dir, cone_type, nb_iterations): - """ - Read a stl-file, refine the triangulation, transform it according to the cone-transformation and save the - transformed data. - :param path: string - path to the stl file - :param output_dir: - path of directory, where transformed STL-file will be saved - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param nb_iterations: int - number of iterations, the triangulation should be refined before the transformation - :return: mesh object - transformed triangulation as mesh object which can be stored as stl file - """ - start = time.time() - my_mesh = mesh.Mesh.from_file(path) - vectors = my_mesh.vectors - vectors_refined = refinement_triangulation(vectors, nb_iterations) - vectors_refined = np.reshape(vectors_refined, (-1, 3)) - vectors_transformed = transformation_cone(vectors_refined, cone_type) - vectors_transformed = np.reshape(vectors_transformed, (-1, 3, 3)) - my_mesh_transformed = np.zeros(vectors_transformed.shape[0], dtype=mesh.Mesh.dtype) - my_mesh_transformed['vectors'] = vectors_transformed - my_mesh_transformed = mesh.Mesh(my_mesh_transformed) - - if not os.path.exists(output_dir): - os.mkdir(output_dir) - file_name = path[path.rfind('/'):] - file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') - output_path = output_dir + file_name - my_mesh_transformed.save(output_path) - end = time.time() - print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) - return None - - -# ------------------------------------------------------------------------------- -# Apply the functions to a STL file -# ------------------------------------------------------------------------------- -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="") - parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) - parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) - parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') - parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') - args = parser.parse_args() - - try: - # STL transformation function call - transformation_STL_file(path=args.file_path, - output_dir=args.dir_transformed, - cone_type=args.transformation_type, - nb_iterations=args.number_iterations, - ) - - except KeyboardInterrupt: - print("Done.") - try: - sys.exit(0) - except SystemExit: +import numpy as np +from stl import mesh +import time +import sys +import os +import argparse + +def refinement_one_triangle(triangle): + """ + Compute a refinement of one triangle. On every side, the midpoint is added. The three corner points and three + midpoints result in four smaller triangles. + :param triangle: array + array of three points of shape (3, 3) (one triangle) + :return: array + array of shape (4, 3, 3) of four triangles + """ + point1 = triangle[0] + point2 = triangle[1] + point3 = triangle[2] + midpoint12 = (point1 + point2) / 2 + midpoint23 = (point2 + point3) / 2 + midpoint31 = (point3 + point1) / 2 + triangle1 = np.array([point1, midpoint12, midpoint31]) + triangle2 = np.array([point2, midpoint23, midpoint12]) + triangle3 = np.array([point3, midpoint31, midpoint23]) + triangle4 = np.array([midpoint12, midpoint23, midpoint31]) + return np.array([triangle1, triangle2, triangle3, triangle4]) + + +def refinement_triangulation(triangle_array, num_iterations): + """ + Compute a refinement of a triangulation using the refinement_four_triangles function. + The number of iteration defines, how often the triangulation has to be refined; n iterations lead to + 4^n times many triangles. + :param triangle_array: array + array of shape (num_triangles, 3, 3) of triangles + :param num_iterations: int + :return: array + array of shape (num_triangles*4^num_iterations, 3, 3) of triangles + """ + refined_array = triangle_array + for i in range(0, num_iterations): + n_triangles = refined_array.shape[0] * 4 + refined_array = np.array(list(map(refinement_one_triangle, refined_array))) + refined_array = np.reshape(refined_array, (n_triangles, 3, 3)) + return refined_array + + +def transformation_cone(points, cone_type): + """ + Compute the cone-transformation (x', y', z') = (\sqrt{2}x, \sqrt{2}y, z + \sqrt{x^{2} + y^{2}}) ('outward') or + (x', y', z') = (\sqrt{2}x, \sqrt{2}y, z - \sqrt{x^{2} + y^{2}}) ('inward') for a list of points + :param points: array + array of points of shape ( , 3) + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :return: array + array of transformed points, of same shape as input array + """ + if cone_type == 'outward': + c = 1 + elif cone_type == 'inward': + c = -1 + else: + raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) + T = (lambda x, y, z: np.array([np.sqrt(2) * x, np.sqrt(2) * y, z + c * np.sqrt(x ** 2 + y ** 2)])) + points_transformed = list(map(T, points[:, 0], points[:, 1], points[:, 2])) + return np.array(points_transformed) + + +def transformation_STL_file(path, output_dir, cone_type, nb_iterations): + """ + Read a stl-file, refine the triangulation, transform it according to the cone-transformation and save the + transformed data. + :param path: string + path to the stl file + :param output_dir: + path of directory, where transformed STL-file will be saved + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param nb_iterations: int + number of iterations, the triangulation should be refined before the transformation + :return: mesh object + transformed triangulation as mesh object which can be stored as stl file + """ + start = time.time() + my_mesh = mesh.Mesh.from_file(path) + vectors = my_mesh.vectors + vectors_refined = refinement_triangulation(vectors, nb_iterations) + vectors_refined = np.reshape(vectors_refined, (-1, 3)) + vectors_transformed = transformation_cone(vectors_refined, cone_type) + vectors_transformed = np.reshape(vectors_transformed, (-1, 3, 3)) + my_mesh_transformed = np.zeros(vectors_transformed.shape[0], dtype=mesh.Mesh.dtype) + my_mesh_transformed['vectors'] = vectors_transformed + my_mesh_transformed = mesh.Mesh(my_mesh_transformed) + + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path[path.rfind('/'):] + file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') + print("output filename: {}".format(file_name)) + output_path = output_dir + file_name + my_mesh_transformed.save(output_path) + end = time.time() + print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) + return None + + +# ------------------------------------------------------------------------------- +# Apply the functions to a STL file +# ------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') + args = parser.parse_args() + + try: + # STL transformation function call + transformation_STL_file(path=args.file_path, + output_dir=args.dir_transformed, + cone_type=args.transformation_type, + nb_iterations=args.number_iterations, + ) + + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: os._exit(0) \ No newline at end of file From 6c1c1850f46a31196a8be525ac32a01e22706ce4 Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 14:42:59 +0200 Subject: [PATCH 3/7] Added proper file-name handling. Now relative paths do work too. --- Backtransformation_GCode.py | 8 +++++--- Transformation_STL.py | 8 +++++--- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Backtransformation_GCode.py b/Backtransformation_GCode.py index a111175..1d45daa 100644 --- a/Backtransformation_GCode.py +++ b/Backtransformation_GCode.py @@ -549,10 +549,12 @@ def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, if not os.path.exists(output_dir): os.mkdir(output_dir) - file_name = path[path.rfind('/'):] + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') - print("output filename: {}".format(file_name)) - output_path = output_dir + file_name + output_path = output_dir + '/' + file_name with open(output_path, 'w+') as f_gcode_bt: f_gcode_bt.write(data_bt_string) diff --git a/Transformation_STL.py b/Transformation_STL.py index 975fdd4..c1c6fc8 100644 --- a/Transformation_STL.py +++ b/Transformation_STL.py @@ -96,10 +96,12 @@ def transformation_STL_file(path, output_dir, cone_type, nb_iterations): if not os.path.exists(output_dir): os.mkdir(output_dir) - file_name = path[path.rfind('/'):] + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') - print("output filename: {}".format(file_name)) - output_path = output_dir + file_name + output_path = output_dir + '/' + file_name my_mesh_transformed.save(output_path) end = time.time() print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) From d424ab5aac724f51123bddb4791385d860b82da7 Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 14:50:02 +0200 Subject: [PATCH 4/7] Extended the README.md --- Backtransformation_GCode.py | 2 +- README.md | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Backtransformation_GCode.py b/Backtransformation_GCode.py index 1d45daa..8254ce1 100644 --- a/Backtransformation_GCode.py +++ b/Backtransformation_GCode.py @@ -301,7 +301,7 @@ def backtransform_data_radial(data, cone_type, maximal_length): def backtransform_data_tangential(data, cone_type, maximal_length): """ - Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement + Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement are detected, x-, y-, z-, e- and U-values are replaced accordingly to the transformation. If a original segment is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed using the function compute_angle_tangential. diff --git a/README.md b/README.md index 723ba2e..61a071f 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ The transformation of the STL file has the following parameters: * transformation_type: 'inward' or 'outward' transformation * nb_iterations: number iterations for the triangulation refinement +On the command line: +``` +python Transformation_STL.py -i 3DBenchy.stl -o out -t inward -n 1 +``` + + ### Back-Transformation of the G-Code The back-transformation of the G-Code has the following parameters: * file_path: path to the G-Code @@ -29,6 +35,11 @@ The back-transformation of the G-Code has the following parameters: * e_parallel: extrusion error to correct in parallel direction * e_perpendicular: extrusion error to correct in perpendicular direction +Type the following command to perform the backtransformation: +``` +python Backtransformation_GCode.py -i 3DBenchy_mod.gcode -o out +``` + ### Scripts for variable angle With this scripts, the cone angle can be changed. So it does not only work for 45° angle as used for RotBot, but can also be used with much smaller angles (e.g. 15°) to do a conical slicing for any printer. So overhangs can be printed on any printer. From 9cb6840683c9be95f0390d49aef94556c76bf6ea Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 15:14:08 +0200 Subject: [PATCH 5/7] Applied all the changes to Scripts for Variable Angle --- .../Backtransformation_GCode_var_angle.py | 71 ++++++++++++++----- .../Transformation_STL_var_angle.py | 56 +++++++++++---- 2 files changed, 96 insertions(+), 31 deletions(-) diff --git a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py index 1d9b82f..cc2b23f 100644 --- a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py +++ b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py @@ -1,6 +1,9 @@ import re import numpy as np +import sys +import os import time +import argparse def insert_Z(row, z_value): @@ -344,11 +347,13 @@ def translate_data(data, cone_type, translate_x, translate_y, z_desired, e_paral return new_data -def backtransform_file(path, cone_type, maximal_length, angle_comp, x_shift, y_shift, cone_angle_deg, z_desired, e_parallel, e_perpendicular): +def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, x_shift, y_shift, cone_angle_deg, z_desired, e_parallel, e_perpendicular): """ Read GCode from file, backtransform and translate it. :param path: string String with the path to the GCode-file + :param output_dir: string + path of directory, where transformed STL-file will be saved :param cone_type: string String, either 'outward' or 'inward', defines which transformation should be used :param maximal_length: float @@ -369,7 +374,7 @@ def backtransform_file(path, cone_type, maximal_length, angle_comp, x_shift, y_s Error perpendicular to nozzle :return: None """ - + start = time.time() cone_angle_rad = cone_angle_deg / 180 * np.pi if angle_comp == 'radial': @@ -383,26 +388,58 @@ def backtransform_file(path, cone_type, maximal_length, angle_comp, x_shift, y_s data_bt = translate_data(data_bt, cone_type, x_shift, y_shift, z_desired, e_parallel, e_perpendicular) data_bt_string = ''.join(data_bt) - path_write = re.sub(r'G_Codes', 'G_Codes_Backtransformed', path) - path_write = re.sub(r'.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode', path_write) - print(path_write) - with open(path_write, 'w+') as f_gcode_bt: + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] + file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') + output_path = output_dir + '/' + file_name + with open(output_path, 'w+') as f_gcode_bt: f_gcode_bt.write(data_bt_string) - print('File successfully backtransformed and translated.') + end = time.time() + print('GCode generated in {:.1f}s, saved in {}'.format(end - start, output_path)) return None # ----------------------------------------------------------------------------------------- # Anwenden der Funktionen auf ein STL File # ----------------------------------------------------------------------------------------- -file_name = 'Oben_dunn.gcode' -folder_name = 'G_Codes/' -file_path = folder_name + file_name - -starttime = time.time() -backtransform_file(path=file_path, cone_type='outward', maximal_length=0.5, angle_comp='radial', x_shift=100, y_shift=100, - cone_angle_deg=15, z_desired=40.2, e_parallel=0, e_perpendicular=0) -endtime = time.time() -print('GCode translated, time used:', endtime - starttime) - +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='GCODE-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_backtransformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-a', '--angle-type', dest='angle_type', type=str, default='radial', help='The angle type: radial or tangential.') + parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') + parser.add_argument('-m', '--max-length', dest='max_length', type=int, default=0.5, help='Maximal length of a segment in mm.') + parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=100, help='Shift the code in x-direction.') + parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=100, help='Shift the code in y-direction.') + parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=40.2, help='Desired height in z-direction.') + parser.add_argument('-ep', '--err-parallel', dest='err_parallel', type=float, default=0, help='Error in parallel direction.') + parser.add_argument('-et', '--err-perpendicular', dest='err_perpendicular', type=float, default=0, help='Error in perpendicular direction.') + args = parser.parse_args() + + try: + # G-Code backtransformation function call + backtransform_file(path=args.file_path, + output_dir=args.dir_backtransformed, + cone_type=args.transformation_type, + maximal_length=args.max_length, + angle_comp=args.angle_type, + x_shift=args.delta_x, + y_shift=args.delta_y, + cone_angle_deg=15, + z_desired=args.z_height, + e_parallel=args.err_parallel, + e_perpendicular=args.err_perpendicular + ) + + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: + os._exit(0) \ No newline at end of file diff --git a/Scripts for Variable Angle/Transformation_STL_var_angle.py b/Scripts for Variable Angle/Transformation_STL_var_angle.py index d7c7e50..3978b4d 100644 --- a/Scripts for Variable Angle/Transformation_STL_var_angle.py +++ b/Scripts for Variable Angle/Transformation_STL_var_angle.py @@ -1,7 +1,9 @@ import numpy as np from stl import mesh import time - +import sys +import os +import argparse def transformation_kegel(points, cone_angle_rad, cone_type): """ @@ -66,11 +68,13 @@ def refinement_triangulation(triangle_array, num_iterations): return refined_array -def transformation_STL_file(path, cone_type, cone_angle_deg, nb_iterations): +def transformation_STL_file(path, output_dir, cone_type, cone_angle_deg, nb_iterations): """ Read a stl-file, refine the triangulation and transform it according to the cone-transformation :param path: string path to the stl file + :param output_dir: string + path of directory, where transformed STL-file will be saved :param cone_type: string String, either 'outward' or 'inward', defines which transformation should be used :param cone_angle: int @@ -80,6 +84,7 @@ def transformation_STL_file(path, cone_type, cone_angle_deg, nb_iterations): :return: mesh object transformed triangulation as mesh object which can be stored as stl file """ + start = time.time() cone_angle_rad = cone_angle_deg / 180 * np.pi my_mesh = mesh.Mesh.from_file(path) vectors = my_mesh.vectors @@ -90,22 +95,45 @@ def transformation_STL_file(path, cone_type, cone_angle_deg, nb_iterations): my_mesh_transformed = np.zeros(vectors_transformed.shape[0], dtype=mesh.Mesh.dtype) my_mesh_transformed['vectors'] = vectors_transformed my_mesh_transformed = mesh.Mesh(my_mesh_transformed) - return my_mesh_transformed + + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] + file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') + output_path = output_dir + '/' + file_name + my_mesh_transformed.save(output_path) + end = time.time() + print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) + return None #----------------------------------------------------------------------------------------- # Anwenden der Funktionen auf ein STL File #----------------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') + parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') + args = parser.parse_args() + try: + # STL transformation function call + transformation_STL_file(path=args.file_path, + output_dir=args.dir_transformed, + cone_type=args.transformation_type, + cone_angle_deg=args.cone_angle_deg, + nb_iterations=args.number_iterations + ) -dateiname = 'Unten_dunn' -ordnername_originale = 'STL_Modelle/' -ordnername_transformierte = 'Modelle_Transformiert_Kegel/' -dateipfad = ordnername_originale + dateiname + '.stl' -transformations_typ = 'outward' - -startzeit = time.time() -transformed_STL = transformation_STL_file(path=dateipfad, cone_type=transformations_typ, cone_angle_deg=15, nb_iterations=1) -transformed_STL.save(ordnername_transformierte + dateiname + '_' + transformations_typ + '_transformiert.stl') -endzeit = time.time() -print('Benoetigte Zeit:', endzeit - startzeit) + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: + os._exit(0) \ No newline at end of file From f81916a02fc105791a436f392baca8716e204f1e Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 15:15:06 +0200 Subject: [PATCH 6/7] Unified Windows and Linux file endings (dos2unix) --- .../Backtransformation_GCode_var_angle.py | 888 +++++++++--------- .../Transformation_STL_var_angle.py | 276 +++--- 2 files changed, 582 insertions(+), 582 deletions(-) diff --git a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py index cc2b23f..065b188 100644 --- a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py +++ b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py @@ -1,445 +1,445 @@ -import re -import numpy as np -import sys -import os -import time -import argparse - - -def insert_Z(row, z_value): - """ - Insert or replace the z-value in a row. The new z-value must be given. - :param row: string - String containing the row, in which a z-value has to be inserted or replaced - :param z_value: float - New z-value, which should be inserted - :return: string - New string, containing the row with replaced z-value - """ - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - match_x = re.search(pattern_X, row) - match_y = re.search(pattern_Y, row) - match_z = re.search(pattern_Z, row) - - if match_z is not None: - row_new = re.sub(pattern_Z, ' Z' + str(round(z_value, 3)), row) - else: - if match_y is not None: - row_new = row[0:match_y.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_y.end(0):] - elif match_x is not None: - row_new = row[0:match_x.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_x.end(0):] - else: - row_new = 'Z' + str(round(z_value, 3)) + ' ' + row - return row_new - - -def replace_E(row, dist_old, dist_new, corr_value): - """ - Replace the amount of extruded filament in a row. The new amount is proportional to the old amount, where - the factor is obtained by the ratio of new distance to old distance. (wuem: Due to the transformation, the amount has to be - divided by sqrt(2). replace_E is accessed 2 times.) - :param row: string - String containing the row, of which the extruder value should be replaced - :param dist_old: float - Length of the distance before backtransformation - :param dist_new: float - Length of the distance after backtransformation - :param corr_value: float - additional correction value due to transformation - :return: string - New string, containing the row with replaced extruder value - """ - pattern_E = r'E[-0-9]+[.]?[0-9]*' - match_e = re.search(pattern_E, row) - if match_e is None: - return row - e_val_old = float(match_e.group(0).replace('E', '')) - if dist_old == 0: - e_val_new = 0 - else: - e_val_new = round(e_val_old * dist_new * corr_value / dist_old , 6) - e_str_new = 'E' + str(e_val_new) - row_new = row[0:match_e.start(0)] + e_str_new + row[match_e.end(0):] - return row_new - - -def compute_angle_radial(x_old, y_old, x_new, y_new, inward_cone): - """ - Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. - (Note: the z-value is not considered for the orientation of the printing head.) The direction is given by the - direction of the new point by the arctan2 value according to the coordinates. - :param x_old: float - x-coordinate of the old point - :param y_old: float - y-coordinate of the old point - :param x_new: float - x-coordinate of the new point - :param y_new: float - y-coordinate of the new point - :param inward_cone: bool - Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to - the angle. - :return: float - Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. - """ - angle = np.arctan2(y_new, x_new) - if inward_cone: - angle = angle + np.pi - return angle - - - - - -def compute_U_values(angle_array): - """ - Compute the U-values, which will be inserted, according to given angle values. - The U-values are computed such that there are no discontinuous jumps from pi to -pi. - :param angle_array: array - Array, which contains the angle values in radian - :return array - Array, which contains U-values in degrees - """ - # angle_candidates = np.around(np.array([angle_array, angle_array - 2 * np.pi, angle_array + 2 * np.pi]).T, 4) - angle_candidates = np.around(np.array([angle_array + k * 2 * np.pi for k in range(-10, 11)]).T, 4) - angle_insert = [angle_array[0]] - for i in range(1, len(angle_array)): - angle_prev = angle_insert[i - 1] - idx = np.argmin(np.absolute(angle_candidates[i] - angle_prev)) - angle_insert.append(angle_candidates[i, idx]) - - angle_insert = np.round(np.array(angle_insert) * 360 / (2 * np.pi), 2) - - return angle_insert - - -def insert_U(row, angle): - """ - Insert or replace the U-value in a row, where the U-values describes the orientation of the printing head. - :param row: string - String containing the row, in which a U-value has to be inserted or replaced - :param angle: float - Value of the angle, which is inserted or replaces the old U-value - :return: string - New string, containing the row with replaced U-value - """ - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - match_z = re.search(pattern_Z, row) - pattern_U = r'U[-0-9]+[.]?[0-9]*' - match_u = re.search(pattern_U, row) - - if match_u is None: - row_new = row[0:match_z.end(0)] + ' U' + str(angle) + row[match_z.end(0):] - else: - row_new = re.sub(pattern_U, 'U' + str(angle), row) - - return row_new - - -def backtransform_data_radial(data, cone_type, maximal_length, cone_angle_rad): - """ - Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement - are detected, x-, y-, z-, E- and U-values are replaced accordingly to the transformation. If a original segment - is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed - using the funciton compute_angle_radial.(wuem: added, that while travel moves, nozzle only rises 1 mm above highest - printed point and not along cone) - :param data: list - List of strings, describing each line of the GCode, which is to be backtransformed - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param maximal_length: float - Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting - segments are shorter than maximal_length - : param cone_angle_rad - Angle of transformation cone in rad - :return: list - List of strings, which describe the new GCode. - """ - new_data = [] - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - pattern_E = r'E[-0-9]+[.]?[0-9]*' - pattern_G = r'\AG[01] ' - - x_old, y_old = 0, 0 - x_new, y_new = 0, 0 - z_layer = 0 - angle_old = 0 - z_max = 0 - update_x, update_y = False, False - visible_print = False - if cone_type == 'outward': - c = -1 - inward_cone = False - elif cone_type == 'inward': - c = 1 - inward_cone = True - else: - raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) - - for row in data: - - g_match = re.search(pattern_G, row) - if g_match is None: - new_data.append(row) - - else: - x_match = re.search(pattern_X, row) - y_match = re.search(pattern_Y, row) - z_match = re.search(pattern_Z, row) - - if x_match is None and y_match is None and z_match is None: - new_data.append(row) - - else: - if z_match is not None: - z_layer = float(z_match.group(0).replace('Z', '')) - if x_match is not None: - x_new = float(x_match.group(0).replace('X', '')) - update_x = True - if y_match is not None: - y_new = float(y_match.group(0).replace('Y', '')) - update_y = True - - # Compute new distance and angle according to new row - e_match = re.search(pattern_E, row) - x_old_bt, x_new_bt = x_old * np.cos(cone_angle_rad), x_new * np.cos(cone_angle_rad) - y_old_bt, y_new_bt = y_old * np.cos(cone_angle_rad), y_new * np.cos(cone_angle_rad) - dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) - - # Compute new values for backtransformation of row - num_segm = int(dist_transformed // maximal_length + 1) - x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) - y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) - if inward_cone and e_match is None and (update_x or update_y): - z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) * np.tan(cone_angle_rad) - z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) * np.tan(cone_angle_rad) - z_vals = np.linspace(z_start, z_end, num_segm + 1) - else: - z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) * np.tan(cone_angle_rad) for x, y in zip(x_vals, y_vals)]) - if e_match and (np.max(z_vals) > z_max or z_max == 0): - z_max = np.max(z_vals) # save hightes point with material extruded - if e_match is None and np.max(z_vals) > z_max: - np.minimum(z_vals, (z_max + 1), z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety - # das hier könnte noch verschönert werden, in dem dann eine alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden - angle_new = compute_angle_radial(x_old_bt, y_old_bt, x_new_bt, y_new_bt, inward_cone) - angle_vals = np.array( - [angle_old] + [ - compute_angle_radial(x_vals[k], y_vals[k], x_vals[k + 1], y_vals[k + 1], inward_cone) - for k in range(0, num_segm)]) - u_vals = compute_U_values(angle_vals) - distances_transformed = dist_transformed / num_segm * np.ones(num_segm) - distances_bt = np.array( - [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) - for i in range(1, num_segm + 1)]) - - # Replace new row with num_seg new rows for movements and possible command rows for the U value - row = insert_Z(row, z_vals[0]) - row = replace_E(row, num_segm, 1, 1 * np.cos(cone_angle_rad)) - replacement_rows = '' - for j in range(0, num_segm): - single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) - single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) - single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) - single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) - if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: - single_row = insert_U(single_row, u_vals[j + 1]) - else: - single_row = 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' + single_row - replacement_rows = replacement_rows + single_row - if np.amax(np.absolute(u_vals)) > 3600: - angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) - replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' - angle_old = angle_new - else: - angle_old = u_vals[-1] * 2 * np.pi / 360 - row = replacement_rows - - if update_x: - x_old = x_new - update_x = False - if update_y: - y_old = y_new - update_y = False - - new_data.append(row) - - return new_data - - - -def translate_data(data, cone_type, translate_x, translate_y, z_desired, e_parallel, e_perpendicular): - """ - Translate the GCode in x- and y-direction. Only the lines, which describe a movement will be translated. - Additionally, if z_translation is True, the z-values will be translated such that the minimal z-value is z_desired. - This happens by traversing the list of strings twice. If cone_type is 'inward', it is assured, that all moves - with no extrusion have at least a hight of z_desired. - :param data: list - List of strings, containing the GCode - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param translate_x: float - Float, which describes the translation in x-direction - :param translate_y: float - Float, which describes the translation in y-direction - :param z_desired: float - Desired minimal z-value - :param e_parallel: float - Error parallel to nozzle - :param e_perpendicular: float - Error perpendicular to nozzle - :return: list - List of strings, which contains the translated GCode - """ - new_data = [] - pattern_X = r'X[-0-9]+[.]?[0-9]*' - pattern_Y = r'Y[-0-9]+[.]?[0-9]*' - pattern_Z = r'Z[-0-9]+[.]?[0-9]*' - pattern_E = r'E[-0-9]+[.]?[0-9]*' - pattern_U = r'U[-0-9]+[.]?[0-9]*' - pattern_G = r'\AG[01] ' - z_initialized = False - u_val = 0.0 - - for row in data: - g_match = re.search(pattern_G, row) - z_match = re.search(pattern_Z, row) - e_match = re.search(pattern_E, row) - if g_match is not None and z_match is not None and e_match is not None: - z_val = float(z_match.group(0).replace('Z', '')) - if not z_initialized: - z_min = z_val - z_initialized = True - if z_val < z_min: - z_min = z_val - z_translate = z_desired - z_min - - for row in data: - - x_match = re.search(pattern_X, row) - y_match = re.search(pattern_Y, row) - z_match = re.search(pattern_Z, row) - g_match = re.search(pattern_G, row) - u_match = re.search(pattern_U, row) - - if u_match is not None: - u_val = np.radians(float(u_match.group(0).replace('U', ''))) - - if g_match is None: - new_data.append(row) - - else: - if x_match is not None: - x_val = round(float(x_match.group(0).replace('X', '')) + translate_x - (e_parallel * np.cos(u_val)) + (e_perpendicular * np.sin(u_val)), 3) - row = re.sub(pattern_X, 'X' + str(x_val), row) - if y_match is not None: - y_val = round(float(y_match.group(0).replace('Y', '')) + translate_y - (e_parallel * np.sin(u_val)) - (e_perpendicular * np.cos(u_val)), 3) - row = re.sub(pattern_Y, 'Y' + str(y_val), row) - if z_match is not None: - z_val = max(round(float(z_match.group(0).replace('Z', '')) + z_translate, 3), z_desired) - row = re.sub(pattern_Z, 'Z' + str(z_val), row) - - new_data.append(row) - - return new_data - - -def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, x_shift, y_shift, cone_angle_deg, z_desired, e_parallel, e_perpendicular): - """ - Read GCode from file, backtransform and translate it. - :param path: string - String with the path to the GCode-file - :param output_dir: string - path of directory, where transformed STL-file will be saved - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param maximal_length: float - Maximal length of a segment in the original GCode - :param angle_comp: string - String, which describes the way, the angle is computed; one of 'radial', 'tangential', 'mixed' - :param x_shift: float - Float, which describes the translation in x-direction - :param y_shift: float - Float, which describes the translation in y-direction - :param cone_angle_deg: int - Angle of transformation cone in degrees - :param z_desired: float - Desired minimal z-value - :param e_parallel: float - Error parallel to nozzle - :param e_perpendicular: float - Error perpendicular to nozzle - :return: None - """ - start = time.time() - cone_angle_rad = cone_angle_deg / 180 * np.pi - - if angle_comp == 'radial': - backtransform_data = backtransform_data_radial - - with open(path, 'r') as f_gcode: - data = f_gcode.readlines() - data_bt = backtransform_data(data, cone_type, maximal_length, cone_angle_rad) - data_bt_string = ''.join(data_bt) - data_bt = [row + ' \n' for row in data_bt_string.split('\n')] - data_bt = translate_data(data_bt, cone_type, x_shift, y_shift, z_desired, e_parallel, e_perpendicular) - data_bt_string = ''.join(data_bt) - - if not os.path.exists(output_dir): - os.mkdir(output_dir) - file_name = path - pos = path.rfind('/') - if pos > -1: - file_name = path[pos:] - file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') - output_path = output_dir + '/' + file_name - with open(output_path, 'w+') as f_gcode_bt: - f_gcode_bt.write(data_bt_string) - - end = time.time() - print('GCode generated in {:.1f}s, saved in {}'.format(end - start, output_path)) - return None - - -# ----------------------------------------------------------------------------------------- -# Anwenden der Funktionen auf ein STL File -# ----------------------------------------------------------------------------------------- -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="") - parser.add_argument('-i', '--infile', dest='file_path', type=str, help='GCODE-file to apply the transformation to.', required=True) - parser.add_argument('-o', '--outpath', dest='dir_backtransformed', type=str, help='Folder to store the output to.', required=True) - parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') - parser.add_argument('-a', '--angle-type', dest='angle_type', type=str, default='radial', help='The angle type: radial or tangential.') - parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') - parser.add_argument('-m', '--max-length', dest='max_length', type=int, default=0.5, help='Maximal length of a segment in mm.') - parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=100, help='Shift the code in x-direction.') - parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=100, help='Shift the code in y-direction.') - parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=40.2, help='Desired height in z-direction.') - parser.add_argument('-ep', '--err-parallel', dest='err_parallel', type=float, default=0, help='Error in parallel direction.') - parser.add_argument('-et', '--err-perpendicular', dest='err_perpendicular', type=float, default=0, help='Error in perpendicular direction.') - args = parser.parse_args() - - try: - # G-Code backtransformation function call - backtransform_file(path=args.file_path, - output_dir=args.dir_backtransformed, - cone_type=args.transformation_type, - maximal_length=args.max_length, - angle_comp=args.angle_type, - x_shift=args.delta_x, - y_shift=args.delta_y, - cone_angle_deg=15, - z_desired=args.z_height, - e_parallel=args.err_parallel, - e_perpendicular=args.err_perpendicular - ) - - except KeyboardInterrupt: - print("Interrupted.") - try: - sys.exit(0) - except SystemExit: +import re +import numpy as np +import sys +import os +import time +import argparse + + +def insert_Z(row, z_value): + """ + Insert or replace the z-value in a row. The new z-value must be given. + :param row: string + String containing the row, in which a z-value has to be inserted or replaced + :param z_value: float + New z-value, which should be inserted + :return: string + New string, containing the row with replaced z-value + """ + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + match_x = re.search(pattern_X, row) + match_y = re.search(pattern_Y, row) + match_z = re.search(pattern_Z, row) + + if match_z is not None: + row_new = re.sub(pattern_Z, ' Z' + str(round(z_value, 3)), row) + else: + if match_y is not None: + row_new = row[0:match_y.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_y.end(0):] + elif match_x is not None: + row_new = row[0:match_x.end(0)] + ' Z' + str(round(z_value, 3)) + row[match_x.end(0):] + else: + row_new = 'Z' + str(round(z_value, 3)) + ' ' + row + return row_new + + +def replace_E(row, dist_old, dist_new, corr_value): + """ + Replace the amount of extruded filament in a row. The new amount is proportional to the old amount, where + the factor is obtained by the ratio of new distance to old distance. (wuem: Due to the transformation, the amount has to be + divided by sqrt(2). replace_E is accessed 2 times.) + :param row: string + String containing the row, of which the extruder value should be replaced + :param dist_old: float + Length of the distance before backtransformation + :param dist_new: float + Length of the distance after backtransformation + :param corr_value: float + additional correction value due to transformation + :return: string + New string, containing the row with replaced extruder value + """ + pattern_E = r'E[-0-9]+[.]?[0-9]*' + match_e = re.search(pattern_E, row) + if match_e is None: + return row + e_val_old = float(match_e.group(0).replace('E', '')) + if dist_old == 0: + e_val_new = 0 + else: + e_val_new = round(e_val_old * dist_new * corr_value / dist_old , 6) + e_str_new = 'E' + str(e_val_new) + row_new = row[0:match_e.start(0)] + e_str_new + row[match_e.end(0):] + return row_new + + +def compute_angle_radial(x_old, y_old, x_new, y_new, inward_cone): + """ + Compute the angle of the printing head, when moving from an old point [x_old, y_old] to a new point [x_new, y_new]. + (Note: the z-value is not considered for the orientation of the printing head.) The direction is given by the + direction of the new point by the arctan2 value according to the coordinates. + :param x_old: float + x-coordinate of the old point + :param y_old: float + y-coordinate of the old point + :param x_new: float + x-coordinate of the new point + :param y_new: float + y-coordinate of the new point + :param inward_cone: bool + Boolean variable, which depends on the kind of transformation. If True, an additional angle of pi is added to + the angle. + :return: float + Angle, which describes orientation of printing head. Its value lies in [-pi, pi]. + """ + angle = np.arctan2(y_new, x_new) + if inward_cone: + angle = angle + np.pi + return angle + + + + + +def compute_U_values(angle_array): + """ + Compute the U-values, which will be inserted, according to given angle values. + The U-values are computed such that there are no discontinuous jumps from pi to -pi. + :param angle_array: array + Array, which contains the angle values in radian + :return array + Array, which contains U-values in degrees + """ + # angle_candidates = np.around(np.array([angle_array, angle_array - 2 * np.pi, angle_array + 2 * np.pi]).T, 4) + angle_candidates = np.around(np.array([angle_array + k * 2 * np.pi for k in range(-10, 11)]).T, 4) + angle_insert = [angle_array[0]] + for i in range(1, len(angle_array)): + angle_prev = angle_insert[i - 1] + idx = np.argmin(np.absolute(angle_candidates[i] - angle_prev)) + angle_insert.append(angle_candidates[i, idx]) + + angle_insert = np.round(np.array(angle_insert) * 360 / (2 * np.pi), 2) + + return angle_insert + + +def insert_U(row, angle): + """ + Insert or replace the U-value in a row, where the U-values describes the orientation of the printing head. + :param row: string + String containing the row, in which a U-value has to be inserted or replaced + :param angle: float + Value of the angle, which is inserted or replaces the old U-value + :return: string + New string, containing the row with replaced U-value + """ + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + match_z = re.search(pattern_Z, row) + pattern_U = r'U[-0-9]+[.]?[0-9]*' + match_u = re.search(pattern_U, row) + + if match_u is None: + row_new = row[0:match_z.end(0)] + ' U' + str(angle) + row[match_z.end(0):] + else: + row_new = re.sub(pattern_U, 'U' + str(angle), row) + + return row_new + + +def backtransform_data_radial(data, cone_type, maximal_length, cone_angle_rad): + """ + Backtransform GCode, which is given in a list, each element describing a row. Rows which describe a movement + are detected, x-, y-, z-, E- and U-values are replaced accordingly to the transformation. If a original segment + is too long, it gets divided into sub-segments before the backtransformation. The U-values are computed + using the funciton compute_angle_radial.(wuem: added, that while travel moves, nozzle only rises 1 mm above highest + printed point and not along cone) + :param data: list + List of strings, describing each line of the GCode, which is to be backtransformed + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param maximal_length: float + Maximal length of a segment in the original GCode; every longer segment is divided, such that the resulting + segments are shorter than maximal_length + : param cone_angle_rad + Angle of transformation cone in rad + :return: list + List of strings, which describe the new GCode. + """ + new_data = [] + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + pattern_E = r'E[-0-9]+[.]?[0-9]*' + pattern_G = r'\AG[01] ' + + x_old, y_old = 0, 0 + x_new, y_new = 0, 0 + z_layer = 0 + angle_old = 0 + z_max = 0 + update_x, update_y = False, False + visible_print = False + if cone_type == 'outward': + c = -1 + inward_cone = False + elif cone_type == 'inward': + c = 1 + inward_cone = True + else: + raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) + + for row in data: + + g_match = re.search(pattern_G, row) + if g_match is None: + new_data.append(row) + + else: + x_match = re.search(pattern_X, row) + y_match = re.search(pattern_Y, row) + z_match = re.search(pattern_Z, row) + + if x_match is None and y_match is None and z_match is None: + new_data.append(row) + + else: + if z_match is not None: + z_layer = float(z_match.group(0).replace('Z', '')) + if x_match is not None: + x_new = float(x_match.group(0).replace('X', '')) + update_x = True + if y_match is not None: + y_new = float(y_match.group(0).replace('Y', '')) + update_y = True + + # Compute new distance and angle according to new row + e_match = re.search(pattern_E, row) + x_old_bt, x_new_bt = x_old * np.cos(cone_angle_rad), x_new * np.cos(cone_angle_rad) + y_old_bt, y_new_bt = y_old * np.cos(cone_angle_rad), y_new * np.cos(cone_angle_rad) + dist_transformed = np.linalg.norm([x_new - x_old, y_new - y_old]) + + # Compute new values for backtransformation of row + num_segm = int(dist_transformed // maximal_length + 1) + x_vals = np.linspace(x_old_bt, x_new_bt, num_segm + 1) + y_vals = np.linspace(y_old_bt, y_new_bt, num_segm + 1) + if inward_cone and e_match is None and (update_x or update_y): + z_start = z_layer + c * np.sqrt(x_old_bt ** 2 + y_old_bt ** 2) * np.tan(cone_angle_rad) + z_end = z_layer + c * np.sqrt(x_new_bt ** 2 + y_new_bt ** 2) * np.tan(cone_angle_rad) + z_vals = np.linspace(z_start, z_end, num_segm + 1) + else: + z_vals = np.array([z_layer + c * np.sqrt(x ** 2 + y ** 2) * np.tan(cone_angle_rad) for x, y in zip(x_vals, y_vals)]) + if e_match and (np.max(z_vals) > z_max or z_max == 0): + z_max = np.max(z_vals) # save hightes point with material extruded + if e_match is None and np.max(z_vals) > z_max: + np.minimum(z_vals, (z_max + 1), z_vals) # cut away all travel moves, that are higher than max height extruded + 1 mm safety + # das hier könnte noch verschönert werden, in dem dann eine alle abgeschnittenen Werte mit einer einer geraden Linie ersetzt werden + angle_new = compute_angle_radial(x_old_bt, y_old_bt, x_new_bt, y_new_bt, inward_cone) + angle_vals = np.array( + [angle_old] + [ + compute_angle_radial(x_vals[k], y_vals[k], x_vals[k + 1], y_vals[k + 1], inward_cone) + for k in range(0, num_segm)]) + u_vals = compute_U_values(angle_vals) + distances_transformed = dist_transformed / num_segm * np.ones(num_segm) + distances_bt = np.array( + [np.linalg.norm([x_vals[i] - x_vals[i - 1], y_vals[i] - y_vals[i - 1], z_vals[i] - z_vals[i - 1]]) + for i in range(1, num_segm + 1)]) + + # Replace new row with num_seg new rows for movements and possible command rows for the U value + row = insert_Z(row, z_vals[0]) + row = replace_E(row, num_segm, 1, 1 * np.cos(cone_angle_rad)) + replacement_rows = '' + for j in range(0, num_segm): + single_row = re.sub(pattern_X, 'X' + str(round(x_vals[j + 1], 3)), row) + single_row = re.sub(pattern_Y, 'Y' + str(round(y_vals[j + 1], 3)), single_row) + single_row = re.sub(pattern_Z, 'Z' + str(round(z_vals[j + 1], 3)), single_row) + single_row = replace_E(single_row, distances_transformed[j], distances_bt[j], 1) + if np.abs(u_vals[j + 1] - u_vals[j]) <= 30: + single_row = insert_U(single_row, u_vals[j + 1]) + else: + single_row = 'G1 E-0.800 \n' + 'G1 U' + str(u_vals[j + 1]) + ' \n' + 'G1 E0.800 \n' + single_row + replacement_rows = replacement_rows + single_row + if np.amax(np.absolute(u_vals)) > 3600: + angle_reset = np.round(angle_vals[-1] * 360 / (2 * np.pi), 2) + replacement_rows = replacement_rows + 'G92 U' + str(angle_reset) + '\n' + angle_old = angle_new + else: + angle_old = u_vals[-1] * 2 * np.pi / 360 + row = replacement_rows + + if update_x: + x_old = x_new + update_x = False + if update_y: + y_old = y_new + update_y = False + + new_data.append(row) + + return new_data + + + +def translate_data(data, cone_type, translate_x, translate_y, z_desired, e_parallel, e_perpendicular): + """ + Translate the GCode in x- and y-direction. Only the lines, which describe a movement will be translated. + Additionally, if z_translation is True, the z-values will be translated such that the minimal z-value is z_desired. + This happens by traversing the list of strings twice. If cone_type is 'inward', it is assured, that all moves + with no extrusion have at least a hight of z_desired. + :param data: list + List of strings, containing the GCode + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param translate_x: float + Float, which describes the translation in x-direction + :param translate_y: float + Float, which describes the translation in y-direction + :param z_desired: float + Desired minimal z-value + :param e_parallel: float + Error parallel to nozzle + :param e_perpendicular: float + Error perpendicular to nozzle + :return: list + List of strings, which contains the translated GCode + """ + new_data = [] + pattern_X = r'X[-0-9]+[.]?[0-9]*' + pattern_Y = r'Y[-0-9]+[.]?[0-9]*' + pattern_Z = r'Z[-0-9]+[.]?[0-9]*' + pattern_E = r'E[-0-9]+[.]?[0-9]*' + pattern_U = r'U[-0-9]+[.]?[0-9]*' + pattern_G = r'\AG[01] ' + z_initialized = False + u_val = 0.0 + + for row in data: + g_match = re.search(pattern_G, row) + z_match = re.search(pattern_Z, row) + e_match = re.search(pattern_E, row) + if g_match is not None and z_match is not None and e_match is not None: + z_val = float(z_match.group(0).replace('Z', '')) + if not z_initialized: + z_min = z_val + z_initialized = True + if z_val < z_min: + z_min = z_val + z_translate = z_desired - z_min + + for row in data: + + x_match = re.search(pattern_X, row) + y_match = re.search(pattern_Y, row) + z_match = re.search(pattern_Z, row) + g_match = re.search(pattern_G, row) + u_match = re.search(pattern_U, row) + + if u_match is not None: + u_val = np.radians(float(u_match.group(0).replace('U', ''))) + + if g_match is None: + new_data.append(row) + + else: + if x_match is not None: + x_val = round(float(x_match.group(0).replace('X', '')) + translate_x - (e_parallel * np.cos(u_val)) + (e_perpendicular * np.sin(u_val)), 3) + row = re.sub(pattern_X, 'X' + str(x_val), row) + if y_match is not None: + y_val = round(float(y_match.group(0).replace('Y', '')) + translate_y - (e_parallel * np.sin(u_val)) - (e_perpendicular * np.cos(u_val)), 3) + row = re.sub(pattern_Y, 'Y' + str(y_val), row) + if z_match is not None: + z_val = max(round(float(z_match.group(0).replace('Z', '')) + z_translate, 3), z_desired) + row = re.sub(pattern_Z, 'Z' + str(z_val), row) + + new_data.append(row) + + return new_data + + +def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, x_shift, y_shift, cone_angle_deg, z_desired, e_parallel, e_perpendicular): + """ + Read GCode from file, backtransform and translate it. + :param path: string + String with the path to the GCode-file + :param output_dir: string + path of directory, where transformed STL-file will be saved + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param maximal_length: float + Maximal length of a segment in the original GCode + :param angle_comp: string + String, which describes the way, the angle is computed; one of 'radial', 'tangential', 'mixed' + :param x_shift: float + Float, which describes the translation in x-direction + :param y_shift: float + Float, which describes the translation in y-direction + :param cone_angle_deg: int + Angle of transformation cone in degrees + :param z_desired: float + Desired minimal z-value + :param e_parallel: float + Error parallel to nozzle + :param e_perpendicular: float + Error perpendicular to nozzle + :return: None + """ + start = time.time() + cone_angle_rad = cone_angle_deg / 180 * np.pi + + if angle_comp == 'radial': + backtransform_data = backtransform_data_radial + + with open(path, 'r') as f_gcode: + data = f_gcode.readlines() + data_bt = backtransform_data(data, cone_type, maximal_length, cone_angle_rad) + data_bt_string = ''.join(data_bt) + data_bt = [row + ' \n' for row in data_bt_string.split('\n')] + data_bt = translate_data(data_bt, cone_type, x_shift, y_shift, z_desired, e_parallel, e_perpendicular) + data_bt_string = ''.join(data_bt) + + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] + file_name = file_name.replace('.gcode', '_bt_' + cone_type + '_' + angle_comp + '.gcode') + output_path = output_dir + '/' + file_name + with open(output_path, 'w+') as f_gcode_bt: + f_gcode_bt.write(data_bt_string) + + end = time.time() + print('GCode generated in {:.1f}s, saved in {}'.format(end - start, output_path)) + return None + + +# ----------------------------------------------------------------------------------------- +# Anwenden der Funktionen auf ein STL File +# ----------------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='GCODE-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_backtransformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-a', '--angle-type', dest='angle_type', type=str, default='radial', help='The angle type: radial or tangential.') + parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') + parser.add_argument('-m', '--max-length', dest='max_length', type=int, default=0.5, help='Maximal length of a segment in mm.') + parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=100, help='Shift the code in x-direction.') + parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=100, help='Shift the code in y-direction.') + parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=40.2, help='Desired height in z-direction.') + parser.add_argument('-ep', '--err-parallel', dest='err_parallel', type=float, default=0, help='Error in parallel direction.') + parser.add_argument('-et', '--err-perpendicular', dest='err_perpendicular', type=float, default=0, help='Error in perpendicular direction.') + args = parser.parse_args() + + try: + # G-Code backtransformation function call + backtransform_file(path=args.file_path, + output_dir=args.dir_backtransformed, + cone_type=args.transformation_type, + maximal_length=args.max_length, + angle_comp=args.angle_type, + x_shift=args.delta_x, + y_shift=args.delta_y, + cone_angle_deg=15, + z_desired=args.z_height, + e_parallel=args.err_parallel, + e_perpendicular=args.err_perpendicular + ) + + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: os._exit(0) \ No newline at end of file diff --git a/Scripts for Variable Angle/Transformation_STL_var_angle.py b/Scripts for Variable Angle/Transformation_STL_var_angle.py index 3978b4d..cc6d587 100644 --- a/Scripts for Variable Angle/Transformation_STL_var_angle.py +++ b/Scripts for Variable Angle/Transformation_STL_var_angle.py @@ -1,139 +1,139 @@ -import numpy as np -from stl import mesh -import time -import sys -import os -import argparse - -def transformation_kegel(points, cone_angle_rad, cone_type): - """ - Computes the cone-transformation (x', y', z') = (x / cos(angle), y / cos(angle), z + \sqrt{x^{2} + y^{2}} * tan(angle)) - for a list of points - :param points: array - array of points of shape ( , 3) - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :return: array - array of transformed points, of same shape as input array - """ - if cone_type == 'outward': - c = 1 - elif cone_type == 'inward': - c = -1 - else: - raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) - f = (lambda x, y, z: np.array([x/np.cos(cone_angle_rad), y/np.cos(cone_angle_rad), z + c * np.sqrt(x**2 + y**2)*np.tan(cone_angle_rad)])) - points_transformed = list(map(f, points[:, 0], points[:, 1], points[:, 2])) - return np.array(points_transformed) - - -def refinement_four_triangles(triangle): - """ - Compute a refinement of a triangle. On every side, the midpoint is added. The three corner points and three - midpoints result in four smaller triangles. - :param triangle: array - array of three points of shape (3, 3) (one triangle) - :return: array - array of shape (4, 3, 3) of four triangles - """ - point1 = triangle[0] - point2 = triangle[1] - point3 = triangle[2] - midpoint12 = (point1 + point2) / 2 - midpoint23 = (point2 + point3) / 2 - midpoint31 = (point3 + point1) / 2 - triangle1 = np.array([point1, midpoint12, midpoint31]) - triangle2 = np.array([point2, midpoint23, midpoint12]) - triangle3 = np.array([point3, midpoint31, midpoint23]) - triangle4 = np.array([midpoint12, midpoint23, midpoint31]) - return np.array([triangle1, triangle2, triangle3, triangle4]) - - -def refinement_triangulation(triangle_array, num_iterations): - """ - Compute a refinement of a triangulation using the refinement_four_triangles function. - The number of iteration defines, how often the triangulation has to be refined; n iterations lead to - 4^n times many triangles. - :param triangle_array: array - array of shape (num_triangles, 3, 3) of triangles - :param num_iterations: int - :return: array - array of shape (num_triangles*4^num_iterations, 3, 3) of triangles - """ - refined_array = triangle_array - for i in range(0, num_iterations): - n_triangles = refined_array.shape[0]*4 - refined_array = np.array(list(map(refinement_four_triangles, refined_array))) - refined_array = np.reshape(refined_array, (n_triangles, 3, 3)) - return refined_array - - -def transformation_STL_file(path, output_dir, cone_type, cone_angle_deg, nb_iterations): - """ - Read a stl-file, refine the triangulation and transform it according to the cone-transformation - :param path: string - path to the stl file - :param output_dir: string - path of directory, where transformed STL-file will be saved - :param cone_type: string - String, either 'outward' or 'inward', defines which transformation should be used - :param cone_angle: int - angle to transform the part - :param nb_iterations: int - number of iterations, the triangulation should be refined before the transformation - :return: mesh object - transformed triangulation as mesh object which can be stored as stl file - """ - start = time.time() - cone_angle_rad = cone_angle_deg / 180 * np.pi - my_mesh = mesh.Mesh.from_file(path) - vectors = my_mesh.vectors - vectors_refined = refinement_triangulation(vectors, nb_iterations) - vectors_refined = np.reshape(vectors_refined, (-1, 3)) - vectors_transformed = transformation_kegel(vectors_refined, cone_angle_rad, cone_type) - vectors_transformed = np.reshape(vectors_transformed, (-1, 3, 3)) - my_mesh_transformed = np.zeros(vectors_transformed.shape[0], dtype=mesh.Mesh.dtype) - my_mesh_transformed['vectors'] = vectors_transformed - my_mesh_transformed = mesh.Mesh(my_mesh_transformed) - - if not os.path.exists(output_dir): - os.mkdir(output_dir) - file_name = path - pos = path.rfind('/') - if pos > -1: - file_name = path[pos:] - file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') - output_path = output_dir + '/' + file_name - my_mesh_transformed.save(output_path) - end = time.time() - print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) - return None - - -#----------------------------------------------------------------------------------------- -# Anwenden der Funktionen auf ein STL File -#----------------------------------------------------------------------------------------- -if __name__ == '__main__': - parser = argparse.ArgumentParser(description="") - parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) - parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) - parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') - parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') - parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') - args = parser.parse_args() - - try: - # STL transformation function call - transformation_STL_file(path=args.file_path, - output_dir=args.dir_transformed, - cone_type=args.transformation_type, - cone_angle_deg=args.cone_angle_deg, - nb_iterations=args.number_iterations - ) - - except KeyboardInterrupt: - print("Interrupted.") - try: - sys.exit(0) - except SystemExit: +import numpy as np +from stl import mesh +import time +import sys +import os +import argparse + +def transformation_kegel(points, cone_angle_rad, cone_type): + """ + Computes the cone-transformation (x', y', z') = (x / cos(angle), y / cos(angle), z + \sqrt{x^{2} + y^{2}} * tan(angle)) + for a list of points + :param points: array + array of points of shape ( , 3) + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :return: array + array of transformed points, of same shape as input array + """ + if cone_type == 'outward': + c = 1 + elif cone_type == 'inward': + c = -1 + else: + raise ValueError('{} is not a admissible type for the transformation'.format(cone_type)) + f = (lambda x, y, z: np.array([x/np.cos(cone_angle_rad), y/np.cos(cone_angle_rad), z + c * np.sqrt(x**2 + y**2)*np.tan(cone_angle_rad)])) + points_transformed = list(map(f, points[:, 0], points[:, 1], points[:, 2])) + return np.array(points_transformed) + + +def refinement_four_triangles(triangle): + """ + Compute a refinement of a triangle. On every side, the midpoint is added. The three corner points and three + midpoints result in four smaller triangles. + :param triangle: array + array of three points of shape (3, 3) (one triangle) + :return: array + array of shape (4, 3, 3) of four triangles + """ + point1 = triangle[0] + point2 = triangle[1] + point3 = triangle[2] + midpoint12 = (point1 + point2) / 2 + midpoint23 = (point2 + point3) / 2 + midpoint31 = (point3 + point1) / 2 + triangle1 = np.array([point1, midpoint12, midpoint31]) + triangle2 = np.array([point2, midpoint23, midpoint12]) + triangle3 = np.array([point3, midpoint31, midpoint23]) + triangle4 = np.array([midpoint12, midpoint23, midpoint31]) + return np.array([triangle1, triangle2, triangle3, triangle4]) + + +def refinement_triangulation(triangle_array, num_iterations): + """ + Compute a refinement of a triangulation using the refinement_four_triangles function. + The number of iteration defines, how often the triangulation has to be refined; n iterations lead to + 4^n times many triangles. + :param triangle_array: array + array of shape (num_triangles, 3, 3) of triangles + :param num_iterations: int + :return: array + array of shape (num_triangles*4^num_iterations, 3, 3) of triangles + """ + refined_array = triangle_array + for i in range(0, num_iterations): + n_triangles = refined_array.shape[0]*4 + refined_array = np.array(list(map(refinement_four_triangles, refined_array))) + refined_array = np.reshape(refined_array, (n_triangles, 3, 3)) + return refined_array + + +def transformation_STL_file(path, output_dir, cone_type, cone_angle_deg, nb_iterations): + """ + Read a stl-file, refine the triangulation and transform it according to the cone-transformation + :param path: string + path to the stl file + :param output_dir: string + path of directory, where transformed STL-file will be saved + :param cone_type: string + String, either 'outward' or 'inward', defines which transformation should be used + :param cone_angle: int + angle to transform the part + :param nb_iterations: int + number of iterations, the triangulation should be refined before the transformation + :return: mesh object + transformed triangulation as mesh object which can be stored as stl file + """ + start = time.time() + cone_angle_rad = cone_angle_deg / 180 * np.pi + my_mesh = mesh.Mesh.from_file(path) + vectors = my_mesh.vectors + vectors_refined = refinement_triangulation(vectors, nb_iterations) + vectors_refined = np.reshape(vectors_refined, (-1, 3)) + vectors_transformed = transformation_kegel(vectors_refined, cone_angle_rad, cone_type) + vectors_transformed = np.reshape(vectors_transformed, (-1, 3, 3)) + my_mesh_transformed = np.zeros(vectors_transformed.shape[0], dtype=mesh.Mesh.dtype) + my_mesh_transformed['vectors'] = vectors_transformed + my_mesh_transformed = mesh.Mesh(my_mesh_transformed) + + if not os.path.exists(output_dir): + os.mkdir(output_dir) + file_name = path + pos = path.rfind('/') + if pos > -1: + file_name = path[pos:] + file_name = file_name.replace('.stl', '_' + cone_type + '_transformed.stl') + output_path = output_dir + '/' + file_name + my_mesh_transformed.save(output_path) + end = time.time() + print('STL file generated in {:.1f}s, saved in {}'.format(end - start, output_path)) + return None + + +#----------------------------------------------------------------------------------------- +# Anwenden der Funktionen auf ein STL File +#----------------------------------------------------------------------------------------- +if __name__ == '__main__': + parser = argparse.ArgumentParser(description="") + parser.add_argument('-i', '--infile', dest='file_path', type=str, help='STL-file to apply the transformation to.', required=True) + parser.add_argument('-o', '--outpath', dest='dir_transformed', type=str, help='Folder to store the output to.', required=True) + parser.add_argument('-t', '--trans-type', dest='transformation_type', type=str, default='inward', help='The transformation type: inward or outward.') + parser.add_argument('-n', '--number-iterations', dest='number_iterations', type=int, default=1, help='The numbers of itartions for triangulation refinement.') + parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') + args = parser.parse_args() + + try: + # STL transformation function call + transformation_STL_file(path=args.file_path, + output_dir=args.dir_transformed, + cone_type=args.transformation_type, + cone_angle_deg=args.cone_angle_deg, + nb_iterations=args.number_iterations + ) + + except KeyboardInterrupt: + print("Interrupted.") + try: + sys.exit(0) + except SystemExit: os._exit(0) \ No newline at end of file From 29ec8627ceb604f82c41056fde3aa1d8ff607609 Mon Sep 17 00:00:00 2001 From: MKesenheimer Date: Mon, 10 Oct 2022 15:35:59 +0200 Subject: [PATCH 7/7] Modified default values --- .../Backtransformation_GCode_var_angle.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py index 065b188..3ffbdc3 100644 --- a/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py +++ b/Scripts for Variable Angle/Backtransformation_GCode_var_angle.py @@ -415,9 +415,9 @@ def backtransform_file(path, output_dir, cone_type, maximal_length, angle_comp, parser.add_argument('-a', '--angle-type', dest='angle_type', type=str, default='radial', help='The angle type: radial or tangential.') parser.add_argument('-c', '--cone-angle', dest='cone_angle_deg', type=float, default=15, help='Cone angle in degree.') parser.add_argument('-m', '--max-length', dest='max_length', type=int, default=0.5, help='Maximal length of a segment in mm.') - parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=100, help='Shift the code in x-direction.') - parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=100, help='Shift the code in y-direction.') - parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=40.2, help='Desired height in z-direction.') + parser.add_argument('-x', '--delta-x', dest='delta_x', type=int, default=0, help='Shift the code in x-direction.') + parser.add_argument('-y', '--delta-y', dest='delta_y', type=int, default=0, help='Shift the code in y-direction.') + parser.add_argument('-z', '--z-height', dest='z_height', type=float, default=0, help='Desired height in z-direction.') parser.add_argument('-ep', '--err-parallel', dest='err_parallel', type=float, default=0, help='Error in parallel direction.') parser.add_argument('-et', '--err-perpendicular', dest='err_perpendicular', type=float, default=0, help='Error in perpendicular direction.') args = parser.parse_args()