################################################################################ # iso.py # # Simple ISO NC code creator # # Hirutso Enni, 2009-01-13 from . import nc import math from .format import Format from .format import * import bpy ################################################################################ class Creator(nc.Creator): def __init__(self): nc.Creator.__init__(self) # internal variables self.a = 0 self.b = 0 self.c = 0 self.f = Address("F", fmt=Format(number_of_decimal_places=2)) self.fh = None self.fv = None self.fhv = False self.g_plane = Address("G", fmt=Format(number_of_decimal_places=0)) self.g_list = [] self.i = 0 self.j = 0 self.k = 0 self.m = [] self.r = 0 self.s = AddressPlusMinus("S", fmt=Format(number_of_decimal_places=2), modal=False) self.t = None self.x = None self.y = None self.z = None self.g0123_modal = False self.drill_modal = False self.prev_f = "" self.prev_g0123 = "" self.prev_drill = "" self.prev_retract = "" self.prev_z = "" self.useCrc = False self.useCrcCenterline = False self.gCRC = "" self.fmt = Format() self.absolute_flag = True self.ffmt = Format(number_of_decimal_places=2) self.sfmt = Format(number_of_decimal_places=1) self.in_quadrant_splitting = False self.in_canned_cycle = False self.first_drill_pos = True self.shift_x = 0.0 self.shift_y = 0.0 self.shift_z = 0.0 self.start_of_line = False self.internal_coolant_on = None self.g98_not_g99 = None # True for G98 ouput, False for G99 output self.current_fixture = None self.fixture_wanted = "54" self.move_done_since_tool_change = False self.tool_defn_params = {} self.program_id = None self.current_sub_id = None self.subroutine_files = [] self.program_name = None self.temp_file_to_append_on_close = None self.fixture_order = ["54", "55", "56", "57", "58", "59"] for i in range(1, 50): self.fixture_order.append("54." + str(i)) self.output_disabled = False self.z_for_g43 = None # optional settings self.arc_centre_absolute = False self.arc_centre_positive = False self.drillExpanded = False self.dwell_allowed_in_G83 = False self.can_do_helical_arcs = True self.z_for_g53 = ( None # set this to a value to output G53 Zvalue in tool change and at program end ) self.output_h_and_d_at_tool_change = False self.output_block_numbers = True self.start_block_number = 10 self.block_number_increment = 10 self.block_number_restart_after = None self.output_tool_definitions = True self.output_tool_change = True self.output_G43_on_tool_change_line = False self.output_internal_coolant_commands = False self.output_g98_and_g99 = True self.output_g43_z_before_drilling_if_g98 = False self.output_cutviewer_comments = False self.output_fixtures = False self.use_this_program_id = None self.subroutines_in_own_files = False self.pattern_done_with_subroutine = False self.output_comment_before_tool_change = True self.output_arcs_as_lines = False self.m_codes_on_their_own_line = False ############################################################################ # Codes def SPACE_STR(self): return "" def SPACE(self): if self.start_of_line == True: self.start_of_line = False return "" else: return self.SPACE_STR() def FORMAT_FEEDRATE(self): return "%.2f" def FORMAT_ANG(self): return "%.1f" def FORMAT_TIME(self): return self.fmt def BLOCK(self): return "N%i" def COMMENT(self, comment): return "(%s)" % comment def VARIABLE(self): return "#%i" def VARIABLE_SET(self): return "=%.3f" def PROGRAM(self): return "O%i" def PROGRAM_END(self): return "M02" def SUBPROG_CALL(self): return "M98" + self.SPACE() + "P%i" def SUBPROG_END(self): return "M99" def STOP_OPTIONAL(self): return "M01" def STOP(self): return "M00" def IMPERIAL(self): return "G20" def METRIC(self): return "G21" def ABSOLUTE(self): return "G90" def INCREMENTAL(self): return "G91" def SET_TEMPORARY_COORDINATE_SYSTEM(self): return "G92" def REMOVE_TEMPORARY_COORDINATE_SYSTEM(self): return "G92.1" def POLAR_ON(self): return "G16" def POLAR_OFF(self): return "G15" def PLANE_XY(self): return "17" def PLANE_XZ(self): return "18" def PLANE_YZ(self): return "19" def TOOL(self): return "T%i" + self.SPACE() + "M06" def TOOL_DEFINITION(self): return "G10" + self.SPACE() + "L1" def WORKPLANE(self): return "G%i" def WORKPLANE_BASE(self): return 53 def SPINDLE_CW(self): return "M03" def SPINDLE_CCW(self): return "M04" def COOLANT_OFF(self): return "M09" def COOLANT_MIST(self): return "M07" def COOLANT_FLOOD(self): return "M08" def GEAR_OFF(self): return "?" def GEAR(self): return "M%i" def GEAR_BASE(self): return 37 def RAPID(self): return "G00" def FEED(self): return "G01" def ARC_CW(self): return "G02" def ARC_CCW(self): return "G03" def DWELL(self, dwell): return "G04" + self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell)) def DRILL(self): return "G81" def DRILL_WITH_DWELL(self, dwell): return "G82" + self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell)) def PECK_DRILL(self): return "G83" def PECK_DEPTH(self, depth): return "Q" + (self.fmt.string(depth)) def RETRACT(self, height): return "R" + (self.fmt.string(height)) def END_CANNED_CYCLE(self): return "G80" def TAP(self): return "G84" def TAP_DEPTH(self, depth): return "K" + (self.fmt.string(depth)) def INTERNAL_COOLANT_ON(self): return "M18" def INTERNAL_COOLANT_OFF(self): return "M9" def X(self): return "X" def Y(self): return "Y" def Z(self): return "Z" def A(self): return "A" def B(self): return "B" def C(self): return "C" def CENTRE_X(self): return "I" def CENTRE_Y(self): return "J" def CENTRE_Z(self): return "K" def RADIUS(self): return "R" def TIME(self): return "P" def PROBE_TOWARDS_WITH_SIGNAL(self): return "G38.2" def PROBE_TOWARDS_WITHOUT_SIGNAL(self): return "G38.3" def PROBE_AWAY_WITH_SIGNAL(self): return "G38.4" def PROBE_AWAY_WITHOUT_SIGNAL(self): return "G38.5" def MACHINE_COORDINATES(self): return "G53" def EXACT_PATH_MODE(self): return "G61" def EXACT_STOP_MODE(self): return "G61.1" def RETRACT_TO_CLEARANCE(self): return "G98" def RETRACT_TO_STANDOFF(self): return "G99" ############################################################################ # Internals def write(self, s): if self.output_disabled == False: nc.Creator.write(self, s) if "\n" in s: self.start_of_line = s[-1] == "\n" def write_feedrate(self): self.f.write(self) def write_preps(self): i = 0 if self.g_plane.str: i += 1 self.g_plane.write(self) for g in self.g_list: if i > 0: self.write("\n" if self.m_codes_on_their_own_line else self.SPACE()) self.write(g) i += 1 self.g_list = [] def write_misc(self): if len(self.m): self.write("\n" if self.m_codes_on_their_own_line else self.SPACE()) self.write(self.m.pop()) def write_spindle(self): if self.s.str: self.write("\n" if self.m_codes_on_their_own_line else "") self.s.write(self) def output_fixture(self): if self.current_fixture != self.fixture_wanted: self.current_fixture = self.fixture_wanted self.g_list.append("G" + str(self.current_fixture)) def increment_fixture(self): for i in range(0, len(self.fixture_order) - 1): if self.fixture_order[i] == self.fixture_wanted: self.fixture_wanted = self.fixture_order[i + 1] return raise "too many fixtures wanted!" def get_fixture(self): return self.fixture_wanted def set_fixture(self, fixture): self.fixture_wanted = fixture ############################################################################ # Programs def program_begin(self, id, name=""): if self.use_this_program_id: id = self.use_this_program_id if self.PROGRAM() != None: self.writem([(self.PROGRAM() % id), self.SPACE(), (self.COMMENT(name))]) self.write("\n") self.program_id = id self.program_name = name def add_stock(self, type_name, params): if self.output_cutviewer_comments: self.write("(STOCK/" + type_name) for param in params: self.write(",") self.write(str(param)) self.write(")\n") def program_stop(self, optional=False): if optional: self.write(self.SPACE() + self.STOP_OPTIONAL() + "\n") self.prev_g0123 = "" else: self.write(self.STOP() + "\n") self.prev_g0123 = "" def number_file(self, filename): import tempfile temp_filename = tempfile.gettempdir() + "/renumbering.txt" # make a copy of file f_in = open(filename, "r") f_out = open(temp_filename, "w") while True: line = f_in.readline() if len(line) == 0: break f_out.write(line) f_in.close() f_out.close() # read copy f_in = open(temp_filename, "r") f_out = open(filename, "w") n = self.start_block_number while True: line = f_in.readline() if len(line) == 0: break f_out.write(self.BLOCK() % n + self.SPACE() + line) n += self.block_number_increment if self.block_number_restart_after != None: if n >= self.block_number_restart_after: n = self.start_block_number f_in.close() f_out.close() def program_end(self): if self.z_for_g53 != None: self.write( self.SPACE() + self.MACHINE_COORDINATES() + self.SPACE() + "Z" + self.fmt.string(self.z_for_g53) + "\n" ) self.write(self.SPACE() + self.PROGRAM_END() + "\n") if self.temp_file_to_append_on_close != None: f_in = open(self.temp_file_to_append_on_close, "r") while True: line = f_in.readline() if len(line) == 0: break self.write(line) f_in.close() self.file_close() if self.output_block_numbers: # number every line of the file afterwards self.number_file(self.filename) for f in self.subroutine_files: self.number_file(f) def flush_nc(self): if len(self.g_list) == 0 and len(self.m) == 0: return self.write_preps() self.write_misc() self.write("\n") ############################################################################ # Subprograms def make_subroutine_name(self, id): s = self.filename for i in reversed(range(0, len(s))): if s[i] == ".": return s[0:i] + "sub" + str(id) + s[i:] # '.' not found return s + "sub" + str(id) def sub_begin(self, id, name=None): if id == None: if self.current_sub_id == None: self.current_sub_id = self.program_id self.current_sub_id += 1 id = self.current_sub_id if name == None: name = self.program_name + " subroutine " + str(id) self.save_file = self.file if self.subroutines_in_own_files: new_name = self.make_subroutine_name(id) self.file = open(new_name, "w") self.subroutine_files.append(new_name) else: # use temporary file import tempfile temp_filename = tempfile.gettempdir() + "/subroutines.txt" if self.temp_file_to_append_on_close == None: self.temp_file_to_append_on_close = temp_filename self.file = open(temp_filename, "w") else: self.file = open(temp_filename, "a") if self.PROGRAM() != None: self.write((self.PROGRAM() % id) + self.SPACE() + (self.COMMENT(name))) self.write("\n") def sub_call(self, id): if id == None: id = self.current_sub_id self.write(self.SPACE() + (self.SUBPROG_CALL() % id) + "\n") def sub_end(self): self.write(self.SPACE() + self.SUBPROG_END() + "\n") self.file.close() self.file = self.save_file def disable_output(self): self.output_disabled = True def enable_output(self): self.output_disabled = False ############################################################################ # Settings def imperial(self): self.g_list.append(self.IMPERIAL()) self.fmt.number_of_decimal_places = 4 def metric(self): self.g_list.append(self.METRIC()) self.fmt.number_of_decimal_places = 3 def absolute(self): self.g_list.append(self.ABSOLUTE()) self.absolute_flag = True def incremental(self): self.g_list.append(self.INCREMENTAL()) self.absolute_flag = False def polar(self, on=True): if on: self.g_list.append(self.POLAR_ON()) else: self.g_list.append(self.POLAR_OFF()) def set_plane(self, plane): if plane == 0: self.g_plane.set(self.PLANE_XY()) elif plane == 1: self.g_plane.set(self.PLANE_XZ()) elif plane == 2: self.g_plane.set(self.PLANE_YZ()) def set_temporary_origin(self, x=None, y=None, z=None, a=None, b=None, c=None): self.write(self.SPACE() + (self.SET_TEMPORARY_COORDINATE_SYSTEM())) if x != None: self.write(self.SPACE() + "X " + (self.fmt.string(x + self.shift_x))) if y != None: self.write(self.SPACE() + "Y " + (self.fmt.string(y + self.shift_y))) if z != None: self.write(self.SPACE() + "Z " + (self.fmt.string(z + self.shift_z))) if a != None: self.write(self.SPACE() + "A " + (self.fmt.string(a))) if b != None: self.write(self.SPACE() + "B " + (self.fmt.string(b))) if c != None: self.write(self.SPACE() + "C " + (self.fmt.string(c))) self.write("\n") def remove_temporary_origin(self): self.write(self.SPACE() + (self.REMOVE_TEMPORARY_COORDINATE_SYSTEM())) self.write("\n") ############################################################################ # new graphics origin- make a new coordinate system and snap it onto the geometry # the toolpath generated should be translated def translate(self, x=None, y=None, z=None): self.shift_x = -x self.shift_y = -y self.shift_z = -z ############################################################################ # Tools def tool_change(self, id): if self.output_comment_before_tool_change: if id in self.tool_defn_params: self.comment("tool change to " + self.tool_defn_params[id]["name"]) if self.output_cutviewer_comments: import cutviewer if id in self.tool_defn_params: cutviewer.tool_defn(self, id, self.tool_defn_params[id]) if (self.t != None) and (self.z_for_g53 != None): self.write("G53 Z" + str(self.z_for_g53) + "\n") self.write(self.SPACE() + (self.TOOL() % id)) if self.output_G43_on_tool_change_line == True: self.write(self.SPACE() + "G43") self.write("\n") if self.output_h_and_d_at_tool_change == True: if self.output_G43_on_tool_change_line == False: self.write(self.SPACE() + "G43") self.write(self.SPACE() + "D" + str(id) + self.SPACE() + "H" + str(id) + "\n") self.t = id self.move_done_since_tool_change = False def tool_defn(self, id, name="", params=None): self.tool_defn_params[id] = params if self.output_tool_definitions: self.write(self.SPACE() + self.TOOL_DEFINITION()) self.write(self.SPACE() + ("P%i" % id) + " ") if params["diameter"] != None: self.write(self.SPACE() + ("R%.3f" % (float(params["diameter"]) / 2))) if params["cutting edge height"] != None: self.write(self.SPACE() + "Z%.3f" % float(params["cutting edge height"])) self.write("\n") def offset_radius(self, id, radius=None): pass def offset_length(self, id, length=None): pass def current_tool(self): return self.t ############################################################################ # Datums def datum_shift(self, x=None, y=None, z=None, a=None, b=None, c=None): pass def datum_set(self, x=None, y=None, z=None, a=None, b=None, c=None): pass # This is the coordinate system we're using. G54->G59, G59.1, G59.2, G59.3 # These are selected by values from 1 to 9 inclusive. def workplane(self, id): if (id >= 1) and (id <= 6): self.g_list.append(self.WORKPLANE() % (id + self.WORKPLANE_BASE())) if (id >= 7) and (id <= 9): self.g_list.append( ((self.WORKPLANE() % (6 + self.WORKPLANE_BASE())) + (".%i" % (id - 6))) ) ############################################################################ ## Rates + Modes def feedrate(self, f): self.f.set(f) self.fhv = False def feedrate_hv(self, fh, fv): self.fh = fh self.fv = fv self.fhv = True def calc_feedrate_hv(self, h, v): if math.fabs(v) > math.fabs(h * 2): # some horizontal, so it should be fine to use the horizontal feed rate self.f.set(self.fv) else: # not much, if any horizontal component, so use the vertical feed rate self.f.set(self.fh) # def spindle(self, s, clockwise): # if clockwise == True: # self.s.set(s, self.SPINDLE_CW(), self.SPINDLE_CCW()) # else: # self.s.set(s, self.SPINDLE_CCW(), self.SPINDLE_CW()) def spindle(self, s, clockwise): # EXPERIMENTAL -- grbl only # Get machine settings machine = bpy.context.scene.cam_machine if ( machine.spindle_slow_start_enable and s > machine.spindle_min + machine.spindle_slow_start_skip_threshold ): # Generate slow start sequence steps = machine.spindle_slow_start_steps total_time = machine.spindle_slow_start_total_time step_time = total_time / steps # Calculate speed increments speed_range = s - machine.spindle_min step_increment = speed_range / steps # Start at minimum speed current_speed = machine.spindle_min if clockwise: self.s.set(current_speed, self.SPINDLE_CW(), self.SPINDLE_CCW()) else: self.s.set(current_speed, self.SPINDLE_CCW(), self.SPINDLE_CW()) self.write_spindle() # Ramp up through intermediate steps for step in range(1, steps + 1): if step < steps: current_speed = machine.spindle_min + (step_increment * step) else: current_speed = s # Final target speed # Dwell for step time self.dwell(step_time) # Set next speed current_speed = int(current_speed) if clockwise: self.s.set(current_speed, self.SPINDLE_CW(), self.SPINDLE_CCW()) else: self.s.set(current_speed, self.SPINDLE_CCW(), self.SPINDLE_CW()) self.write_spindle() else: # Normal spindle start (existing code) if clockwise: self.s.set(s, self.SPINDLE_CW(), self.SPINDLE_CCW()) else: self.s.set(s, self.SPINDLE_CCW(), self.SPINDLE_CW()) def coolant(self, mode=0): if mode <= 0: self.m.append(self.COOLANT_OFF()) elif mode == 1: self.m.append(self.COOLANT_MIST()) elif mode == 2: self.m.append(self.COOLANT_FLOOD()) def gearrange(self, gear=0): if gear <= 0: self.m.append(self.GEAR_OFF()) elif gear <= 4: self.m.append(self.GEAR() % (gear + GEAR_BASE())) ############################################################################ # Moves def rapid(self, x=None, y=None, z=None, a=None, b=None, c=None): (x, y, z, a, b, c, axis_count) = self.filter_xyz(x, y, z, a, b, c) if axis_count == 0: return self.on_move() if self.g0123_modal: if self.prev_g0123 != self.RAPID(): self.write(self.SPACE() + self.RAPID()) self.prev_g0123 = self.RAPID() else: self.write(self.SPACE() + self.RAPID()) self.write_preps() if x != None: if self.absolute_flag: self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) else: dx = x - self.x self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) self.x = x if y != None: if self.absolute_flag: self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) else: dy = y - self.y self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) self.y = y if z != None: if self.absolute_flag: self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) else: dz = z - self.z self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) self.z = z if a != None: if self.absolute_flag: self.write(self.SPACE() + self.A() + (self.fmt.string(a))) else: da = a - self.a self.write(self.SPACE() + self.A() + (self.fmt.string(da))) self.a = a if b != None: if self.absolute_flag: self.write(self.SPACE() + self.B() + (self.fmt.string(b))) else: db = b - self.b self.write(self.SPACE() + self.B() + (self.fmt.string(db))) self.b = b if c != None: if self.absolute_flag: self.write(self.SPACE() + self.C() + (self.fmt.string(c))) else: dc = c - self.c self.write(self.SPACE() + self.C() + (self.fmt.string(dc))) self.c = c self.write_spindle() self.write_misc() self.write("\n") def feed(self, x=None, y=None, z=None, a=None, b=None, c=None): (x, y, z, a, b, c, axis_count) = self.filter_xyz(x, y, z, a, b, c) if axis_count == 0: return self.on_move() if self.g0123_modal: if self.prev_g0123 != self.FEED(): self.writem([self.SPACE(), self.FEED()]) self.prev_g0123 = self.FEED() else: self.write(self.SPACE() + self.FEED()) self.write_preps() dx = dy = dz = 0 if x != None: dx = x - self.x if self.absolute_flag: self.writem([self.SPACE(), self.X(), (self.fmt.string(x + self.shift_x))]) else: self.writem([self.SPACE(), self.X(), (self.fmt.string(dx))]) self.x = x if y != None: dy = y - self.y if self.absolute_flag: self.writem([self.SPACE(), self.Y(), (self.fmt.string(y + self.shift_y))]) else: self.writem([self.SPACE(), self.Y(), (self.fmt.string(dy))]) self.y = y if z != None: dz = z - self.z if self.absolute_flag: self.writem([self.SPACE(), self.Z(), (self.fmt.string(z + self.shift_z))]) else: self.writem([self.SPACE(), self.Z(), (self.fmt.string(dz))]) self.z = z if a != None: da = a - self.a if self.absolute_flag: self.writem([self.SPACE(), self.A(), (self.fmt.string(a))]) else: self.writem([self.SPACE(), self.A(), (self.fmt.string(da))]) self.a = a if b != None: db = b - self.b if self.absolute_flag: self.writem([self.SPACE(), self.B(), (self.fmt.string(b))]) else: self.writem([self.SPACE(), self.B(), (self.fmt.string(db))]) self.b = b if c != None: dc = c - self.c if self.absolute_flag: self.writem([self.SPACE(), self.C(), (self.fmt.string(c))]) else: self.writem([self.SPACE(), self.C(), (self.fmt.string(dc))]) self.c = c if self.fhv: self.calc_feedrate_hv(math.sqrt(dx * dx + dy * dy), math.fabs(dz)) self.write_feedrate() self.write_spindle() self.write_misc() self.write("\n") def filter_xyz(self, x=None, y=None, z=None, a=None, b=None, c=None): """Check if x,y,z,a,b,c are the same and set them to None if they are return value = (x,y,z,a,b,c,count) where count is the number of axis moves left. """ rv = [x, y, z, a, b, c, 0] comparisons = ( (x, self.shift_x, self.x), (y, self.shift_y, self.y), (z, self.shift_z, self.z), (a, 0, self.a), (b, 0, self.b), (c, 0, self.c), ) for i, (new_val, shift, current_val) in enumerate(comparisons): if new_val is not None: if self.fmt.string(new_val + shift) == self.fmt.string(current_val): rv[i] = None else: rv[6] += 1 return rv def get_quadrant(self, dx, dy): if dx < 0: if dy < 0: return 2 else: return 1 else: if dy < 0: return 3 else: return 0 def quadrant_start(self, q, i, j, rad): while q > 3: q = q - 4 if q == 0: return i + rad, j if q == 1: return i, j + rad if q == 2: return i - rad, j return i, j - rad def quadrant_end(self, q, i, j, rad): return self.quadrant_start(q + 1, i, j, rad) def get_arc_angle(self, sdx, sdy, edx, edy, cw): angle_s = math.atan2(sdy, sdx) angle_e = math.atan2(edy, edx) if cw: if angle_s < angle_e: angle_s = angle_s + 2 * math.pi else: if angle_e < angle_s: angle_e = angle_e + 2 * math.pi return angle_e - angle_s def arc(self, cw, x=None, y=None, z=None, i=None, j=None, k=None, r=None): (x, y, z, _, _, _, axis_count) = self.filter_xyz(x, y, z) if axis_count == 0: return if self.output_arcs_as_lines or ( self.can_do_helical_arcs == False and self.in_quadrant_splitting == False and (z != None) and (math.fabs(z - self.z) > 0.000001) and (self.fmt.string(z) != self.fmt.string(self.z)) ): # split the helical arc into little line feed moves if x == None: x = self.x if y == None: y = self.y sdx = self.x - i sdy = self.y - j edx = x - i edy = y - j radius = math.sqrt(sdx * sdx + sdy * sdy) arc_angle = self.get_arc_angle(sdx, sdy, edx, edy, cw) angle_start = math.atan2(sdy, sdx) tolerance = 0.02 angle_step = 2.0 * math.atan(math.sqrt(tolerance / (radius - tolerance))) segments = int(math.fabs(arc_angle / angle_step) + 1) angle_step = arc_angle / segments angle = angle_start if z != None: z_step = float(z - self.z) / segments next_z = self.z for p in range(0, segments): angle = angle + angle_step next_x = i + radius * math.cos(angle) next_y = j + radius * math.sin(angle) if z == None: next_z = None else: next_z = next_z + z_step self.feed(next_x, next_y, next_z) return if self.arc_centre_positive == True and self.in_quadrant_splitting == False: # split in to quadrant arcs self.in_quadrant_splitting = True if x == None: x = self.x if y == None: y = self.y sdx = self.x - i sdy = self.y - j edx = x - i edy = y - j qs = self.get_quadrant(sdx, sdy) qe = self.get_quadrant(edx, edy) if qs == qe: arc_angle = math.fabs(self.get_arc_angle(sdx, sdy, edx, edy, cw)) # arc_angle will be either less than pi/2 or greater than 3pi/2 if arc_angle > 3.14: if cw: qs = qs + 4 else: qe = qe + 4 if qs == qe: self.arc(cw, x, y, z, i, j, k, r) else: rad = math.sqrt(sdx * sdx + sdy * sdy) if cw: if qs < qe: qs = qs + 4 else: if qe < qs: qe = qe + 4 q = qs while 1: x1 = x y1 = y if q != qe: if cw: x1, y1 = self.quadrant_start(q, i, j, rad) else: x1, y1 = self.quadrant_end(q, i, j, rad) if (self.fmt.string(x1) != self.fmt.string(self.x)) or ( self.fmt.string(y1) != self.fmt.string(self.y) ): if (math.fabs(x1 - self.x) > 0.01) or (math.fabs(y1 - self.y) > 0.01): self.arc(cw, x1, y1, z, i, j, k, r) else: self.feed(x1, y1, z) if q == qe: break if cw: q = q - 1 else: q = q + 1 self.in_quadrant_splitting = False return self.on_move() arc_g_code = "" if cw: arc_g_code = self.ARC_CW() else: arc_g_code = self.ARC_CCW() if self.g0123_modal: if self.prev_g0123 != arc_g_code: self.write(self.SPACE() + arc_g_code) self.prev_g0123 = arc_g_code else: self.write(self.SPACE() + arc_g_code) self.write_preps() if x != None: dx = x - self.x if self.absolute_flag: self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) else: self.write(self.SPACE() + self.X() + (self.fmt.string(dx))) if y != None: dy = y - self.y if self.absolute_flag: self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) else: self.write(self.SPACE() + self.Y() + (self.fmt.string(dy))) if z != None: dz = z - self.z if self.absolute_flag: self.write(self.SPACE() + self.Z() + (self.fmt.string(z + self.shift_z))) else: self.write(self.SPACE() + self.Z() + (self.fmt.string(dz))) if i != None: if self.arc_centre_absolute == False: i = i - self.x s = self.fmt.string(i) if self.arc_centre_positive == True: if s[0] == "-": s = s[1:] self.write(self.SPACE() + self.CENTRE_X() + s) if j != None: if self.arc_centre_absolute == False: j = j - self.y s = self.fmt.string(j) if self.arc_centre_positive == True: if s[0] == "-": s = s[1:] self.write(self.SPACE() + self.CENTRE_Y() + s) if k != None: if self.arc_centre_absolute == False: k = k - self.z s = self.fmt.string(k) if self.arc_centre_positive == True: if s[0] == "-": s = s[1:] self.write(self.SPACE() + self.CENTRE_Z() + s) if r != None: s = self.fmt.string(r) if self.arc_centre_positive == True: if s[0] == "-": s = s[1:] self.write(self.SPACE() + self.RADIUS() + s) # use horizontal feed rate if self.fhv: self.calc_feedrate_hv(1, 0) self.write_feedrate() self.write_spindle() self.write_misc() self.write("\n") if x != None: self.x = x if y != None: self.y = y if z != None: self.z = z def arc_cw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.arc(True, x, y, z, i, j, k, r) def arc_ccw(self, x=None, y=None, z=None, i=None, j=None, k=None, r=None): self.arc(False, x, y, z, i, j, k, r) def dwell(self, t): self.write_preps() self.write(self.SPACE() + self.DWELL(t)) self.write_misc() self.write("\n") def on_move(self): if self.output_fixtures: self.output_fixture() self.move_done_since_tool_change = True def rapid_home(self, x=None, y=None, z=None, a=None, b=None, c=None): pass def rapid_unhome(self): pass def set_machine_coordinates(self): self.write(self.SPACE() + self.MACHINE_COORDINATES()) self.prev_g0123 = "" ############################################################################ # CRC def use_CRC(self): return self.useCrc def CRC_nominal_path(self): return self.useCrcCenterline def start_CRC(self, left=True, radius=0.0): # set up prep code, to be output on next line if self.t == None: raise "No tool specified for start_CRC()" if left: self.write(self.SPACE() + "G41") else: self.write(self.SPACE() + "G42") self.write((self.SPACE() + "D%i\n") % self.t) def end_CRC(self): self.write(self.SPACE() + "G40\n") ############################################################################ # Cycles def pattern(self): pass def pattern_uses_subroutine(self): return self.pattern_done_with_subroutine def pocket(self): pass def profile(self): pass def write_internal_coolant_commands(self, internal_coolant_on): if (internal_coolant_on != None) and (self.output_internal_coolant_commands == True): if internal_coolant_on == True: if self.internal_coolant_on != True: self.write(self.SPACE()) self.write(self.INTERNAL_COOLANT_ON() + "\n") self.internal_coolant_on = True else: if self.internal_coolant_on != False: self.write(self.SPACE()) self.write(self.INTERNAL_COOLANT_OFF() + "\n") self.internal_coolant_on = False # The drill routine supports drilling (G81), drilling with dwell (G82) and peck drilling (G83). # The x,y,z values are INITIAL locations (above the hole to be made. This is in contrast to # the Z value used in the G8[1-3] cycles where the Z value is that of the BOTTOM of the hole. # Instead, this routine combines the Z value and the depth value to determine the bottom of # the hole. # # The standoff value is the distance up from the 'z' value (normally just above the surface) where the bit retracts # to in order to clear the swarf. This combines with 'z' to form the 'R' value in the G8[1-3] cycles. # # The peck_depth value is the incremental depth (Q value) that tells the peck drilling # cycle how deep to go on each peck until the full depth is achieved. # # NOTE: This routine forces the mode to absolute mode so that the values passed into # the G8[1-3] cycles make sense. I don't know how to find the mode to revert it so I won't # revert it. I must set the mode so that I can be sure the values I'm passing in make # sense to the end-machine. # def drill( self, x=None, y=None, dwell=None, depthparams=None, retract_mode=None, spindle_mode=None, internal_coolant_on=None, rapid_to_clearance=None, ): if depthparams.clearance_height == None: self.first_drill_pos = False return self.write_internal_coolant_commands(internal_coolant_on) drillExpanded = self.drillExpanded if (depthparams.step_down != 0) and (dwell != 0): # pecking and dwell together if self.dwell_allowed_in_G83 != True: drillExpanded = True if drillExpanded: # for machines which don't understand G81, G82 etc. peck_depth = depthparams.step_down if peck_depth == None: peck_depth = depthparams.final_depth current_z = depthparams.start_depth self.rapid(x, y) first = True last_cut = False while True: next_z = current_z - peck_depth if next_z < (depthparams.final_depth + 0.001): next_z = depthparams.final_depth last_cut = True if next_z >= current_z: break if first: self.rapid(z=depthparams.start_depth + depthparams.rapid_safety_space) else: self.rapid(z=current_z) self.feed(z=next_z) if dwell != 0 and last_cut: self.dwell(dwell) if last_cut: self.rapid(z=depthparams.clearance_height) else: if rapid_to_clearance: self.rapid(z=depthparams.clearance_height) else: self.rapid(z=depthparams.start_depth + depthparams.rapid_safety_space) current_z = next_z first = False self.first_drill_pos = False return if self.output_g98_and_g99 == True: if rapid_to_clearance == True: if self.output_g43_z_before_drilling_if_g98: if self.fmt.string(depthparams.clearance_height) != self.z_for_g43: self.z_for_g43 = self.fmt.string(depthparams.clearance_height) self.write( self.SPACE() + "G43" + self.SPACE() + "Z" + self.z_for_g43 + "\n" ) if self.first_drill_pos == True and rapid_to_clearance == True: self.rapid(x, y) self.rapid(z=depthparams.clearance_height) self.in_canned_cycle = True self.write_preps() if depthparams.step_down != 0: # G83 peck drilling if self.drill_modal: if self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) != self.prev_drill: self.write( self.SPACE() + self.PECK_DRILL() + self.SPACE() + self.PECK_DEPTH(depthparams.step_down) ) self.prev_drill = self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down) else: self.write(self.PECK_DRILL() + self.PECK_DEPTH(depthparams.step_down)) if (self.dwell != 0) and self.dwell_allowed_in_G83: self.write(self.SPACE() + self.TIME() + (self.FORMAT_TIME().string(dwell))) else: # We're either just drilling or drilling with dwell. if dwell == 0: # We're just drilling. if self.drill_modal: if self.DRILL() != self.prev_drill: self.write(self.SPACE() + self.DRILL()) self.prev_drill = self.DRILL() else: self.write(self.SPACE() + self.DRILL()) else: # We're drilling with dwell. if self.drill_modal: if self.DRILL_WITH_DWELL(dwell) != self.prev_drill: self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) self.prev_drill = self.DRILL_WITH_DWELL(dwell) else: self.write(self.SPACE() + self.DRILL_WITH_DWELL(dwell)) if self.output_g98_and_g99 == True: if rapid_to_clearance == True: if self.g98_not_g99 != True: self.write(self.SPACE() + self.RETRACT_TO_CLEARANCE()) self.g98_not_g99 = True else: if self.g98_not_g99 != False: self.write(self.SPACE() + self.RETRACT_TO_STANDOFF()) self.g98_not_g99 = False # Set the retraction point to the 'standoff' distance above the starting z height. retract_height = depthparams.start_depth + depthparams.rapid_safety_space if x != None: self.write(self.SPACE() + self.X() + (self.fmt.string(x + self.shift_x))) self.x = x if y != None: self.write(self.SPACE() + self.Y() + (self.fmt.string(y + self.shift_y))) self.y = y if self.drill_modal: if depthparams.start_depth != self.prev_z: self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) self.prev_z = depthparams.start_depth else: # This is the 'z' value for the bottom of the hole. self.write(self.SPACE() + self.Z() + (self.fmt.string(depthparams.final_depth))) # We want to remember where z is at the end (at the top of the hole) self.z = depthparams.start_depth + depthparams.rapid_safety_space if self.drill_modal: if self.prev_retract != self.RETRACT(retract_height): self.write(self.SPACE() + self.RETRACT(retract_height)) self.prev_retract = self.RETRACT(retract_height) else: self.write(self.SPACE() + self.RETRACT(retract_height)) if self.fv: self.f.set(self.fv) self.write_feedrate() self.write_spindle() self.write_misc() self.write("\n") self.first_drill_pos = False def end_canned_cycle(self): if self.in_canned_cycle == False: return self.write(self.SPACE() + self.END_CANNED_CYCLE() + "\n") self.write_internal_coolant_commands(0) self.prev_drill = "" self.prev_g0123 = "" self.prev_z = "" self.prev_f = "" self.prev_retract = "" self.in_canned_cycle = False self.first_drill_pos = True ############################################################################ # Misc def comment(self, text): self.write((self.COMMENT(text) + "\n")) def insert(self, text): pass def block_delete(self, on=False): pass def variable(self, id): return self.VARIABLE() % id def variable_set(self, id, value): self.write( self.SPACE() + (self.VARIABLE() % id) + self.SPACE() + (self.VARIABLE_SET() % value) + "\n" ) # This routine uses the G92 coordinate system offsets to establish a temporary coordinate # system at the machine's current position. It can then use absolute coordinates relative # to this position which makes coding easy. It then moves to the 'point along edge' which # should be above the workpiece but still on one edge. It then backs off from the edge # to the 'retracted point'. It then plunges down by the depth value specified. It then # probes back towards the 'destination point'. The probed X,Y location are stored # into the 'intersection variable' variables. Finally the machine moves back to the # original location. This is important so that the results of multiple calls to this # routine may be compared meaningfully. def probe_single_point( self, point_along_edge_x=None, point_along_edge_y=None, depth=None, retracted_point_x=None, retracted_point_y=None, destination_point_x=None, destination_point_y=None, intersection_variable_x=None, intersection_variable_y=None, probe_offset_x_component=None, probe_offset_y_component=None, ): self.write( self.SPACE() + ( self.SET_TEMPORARY_COORDINATE_SYSTEM() + (" X 0 Y 0 Z 0") + ("\t(Temporarily make this the origin)\n") ) ) if self.fhv: self.calc_feedrate_hv(1, 0) self.write_feedrate() self.write("\t(Set the feed rate for probing)\n") self.rapid(point_along_edge_x, point_along_edge_y) self.rapid(retracted_point_x, retracted_point_y) self.feed(z=depth) self.write( ( self.PROBE_TOWARDS_WITH_SIGNAL() + ( " X " + (self.fmt.string(destination_point_x)) + " Y " + (self.fmt.string(destination_point_y)) ) + ("\t(Probe towards our destination point)\n") ) ) self.comment("Back off the workpiece and re-probe more slowly") self.write( self.SPACE() + ( "#" + intersection_variable_x + "= [#5061 - [ 0.5 * " + probe_offset_x_component + "]]\n" ) ) self.write( self.SPACE() + ( "#" + intersection_variable_y + "= [#5062 - [ 0.5 * " + probe_offset_y_component + "]]\n" ) ) self.write(self.RAPID()) self.write( self.SPACE() + " X #" + intersection_variable_x + " Y #" + intersection_variable_y + "\n" ) self.write(self.SPACE() + self.FEEDRATE() + self.ffmt.string(self.fh / 2.0) + "\n") self.write( ( self.SPACE() + self.PROBE_TOWARDS_WITH_SIGNAL() + ( " X " + (self.fmt.string(destination_point_x)) + " Y " + (self.fmt.string(destination_point_y)) ) + ("\t(Probe towards our destination point)\n") ) ) self.comment("Store the probed location somewhere we can get it again later") self.write( ( "#" + intersection_variable_x + "=" + probe_offset_x_component + " (Portion of probe radius that contributes to the X coordinate)\n" ) ) self.write( ("#" + intersection_variable_x + "=[#" + intersection_variable_x + " + #5061]\n") ) self.write( ( "#" + intersection_variable_y + "=" + probe_offset_y_component + " (Portion of probe radius that contributes to the Y coordinate)\n" ) ) self.write( ("#" + intersection_variable_y + "=[#" + intersection_variable_y + " + #5062]\n") ) self.comment("Now move back to the original location") self.rapid(retracted_point_x, retracted_point_y) self.rapid(z=0) self.rapid(point_along_edge_x, point_along_edge_y) self.rapid(x=0, y=0) self.write( ( self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ("\t(Restore the previous coordinate system)\n") ) ) def probe_downward_point(self, x=None, y=None, depth=None, intersection_variable_z=None): self.write( ( self.SET_TEMPORARY_COORDINATE_SYSTEM() + (" X 0 Y 0 Z 0") + ("\t(Temporarily make this the origin)\n") ) ) if self.fhv: self.calc_feedrate_hv(1, 0) self.write(self.FEEDRATE() + " [" + self.ffmt.string(self.fh) + " / 5.0 ]") self.write("\t(Set the feed rate for probing)\n") if x != None and y != None: self.write(self.RAPID()) self.write(" X " + x + " Y " + y + "\n") self.write( ( self.PROBE_TOWARDS_WITH_SIGNAL() + " Z " + (self.fmt.string(depth)) + ("\t(Probe towards our destination point)\n") ) ) self.comment("Store the probed location somewhere we can get it again later") self.write(("#" + intersection_variable_z + "= #5063\n")) self.comment("Now move back to the original location") self.rapid(z=0) self.rapid(x=0, y=0) self.write( ( self.REMOVE_TEMPORARY_COORDINATE_SYSTEM() + ("\t(Restore the previous coordinate system)\n") ) ) def report_probe_results( self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None, x3=None, y3=None, z3=None, x4=None, y4=None, z4=None, x5=None, y5=None, z5=None, x6=None, y6=None, z6=None, xml_file_name=None, ): pass def open_log_file(self, xml_file_name=None): pass def log_coordinate(self, x=None, y=None, z=None): pass def log_message(self, message=None): pass def close_log_file(self): pass # Rapid movement to the midpoint between the two points specified. # NOTE: The points are specified either as strings representing numbers or as strings # representing variable names. This allows the HeeksCNC module to determine which # variable names are used in these various routines. def rapid_to_midpoint(self, x1=None, y1=None, z1=None, x2=None, y2=None, z2=None): self.write(self.RAPID()) if (x1 != None) and (x2 != None): self.write((" X " + "[[[" + x1 + " - " + x2 + "] / 2.0] + " + x2 + "]")) if (y1 != None) and (y2 != None): self.write((" Y " + "[[[" + y1 + " - " + y2 + "] / 2.0] + " + y2 + "]")) if (z1 != None) and (z2 != None): self.write((" Z " + "[[[" + z1 + " - " + z2 + "] / 2.0] + " + z2 + "]")) self.write("\n") # Rapid movement to the intersection of two lines (in the XY plane only). This routine # is based on information found in http://local.wasp.uwa.edu.au/~pbourke/geometry/lineline2d/ # written by Paul Bourke. The ua_numerator, ua_denominator, ua and ub parameters # represent variable names (with the preceding '#' included in them) for use as temporary # variables. They're specified here simply so that HeeksCNC can manage which variables # are used in which GCode calculations. # # As per the notes on the web page, the ua_denominator and ub_denominator formulae are # the same so we don't repeat this. If the two lines are coincident or parallel then # no movement occurs. # # NOTE: The points are specified either as strings representing numbers or as strings # representing variable names. This allows the HeeksCNC module to determine which # variable names are used in these various routines. def rapid_to_intersection( self, x1, y1, x2, y2, x3, y3, x4, y4, intersection_x, intersection_y, ua_numerator, ua_denominator, ua, ub_numerator, ub, ): self.comment("Find the intersection of the two lines made up by the four probed points") self.write( ua_numerator + "=[[[" + x4 + " - " + x3 + "] * [" + y1 + " - " + y3 + "]] - [[" + y4 + " - " + y3 + "] * [" + x1 + " - " + x3 + "]]]\n" ) self.write( ua_denominator + "=[[[" + y4 + " - " + y3 + "] * [" + x2 + " - " + x1 + "]] - [[" + x4 + " - " + x3 + "] * [" + y2 + " - " + y1 + "]]]\n" ) self.write( ub_numerator + "=[[[" + x2 + " - " + x1 + "] * [" + y1 + " - " + y3 + "]] - [[" + y2 + " - " + y1 + "] * [" + x1 + " - " + x3 + "]]]\n" ) self.comment("If they are not parallel") self.write("O900 IF [" + ua_denominator + " NE 0]\n") self.comment("And if they are not coincident") self.write("O901 IF [" + ua_numerator + " NE 0 ]\n") self.write(" " + ua + "=[" + ua_numerator + " / " + ua_denominator + "]\n") # NOTE: ub denominator is the same as ua denominator self.write(" " + ub + "=[" + ub_numerator + " / " + ua_denominator + "]\n") self.write( " " + intersection_x + "=[" + x1 + " + [[" + ua + " * [" + x2 + " - " + x1 + "]]]]\n" ) self.write( " " + intersection_y + "=[" + y1 + " + [[" + ua + " * [" + y2 + " - " + y1 + "]]]]\n" ) self.write(" " + self.RAPID()) self.write(" X " + intersection_x + " Y " + intersection_y + "\n") self.write("O901 ENDIF\n") self.write("O900 ENDIF\n") # We need to calculate the rotation angle based on the line formed by the # x1,y1 and x2,y2 coordinate pair. With that angle, we need to move # x_offset and y_offset distance from the current (0,0,0) position. # # The x1,y1,x2 and y2 parameters are all variable names that contain the actual # values. # The x_offset and y_offset are both numeric (floating point) values def rapid_to_rotated_coordinate( self, x1, y1, x2, y2, ref_x, ref_y, x_current, y_current, x_final, y_final ): self.comment("Rapid to rotated coordinate") self.write( "#1 = [atan[" + y2 + " - " + y1 + "]/[" + x2 + " - " + x1 + "]] (nominal_angle)\n" ) self.write("#2 = [atan[" + ref_y + "]/[" + ref_x + "]] (reference angle)\n") self.write("#3 = [#1 - #2] (angle)\n") self.write( "#4 = [[[" + (self.fmt.string(0)) + " - " + (self.fmt.string(x_current)) + "] * COS[ #3 ]] - [[" + (self.fmt.string(0)) + " - " + (self.fmt.string(y_current)) + "] * SIN[ #3 ]]]\n" ) self.write( "#5 = [[[" + (self.fmt.string(0)) + " - " + (self.fmt.string(x_current)) + "] * SIN[ #3 ]] + [[" + (self.fmt.string(0)) + " - " + (self.fmt.string(y_current)) + "] * COS[ #3 ]]]\n" ) self.write( "#6 = [[" + (self.fmt.string(x_final)) + " * COS[ #3 ]] - [" + (self.fmt.string(y_final)) + " * SIN[ #3 ]]]\n" ) self.write( "#7 = [[" + (self.fmt.string(y_final)) + " * SIN[ #3 ]] + [" + (self.fmt.string(y_final)) + " * COS[ #3 ]]]\n" ) self.write(self.RAPID() + " X [ #4 + #6 ] Y [ #5 + #7 ]\n") def BEST_POSSIBLE_SPEED(self, motion_blending_tolerance, naive_cam_tolerance): statement = "G64" if motion_blending_tolerance > 0: statement += " P " + str(motion_blending_tolerance) if naive_cam_tolerance > 0: statement += " Q " + str(naive_cam_tolerance) return statement def set_path_control_mode(self, mode, motion_blending_tolerance, naive_cam_tolerance): if mode == 0: self.write(self.EXACT_PATH_MODE() + "\n") if mode == 1: self.write(self.EXACT_STOP_MODE() + "\n") if mode == 2: self.write( self.BEST_POSSIBLE_SPEED(motion_blending_tolerance, naive_cam_tolerance) + "\n" ) ################################################################################ nc.creator = Creator()