class NumericalToleranceMesh(object):
class NumericalResolutionMesh(object):
class NumericalTolerancePath(object):
class NumericalResolutionPath(object):
class MeshError(Exception):
class TransformError(Exception):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
def _log_time(method):
    def timed(*args, **kwargs): class NumericalToleranceMesh(object):
class NumericalResolutionMesh(object):
class NumericalTolerancePath(object):
class NumericalResolutionPath(object):
class MeshError(Exception):
class TransformError(Exception):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
    def __init__(self, **kwargs):
def _log_time(method):
    def timed(*args, **kwargs): class Line(Entity):
    '''
    A line or poly-line entity
    '''

    def discrete(self, vertices, scale=1.0):
        return vertices[self.points]

    @property
    def is_valid(self):
        valid = np.any((self.points - self.points[0]) != 0)
        return valid

    def explode(self):
        points = np.column_stack((self.points,
                                  self.points)).ravel()[1:-1].reshape((-1, 2))
        return [Line(i) for i in points] class Arc(Entity):

    @property
    def closed(self):
        if hasattr(self, '_closed'):
            return self._closed
        return False

    @closed.setter
    def closed(self, value):
        self._closed = bool(value)

    def discrete(self, vertices, scale=1.0):
        return discretize_arc(vertices[self.points],
                              close=self.closed,
                              scale=scale)

    def center(self, vertices):
        return arc_center(vertices[self.points]) class BSpline(Curve):

    def __init__(self, points, knots, closed=None):
        self.points = points
        self.knots = knots

    def discrete(self, vertices, count=None, scale=1.0):
        result = discretize_bspline(control=vertices[self.points],
                                    knots=self.knots,
                                    count=count,
                                    scale=scale)
        return result

    def to_dict(self):
        '''
        Returns a dictionary with all of the information about the entity.
        '''
        return {'type': self.__class__.__name__,
                'points': self.points.tolist(),
                'knots': self.knots.tolist(),
                'closed': self.closed} def angles_to_threepoint(angles, center, radius, normal=[0, 0, 1]):
    if angles[1] < angles[0]:
        angles[1] += np.pi * 2
    angles = [angles[0], np.mean(angles), angles[1]]
    planar = np.column_stack((np.cos(angles), np.sin(angles))) * radius
    points = planar + center
    return points def is_ccw(points):
    '''
    Given an (n,2) set of points, return True if they are counterclockwise
    '''
    xd = np.diff(points[:, 0])
    yd = np.sum(np.column_stack((points[:, 1],
                                 points[:, 1])).reshape(-1)[1:-1].reshape((-1, 2)), axis=1)
    area = np.sum(xd * yd) * .5
    ccw = area < 0

    return ccw def is_binary_file(file_obj):
    '''
    Returns True if file has non-ASCII characters (> 0x7F, or 127)
    Should work in both Python 2 and 3
    '''
    start = file_obj.tell()
    fbytes = file_obj.read(1024)
    file_obj.seek(start)
    is_str = isinstance(fbytes, str)
    for fbyte in fbytes:
        if is_str:
            code = ord(fbyte)
        else:
            code = fbyte
        if code > 127:
            return True
    return False def multi_dict(pairs):
    '''
    Given a set of key value pairs, create a dictionary.
    If a key occurs multiple times, stack the values into an array.

    Can be called like the regular dict(pairs) constructor

    Arguments
    ----------
    pairs: (n,2) array of key, value pairs

    Returns
    ----------
    result: dict, with all values stored (rather than last with regular dict)

    '''
    result = collections.defaultdict(list)
    for k, v in pairs:
        result[k].append(v)
    return result def make_sequence(obj):
    '''
    Given an object, if it is a sequence return, otherwise
    add it to a length 1 sequence and return.

    Useful for wrapping functions which sometimes return single
    objects and other times return lists of objects.
    '''
    if is_sequence(obj):
        return np.array(list(obj))
    else:
        return np.array([obj])
import numpy as np
import collections
from ...constants import log
from ...constants import tol_path as tol
from ..entities import Line, Arc, BSpline
from ..util import angles_to_threepoint, is_ccw
from ...util import is_binary_file, multi_dict, make_sequence


# unit codes
_DXF_UNITS = {1: 'inches',
              2: 'feet',
              3: 'miles',
              4: 'millimeters',
              5: 'centimeters',
              6: 'meters',
              7: 'kilometers',
              8: 'microinches',
              9: 'mils',
              10: 'yards',
              11: 'angstroms',
              12: 'nanometers',
              13: 'microns',
              14: 'decimeters',
              15: 'decameters',
              16: 'hectometers',
              17: 'gigameters',
              18: 'AU',
              19: 'light years',
              20: 'parsecs'}


def get_key(blob, field, code):
    try:
        line = blob[np.nonzero(blob[:, 1] == field)[0][0] + 1]
    except IndexError:
        return None
    if line[0] == code:
        return int(line[1])
    else:
        return None


def load_dxf(file_obj):
    '''
    Load a DXF file to a dictionary containing vertices and entities.

    Arguments
    ----------
    file_obj: file or file- like object (has object.read method)

    Returns
    ----------
    result: dict, keys are  entities, vertices and metadata
    '''
    def update_metadata(e_data):
        '''
        Pull metadata based on group code
        '''
        # which keys should we extract from the entity data
        # DXF group code : our metadata key
        candidates = {'8': 'layers'}
        for k, v in candidates.items():
            # dict.get will return None if key is not present,
            # maintaining correct length of deque for values
            # so the indexes of a metadata entry correspond with
            # an entity object.
            e_value = make_sequence(e_data.get(k))
            entity_metadata[v].extend(e_value)

    def convert_line(e):
        entities.append(Line(len(vertices) + np.arange(2)))
        vertices.extend(np.array([[e['10'], e['20']],
                                  [e['11'], e['21']]], dtype=np.float64))

    def convert_circle(e):
        R = float(e['40'])
        C = np.array([e['10'],
                      e['20']]).astype(np.float64)
        points = angles_to_threepoint([0, np.pi], C[0:2], R)
        entities.append(Arc(points=(len(vertices) + np.arange(3)),
                            closed=True))
        vertices.extend(points)

    def convert_arc(e):
        R = float(e['40'])
        C = np.array([e['10'],
                      e['20']], dtype=np.float64)
        A = np.radians(np.array([e['50'],
                                 e['51']], dtype=np.float64))
        points = angles_to_threepoint(A, C[0:2], R)
        entities.append(Arc(len(vertices) + np.arange(3),
                            closed=False))
        vertices.extend(points)

    def convert_polyline(e):
        lines = np.column_stack((e['10'], e['20'])).astype(np.float64)

        # 70 is the closed flag for polylines
        # if the closed flag is set, make sure we connect the end to the
        # beginning
        if ('70' in e and
                int(e['70'][0]) == 1):
            lines = np.vstack((lines, lines[:1]))

        # 42 is the bulge flag for polylines
        # "bulge" is autocad for "add a stupid arc using implicit flags
        # in my otherwise normal polygon"
        if '42' in e:
            log.warning('polyline with bulge %s detected, ignoring!',
                        e['42'])


        entities.append(Line(np.arange(len(lines)) + len(vertices)))