Commit ba7d4da4 authored by sumpfralle's avatar sumpfralle

added support for almost all missing DXF features - incl. 3D models

added a FontCache object for incremental font loading (useful for DXF files with text elements)


git-svn-id: https://pycam.svn.sourceforge.net/svnroot/pycam/trunk@1017 bbaffbd6-741e-11dd-a85d-61de82d9cad9
parent f16bc543
...@@ -11,7 +11,9 @@ Version 0.5 - UNRELEASED ...@@ -11,7 +11,9 @@ Version 0.5 - UNRELEASED
* added automatic repair of inconsistent polygon winding (inside/outside detection) * added automatic repair of inconsistent polygon winding (inside/outside detection)
* added toolpath cropping * added toolpath cropping
* added toolpath grid pattern: clone a single toolpath in rows and columns * added toolpath grid pattern: clone a single toolpath in rows and columns
* added support for DXF features "LWPOLYLINE" and "ARC" * added support for more DXF features:
* 2D: "LWPOLYLINE", "ARC", "TEXT", "MTEXT", "CIRCLE", "POLYLINE"
* 3D: "3DFACE"
* added a configuration setting for automatically loading a custom task settings file on startup * added a configuration setting for automatically loading a custom task settings file on startup
* added a simple "undo" feature for reversing model manipulations * added a simple "undo" feature for reversing model manipulations
* GCode features: * GCode features:
......
...@@ -39,6 +39,9 @@ TRANSFORMATIONS = { ...@@ -39,6 +39,9 @@ TRANSFORMATIONS = {
"x_swap_y": ((0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 1, 0)), "x_swap_y": ((0, 1, 0, 0), (1, 0, 0, 0), (0, 0, 1, 0)),
"x_swap_z": ((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)), "x_swap_z": ((0, 0, 1, 0), (0, 1, 0, 0), (1, 0, 0, 0)),
"y_swap_z": ((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)), "y_swap_z": ((1, 0, 0, 0), (0, 0, 1, 0), (0, 1, 0, 0)),
"xy_mirror": ((1, 0, 0, 0), (0, 1, 0, 0), (0, 0, -1, 0)),
"xz_mirror": ((1, 0, 0, 0), (0, -1, 0, 0), (0, 0, 1, 0)),
"yz_mirror": ((-1, 0, 0, 0), (0, 1, 0, 0), (0, 0, 1, 0)),
} }
......
...@@ -481,7 +481,7 @@ class ContourModel(BaseModel): ...@@ -481,7 +481,7 @@ class ContourModel(BaseModel):
if progress_callback and progress_callback(): if progress_callback and progress_callback():
self.reset_cache() self.reset_cache()
return return
log.info("The winding of %d polygons was fixed." % change_counter) log.info("The winding of %d polygon(s) was fixed." % change_counter)
self.reset_cache() self.reset_cache()
def reverse_directions(self, callback=None): def reverse_directions(self, callback=None):
......
...@@ -58,8 +58,10 @@ class Point(object): ...@@ -58,8 +58,10 @@ class Point(object):
Otherwise the result is based on the individual x/y/z comparisons. Otherwise the result is based on the individual x/y/z comparisons.
""" """
if self.__class__ == other.__class__: if self.__class__ == other.__class__:
if (_is_near(self.x, other.x)) and (_is_near(self.y, other.y)) \ if (self.id == other.id) or \
and (_is_near(self.z, other.z)): ((_is_near(self.x, other.x)) and \
(_is_near(self.y, other.y)) and \
(_is_near(self.z, other.z))):
return 0 return 0
elif not _is_near(self.x, other.x): elif not _is_near(self.x, other.x):
return cmp(self.x, other.x) return cmp(self.x, other.x)
...@@ -71,12 +73,19 @@ class Point(object): ...@@ -71,12 +73,19 @@ class Point(object):
return cmp(str(self), str(other)) return cmp(str(self), str(other))
def transform_by_matrix(self, matrix, transformed_list=None, callback=None): def transform_by_matrix(self, matrix, transformed_list=None, callback=None):
# accept 3x4 matrices as well as 3x3 matrices
offsets = []
for column in matrix:
if len(column) < 4:
offsets.append(0)
else:
offsets.append(column[3])
x = self.x * matrix[0][0] + self.y * matrix[0][1] \ x = self.x * matrix[0][0] + self.y * matrix[0][1] \
+ self.z * matrix[0][2] + matrix[0][3] + self.z * matrix[0][2] + offsets[0]
y = self.x * matrix[1][0] + self.y * matrix[1][1] \ y = self.x * matrix[1][0] + self.y * matrix[1][1] \
+ self.z * matrix[1][2] + matrix[1][3] + self.z * matrix[1][2] + offsets[1]
z = self.x * matrix[2][0] + self.y * matrix[2][1] \ z = self.x * matrix[2][0] + self.y * matrix[2][1] \
+ self.z * matrix[2][2] + matrix[2][3] + self.z * matrix[2][2] + offsets[2]
self.x = x self.x = x
self.y = y self.y = y
self.z = z self.z = z
......
...@@ -42,6 +42,7 @@ from pycam.Geometry.Letters import TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, \ ...@@ -42,6 +42,7 @@ from pycam.Geometry.Letters import TEXT_ALIGN_LEFT, TEXT_ALIGN_CENTER, \
import pycam.Geometry.Model import pycam.Geometry.Model
from pycam.Utils import ProgressCounter, check_uri_exists from pycam.Utils import ProgressCounter, check_uri_exists
from pycam.Toolpath import Bounds from pycam.Toolpath import Bounds
import pycam.Utils.FontCache
from pycam import VERSION from pycam import VERSION
import pycam.Physics.ode_physics import pycam.Physics.ode_physics
# this requires ODE - we import it later, if necessary # this requires ODE - we import it later, if necessary
...@@ -100,6 +101,11 @@ FILTER_MODEL = (("All supported model filetypes", ...@@ -100,6 +101,11 @@ FILTER_MODEL = (("All supported model filetypes",
FILTER_CONFIG = (("Config files", "*.conf"),) FILTER_CONFIG = (("Config files", "*.conf"),)
FILTER_EMC_TOOL = (("EMC tool files", "*.tbl"),) FILTER_EMC_TOOL = (("EMC tool files", "*.tbl"),)
CLIPBOARD_TARGETS = {
"svg": ("image/x-inkscape-svg", "image/svg+xml"),
"filename_drag": ("text/uri-list", "text-plain"),
}
PREFERENCES_DEFAULTS = { PREFERENCES_DEFAULTS = {
"enable_ode": False, "enable_ode": False,
"boundary_mode": -1, "boundary_mode": -1,
...@@ -237,37 +243,6 @@ def get_font_dir(): ...@@ -237,37 +243,6 @@ def get_font_dir():
+ "No fonts will be available.") % FONT_DIR_FALLBACK) + "No fonts will be available.") % FONT_DIR_FALLBACK)
return None return None
def get_font_files():
font_dir = get_font_dir()
if not font_dir:
return []
log.info("Loading font files from '%s'." % font_dir)
result = []
files = os.listdir(font_dir)
for fname in files:
filename = os.path.join(font_dir, fname)
if filename.lower().endswith(".cxf") and os.path.isfile(filename):
result.append(filename)
result.sort()
return result
def load_fonts(callback=None):
fonts = {}
all_font_files = get_font_files()
if not callback is None:
progress_counter = ProgressCounter(len(all_font_files), callback)
else:
progress_counter = None
for font_file in all_font_files:
charset = pycam.Importers.CXFImporter.import_font(font_file,
callback=progress_counter.update)
if (not progress_counter is None) and progress_counter.increment():
break
if not charset is None:
for name in charset.get_names():
fonts[name] = charset
return fonts
class ProjectGui: class ProjectGui:
...@@ -295,6 +270,9 @@ class ProjectGui: ...@@ -295,6 +270,9 @@ class ProjectGui:
self._progress_cancel_requested = False self._progress_cancel_requested = False
self._last_gtk_events_time = None self._last_gtk_events_time = None
self._undo_states = [] self._undo_states = []
self.clipboard = gtk.clipboard_get()
self._fonts_cache = pycam.Utils.FontCache.FontCache(get_font_dir(),
callback=self.update_progress_bar)
self.gui = gtk.Builder() self.gui = gtk.Builder()
gtk_build_file = get_data_file_location(GTKBUILD_FILE) gtk_build_file = get_data_file_location(GTKBUILD_FILE)
if gtk_build_file is None: if gtk_build_file is None:
...@@ -451,7 +429,6 @@ class ProjectGui: ...@@ -451,7 +429,6 @@ class ProjectGui:
self.update_font_dialog_preview) self.update_font_dialog_preview)
self._font_dialog_window_visible = False self._font_dialog_window_visible = False
self._font_dialog_window_position = None self._font_dialog_window_position = None
self._fonts = None
# set defaults # set defaults
self.model = None self.model = None
self.toolpath = pycam.Toolpath.ToolpathList() self.toolpath = pycam.Toolpath.ToolpathList()
...@@ -1945,14 +1922,13 @@ class ProjectGui: ...@@ -1945,14 +1922,13 @@ class ProjectGui:
if state is None: if state is None:
state = not self._font_dialog_window_visible state = not self._font_dialog_window_visible
if state: if state:
if self._fonts is None: if not self._fonts_cache.is_loading_complete():
self.update_progress_bar("Initializing fonts") self.update_progress_bar("Initializing fonts")
self._fonts = load_fonts(callback=self.update_progress_bar)
# create it manually to ease access # create it manually to ease access
font_selector = gtk.combo_box_new_text() font_selector = gtk.combo_box_new_text()
self.gui.get_object("FontSelectionBox").pack_start( self.gui.get_object("FontSelectionBox").pack_start(
font_selector, expand=False, fill=False) font_selector, expand=False, fill=False)
sorted_keys = self._fonts.keys() sorted_keys = list(self._fonts_cache.get_font_names())
sorted_keys.sort(key=lambda x: x.upper()) sorted_keys.sort(key=lambda x: x.upper())
for name in sorted_keys: for name in sorted_keys:
font_selector.append_text(name) font_selector.append_text(name)
...@@ -1964,7 +1940,7 @@ class ProjectGui: ...@@ -1964,7 +1940,7 @@ class ProjectGui:
self.update_font_dialog_preview) self.update_font_dialog_preview)
font_selector.show() font_selector.show()
self.font_selector = font_selector self.font_selector = font_selector
if self._fonts: if len(self._fonts_cache) > 0:
# show the dialog only if fonts are available # show the dialog only if fonts are available
if self._font_dialog_window_position: if self._font_dialog_window_position:
self.font_dialog_window.move( self.font_dialog_window.move(
...@@ -2002,8 +1978,9 @@ class ProjectGui: ...@@ -2002,8 +1978,9 @@ class ProjectGui:
align = value align = value
input_field.set_justification(justification) input_field.set_justification(justification)
font_name = self.font_selector.get_active_text() font_name = self.font_selector.get_active_text()
return self._fonts[font_name].render(text, skew=skew, charset = self._fonts_cache.get_font(font_name)
line_spacing=line_space, pitch=pitch, align=align) return charset.render(text, skew=skew, line_spacing=line_space,
pitch=pitch, align=align)
else: else:
# empty text # empty text
return None return None
...@@ -2024,23 +2001,27 @@ class ProjectGui: ...@@ -2024,23 +2001,27 @@ class ProjectGui:
text_buffer = StringIO.StringIO() text_buffer = StringIO.StringIO()
text_model.export(comment=self.get_meta_data(), text_model.export(comment=self.get_meta_data(),
unit=self.settings.get("unit")).write(text_buffer) unit=self.settings.get("unit")).write(text_buffer)
clipboard_target = "image/svg+xml" text_buffer.seek(0)
clipboard = gtk.clipboard_get() text = text_buffer.read()
targets = [(clipboard_target, gtk.TARGET_OTHER_WIDGET, 0)] self._copy_text_to_clipboard(text, CLIPBOARD_TARGETS["svg"])
def _copy_text_to_clipboard(self, text, targets):
targets = [(key, gtk.TARGET_OTHER_WIDGET, index)
for index, key in enumerate(targets)]
def get_func(clipboard, selectiondata, info, text): def get_func(clipboard, selectiondata, info, text):
text.seek(0) # Inkscape for Windows strictly requires the BITMAP type
selectiondata.set("STRING", 8, text.read()) selectiondata.set(gtk.gdk.SELECTION_TYPE_BITMAP, 8, text.read())
result = clipboard.set_with_data(targets, get_func, result = self.clipboard.set_with_data(targets, get_func,
lambda *args: None, text_buffer) lambda *args: None, text)
clipboard.store() self.clipboard.store()
@gui_activity_guard @gui_activity_guard
def update_font_dialog_preview(self, widget=None, event=None): def update_font_dialog_preview(self, widget=None, event=None):
if not self._fonts: if len(self._fonts_cache) == 0:
# not initialized or empty # empty
return return
font_name = self.font_selector.get_active_text() font_name = self.font_selector.get_active_text()
font = self._fonts[font_name] font = self._fonts_cache.get_font(font_name)
self.gui.get_object("FontAuthorText").set_label( self.gui.get_object("FontAuthorText").set_label(
"\n".join(font.get_authors())) "\n".join(font.get_authors()))
preview_widget = self.gui.get_object("FontDialogPreview") preview_widget = self.gui.get_object("FontDialogPreview")
...@@ -2158,8 +2139,7 @@ class ProjectGui: ...@@ -2158,8 +2139,7 @@ class ProjectGui:
columns.append(model.get_value(it, column)) columns.append(model.get_value(it, column))
content.append(" ".join(columns)) content.append(" ".join(columns))
self.log_model.foreach(copy_row, content) self.log_model.foreach(copy_row, content)
clipboard = gtk.Clipboard() self.clipboard.set_text(os.linesep.join(content))
clipboard.set_text(os.linesep.join(content))
self.gui.get_object("StatusBarWarning").hide() self.gui.get_object("StatusBarWarning").hide()
@gui_activity_guard @gui_activity_guard
...@@ -2932,8 +2912,8 @@ class ProjectGui: ...@@ -2932,8 +2912,8 @@ class ProjectGui:
def configure_drag_and_drop(self, obj): def configure_drag_and_drop(self, obj):
obj.connect("drag-data-received", self.handle_data_drop) obj.connect("drag-data-received", self.handle_data_drop)
flags = gtk.DEST_DEFAULT_ALL flags = gtk.DEST_DEFAULT_ALL
targets = [("text/uri-list", gtk.TARGET_OTHER_APP, 0), targets = [(key, gtk.TARGET_OTHER_APP, index)
("text/plain", gtk.TARGET_OTHER_APP, 1)] for index, key in enumerate(CLIPBOARD_TARGETS["filename_drag"])]
actions = gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | \ actions = gtk.gdk.ACTION_COPY | gtk.gdk.ACTION_LINK | \
gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE | \ gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_PRIVATE | \
gtk.gdk.ACTION_ASK gtk.gdk.ACTION_ASK
...@@ -2998,6 +2978,7 @@ class ProjectGui: ...@@ -2998,6 +2978,7 @@ class ProjectGui:
if self.load_model(importer(filename, if self.load_model(importer(filename,
program_locations=program_locations, program_locations=program_locations,
unit=self.settings.get("unit"), unit=self.settings.get("unit"),
fonts_cache=self._fonts_cache,
callback=self.update_progress_bar)): callback=self.update_progress_bar)):
self.set_model_filename(filename) self.set_model_filename(filename)
self.add_to_recent_file_list(filename) self.add_to_recent_file_list(filename)
...@@ -3565,7 +3546,7 @@ class ProjectGui: ...@@ -3565,7 +3546,7 @@ class ProjectGui:
self.menubar.set_sensitive(False) self.menubar.set_sensitive(False)
self.task_pane.set_sensitive(False) self.task_pane.set_sensitive(False)
self._progress_start_time = time.time() self._progress_start_time = time.time()
self.update_progress_bar("", 0) self.update_progress_bar(text="", percent=0)
self.progress_cancel_button.set_sensitive(True) self.progress_cancel_button.set_sensitive(True)
# enable "pulse" mode for a start (in case of unknown ETA) # enable "pulse" mode for a start (in case of unknown ETA)
self.progress_bar.pulse() self.progress_bar.pulse()
...@@ -3585,7 +3566,7 @@ class ProjectGui: ...@@ -3585,7 +3566,7 @@ class ProjectGui:
if not percent is None: if not percent is None:
percent = min(max(percent, 0.0), 100.0) percent = min(max(percent, 0.0), 100.0)
self.progress_bar.set_fraction(percent/100.0) self.progress_bar.set_fraction(percent/100.0)
if (percent is None) and (self.progress_bar.get_fraction() == 0): if (not percent) and (self.progress_bar.get_fraction() == 0):
# use "pulse" mode until we reach 1% of the work to be done # use "pulse" mode until we reach 1% of the work to be done
self.progress_bar.pulse() self.progress_bar.pulse()
# update the GUI # update the GUI
......
...@@ -20,16 +20,32 @@ You should have received a copy of the GNU General Public License ...@@ -20,16 +20,32 @@ You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>. along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
""" """
from pycam.Geometry.Triangle import Triangle
from pycam.Geometry.Point import Point from pycam.Geometry.Point import Point
from pycam.Geometry.Line import Line from pycam.Geometry.Line import Line
import pycam.Geometry.Model import pycam.Geometry.Model
import pycam.Geometry.Matrix
import pycam.Geometry import pycam.Geometry
import pycam.Utils.log import pycam.Utils.log
from pycam.Utils import open_url from pycam.Utils import open_url
import math
import re
import os
log = pycam.Utils.log.get_logger() log = pycam.Utils.log.get_logger()
def _unescape_control_characters(text):
# see http://www.kxcad.net/autodesk/autocad/AutoCAD_2008_Command_Reference/d0e73428.htm
# and QCad: qcadlib/src/filters/rs_filterdxf.cpp
for src, dest in (("%%d", u"\u00B0"), ("%%p", u"\u00B1"),
("%%c", u"\u2205"), (r"\P", os.linesep), (r"\~", " ")):
text = text.replace(src, dest)
# convert "\U+xxxx" to unicode characters
return re.sub(r"\\U\+([0-9a-fA-F]{4})",
lambda hex_in: unichr(int(hex_in.groups()[0], 16)), text)
class DXFParser(object): class DXFParser(object):
# see http://www.autodesk.com/techpubs/autocad/acad2000/dxf/group_code_value_types_dxf_01.htm # see http://www.autodesk.com/techpubs/autocad/acad2000/dxf/group_code_value_types_dxf_01.htm
...@@ -37,30 +53,63 @@ class DXFParser(object): ...@@ -37,30 +53,63 @@ class DXFParser(object):
KEYS = { KEYS = {
"MARKER": 0, "MARKER": 0,
"START_X": 10, "DEFAULT": 1,
"START_Y": 20, "TEXT_MORE": 3,
"START_Z": 30, "TEXT_FONT": 7,
"END_X": 11, "P1_X": 10,
"END_Y": 21, "P1_Y": 20,
"END_Z": 31, "P1_Z": 30,
"P2_X": 11,
"P2_Y": 21,
"P2_Z": 31,
"P3_X": 12,
"P3_Y": 22,
"P3_Z": 32,
"P4_X": 13,
"P4_Y": 23,
"P4_Z": 33,
"RADIUS": 40, "RADIUS": 40,
"TEXT_HEIGHT": 40,
"TEXT_WIDTH_FINAL": 41,
"ANGLE_START": 50, "ANGLE_START": 50,
"TEXT_ROTATION": 50,
"ANGLE_END": 51, "ANGLE_END": 51,
"TEXT_SKEW_ANGLE": 51,
"COLOR": 62, "COLOR": 62,
"TEXT_MIRROR_FLAGS": 71,
"MTEXT_ALIGNMENT": 71,
"TEXT_ALIGN_HORIZONTAL": 72,
"TEXT_ALIGN_VERTICAL": 73,
} }
def __init__(self, inputstream, color_as_height=False, callback=None): IGNORE_KEYS = ("DICTIONARY", "VPORT", "LTYPE", "STYLE", "APPID", "DIMSTYLE",
"BLOCK_RECORD", "BLOCK", "ENDBLK", "ACDBDICTIONARYWDFLT", "POINT",
"ACDBPLACEHOLDER", "LAYOUT", "MLINESTYLE", "DICTIONARYVAR", "CLASS",
"HATCH")
def __init__(self, inputstream, color_as_height=False, fonts_cache=None,
callback=None):
self.inputstream = inputstream self.inputstream = inputstream
self.line_number = 0 self.line_number = 0
self.lines = [] self.lines = []
self.triangles = []
self._input_stack = [] self._input_stack = []
self._color_as_height = color_as_height self._color_as_height = color_as_height
self.callback = callback if callback:
# no "percent" updates - just pulse ...
callback_wrapper = lambda text="", percent=None: callback()
self.callback = callback_wrapper
else:
self.callback = None
self._fonts_cache = fonts_cache
self._open_sequence = None
self._open_sequence_items = []
# run the parser
self.parse_content() self.parse_content()
self.optimize_line_order() self.optimize_line_order()
def get_model(self): def get_model(self):
return {"lines": self.lines} return {"lines": self.lines, "triangles": self.triangles}
def optimize_line_order(self): def optimize_line_order(self):
groups = [] groups = []
...@@ -132,9 +181,10 @@ class DXFParser(object): ...@@ -132,9 +181,10 @@ class DXFParser(object):
log.warn("DXFImporter: Invalid key in line " \ log.warn("DXFImporter: Invalid key in line " \
+ "%d (int expected): %s" % (self.line_number, line1)) + "%d (int expected): %s" % (self.line_number, line1))
return None, None return None, None
if line1 in [self.KEYS[key] for key in ("START_X", "START_Y", "START_Z", if line1 in [self.KEYS[key] for key in ("P1_X", "P1_Y", "P1_Z",
"END_X", "END_Y", "END_Z", "RADIUS", "ANGLE_START", "P2_X", "P2_Y", "P2_Z", "RADIUS", "ANGLE_START", "ANGLE_END",
"ANGLE_END")]: "TEXT_HEIGHT", "TEXT_WIDTH_FINAL", "TEXT_ROTATION",
"TEXT_SKEW_ANGLE")]:
try: try:
line2 = float(line2) line2 = float(line2)
except ValueError: except ValueError:
...@@ -142,7 +192,9 @@ class DXFParser(object): ...@@ -142,7 +192,9 @@ class DXFParser(object):
+ "%d (float expected): %s" % (self.line_number, line2)) + "%d (float expected): %s" % (self.line_number, line2))
line1 = None line1 = None
line2 = None line2 = None
elif line1 in (self.KEYS["COLOR"],): elif line1 in [self.KEYS[key] for key in ("COLOR", "TEXT_MIRROR_FLAGS",
"TEXT_ALIGN_HORIZONTAL", "TEXT_ALIGN_VERTICAL",
"MTEXT_ALIGNMENT")]:
try: try:
line2 = int(line2) line2 = int(line2)
except ValueError: except ValueError:
...@@ -150,6 +202,8 @@ class DXFParser(object): ...@@ -150,6 +202,8 @@ class DXFParser(object):
+ "%d (int expected): %s" % (self.line_number, line2)) + "%d (int expected): %s" % (self.line_number, line2))
line1 = None line1 = None
line2 = None line2 = None
elif line1 in [self.KEYS[key] for key in ("DEFAULT", "TEXT_MORE")]:
line2 = _unescape_control_characters(line2)
else: else:
line2 = line2.upper() line2 = line2.upper()
self.line_number += 2 self.line_number += 2
...@@ -168,16 +222,93 @@ class DXFParser(object): ...@@ -168,16 +222,93 @@ class DXFParser(object):
elif value == "LINE": elif value == "LINE":
self.parse_line() self.parse_line()
elif value == "LWPOLYLINE": elif value == "LWPOLYLINE":
self.parse_polyline() 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": elif value == "ARC":
self.parse_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: else:
# not supported # not supported
log.warn("DXFImporter: Ignored unsupported element in " \ log.warn("DXFImporter: Ignored unsupported element " \
+ "line %d: %s" % (self.line_number, value)) + "in line %d: %s" % (self.line_number, value))
key, value = self._read_key_value()
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 parse_vertex(self):
start_line = self.line_number
point = [None, None, 0]
color = None
key, value = self._read_key_value() 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
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]))
def parse_polyline(self, init):
start_line = self.line_number
if init:
self._open_sequence = "POLYLINE"
self._open_sequence_items = []
key, value = self._read_key_value()
while (not key is None) and (key != self.KEYS["MARKER"]):
key, value = self._read_key_value()
if not key is None:
self._push_on_stack(key, value)
else:
# closing
points = self._open_sequence_items
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))
self._open_sequence_items = []
self._open_sequence = None
def parse_polyline(self): def parse_lwpolyline(self):
start_line = self.line_number
points = [] points = []
def add_point(p_array): def add_point(p_array):
# fill all "None" values with zero # fill all "None" values with zero
...@@ -189,15 +320,14 @@ class DXFParser(object): ...@@ -189,15 +320,14 @@ class DXFParser(object):
(self.line_number, p_array)) (self.line_number, p_array))
p_array[index] = 0 p_array[index] = 0
points.append(Point(p_array[0], p_array[1], p_array[2])) points.append(Point(p_array[0], p_array[1], p_array[2]))
start_line = self.line_number
current_point = [None, None, None] current_point = [None, None, None]
key, value = self._read_key_value() key, value = self._read_key_value()
while (not key is None) and (key != self.KEYS["MARKER"]): while (not key is None) and (key != self.KEYS["MARKER"]):
if key == self.KEYS["START_X"]: if key == self.KEYS["P1_X"]:
axis = 0 axis = 0
elif key == self.KEYS["START_Y"]: elif key == self.KEYS["P1_Y"]:
axis = 1 axis = 1
elif not self._color_as_height and (key == self.KEYS["START_Z"]): elif not self._color_as_height and (key == self.KEYS["P1_Z"]):
axis = 2 axis = 2
elif self._color_as_height and (key == self.KEYS["COLOR"]): elif self._color_as_height and (key == self.KEYS["COLOR"]):
# interpret the color as the height # interpret the color as the height
...@@ -238,6 +368,352 @@ class DXFParser(object): ...@@ -238,6 +368,352 @@ class DXFParser(object):
+ "(between input line %d and %d): %s" \ + "(between input line %d and %d): %s" \
% (start_line, end_line, point)) % (start_line, end_line, point))
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 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 (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 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))
if not None in 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))
def parse_line(self): def parse_line(self):
start_line = self.line_number start_line = self.line_number
# the z-level defaults to zero (for 2D models) # the z-level defaults to zero (for 2D models)
...@@ -246,17 +722,17 @@ class DXFParser(object): ...@@ -246,17 +722,17 @@ class DXFParser(object):
color = None color = None
key, value = self._read_key_value() key, value = self._read_key_value()
while (not key is None) and (key != self.KEYS["MARKER"]): while (not key is None) and (key != self.KEYS["MARKER"]):
if key == self.KEYS["START_X"]: if key == self.KEYS["P1_X"]:
p1[0] = value p1[0] = value
elif key == self.KEYS["START_Y"]: elif key == self.KEYS["P1_Y"]:
p1[1] = value p1[1] = value
elif key == self.KEYS["START_Z"]: elif key == self.KEYS["P1_Z"]:
p1[2] = value p1[2] = value
elif key == self.KEYS["END_X"]: elif key == self.KEYS["P2_X"]:
p2[0] = value p2[0] = value
elif key == self.KEYS["END_Y"]: elif key == self.KEYS["P2_Y"]:
p2[1] = value p2[1] = value
elif key == self.KEYS["END_Z"]: elif key == self.KEYS["P2_Z"]:
p2[2] = value p2[2] = value
elif key == self.KEYS["COLOR"]: elif key == self.KEYS["COLOR"]:
color = value color = value
...@@ -283,21 +759,26 @@ class DXFParser(object): ...@@ -283,21 +759,26 @@ class DXFParser(object):
log.warn("DXFImporter: Ignoring zero-length LINE (between " \ log.warn("DXFImporter: Ignoring zero-length LINE (between " \
+ "input line %d and %d): %s" % (start_line, end_line, + "input line %d and %d): %s" % (start_line, end_line,
line)) line))
def parse_arc(self):
def parse_arc(self, circle=False):
start_line = self.line_number start_line = self.line_number
# the z-level defaults to zero (for 2D models) # the z-level defaults to zero (for 2D models)
center = [None, None, 0] center = [None, None, 0]
color = None color = None
radius = None radius = None
if circle:
angle_start = 0
angle_end = 360
else:
angle_start = None angle_start = None
angle_end = None angle_end = None
key, value = self._read_key_value() key, value = self._read_key_value()
while (not key is None) and (key != self.KEYS["MARKER"]): while (not key is None) and (key != self.KEYS["MARKER"]):
if key == self.KEYS["START_X"]: if key == self.KEYS["P1_X"]:
center[0] = value center[0] = value
elif key == self.KEYS["START_Y"]: elif key == self.KEYS["P1_Y"]:
center[1] = value center[1] = value
elif key == self.KEYS["START_Z"]: elif key == self.KEYS["P1_Z"]:
center[2] = value center[2] = value
elif key == self.KEYS["RADIUS"]: elif key == self.KEYS["RADIUS"]:
radius = value radius = value
...@@ -349,8 +830,8 @@ class DXFParser(object): ...@@ -349,8 +830,8 @@ class DXFParser(object):
return None return None
def import_model(filename, program_locations=None, unit=None, def import_model(filename, color_as_height=False, fonts_cache=None,
color_as_height=False, callback=None): callback=None, **kwargs):
try: try:
infile = open_url(filename) infile = open_url(filename)
except IOError, err_msg: except IOError, err_msg:
...@@ -359,15 +840,31 @@ def import_model(filename, program_locations=None, unit=None, ...@@ -359,15 +840,31 @@ def import_model(filename, program_locations=None, unit=None,
return None return None
result = DXFParser(infile, color_as_height=color_as_height, result = DXFParser(infile, color_as_height=color_as_height,
callback=callback) fonts_cache=fonts_cache, callback=callback)
lines = result.get_model()["lines"] model_data = result.get_model()
lines = model_data["lines"]
triangles = model_data["triangles"]
if callback and callback(): if callback and callback():
log.warn("DXFImporter: load model operation was cancelled") log.warn("DXFImporter: load model operation was cancelled")
return None return None
# 3D models are preferred over 2D models
if triangles:
if lines: 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() model = pycam.Geometry.Model.ContourModel()
for index, line in enumerate(lines): for index, line in enumerate(lines):
model.append(line) model.append(line)
...@@ -388,8 +885,9 @@ def import_model(filename, program_locations=None, unit=None, ...@@ -388,8 +885,9 @@ def import_model(filename, program_locations=None, unit=None,
if callback: if callback:
callback(text="Shifting 2D model down to to z=0") callback(text="Shifting 2D model down to to z=0")
model.shift(0, 0, -model.minz, callback=callback) model.shift(0, 0, -model.minz, callback=callback)
log.info("DXFImporter: Imported DXF model: %d lines / %d polygons" \ log.info("DXFImporter: Imported DXF model (2D): " + \
% (len(lines), len(model.get_polygons()))) "%d lines / %d polygons" % \
(len(lines), len(model.get_polygons())))
return model return model
else: else:
link = "http://sf.net/apps/mediawiki/pycam/?title=SupportedFormats" link = "http://sf.net/apps/mediawiki/pycam/?title=SupportedFormats"
......
...@@ -29,7 +29,8 @@ import os ...@@ -29,7 +29,8 @@ import os
log = pycam.Utils.log.get_logger() log = pycam.Utils.log.get_logger()
def import_model(filename, program_locations=None, unit="mm", callback=None): def import_model(filename, program_locations=None, unit="mm", callback=None,
**kwargs):
if not check_uri_exists(filename): if not check_uri_exists(filename):
log.error("PSImporter: file (%s) does not exist" % filename) log.error("PSImporter: file (%s) does not exist" % filename)
return None return None
......
...@@ -52,8 +52,7 @@ def UniqueVertex(x, y, z): ...@@ -52,8 +52,7 @@ def UniqueVertex(x, y, z):
vertices += 1 vertices += 1
return Point(x, y, z) return Point(x, y, z)
def ImportModel(filename, use_kdtree=True, program_locations=None, unit=None, def ImportModel(filename, use_kdtree=True, callback=None, **kwargs):
callback=None):
global vertices, edges, kdtree global vertices, edges, kdtree
vertices = 0 vertices = 0
edges = 0 edges = 0
......
...@@ -81,7 +81,8 @@ def convert_eps2dxf(eps_filename, dxf_filename, location=None, unit="mm"): ...@@ -81,7 +81,8 @@ def convert_eps2dxf(eps_filename, dxf_filename, location=None, unit="mm"):
process.stderr.read())) process.stderr.read()))
return False return False
def import_model(filename, program_locations=None, unit="mm", callback=None): def import_model(filename, program_locations=None, unit="mm", callback=None,
**kwargs):
if not check_uri_exists(filename): if not check_uri_exists(filename):
log.error("SVGImporter: file (%s) does not exist" % filename) log.error("SVGImporter: file (%s) does not exist" % filename)
return None return None
......
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
$Id$
Copyright 2010 Lars Kruse <devel@sumpfralle.de>
This file is part of PyCAM.
PyCAM is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
PyCAM is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with PyCAM. If not, see <http://www.gnu.org/licenses/>.
"""
import pycam.Utils.log
import os
DEFAULT_NAMES = ("normal", "default", "standard")
log = pycam.Utils.log.get_logger()
class FontCache(object):
""" The FontCache gradually loads fonts. This is more efficient than an
immeadiate initialization of all fonts for the DXF importer.
Use "get_font" for loading (incrementally) fonts until the requested font
name was found.
The functions "get_names" and "len()" trigger an immediate initialization
of all available fonts.
"""
def __init__(self, font_dir=None, callback=None):
self.font_dir = font_dir
self.fonts = {}
self.callback = callback
self._unused_font_files = list(self._get_font_files())
def is_loading_complete(self):
return len(self._unused_font_files) == 0
def _get_font_files(self):
if self.font_dir is None:
return []
log.info("Font directory: %s" % self.font_dir)
result = []
files = os.listdir(self.font_dir)
for fname in files:
filename = os.path.join(self.font_dir, fname)
if filename.lower().endswith(".cxf") and os.path.isfile(filename):
result.append(filename)
result.sort()
return result
def __len__(self):
self._load_all_files()
return len(self.fonts)
def _get_font_without_loading(self, name):
for font_name in self.fonts:
if font_name.lower() == name.lower():
return self.fonts[font_name]
else:
return None
def get_font_names(self):
self._load_all_files()
return self.fonts.keys()
def get_font(self, name):
font = self._get_font_without_loading(name)
while not font and not self.is_loading_complete():
self._load_next_file()
font = self._get_font_without_loading(name)
if font:
return font
else:
# no font with that name is available
for other_name in DEFAULT_NAMES:
font = self._get_font_without_loading(other_name)
if font:
return font
else:
if self.fonts:
# return the first (random) font in the dictionary
return self.fonts.values()[0]
def _load_all_files(self):
while not self.is_loading_complete():
self._load_next_file()
def _load_next_file(self):
if self.is_loading_complete():
return
filename = self._unused_font_files.pop(0)
charset = pycam.Importers.CXFImporter.import_font(filename,
callback=self.callback)
if not charset is None:
for name in charset.get_names():
self.fonts[name] = charset
...@@ -112,7 +112,7 @@ class RepetitionsFilter(logging.Filter): ...@@ -112,7 +112,7 @@ class RepetitionsFilter(logging.Filter):
if self._suppressed_messages_counter > 0: if self._suppressed_messages_counter > 0:
# inject a message regarding the previously suppressed messages # inject a message regarding the previously suppressed messages
self._last_record.msg = \ self._last_record.msg = \
"*** %d similar messages were suppressed ***" % \ "*** skipped %d similar message(s) ***" % \
self._suppressed_messages_counter self._suppressed_messages_counter
self._handler.emit(self._last_record) self._handler.emit(self._last_record)
self._last_record = record self._last_record = record
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment