Example #1
0
class MText(object):
    """ MultiLine-Text buildup with simple Text-Entities.

    Mostly the same kwargs like DXFEngine.text().
    Caution: align point is always the insert point, I don't need a second
    alignpoint because horizontal alignment FIT, ALIGN, BASELINE_MIDDLE is not
    supported.

    linespacing -- linespacing in percent of height, 1.5 = 150% = 1+1/2 lines
    """
    name = 'MTEXT'

    def __init__(self, text, insert, linespacing=1.5, **kwargs):
        self.textlines = text.split('\n')
        self.insert = insert
        self.linespacing = linespacing
        self.valign = kwargs.get('valign', dxfwrite.TOP) # only top, middle, bottom
        if self.valign == dxfwrite.BASELINE: # baseline for MText not usefull
            self.valign = dxfwrite.BOTTOM
        self.halign = kwargs.get('halign', dxfwrite.LEFT) # only left, center, right
        self.height = kwargs.get('height', 1.0)
        self.style = kwargs.get('style', 'STANDARD')
        self.oblique = kwargs.get('oblique', 0.0) # in degree
        self.rotation = kwargs.get('rotation', 0.0) # in degree
        self.xscale = kwargs.get('xscale', 1.0)
        self.mirror = kwargs.get('mirror', 0)
        self.layer = kwargs.get('layer', '0')
        self.color = kwargs.get('color', dxfwrite.BYLAYER)
        self.data = DXFList()

        if len(self.textlines)>1: # more than one line
            self._build_dxf_text_entities()
        elif len(self.textlines) == 1: # just a normal text with one line
            kwargs['alignpoint'] = insert # text() needs the align point
            self.data.append(Text(text=text, insert=insert, **kwargs))

    @property
    def lineheight(self):
        """ absolute linespacing in drawing units """
        return self.height * self.linespacing

    def _build_dxf_text_entities(self):
        """ create the dxf TEXT entities """
        if self.mirror & dxfwrite.MIRROR_Y:
            self.textlines.reverse()
        for linenum, text in enumerate(self.textlines):
            alignpoint = self._get_align_point(linenum)
            params = self._build_text_params(alignpoint)
            self.data.append(Text(text=text, **params))

    def _get_align_point(self, linenum):
        """Calculate the align point depending on the line number. """
        x = self.insert[0]
        y = self.insert[1]
        try:
            z = self.insert[2]
        except IndexError:
            z = 0.
        # rotation not respected
        if self.valign == dxfwrite.TOP:
            y -= linenum * self.lineheight
        elif self.valign == dxfwrite.MIDDLE:
            y0 = linenum * self.lineheight
            fullheight = (len(self.textlines) - 1) * self.lineheight
            y += (fullheight/2) - y0
        else: # dxfwrite.BOTTOM
            y += (len(self.textlines) - 1 - linenum) * self.lineheight
        return self._rotate( (x, y, z) ) # consider rotation

    def _rotate(self, alignpoint):
        """Rotate alignpoint around insert point about rotation degrees."""
        dx = alignpoint[0] - self.insert[0]
        dy = alignpoint[1] - self.insert[1]
        beta = math.radians(self.rotation)
        x = self.insert[0] + dx * math.cos(beta) - dy * math.sin(beta)
        y = self.insert[1] + dy * math.cos(beta) + dx * math.sin(beta)
        return (round(x, 6), round(y, 6), alignpoint[2])

    def _build_text_params(self, alignpoint):
        """Build the calling dict for Text()."""
        return {
            'insert': alignpoint,
            'alignpoint': alignpoint,
            'layer': self.layer,
            'color': self.color,
            'style': self.style,
            'height': self.height,
            'xscale': self.xscale,
            'mirror': self.mirror,
            'rotation': self.rotation,
            'oblique': self.oblique,
            'halign': self.halign,
            'valign': self.valign,
        }

    def __dxf__(self):
        """ get the dxf string """
        return self.data.__dxf__()
Example #2
0
class Table(object):
    """A HTML-table like object.

    The table object contains the table data cells.
    """
    name = 'TABLE'

    def __init__(self, insert, nrows, ncols, default_grid=True):
        """
        :param insert: insert point as 2D or 3D point
        :param int nrows: row count
        :param int ncols: column count
        :param bool default_grid: if **True** always a solid line grid will
            be drawn, if **False**, only explicit defined borders will be
            drawn, default grid has a priority of 50.
        """
        self.insert = insert
        self.nrows = nrows
        self.ncols = ncols
        self.row_heights = [DEFAULT_TABLE_HEIGHT] * nrows
        self.col_widths = [DEFAULT_TABLE_WIDTH] * ncols
        self.bglayer = DEFAULT_TABLE_BGLAYER
        self.fglayer = DEFAULT_TABLE_FGLAYER
        self.gridlayer = DEFAULT_TABLE_GRIDLAYER
        self.styles = {'default': Style.get_default_cell_style()}
        if not default_grid:
            default_style = self.get_cell_style('default')
            default_style.set_border_status(False, False, False, False)

        self._cells = {} # data cells
        self.frames = [] # border frame objects
        # visibility_map stores the visibilty of the cells, created in _setup
        self.visibility_map = None
        # grid manages the border lines, created in _setup
        self.grid = None
        # data contains the resulting dxf entities
        self.data = DXFList()
        self.empty_cell = Cell(self) # represents all empty cells

    def set_col_width(self, column, value):
        """Set column width of **column** to **value** (in drawing units).
        """
        self.col_widths[column] = float(value)

    def set_row_height(self, row, value):
        """Set row heigth of **row** to **value** (in drawing units).
        """
        self.row_heights[row] = float(value)

    def text_cell(self, row, col, text, span=(1, 1), style='default'):
        """Create a new text cell at pos (**row**, **col**), with **text** as
        content, text can be a multi line text, use ``'\\n'`` as line
        seperator.

        The cell spans over **span** cells and has the cell style with the
        name **style**.
        """
        cell = TextCell(self, text, style=style, span=span)
        return self.set_cell(row, col, cell)

    # pylint: disable-msg=W0102
    def block_cell(self, row, col, blockdef, span=(1, 1), attribs={}, style='default'):
        """Create a new block cell at position (**row**, **col**), content is
        a block reference inserted by a :ref:`INSERT` entity, attributes will
        be added if the block definition contains :ref:`ATTDEF`. Assignments
        are defined by attribs-key to attdef-tag association.
        Example: attribs = {'num': 1} if an :ref:`ATTDEF` with tag=='num' in
        the block definition exists, an attrib with text=str(1) will be
        created and added to the insert entity.

        The cell spans over **span** cells and has the cell style with the
        name **style**.
        """
        cell = BlockCell(self, blockdef, style=style, attribs=attribs, span=span)
        return self.set_cell(row, col, cell)

    def set_cell(self, row, col, cell):
        """Insert a **cell** at position (**row**, **col**)."""
        row, col = self.validate_index(row, col)
        self._cells[row, col] = cell
        return cell

    def get_cell(self, row, col):
        """Get cell at position (**row**, **col**)."""
        row, col = self.validate_index(row, col)
        try:
            return self._cells[row, col]
        except KeyError:
            return self.empty_cell # emtpy cell with default style

    def validate_index(self, row, col):
        row = int(row)
        col = int(col)
        if row < 0 or row >= self.nrows or \
           col < 0 or col >= self.ncols:
            raise IndexError('cell index out of range')
        return row, col

    def frame(self, row, col, width=1, height=1, style='default'):
        """Create a Frame object which frames the cell area starting at
        **row**, **col** covering **widths** columns and **heigth** rows.
        """
        frame = Frame(self, pos=(row, col), span=(height, width),
                      style=style)
        self.frames.append(frame)
        return frame

    def new_cell_style(self, name, **kwargs):
        """Create a new Style object with the name **name**.

        :param kwargs: see Style.get_default_cell_style()
        """
        style = deepcopy(self.get_cell_style('default'))
        style.update(kwargs)
        self.styles[name] = style
        return style

    def new_border_style(self, color=const.BYLAYER, status=True,
                         priority=100, linetype=None):
        """Create a new border style.

        :param bool status: if **True** border is visible, **False** border
            is hidden
        :param int color: dxf color index
        :param string linetype: linetype name, BYLAYER if None
        :param int priority: drawing priority - higher values covers lower
            values
        """
        border_style = Style.get_default_border_style()
        border_style['color'] = color
        border_style['linetype'] = linetype
        border_style['status'] = status
        border_style['priority'] = priority
        return border_style

    def get_cell_style(self, name):
        """Get cell style by **name**.
        """
        return self.styles[name]

    def iter_visible_cells(self):
        """Iterate over all visible cells.

        returns a generator which yields all visible cells as tuples:
        **row**, **col**, **cell**
        """
        if self.visibility_map is None:
            raise Exception("Can only be called at dxf creation.")
        return ((row, col, self.get_cell(row, col))
                for row, col in self.visibility_map)

    def __dxf__(self):
        self._build_table()
        result = self.data.__dxf__()
        self.data = DXFList() # don't need to keep this data in memory
        return result

    def _setup(self):
        """Table generation setup."""
        self.visibility_map = VisibilityMap(self, status=VISIBLE)
        self.grid = Grid(self)

    def _build_table(self):
        """Table is generated on calling the __dxf__() method."""
        self._setup()
        self.grid.draw_lines()
        for row, col, cell in self.iter_visible_cells():
            self.grid.draw_cell_background(row, col, cell)
            self.grid.draw_cell_content(row, col, cell)
        self._cleanup()

    def _cleanup(self):
        """Table generation cleanup. """
        self.visibility_map = None
        self.grid = None
Example #3
0
class Rectangle(object):
    """ 2D Rectangle, consisting of a polyline and a solid as background filling.
    """
    name = 'RECTANGLE'

    def __init__(self, insert, width, height, rotation=0.,
                 halign=const.LEFT, valign=const.TOP,
                 color=const.BYLAYER, bgcolor=None,
                 layer='0', linetype=None):
        self.insert = insert
        self.width = float(width)
        self.height = float(height)
        self.rotation = math.radians(rotation)
        self.halign = halign
        self.valign = valign
        self.color = color
        self.bgcolor = bgcolor
        self.layer = layer
        self.linetype = linetype
        self.points = None
        self.data = DXFList()

    def _build_rect(self):
        self._calc_corners()
        if self.color is not None:
            self._build_polyline()
        if self.bgcolor is not None:
            self._build_solid()

    def _calc_corners(self):
        points = [(0., 0.), (self.width, 0.), (self.width, self.height),
                  (0., self.height)]
        align_vector = self._get_align_vector()
        self.points = [vadd(self.insert, # move to insert point
                            rotate_2d( # rotate at origin
                                vadd(point, align_vector), self.rotation))
                       for point in points]

    def _get_align_vector(self):
        if self.halign == const.CENTER:
            dx = -self.width/2.
        elif self.halign == const.RIGHT:
            dx = -self.width
        else: # const.LEFT
            dx = 0.

        if self.valign == const.MIDDLE:
            dy = -self.height/2.
        elif self.valign == const.BOTTOM:
            dy = -self.height
        else: #const.TOP
            dy = 0.

        return (dx, dy)

    def _build_polyline(self):
        """ build the rectangle with a polyline """
        polyline = Polyline(self.points, color=self.color, layer=self.layer)
        polyline.close()
        if self.linetype is not None:
            polyline['linetype'] = self.linetype
        self.data.append(polyline)

    def _build_solid(self):
        """ build the background solid """
        self.data.append(Solid(
            self.points, color=self.bgcolor, layer=self.layer))

    def __dxf__(self):
        """ get the dxf string """
        self._build_rect()
        return self.data.__dxf__()