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__()
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
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__()