def parse(self, text): text_stream = StringIO.StringIO(text) config = ConfigParser.SafeConfigParser() config.readfp(text_stream) for config_dict, section in ((self.bounds, "Bounds"), (self.tool_settings, "Tool"), (self.process_settings, "Process")): for key, value_type in self.SECTIONS[section].items(): value_raw = config.get(section, key, None) if value_raw is None: continue elif value_type == bool: value = value_raw.lower() in ("1", "true", "yes", "on") elif isinstance(value_type, basestring) \ and (value_type.startswith("list_of_")): item_type = value_type[len("list_of_"):] if item_type == "float": item_type = float else: continue try: value = [item_type(one_val) for one_val in value_raw.split(",")] except ValueError: log.warn("Settings: Ignored invalid setting due to " \ + "a failed list type parsing: " \ + "(%s -> %s): %s" % (section, key, value_raw)) else: try: value = value_type(value_raw) except ValueError: log.warn("Settings: Ignored invalid setting " \ + "(%s -> %s): %s" % (section, key, value_raw)) config_dict[key] = value
def parse_arc(self, circle=False): start_line = self.line_number # the z-level defaults to zero (for 2D models) center = [None, None, 0] color = None radius = None if circle: angle_start = 0 angle_end = 360 else: angle_start = None angle_end = None key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: center[0] = value elif key == self.KEYS["P1_Y"]: center[1] = value elif key == self.KEYS["P1_Z"]: center[2] = value elif key == self.KEYS["RADIUS"]: radius = value elif key == self.KEYS["ANGLE_START"]: angle_start = value elif key == self.KEYS["ANGLE_END"]: angle_end = value elif key == self.KEYS["COLOR"]: color = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if not key is None: self._push_on_stack(key, value) if (None in center) or (None in (radius, angle_start, angle_end)): log.warn("DXFImporter: Incomplete ARC definition between line " \ + "%d and %d" % (start_line, end_line)) else: if self._color_as_height and (not color is None): # use the color code as the z coordinate center[2] = float(color) / 255 center = tuple(center) xy_point_coords = pycam.Geometry.get_points_of_arc(center, radius, angle_start, angle_end) # Somehow the order of points seems to be the opposite of what is # expected. xy_point_coords.reverse() if len(xy_point_coords) > 1: for index in range(len(xy_point_coords) - 1): p1 = xy_point_coords[index] p1 = (p1[0], p1[1], center[2]) p2 = xy_point_coords[index + 1] p2 = (p2[0], p2[1], center[2]) if p1 != p2: self.lines.append(Line(p1, p2)) else: log.warn("DXFImporter: Ignoring tiny ARC (between input " + \ "line %d and %d): %s / %s (%s - %s)" % (start_line, end_line, center, radius, angle_start, angle_end))
def parse_vertex(self): start_line = self.line_number point = [None, None, 0] color = None bulge = None key, value = self._read_key_value() while (key is not None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: point[0] = value elif key == self.KEYS["P1_Y"]: point[1] = value elif key == self.KEYS["P1_Z"]: point[2] = value elif key == self.KEYS["COLOR"]: color = value elif key == self.KEYS["VERTEX_BULGE"]: bulge = value else: pass key, value = self._read_key_value() end_line = self.line_number if key is not None: self._push_on_stack(key, value) if self._color_as_height and (color is not None): # use the color code as the z coordinate point[2] = float(color) / 255 if None in point: log.warn( "DXFImporter: Missing attribute of VERTEX item between line %d and %d", start_line, end_line) else: self._open_sequence_items.append( ((point[0], point[1], point[2]), bulge))
def parse_vertex(self): start_line = self.line_number point = [None, None, 0] color = None bulge = None key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: point[0] = value elif key == self.KEYS["P1_Y"]: point[1] = value elif key == self.KEYS["P1_Z"]: point[2] = value elif key == self.KEYS["COLOR"]: color = value elif key == self.KEYS["VERTEX_BULGE"]: bulge = value else: pass key, value = self._read_key_value() end_line = self.line_number if not key is None: self._push_on_stack(key, value) if self._color_as_height and (not color is None): # use the color code as the z coordinate point[2] = float(color) / 255 if None in point: log.warn("DXFImporter: Missing attribute of VERTEX item" + \ "between line %d and %d" % (start_line, end_line)) else: self._open_sequence_items.append( (Point(point[0], point[1], point[2]), bulge))
def _carefully_decode(self, text): try: return text.decode("utf-8") except UnicodeDecodeError: log.warn("DXFImporter: Invalid character in string in line %d", self.line_number) return text.decode("utf-8", errors="ignore")
def close_sequence(self): start_line = self.line_number if self._open_sequence == "POLYLINE": self.parse_polyline(False) else: log.warn("DXFImporter: unexpected SEQEND found at line %d" % \ start_line)
def combine_triangles(t1, t2): unique_vertices = [] shared_vertices = [] for point in t1.get_points(): for point2 in t2.get_points(): if point == point2: shared_vertices.append(point) break else: unique_vertices.append(point) if len(shared_vertices) != 2: return None for point in t2.get_points(): for point2 in shared_vertices: if point == point2: break else: unique_vertices.append(point) if len(unique_vertices) != 2: log.error("Invalid number of vertices: %s" % unique_vertices) return None if abs(unique_vertices[0].sub(unique_vertices[1]).norm - \ shared_vertices[0].sub(shared_vertices[1]).norm) < epsilon: try: return Rectangle(unique_vertices[0], unique_vertices[1], shared_vertices[0], shared_vertices[1], normal=t1.normal) except ValueError: log.warn("Triangles not combined: %s, %s" % (unique_vertices, shared_vertices)) return None else: return None
def load_preferences(self): """ load all settings that are available in the Preferences window from a file in the user's home directory """ config_filename = pycam.Gui.Settings.get_config_filename() if config_filename is None: # failed to create the personal preferences directory return config = ConfigParser.ConfigParser() if not config.read(config_filename): # no config file was read return # report any ignored (obsolete) preference keys present in the file for item, value in config.items("DEFAULT"): if not item in PREFERENCES_DEFAULTS.keys(): log.warn("Skipping obsolete preference item: %s" % str(item)) for item in PREFERENCES_DEFAULTS.keys(): if not config.has_option("DEFAULT", item): # a new preference setting is missing in the (old) file continue value_raw = config.get("DEFAULT", item) old_value = self.settings.get(item) value_type = type(PREFERENCES_DEFAULTS[item]) if isinstance(value_type(), basestring): # keep strings as they are value = str(value_raw) else: # parse tuples, integers, bools, ... value = eval(value_raw) self.settings.set(item, value)
def load_preferences(self): """ load all settings that are available in the Preferences window from a file in the user's home directory """ config_filename = pycam.Gui.Settings.get_config_filename() if config_filename is None: # failed to create the personal preferences directory return config = ConfigParser() if not config.read(config_filename): # no config file was read return # report any ignored (obsolete) preference keys present in the file for item, value in config.items("DEFAULT"): if item not in PREFERENCES_DEFAULTS.keys(): log.warn("Skipping obsolete preference item: %s", str(item)) for item in PREFERENCES_DEFAULTS: if not config.has_option("DEFAULT", item): # a new preference setting is missing in the (old) file continue value_raw = config.get("DEFAULT", item) value_type = type(PREFERENCES_DEFAULTS[item]) if hasattr(value_type(), "split"): # keep strings as they are value = str(value_raw) else: # parse tuples, integers, bools, ... value = eval(value_raw) self.settings.set(item, value)
def __init__(self, title, message): try: import Tkinter except ImportError: # tk is not installed log.warn("Failed to show error dialog due to a missing Tkinter Python package.") return try: root = Tkinter.Tk() except Tkinter.TclError as err_msg: log.info("Failed to create error dialog window (%s). Probably you are running PyCAM " "from a terminal.", err_msg) return root.title(title) root.bind("<Return>", self.finish) root.bind("<Escape>", self.finish) root.minsize(300, 100) self.root = root frame = Tkinter.Frame(root) frame.pack() # add text output as label message = Tkinter.Message(root, text=message) # we need some space for the dependency report message["width"] = 800 message.pack() # add the "close" button close = Tkinter.Button(root, text="Close") close["command"] = self.finish close.pack(side=Tkinter.BOTTOM) root.mainloop()
def parse_toolpath_settings(filename): """ parse potential PyCAM settings from a given file This is mainly useful to retrieve task settings from a GCode file. @value filename: the name of the file to be read @type filename: str @returns: a dictionary (of all setting names and values) and the content of the 'comment' section (as a single string) @rtype: tuple(dict, str) """ keywords = {} in_meta_zone = False meta_content = [] if filename == "-": # read from stdin, if the input filename is "-" infile = sys.stdin close_file = False else: # open the file try: infile = pycam.Utils.URIHandler(filename).open() except IOError, err_msg: log.warn("ToolpathSettingsParser: Failed to read file (%s): %s" % \ (filename, err_msg)) return None close_file = True
def parse_lwpolyline(self): start_line = self.line_number points = [] def add_point(p_array): # fill all "None" values with zero for index in range(len(p_array)): if p_array[index] is None: if (index == 0) or (index == 1): log.debug("DXFImporter: weird LWPOLYLINE input " + \ "date in line %d: %s" % \ (self.line_number, p_array)) p_array[index] = 0 points.append(Point(p_array[0], p_array[1], p_array[2])) current_point = [None, None, None] key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: axis = 0 elif key == self.KEYS["P1_Y"]: axis = 1 elif not self._color_as_height and (key == self.KEYS["P1_Z"]): axis = 2 elif self._color_as_height and (key == self.KEYS["COLOR"]): # interpret the color as the height axis = 2 value = float(value) / 255 else: axis = None if not axis is None: if current_point[axis] is None: # The current point definition is not complete, yet. current_point[axis] = value else: # The current point seems to be complete. add_point(current_point) current_point = [None, None, None] current_point[axis] = value key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if not key is None: self._push_on_stack(key, value) # check if there is a remaining item in "current_point" if len(current_point) != current_point.count(None): add_point(current_point) if len(points) < 2: # too few points for a polyline log.warn("DXFImporter: Empty LWPOLYLINE definition between line " \ + "%d and %d" % (start_line, end_line)) else: for index in range(len(points) - 1): point = points[index] next_point = points[index + 1] if point != next_point: self.lines.append(Line(point, next_point)) else: log.warn("DXFImporter: Ignoring zero-length LINE " \ + "(between input line %d and %d): %s" \ % (start_line, end_line, point))
def close_sequence(self): start_line = self.line_number if self._open_sequence == "POLYLINE": self.parse_polyline(False) else: log.warn("DXFImporter: unexpected SEQEND found at line %d", start_line)
def recommends_details_gtk(): result = {} try: import gi.repository.WebKit2 # noqa F401 result["webkitgtk"] = True except ImportError as err_msg: log.warn("Failed to import WebKitGTK: %s", str(err_msg)) result["webkitgtk"] = False
def recommends_details_gtk(): result = {} try: import gtk.gtkgl result["gtkgl"] = True result["gl"] = True except ImportError, err_msg: log.warn("Failed to import OpenGL for GTK (ImportError): %s" % \ str(err_msg)) result["gtkgl"] = False
def get_lines_layer(lines, z, last_z=None, step_width=None, milling_style=MILLING_STYLE_CONVENTIONAL): get_proj_point = lambda proj_point: (proj_point[0], proj_point[1], z) projected_lines = [] for line in lines: if (not last_z is None) and (last_z < line.minz): # the line was processed before continue elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1[2]) / (line.p2[2] - line.p1[2]) plane_point = padd(line.p1, pmul(line.vector, factor)) if line.p1[2] < z: p1 = get_proj_point(line.p1) p2 = line.p2 else: p1 = line.p1 p2 = get_proj_point(line.p2) projected_lines.append(Line(p1, plane_point)) yield Line(plane_point, p2) elif line.minz < last_z < line.maxz: plane = Plane((0, 0, last_z), (0, 0, 1, 'v')) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1[2] > last_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp projected_lines.append(Line(p1, p2)) else: if line.maxz <= z: # the line is completely below z projected_lines.append(Line(get_proj_point(line.p1), get_proj_point(line.p2))) elif line.minz >= z: projected_lines.append(line) else: log.warn("Unexpected condition 'get_lines_layer': " + \ "%s / %s / %s / %s" % (line.p1, line.p2, z, last_z)) # process all projected lines for line in projected_lines: points = [] if step_width is None: points.append(line.p1) points.append(line.p2) else: if isiterable(step_width): steps = step_width else: steps = floatrange(0.0, line.len, inc=step_width) for step in steps: next_point = padd(line.p1, pmul(line.dir, step)) points.append(next_point) yield points
def save_preferences(self): """ save all settings (see Preferences window) to a file in the user's home directory """ config = ConfigParser() for item in PREFERENCES_DEFAULTS: config.set("DEFAULT", item, json.dumps(self.settings.get(item))) try: with pycam.Gui.Settings.open_preferences_file( mode="w") as out_file: config.write(out_file) except OSError as exc: log.warn("Failed to write preferences file: %s", exc)
def create_named_temporary_file(suffix=None): file_handle, filename = tempfile.mkstemp(suffix=".dxf") os.close(file_handle) try: yield filename finally: if os.path.isfile(filename): try: os.remove(filename) except OSError as exc: log.warn("Failed to remove temporary file (%s): %s", filename, exc)
def get_default_model(): """ return a filename or a Model instance """ # try to load the default model file ("pycam" logo) for inputdir in EXAMPLE_MODEL_LOCATIONS: inputfile = os.path.join(inputdir, DEFAULT_MODEL_FILE) if os.path.isfile(inputfile): return inputfile else: # fall back to the simple test model log.warn("Failed to find the default model (%s) in the " \ "following locations: %s" % (DEFAULT_MODEL_FILE, ", ".join(EXAMPLE_MODEL_LOCATIONS))) return pycam.Importers.TestModel.get_test_model()
def GenerateToolPathLinePush(self, pa, line, z, previous_z, draw_callback=None): if previous_z <= line.minz: # the line is completely above the previous level pass elif line.minz < z < line.maxz: # Split the line at the point at z level and do the calculation # for both point pairs. factor = (z - line.p1.z) / (line.p2.z - line.p1.z) plane_point = line.p1.add(line.vector.mul(factor)) self.GenerateToolPathLinePush(pa, Line(line.p1, plane_point), z, previous_z, draw_callback=draw_callback) self.GenerateToolPathLinePush(pa, Line(plane_point, line.p2), z, previous_z, draw_callback=draw_callback) elif line.minz < previous_z < line.maxz: plane = Plane(Point(0, 0, previous_z), Vector(0, 0, 1)) cp = plane.intersect_point(line.dir, line.p1)[0] # we can be sure that there is an intersection if line.p1.z > previous_z: p1, p2 = cp, line.p2 else: p1, p2 = line.p1, cp self.GenerateToolPathLinePush(pa, Line(p1, p2), z, previous_z, draw_callback=draw_callback) else: if line.maxz <= z: # the line is completely below z p1 = Point(line.p1.x, line.p1.y, z) p2 = Point(line.p2.x, line.p2.y, z) elif line.minz >= z: p1 = line.p1 p2 = line.p2 else: log.warn("Unexpected condition EC_GTPLP: %s / %s / %s / %s" % \ (line.p1, line.p2, z, previous_z)) return # no model -> no possible obstacles # model is completely below z (e.g. support bridges) -> no obstacles relevant_models = [m for m in self.models if m.maxz >= z] if not relevant_models: points = [p1, p2] elif self.physics: points = get_free_paths_ode(self.physics, p1, p2) else: points = get_free_paths_triangles(relevant_models, self.cutter, p1, p2) if points: for point in points: pa.append(point) if draw_callback: draw_callback(tool_position=points[-1], toolpath=pa.paths)
def get_barycenter(self): area = self.get_area() if not area: return None # see: http://stackoverflow.com/questions/2355931/foo/2360507 # first: calculate cx and y cxy, cxz, cyx, cyz, czx, czy = (0, 0, 0, 0, 0, 0) for index in range(len(self._points)): p1 = self._points[index] p2 = self._points[(index + 1) % len(self._points)] cxy += (p1[0] + p2[0]) * (p1[0] * p2[1] - p1[1] * p2[0]) cxz += (p1[0] + p2[0]) * (p1[0] * p2[2] - p1[2] * p2[0]) cyx += (p1[1] + p2[1]) * (p1[0] * p2[1] - p1[1] * p2[0]) cyz += (p1[1] + p2[1]) * (p1[1] * p2[2] - p1[2] * p2[1]) czx += (p1[2] + p2[2]) * (p1[2] * p2[0] - p1[0] * p2[2]) czy += (p1[2] + p2[2]) * (p1[1] * p2[2] - p1[2] * p2[1]) if abs(self.maxz - self.minz) < epsilon: return (cxy / (6 * area), cyx / (6 * area), self.minz) elif abs(self.maxy - self.miny) < epsilon: return (cxz / (6 * area), self.miny, czx / (6 * area)) elif abs(self.maxx - self.minx) < epsilon: return (self.minx, cyz / (6 * area), czy / (6 * area)) else: # calculate area of xy projection poly_xy = self.get_plane_projection(Plane((0, 0, 0), (0, 0, 1))) poly_xz = self.get_plane_projection(Plane((0, 0, 0), (0, 1, 0))) poly_yz = self.get_plane_projection(Plane((0, 0, 0), (1, 0, 0))) if (poly_xy is None) or (poly_xz is None) or (poly_yz is None): log.warn("Invalid polygon projection for barycenter: %s", str(self)) return None area_xy = poly_xy.get_area() area_xz = poly_xz.get_area() area_yz = poly_yz.get_area() if 0 in (area_xy, area_xz, area_yz): log.info( "Failed assumtion: zero-sized projected area - %s / %s / %s", area_xy, area_xz, area_yz) return None if abs(cxy / area_xy - cxz / area_xz) > epsilon: log.info("Failed assumption: barycenter xy/xz - %s / %s", cxy / area_xy, cxz / area_xz) if abs(cyx / area_xy - cyz / area_yz) > epsilon: log.info("Failed assumption: barycenter yx/yz - %s / %s", cyx / area_xy, cyz / area_yz) if abs(czx / area_xz - czy / area_yz) > epsilon: log.info("Failed assumption: barycenter zx/zy - %s / %s", czx / area_xz, cyz / area_yz) return (cxy / (6 * area_xy), cyx / (6 * area_xy), czx / (6 * area_xz))
def __init__(self, title, message): try: import Tkinter except ImportError: # tk is not installed log.warn("Failed to show error dialog due to a missing Tkinter " \ + "Python package.") return try: root = Tkinter.Tk() except Tkinter.TclError, err_msg: log.info(("Failed to create error dialog window (%s). Probably " \ + "you are running PyCAM from a terminal.") % err_msg) return
def load_model_file(filename, program_locations, unit=None): uri = pycam.Utils.URIHandler(filename) if uri.is_local(): uri = pycam.Utils.URIHandler(os.path.expanduser(str(filename))) if not uri.exists(): log.warn("The input file ('%s') was not found!" % uri) return None importer = pycam.Importers.detect_file_type(uri)[1] model = importer(uri, program_locations=program_locations, unit=unit) if not model: log.warn("Failed to load the model file (%s)." % uri) return None else: return model
def _read_key_value(self): if self._input_stack: return self._input_stack.pop() try: line1 = self.inputstream.readline(self.MAX_CHARS_PER_LINE).strip() line2 = self.inputstream.readline(self.MAX_CHARS_PER_LINE).strip() except IOError: return None, None if not line1 and not line2: return None, None try: line1 = int(line1) except ValueError: log.warn("DXFImporter: Invalid key in line %d (int expected): %s", self.line_number, line1) return None, None if line1 in [ self.KEYS[key] for key in ("P1_X", "P1_Y", "P1_Z", "P2_X", "P2_Y", "P2_Z", "RADIUS", "ANGLE_START", "ANGLE_END", "TEXT_HEIGHT", "TEXT_WIDTH_FINAL", "TEXT_ROTATION", "TEXT_SKEW_ANGLE", "VERTEX_BULGE") ]: try: line2 = float(line2) except ValueError: log.warn( "DXFImporter: Invalid input in line %d (float expected): %s", self.line_number, line2) line1 = None line2 = None elif line1 in [ self.KEYS[key] for key in ("COLOR", "TEXT_MIRROR_FLAGS", "TEXT_ALIGN_HORIZONTAL", "TEXT_ALIGN_VERTICAL", "MTEXT_ALIGNMENT", "CURVE_TYPE", "VERTEX_FLAGS") ]: try: line2 = int(line2) except ValueError: log.warn( "DXFImporter: Invalid input in line %d (int expected): %s", self.line_number, line2) line1 = None line2 = None elif line1 in [self.KEYS[key] for key in ("DEFAULT", "TEXT_MORE")]: # check the string for invalid characters try: text = line2.decode("utf") except UnicodeDecodeError: log.warn("DXFImporter: Invalid character in string in line %d", self.line_number) text = line2.decode("utf", errors="ignore") line2 = _unescape_control_characters(text) else: line2 = line2.upper() self.line_number += 2 return line1, line2
def get_plane_projection(self, plane): if plane == self.plane: return self elif pdot(plane.n, self.plane.n) == 0: log.warn("Polygon projection onto plane: orthogonal projection is not possible") return None else: result = Polygon(plane) for line in self.get_lines(): p1 = plane.get_point_projection(line.p1) p2 = plane.get_point_projection(line.p2) result.append(Line(p1, p2)) # check if the projection would revert the direction of the polygon if pdot(plane.n, self.plane.n) < 0: result.reverse_direction() return result
def _read_key_value(self): if self._input_stack: return self._input_stack.pop() try: line1 = self.inputstream.readline(self.MAX_CHARS_PER_LINE).strip() line2 = self.inputstream.readline(self.MAX_CHARS_PER_LINE).strip() except IOError: return None, None if not line1 and not line2: return None, None try: line1 = int(line1) except ValueError: log.warn("DXFImporter: Invalid key in line " \ + "%d (int expected): %s" % (self.line_number, line1)) return None, None if line1 in [self.KEYS[key] for key in ("P1_X", "P1_Y", "P1_Z", "P2_X", "P2_Y", "P2_Z", "RADIUS", "ANGLE_START", "ANGLE_END", "TEXT_HEIGHT", "TEXT_WIDTH_FINAL", "TEXT_ROTATION", "TEXT_SKEW_ANGLE", "VERTEX_BULGE")]: try: line2 = float(line2) except ValueError: log.warn("DXFImporter: Invalid input in line " \ + "%d (float expected): %s" % (self.line_number, line2)) line1 = None line2 = None elif line1 in [self.KEYS[key] for key in ("COLOR", "TEXT_MIRROR_FLAGS", "TEXT_ALIGN_HORIZONTAL", "TEXT_ALIGN_VERTICAL", "MTEXT_ALIGNMENT", "CURVE_TYPE", "VERTEX_FLAGS")]: try: line2 = int(line2) except ValueError: log.warn("DXFImporter: Invalid input in line " \ + "%d (int expected): %s" % (self.line_number, line2)) line1 = None line2 = None elif line1 in [self.KEYS[key] for key in ("DEFAULT", "TEXT_MORE")]: # check the string for invalid characters try: text = unicode(line2) except UnicodeDecodeError: log.warn("DXFImporter: Invalid character in string in " + \ "line %d" % self.line_number) text_chars = [] for char in line2: try: text_chars.append(unicode(char)) except: pass text = u"".join(text_chars) line2 = _unescape_control_characters(text) else: line2 = line2.upper() self.line_number += 2 return line1, line2
def parse_line(self): start_line = self.line_number # the z-level defaults to zero (for 2D models) p1 = [None, None, 0] p2 = [None, None, 0] color = None key, value = self._read_key_value() while (key is not None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: p1[0] = value elif key == self.KEYS["P1_Y"]: p1[1] = value elif key == self.KEYS["P1_Z"]: p1[2] = value elif key == self.KEYS["P2_X"]: p2[0] = value elif key == self.KEYS["P2_Y"]: p2[1] = value elif key == self.KEYS["P2_Z"]: p2[2] = value elif key == self.KEYS["COLOR"]: color = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if key is not None: self._push_on_stack(key, value) if (None in p1) or (None in p2): log.warn( "DXFImporter: Incomplete LINE definition between line %d and %d", start_line, end_line) else: if self._color_as_height and (color is not None): # use the color code as the z coordinate p1[2] = float(color) / 255 p2[2] = float(color) / 255 line = Line((p1[0], p1[1], p1[2]), (p2[0], p2[1], p2[2])) if line.p1 != line.p2: self.lines.append(line) else: log.warn( "DXFImporter: Ignoring zero-length LINE (between input line %d and %d): " "%s", start_line, end_line, line)
def parse_line(self): start_line = self.line_number # the z-level defaults to zero (for 2D models) p1 = [None, None, 0] p2 = [None, None, 0] color = None key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: p1[0] = value elif key == self.KEYS["P1_Y"]: p1[1] = value elif key == self.KEYS["P1_Z"]: p1[2] = value elif key == self.KEYS["P2_X"]: p2[0] = value elif key == self.KEYS["P2_Y"]: p2[1] = value elif key == self.KEYS["P2_Z"]: p2[2] = value elif key == self.KEYS["COLOR"]: color = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if not key is None: self._push_on_stack(key, value) if (None in p1) or (None in p2): log.warn("DXFImporter: Incomplete LINE definition between line " \ + "%d and %d" % (start_line, end_line)) else: if self._color_as_height and (not color is None): # use the color code as the z coordinate p1[2] = float(color) / 255 p2[2] = float(color) / 255 line = Line(Point(p1[0], p1[1], p1[2]), Point(p2[0], p2[1], p2[2])) if line.p1 != line.p2: self.lines.append(line) else: log.warn("DXFImporter: Ignoring zero-length LINE (between " \ + "input line %d and %d): %s" % (start_line, end_line, line))
def save_preferences(self): """ save all settings that are available in the Preferences window to a file in the user's home directory """ config_filename = pycam.Gui.Settings.get_config_filename() if config_filename is None: # failed to create the personal preferences directory log.warn("Failed to create a preferences directory in " \ + "your user's home directory.") return config = ConfigParser.ConfigParser() for item in PREFERENCES_DEFAULTS.keys(): config.set("DEFAULT", item, self.settings.get(item)) try: config_file = file(config_filename, "w") config.write(config_file) config_file.close() except IOError, err_msg: log.warn("Failed to write preferences file (%s): %s" % (config_filename, err_msg))
def parse_toolpath_settings(filename): """ parse potential PyCAM settings from a given file This is mainly useful to retrieve task settings from a GCode file. @value filename: the name of the file to be read @type filename: str @returns: a dictionary (of all setting names and values) and the content of the 'comment' section (as a single string) @rtype: tuple(dict, str) """ keywords = {} in_meta_zone = False meta_content = [] if filename == "-": # read from stdin, if the input filename is "-" infile = sys.stdin close_file = False else: # open the file try: infile = pycam.Utils.URIHandler(filename).open() except IOError as err_msg: log.warn("ToolpathSettingsParser: Failed to read file (%s): %s", filename, err_msg) return None close_file = True for line in infile.readlines(): match = re.match(REGEX_META_KEYWORDS, line) if match: keywords[match.groups()[0]] = match.groups()[1].strip() if in_meta_zone: if re.match(REGEX_SETTINGS_END, line): in_meta_zone = False else: if line and line[0] in COMMENT_CHARACTERS: meta_content[-1].append(line[1:].strip()) else: if re.match(REGEX_SETTINGS_START, line): in_meta_zone = True meta_content.append([]) if close_file: # only close the file if it was opened before (e.g. not stdin) infile.close() return keywords, [os.linesep.join(one_block) for one_block in meta_content]
def get_config_dirname(): try: from win32com.shell import shellcon, shell homedir = shell.SHGetFolderPath(0, shellcon.CSIDL_APPDATA, 0, 0) config_dir = os.path.join(homedir, CONFIG_DIR) except ImportError: # quick semi-nasty fallback for non-windows/win32com case homedir = os.path.expanduser("~") # hide the config directory for unixes config_dir = os.path.join(homedir, "." + CONFIG_DIR) if not os.path.isdir(config_dir): try: os.makedirs(config_dir) except OSError: log.warn( "Failed to create preferences directory in your user's home directory: %s", config_dir) config_dir = None return config_dir
def import_font(filename, callback=None): try: infile = pycam.Utils.URIHandler(filename).open() except IOError as err_msg: log.error("CXFImporter: Failed to read file (%s): %s", filename, err_msg) return None try: parsed_font = CXFParser(infile, callback=callback) except _CXFParseError as err_msg: log.warn("CFXImporter: Skipped font defintion file '%s'. Reason: %s.", filename, err_msg) return None charset = Charset(**parsed_font.meta) for key, value in parsed_font.letters.iteritems(): charset.add_character(key, value) log.info("CXFImporter: Imported CXF font from '%s': %d letters", filename, len(parsed_font.letters)) infile.close() return charset
def parse_content(self): key, value = self._read_key_value() while (key is not None) and not ((key == self.KEYS["MARKER"]) and (value == "EOF")): if self.callback and self.callback(): return if key == self.KEYS["MARKER"]: if value in ("SECTION", "TABLE", "LAYER", "ENDTAB", "ENDSEC"): # we don't handle these meta-information pass elif value == "LINE": self.parse_line() elif value == "LWPOLYLINE": self.parse_lwpolyline() elif value == "POLYLINE": self.parse_polyline(True) elif value == "VERTEX": self.parse_vertex() elif value == "SEQEND": self.close_sequence() elif value == "ARC": self.parse_arc() elif value == "CIRCLE": self.parse_arc(circle=True) elif value == "TEXT": self.parse_text() elif value == "MTEXT": self.parse_mtext() elif value == "3DFACE": self.parse_3dface() elif value in self.IGNORE_KEYS: log.debug( "DXFImporter: Ignored a blacklisted element in line %d: %s", self.line_number, value) else: # not supported log.warn( "DXFImporter: Ignored unsupported element in line %d: %s", self.line_number, value) key, value = self._read_key_value()
def load_preferences(self): """ load all settings (see Preferences window) from a file in the user's home directory """ config = ConfigParser() try: with pycam.Gui.Settings.open_preferences_file() as in_file: config.read_file(in_file) except FileNotFoundError as exc: log.info( "No preferences file found (%s). Starting with default preferences.", exc) except OSError as exc: log.error("Failed to read preferences: %s", exc) return # report any ignored (obsolete) preference keys present in the file for item, value in config.items("DEFAULT"): if item not in PREFERENCES_DEFAULTS.keys(): log.warn("Skipping obsolete preference item: %s", str(item)) for item in PREFERENCES_DEFAULTS: if not config.has_option("DEFAULT", item): # a new preference setting is missing in the (old) file continue value_json = config.get("DEFAULT", item) try: value = json.loads(value_json) except ValueError as exc: log.warning("Failed to parse configuration setting '%s': %s", item, exc) value = PREFERENCES_DEFAULTS[item] wanted_type = type(PREFERENCES_DEFAULTS[item]) if wanted_type is float: # int is accepted for floats, too wanted_type = (float, int) if not isinstance(value, wanted_type): log.warning( "Falling back to default configuration setting for '%s' due to " "an invalid value type being parsed: %s != %s", item, type(value), wanted_type) value = PREFERENCES_DEFAULTS[item] self.settings.set(item, value)
def parse_content(self): key, value = self._read_key_value() while (not key is None) \ and not ((key == self.KEYS["MARKER"]) and (value == "EOF")): if self.callback and self.callback(): return if key == self.KEYS["MARKER"]: if value in ("SECTION", "TABLE", "LAYER", "ENDTAB", "ENDSEC"): # we don't handle these meta-information pass elif value == "LINE": self.parse_line() elif value == "LWPOLYLINE": self.parse_lwpolyline() elif value == "POLYLINE": self.parse_polyline(True) elif value == "VERTEX": self.parse_vertex() elif value == "SEQEND": self.close_sequence() elif value == "ARC": self.parse_arc() elif value == "CIRCLE": self.parse_arc(circle=True) elif value == "TEXT": self.parse_text() elif value == "MTEXT": self.parse_mtext() elif value == "3DFACE": self.parse_3dface() elif value in self.IGNORE_KEYS: log.debug("DXFImporter: Ignored a blacklisted element " \ + "in line %d: %s" % (self.line_number, value)) else: # not supported log.warn("DXFImporter: Ignored unsupported element " \ + "in line %d: %s" % (self.line_number, value)) key, value = self._read_key_value()
def recommends_details_gtk(): result = {} try: import gtk.gtkgl # noqa F401 result["gtkgl"] = True result["gl"] = True except ImportError as err_msg: log.warn("Failed to import OpenGL for GTK (ImportError): %s", str(err_msg)) result["gtkgl"] = False except RuntimeError as err_msg: log.warn("Failed to import OpenGL for GTK (RuntimeError): %s", str(err_msg)) result["gl"] = False try: import OpenGL # noqa F401 result["opengl"] = True except ImportError as err_msg: log.warn("Failed to import OpenGL: %s", str(err_msg)) result["opengl"] = False
def get_font_dir(): if FONT_DIR_OVERRIDE: if os.path.isdir(FONT_DIR_OVERRIDE): return FONT_DIR_OVERRIDE else: log.warn("You specified a font dir that does not exist (%s). I will ignore it.", FONT_DIR_OVERRIDE) font_dir = get_data_file_location(FONTS_SUBDIR, silent=True) if font_dir is not None: return font_dir else: log.warn("Failed to locate the fonts directory '%s' below '%s'. Falling back to '%s'.", FONTS_SUBDIR, DATA_BASE_DIRS, ":".join(FONT_DIRS_FALLBACK)) for font_dir_fallback in FONT_DIRS_FALLBACK: if os.path.isdir(font_dir_fallback): return font_dir_fallback log.warn("None of the fallback font directories (%s) exist. No fonts will be available.", ":".join(FONT_DIRS_FALLBACK)) return None
def get_font_dir(): if FONT_DIR_OVERRIDE: if os.path.isdir(FONT_DIR_OVERRIDE): return FONT_DIR_OVERRIDE else: log.warn(("You specified a font dir that does not exist (%s). " + "I will ignore it.") % FONT_DIR_OVERRIDE) font_dir = get_data_file_location(FONTS_SUBDIR, silent=True) if not font_dir is None: return font_dir else: log.warn( ("Failed to locate the fonts directory '%s' below '%s'. " + "Falling back to '%s'.") % (FONTS_SUBDIR, DATA_BASE_DIRS, ":".join(FONT_DIRS_FALLBACK)) ) for font_dir_fallback in FONT_DIRS_FALLBACK: if os.path.isdir(font_dir_fallback): return font_dir_fallback else: log.warn( ("None of the fallback font directories (%s) exist. " + "No fonts will be available.") % ":".join(FONT_DIRS_FALLBACK) ) return None
log.error("STLImporter: STL binary/ascii detection failed") return None if use_kdtree: kdtree = PointKdtree([], 3, 1, epsilon) model = Model(use_kdtree) t = None p1 = None p2 = None p3 = None if binary: for i in range(1, numfacets + 1): if callback and callback(): log.warn("STLImporter: load model operation cancelled") return None a1 = unpack("<f", f.read(4))[0] a2 = unpack("<f", f.read(4))[0] a3 = unpack("<f", f.read(4))[0] n = (float(a1), float(a2), float(a3), 'v') v11 = unpack("<f", f.read(4))[0] v12 = unpack("<f", f.read(4))[0] v13 = unpack("<f", f.read(4))[0] p1 = UniqueVertex(float(v11), float(v12), float(v13)) v21 = unpack("<f", f.read(4))[0] v22 = unpack("<f", f.read(4))[0]
def parse_text(self): start_line = self.line_number # the z-level defaults to zero (for 2D models) ref_point = [None, None, 0] ref_point2 = [None, None, 0] color = None text = None text_height = None rotation = 0 width_final = None skew_angle = 0 font_name = "normal" mirror_flags = 0 align_horiz = 0 align_vert = 0 key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["DEFAULT"]: text = value elif key == self.KEYS["P1_X"]: ref_point[0] = value elif key == self.KEYS["P1_Y"]: ref_point[1] = value elif key == self.KEYS["P1_Z"]: ref_point[2] = value elif key == self.KEYS["P2_X"]: ref_point2[0] = value elif key == self.KEYS["P2_Y"]: ref_point2[1] = value elif key == self.KEYS["P2_Z"]: ref_point2[2] = value elif key == self.KEYS["COLOR"]: color = value elif key == self.KEYS["TEXT_HEIGHT"]: text_height = value elif key == self.KEYS["TEXT_ROTATION"]: rotation = value elif key == self.KEYS["TEXT_SKEW_ANGLE"]: skew_angle = value elif key == self.KEYS["TEXT_FONT"]: font_name = value elif key == self.KEYS["TEXT_MIRROR_FLAGS"]: mirror_flags = value elif key == self.KEYS["TEXT_ALIGN_HORIZONTAL"]: align_horiz = value elif key == self.KEYS["TEXT_ALIGN_VERTICAL"]: align_vert = value elif key == self.KEYS["TEXT_WIDTH_FINAL"]: width_final = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if not key is None: self._push_on_stack(key, value) if (not None in ref_point2) and (ref_point != ref_point2): # just a warning - continue as usual log.warn("DXFImporter: Second alignment point is not " + \ "implemented for TEXT items - the text specified " + \ "between line %d and %d may be slightly misplaced" % \ (start_line, end_line)) if None in ref_point: log.warn("DXFImporter: Incomplete TEXT definition between line " \ + "%d and %d: missing location point" % \ (start_line, end_line)) elif not text: log.warn("DXFImporter: Incomplete TEXT definition between line " \ + "%d and %d: missing text" % (start_line, end_line)) elif not text_height: log.warn("DXFImporter: Incomplete TEXT definition between line " \ + "%d and %d: missing height" % (start_line, end_line)) else: if self._color_as_height and (not color is None): # use the color code as the z coordinate ref_point[2] = float(color) / 255 if self._fonts_cache: font = self._fonts_cache.get_font(font_name) else: font = None if not font: log.warn("DXFImporter: No fonts are available - skipping " + \ "TEXT item between line %d and %d" % \ (start_line, end_line)) return if skew_angle: # calculate the "skew" factor if (skew_angle <= -90) or (skew_angle >= 90): log.warn("DXFImporter: Invalid skew angle for TEXT " + \ "between line %d and %d" % (start_line, end_line)) skew = 0 else: skew = math.tan(skew_angle) else: skew = 0 model = font.render(text, skew=skew) if (model.minx is None) or (model.miny is None) or \ (model.minz is None) or (model.minx == model.maxx) or \ (model.miny == model.maxy): log.warn("DXFImporter: Empty rendered TEXT item between " + \ "line %d and %d" % (start_line, end_line)) return model.scale(text_height / (model.maxy - model.miny), callback=self.callback) if mirror_flags & 2: # x mirror left/right model.transform_by_template("yz_mirror", callback=self.callback) if mirror_flags & 4: # y mirror upside/down model.transform_by_template("xz_mirror", callback=self.callback) # this setting seems to refer to a box - not the text width - ignore if False and width_final: scale_x = width_final / (model.maxx - model.minx) model.scale(scale_x, callback=self.callback) if rotation: matrix = pycam.Geometry.Matrix.get_rotation_matrix_axis_angle( (0, 0, 1), rotation) model.transform_by_matrix(matrix, callback=self.callback) # horizontal alignment if align_horiz == 0: offset_horiz = 0 elif align_horiz == 1: offset_horiz = - (model.maxx - model.minx) / 2 elif align_horiz == 2: offset_horiz = - (model.maxx - model.minx) else: log.warn("DXFImporter: Horizontal TEXT justifications " + \ "(3..5) are not supported - ignoring (between line " + \ "%d and %d)" % (start_line, end_line)) offset_horiz = 0 # vertical alignment if align_vert in (0, 1): # we don't distinguish between "bottom" and "base" offset_vert = 0 elif align_vert == 2: offset_vert = - (model.maxy - model.miny) / 2 elif align_vert == 3: offset_vert = - (model.maxy - model.miny) else: log.warn("DXFImporter: Invalid vertical TEXT justification " + \ " between line %d and %d" % (start_line, end_line)) offset_vert = 0 # shift the text to its final destination shift_x = ref_point[0] - model.minx + offset_horiz shift_y = ref_point[1] - model.miny + offset_vert shift_z = ref_point[2] - model.minz model.shift(shift_x, shift_y, shift_z, callback=self.callback) for polygon in model.get_polygons(): for line in polygon.get_lines(): self.lines.append(line)
def import_model(filename, color_as_height=False, fonts_cache=None, callback=None, **kwargs): if hasattr(filename, "read"): infile = filename else: try: infile = pycam.Utils.URIHandler(filename).open() except IOError as exc: raise LoadFileError( "DXFImporter: Failed to read file ({}): {}".format( filename, exc)) result = DXFParser(infile, color_as_height=color_as_height, fonts_cache=fonts_cache, callback=callback) model_data = result.get_model() lines = model_data["lines"] triangles = model_data["triangles"] if callback and callback(): raise AbortOperationException( "DXFImporter: load model operation was cancelled") # 3D models are preferred over 2D models if triangles: if lines: log.warn("DXFImporter: Ignoring 2D elements in DXF file: %d lines", len(lines)) model = pycam.Geometry.Model.Model() for index, triangle in enumerate(triangles): model.append(triangle) # keep the GUI smooth if callback and (index % 50 == 0): callback() log.info("DXFImporter: Imported DXF model (3D): %d triangles", len(model.triangles())) return model elif lines: model = pycam.Geometry.Model.ContourModel() for index, line in enumerate(lines): model.append(line) # keep the GUI smooth if callback and (index % 50 == 0): callback() # z scaling is always targeted at the 0..1 range if color_as_height and (model.minz != model.maxz): # scale z to 1 scale_z = 1.0 / (model.maxz - model.minz) if callback: callback(text="Scaling height for multi-layered 2D model") log.info("DXFImporter: scaling height for multi-layered 2D model") model.scale(scale_x=1.0, scale_y=1.0, scale_z=scale_z, callback=callback) # shift the model down to z=0 if model.minz != 0: if callback: callback(text="Shifting 2D model down to to z=0") model.shift(0, 0, -model.minz, callback=callback) log.info( "DXFImporter: Imported DXF model (2D): %d lines / %d polygons", len(lines), len(model.get_polygons())) return model else: link = "http://pycam.sourceforge.net/supported-formats" raise LoadFileError( 'DXFImporter: No supported elements found in DXF file!\n' '<a href="%s">Read PyCAM\'s modeling hints.</a>'.format(link))
def init_threading(number_of_processes=None, enable_server=False, remote=None, run_server=False, server_credentials="", local_port=DEFAULT_PORT): global __multiprocessing, __num_of_processes, __manager, __closing, \ __task_source_uuid if __multiprocessing: # kill the manager and clean everything up for a re-initialization cleanup() if (not is_server_mode_available()) and (enable_server or run_server): # server mode is disabled for the Windows pyinstaller standalone # due to "pickle errors". How to reproduce: run the standalone binary # with "--enable-server --server-auth-key foo". feature_matrix_text = "Take a look at the wiki for a matrix of " + \ "platforms and available features: " + \ "http://sf.net/apps/mediawiki/pycam/?title=" + \ "Parallel_Processing_on_different_Platforms" if enable_server: log.warn("Unable to enable server mode with your current " + \ "setup.\n" + feature_matrix_text) elif run_server: log.warn("Unable to run in server-only mode with the Windows " + \ "standalone executable.\n" + feature_matrix_text) else: # no further warnings required pass enable_server = False run_server = False # only local -> no server settings allowed if (not enable_server) and (not run_server): remote = None run_server = None server_credentials = "" try: import multiprocessing mp_is_available = True except ImportError: mp_is_available = False if not mp_is_available: __multiprocessing = False # Maybe a multiprocessing feature was explicitely requested? # Issue some warnings if necessary. multiprocessing_missing_text = "Failed to enable server mode due to " \ + "a lack of 'multiprocessing' capabilities. Please use " \ + "Python2.6 or install the 'python-multiprocessing' package." if enable_server: log.warn("Failed to enable server mode due to a lack of " \ + "'multiprocessing' capabilities. " \ + multiprocessing_missing_text) elif run_server: log.warn("Failed to run in server-only mode due to a lack of " \ + "'multiprocessing' capabilities. " \ + multiprocessing_missing_text) else: # no further warnings required pass else: if number_of_processes is None: # use defaults # don't enable threading for a single cpu if (multiprocessing.cpu_count() > 1) or remote or run_server or \ enable_server: __multiprocessing = multiprocessing __num_of_processes = multiprocessing.cpu_count() else: __multiprocessing = False elif (number_of_processes < 1) and (remote is None) and \ (enable_server is None): # Zero processes are allowed if we use a remote server or offer a # server. __multiprocessing = False else: __multiprocessing = multiprocessing __num_of_processes = number_of_processes # initialize the manager if not __multiprocessing: __manager = None log.info("Disabled parallel processing") elif not enable_server and not run_server: __manager = None log.info("Enabled %d parallel local processes" % __num_of_processes) else: # with multiprocessing log.info("Enabled %d parallel local processes" % __num_of_processes) log.info("Allow remote processing") # initialize the uuid list for all workers worker_uuid_list = [str(uuid.uuid1()) for index in range(__num_of_processes)] __task_source_uuid = str(uuid.uuid1()) if remote is None: # try to guess an appropriate interface for binding if pycam.Utils.get_platform() == pycam.Utils.PLATFORM_WINDOWS: # Windows does not support a wildcard interface listener all_ips = pycam.Utils.get_all_ips() if all_ips: address = (all_ips[0], local_port) log.info("Binding to local interface with IP %s" % \ str(all_ips[0])) else: return "Failed to find any local IP" else: # empty hostname -> wildcard interface # (this does not work with Windows - see above) address = ('', local_port) else: if ":" in remote: host, port = remote.split(":", 1) try: port = int(port) except ValueError: log.warning(("Invalid port specified: '%s' - using " + \ "default port (%d) instead") % \ (port, DEFAULT_PORT)) port = DEFAULT_PORT else: host = remote port = DEFAULT_PORT address = (host, port) if remote is None: tasks_queue = multiprocessing.Queue() results_queue = multiprocessing.Queue() statistics = ProcessStatistics() cache = ProcessDataCache() pending_tasks = PendingTasks() info = ManagerInfo(tasks_queue, results_queue, statistics, cache, pending_tasks) TaskManager.register("tasks", callable=info.get_tasks_queue) TaskManager.register("results", callable=info.get_results_queue) TaskManager.register("statistics", callable=info.get_statistics) TaskManager.register("cache", callable=info.get_cache) TaskManager.register("pending_tasks", callable=info.get_pending_tasks) else: TaskManager.register("tasks") TaskManager.register("results") TaskManager.register("statistics") TaskManager.register("cache") TaskManager.register("pending_tasks") __manager = TaskManager(address=address, authkey=server_credentials) # run the local server, connect to a remote one or begin serving try: if remote is None: __manager.start() log.info("Started a local server.") else: __manager.connect() log.info("Connected to a remote task server.") except (multiprocessing.AuthenticationError, socket.error), err_msg: __manager = None return err_msg except EOFError: __manager = None return "Failed to bind to socket for unknown reasons"
def parse_3dface(self): start_line = self.line_number # the z-level defaults to zero (for 2D models) p1 = [None, None, 0] p2 = [None, None, 0] p3 = [None, None, 0] p4 = [None, None, 0] color = None key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["P1_X"]: p1[0] = value elif key == self.KEYS["P1_Y"]: p1[1] = value elif key == self.KEYS["P1_Z"]: p1[2] = value elif key == self.KEYS["P2_X"]: p2[0] = value elif key == self.KEYS["P2_Y"]: p2[1] = value elif key == self.KEYS["P2_Z"]: p2[2] = value elif key == self.KEYS["P3_X"]: p3[0] = value elif key == self.KEYS["P3_Y"]: p3[1] = value elif key == self.KEYS["P3_Z"]: p3[2] = value elif key == self.KEYS["P4_X"]: p4[0] = value elif key == self.KEYS["P4_Y"]: p4[1] = value elif key == self.KEYS["P4_Z"]: p4[2] = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. if not key is None: self._push_on_stack(key, value) if (None in p1) or (None in p2) or (None in p3): log.warn("DXFImporter: Incomplete 3DFACE definition between line " \ + "%d and %d" % (start_line, end_line)) else: # no color height adjustment for 3DFACE point1 = Point(p1[0], p1[1], p1[2]) point2 = Point(p2[0], p2[1], p2[2]) point3 = Point(p3[0], p3[1], p3[2]) triangles = [] triangles.append((point1, point2, point3)) # DXF specifies, that p3=p4 if triangles (instead of quads) are # written. if (not None in p4) and (p3 != p4): point4 = Point(p4[0], p4[1], p4[2]) triangles.append((point3, point4, point1)) for t in triangles: if (t[0] != t[1]) and (t[0] != t[2]) and (t[1] != t[2]): self.triangles.append(Triangle(t[0], t[1], t[2])) else: log.warn("DXFImporter: Ignoring zero-sized 3DFACE " + \ "(between input line %d and %d): %s" % \ (start_line, end_line, t))
result["gtk"] = False return result def recommends_details_gtk(): result = {} try: import gtk.gtkgl result["gtkgl"] = True result["gl"] = True except ImportError, err_msg: log.warn("Failed to import OpenGL for GTK (ImportError): %s" % \ str(err_msg)) result["gtkgl"] = False except RuntimeError, err_msg: log.warn("Failed to import OpenGL for GTK (RuntimeError): %s" % \ str(err_msg)) result["gl"] = False try: import OpenGL result["opengl"] = True except ImportError, err_msg: log.warn("Failed to import OpenGL: %s" % str(err_msg)) result["opengl"] = False def check_dependencies(details): """you can feed this function with the output of '(requirements|recommends)_details_*'. The result is True if all dependencies are met. """ failed = [key for (key, state) in details.items() if not state]
def parse_mtext(self): start_line = self.line_number # the z-level defaults to zero (for 2D models) ref_point = [None, None, 0] direction_vector = [None, None, None] color = None text_groups_start = [] text_end = [] text_height = None rotation = 0 width_final = None font_name = "normal" alignment = 0 key, value = self._read_key_value() while (not key is None) and (key != self.KEYS["MARKER"]): if key == self.KEYS["DEFAULT"]: text_end = value elif key == self.KEYS["TEXT_MORE"]: text_groups_start.append(value) elif key == self.KEYS["P1_X"]: ref_point[0] = value elif key == self.KEYS["P1_Y"]: ref_point[1] = value elif key == self.KEYS["P1_Z"]: ref_point[2] = value elif key == self.KEYS["P2_X"]: direction_vector[0] = value # according to DXF spec: the last one wins rotation = None elif key == self.KEYS["P2_Y"]: direction_vector[1] = value # according to DXF spec: the last one wins rotation = None elif key == self.KEYS["P2_Z"]: direction_vector[2] = value # according to DXF spec: the last one wins rotation = None elif key == self.KEYS["COLOR"]: color = value elif key == self.KEYS["TEXT_HEIGHT"]: text_height = value elif key == self.KEYS["TEXT_ROTATION"]: rotation = value # according to DXF spec: the last one wins direction_vector = [None, None, None] elif key == self.KEYS["TEXT_FONT"]: font_name = value elif key == self.KEYS["MTEXT_ALIGNMENT"]: alignment = value elif key == self.KEYS["TEXT_WIDTH_FINAL"]: width_final = value else: pass key, value = self._read_key_value() end_line = self.line_number # The last lines were not used - they are just the marker for the next # item. text = "".join(text_groups_start) + text_end if not key is None: self._push_on_stack(key, value) if None in ref_point: log.warn("DXFImporter: Incomplete MTEXT definition between line " \ + "%d and %d: missing location point" % \ (start_line, end_line)) elif not text: log.warn("DXFImporter: Incomplete MTEXT definition between line " \ + "%d and %d: missing text" % (start_line, end_line)) elif not text_height: log.warn("DXFImporter: Incomplete MTEXT definition between line " \ + "%d and %d: missing height" % (start_line, end_line)) else: if self._color_as_height and (not color is None): # use the color code as the z coordinate ref_point[2] = float(color) / 255 if self._fonts_cache: font = self._fonts_cache.get_font(font_name) else: font = None if not font: log.warn("DXFImporter: No fonts are available - skipping " + \ "MTEXT item between line %d and %d" % \ (start_line, end_line)) return model = font.render(text) if (model.minx is None) or (model.miny is None) or \ (model.minz is None) or (model.minx == model.maxx) or \ (model.miny == model.maxy): log.warn("DXFImporter: Empty rendered MTEXT item between " + \ "line %d and %d" % (start_line, end_line)) return model.scale(text_height / (model.maxy - model.miny), callback=self.callback) # this setting seems to refer to a box - not the text width - ignore if False and width_final: scale_x = width_final / (model.maxx - model.minx) model.scale(scale_x, callback=self.callback) if rotation: matrix = pycam.Geometry.Matrix.get_rotation_matrix_axis_angle( (0, 0, 1), rotation) elif not None in direction_vector: # Due to the parsing code above only "rotation" or # "direction_vector" is set at the same time. matrix = pycam.Geometry.Matrix.get_rotation_matrix_from_to( (1, 0, 0), direction_vector) else: matrix = None if matrix: model.transform_by_matrix(matrix, callback=self.callback) # horizontal alignment if alignment % 3 == 1: offset_horiz = 0 elif alignment % 3 == 2: offset_horiz = -(model.maxx - model.minx) / 2 else: offset_horiz = -(model.maxx - model.minx) # vertical alignment if alignment <= 3: offset_vert = -(model.maxy - model.miny) elif alignment <= 6: offset_vert = -(model.maxy - model.miny) / 2 else: offset_vert = 0 # shift the text to its final destination shift_x = ref_point[0] - model.minx + offset_horiz shift_y = ref_point[1] - model.miny + offset_vert shift_z = ref_point[2] - model.minz model.shift(shift_x, shift_y, shift_z, callback=self.callback) for polygon in model.get_polygons(): for line in polygon.get_lines(): self.lines.append(line)
def _get_pathgenerator_instance(trimesh_models, contour_model, cutter, pathgenerator, pathprocessor, physics, milling_style): if pathgenerator != "EngraveCutter" and contour_model: return ("The only available toolpath strategy for 2D contour models " \ + "is 'Engraving'.") if pathgenerator == "DropCutter": if pathprocessor == "ZigZagCutter": processor = PathAccumulator.PathAccumulator(zigzag=True) elif pathprocessor == "PathAccumulator": processor = PathAccumulator.PathAccumulator() else: return ("Invalid postprocessor (%s) for 'DropCutter': only " \ + "'ZigZagCutter' or 'PathAccumulator' are allowed") \ % str(pathprocessor) return DropCutter.DropCutter(cutter, trimesh_models, processor, physics=physics) elif pathgenerator == "PushCutter": if pathprocessor == "PathAccumulator": processor = PathAccumulator.PathAccumulator() elif pathprocessor == "SimpleCutter": processor = SimpleCutter.SimpleCutter() elif pathprocessor == "ZigZagCutter": processor = ZigZagCutter.ZigZagCutter() elif pathprocessor == "PolygonCutter": processor = PolygonCutter.PolygonCutter() elif pathprocessor == "ContourCutter": processor = ContourCutter.ContourCutter() else: return ("Invalid postprocessor (%s) for 'PushCutter' - it " + \ "should be one of these: %s") % \ (pathprocessor, PATH_POSTPROCESSORS) return PushCutter.PushCutter(cutter, trimesh_models, processor, physics=physics) elif pathgenerator == "EngraveCutter": clockwise = (milling_style == "climb") if pathprocessor == "SimpleCutter": processor = SimpleCutter.SimpleCutter() else: return ("Invalid postprocessor (%s) for 'EngraveCutter' - it " \ + "should be: SimpleCutter") % str(pathprocessor) if not contour_model: return "The 'Engraving' toolpath strategy requires a 2D contour " \ + "model (e.g. from a DXF or SVG file)." return EngraveCutter.EngraveCutter(cutter, trimesh_models, contour_model, processor, clockwise=clockwise, physics=physics) elif pathgenerator == "ContourFollow": reverse = (milling_style == "conventional") if pathprocessor == "SimpleCutter": processor = SimpleCutter.SimpleCutter(reverse=reverse) else: return ("Invalid postprocessor (%s) for 'ContourFollow' - it " \ + "should be: SimpleCutter") % str(pathprocessor) if not isinstance(cutter, CylindricalCutter): log.warn("The ContourFollow strategy only works reliably with " + \ "the cylindrical cutter shape. Maybe you should use " + \ "the alternative ContourPolygon strategy instead.") return ContourFollow.ContourFollow(cutter, trimesh_models, processor, physics=physics) elif pathgenerator == "Contour2dCutter": #JULIEN return Contour2dCutter.Contour2dCutter(cutter, trimesh_models, \ Contour2dFlooder.Contour2dFlooder()) else: return "Invalid path generator (%s): not one of %s" \ % (pathgenerator, PATH_GENERATORS)
raise _CXFParseError("Failed to read item coordinates" \ + " in line %d" % feeder.get_index()) self.letters[character] = char_definition else: # unknown line format raise _CXFParseError("Failed to parse unknown content in " \ + "line %d" % feeder.get_index()) def import_font(filename, callback=None): try: infile = pycam.Utils.URIHandler(filename).open() except IOError, err_msg: log.error("CXFImporter: Failed to read file (%s): %s" \ % (filename, err_msg)) return None try: parsed_font = CXFParser(infile, callback=callback) except _CXFParseError, err_msg: log.warn("CFXImporter: Skipped font defintion file '%s'. Reason: %s." \ % (filename, err_msg)) return None charset = Charset(**parsed_font.meta) for key, value in parsed_font.letters.iteritems(): charset.add_character(key, value) log.info("CXFImporter: Imported CXF font from '%s': %d letters" \ % (filename, len(parsed_font.letters))) infile.close() return charset
try: infile = pycam.Utils.URIHandler(filename).open() except IOError, err_msg: log.error("DXFImporter: Failed to read file (%s): %s" \ % (filename, err_msg)) return None result = DXFParser(infile, color_as_height=color_as_height, fonts_cache=fonts_cache, callback=callback) model_data = result.get_model() lines = model_data["lines"] triangles = model_data["triangles"] if callback and callback(): log.warn("DXFImporter: load model operation was cancelled") return None # 3D models are preferred over 2D models if triangles: if lines: log.warn("DXFImporter: Ignoring 2D elements in DXF file: " + \ "%d lines" % len(lines)) model = pycam.Geometry.Model.Model() for index, triangle in enumerate(triangles): model.append(triangle) # keep the GUI smooth if callback and (index % 50 == 0): callback() log.info("DXFImporter: Imported DXF model (3D): %d triangles" % \ len(model.triangles()))