#!/usr/bin/env python # coding=utf-8 # # Copyright (C) 2007 John Beard john.j.beard@gmail.com # # This program 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 2 of the License, or # (at your option) any later version. # # This program 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 this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # """ This extension allows you to draw a Cartesian grid in Inkscape. There is a wide range of options including subdivision, subsubdivions and logarithmic scales. Custom line widths are also possible. All elements are grouped with similar elements (eg all x-subdivs) """ from math import log import inkex from inkex import Group, PathElement, Rectangle def draw_line(x1, y1, x2, y2, width, name, parent): """Draw an SVG line""" line = parent.add(PathElement()) line.style = {"stroke": "#000000", "stroke-width": str(width), "fill": "none"} line.path = "M {},{} L {},{}".format(x1, y1, x2, y2) line.label = name def draw_rect(x, y, w, h, width, fill, name, parent): """Draw an SVG Rectangle""" rect = parent.add(Rectangle(x=str(x), y=str(y), width=str(w), height=str(h))) rect.style = {"stroke": "#000000", "stroke-width": str(width), "fill": fill} rect.label = name class GridCartesian(inkex.GenerateExtension): def add_arguments(self, pars): pars.add_argument("--border_th", type=float, default=1.0) pars.add_argument("--border_th_unit", default="px") pars.add_argument("--tab", default="x_tab") pars.add_argument("--x_divs", type=int, default=6) pars.add_argument("--dx", type=float, default=5.0) pars.add_argument("--dx_unit", default="mm") pars.add_argument("--x_subdivs", type=int, default=1) pars.add_argument("--x_log", type=inkex.Boolean, default=False) pars.add_argument("--x_subsubdivs", type=int, default=1) pars.add_argument("--x_half_freq", type=int, default=1) pars.add_argument("--x_divs_th", type=float, default=1.0) pars.add_argument("--x_subdivs_th", type=float, default=0.5) pars.add_argument("--x_subsubdivs_th", type=float, default=0.3) pars.add_argument("--x_div_unit", default="px") pars.add_argument("--y_divs", type=int, default=5) pars.add_argument("--dy", type=float, default=5.0) pars.add_argument("--dy_unit", default="mm") pars.add_argument("--y_subdivs", type=int, default=1) pars.add_argument("--y_log", type=inkex.Boolean, default=False) pars.add_argument("--y_subsubdivs", type=int, default=5) pars.add_argument("--y_half_freq", type=int, default=1) pars.add_argument("--y_divs_th", type=float, default=1.0) pars.add_argument("--y_subdivs_th", type=float, default=0.5) pars.add_argument("--y_subsubdivs_th", type=float, default=0.3) pars.add_argument("--y_div_unit", default="px") def generate(self): self.options.border_th = self.svg.unittouu( str(self.options.border_th) + self.options.border_th_unit ) self.options.dx = self.svg.unittouu(str(self.options.dx) + self.options.dx_unit) self.options.x_divs_th = self.svg.unittouu( str(self.options.x_divs_th) + self.options.x_div_unit ) self.options.x_subdivs_th = self.svg.unittouu( str(self.options.x_subdivs_th) + self.options.x_div_unit ) self.options.x_subsubdivs_th = self.svg.unittouu( str(self.options.x_subsubdivs_th) + self.options.x_div_unit ) self.options.dy = self.svg.unittouu(str(self.options.dy) + self.options.dy_unit) self.options.y_divs_th = self.svg.unittouu( str(self.options.y_divs_th) + self.options.y_div_unit ) self.options.y_subdivs_th = self.svg.unittouu( str(self.options.y_subdivs_th) + self.options.y_div_unit ) self.options.y_subsubdivs_th = self.svg.unittouu( str(self.options.y_subsubdivs_th) + self.options.y_div_unit ) # find the pixel dimensions of the overall grid ymax = self.options.dy * self.options.y_divs xmax = self.options.dx * self.options.x_divs # Embed grid in group # Put in in the centre of the current view grid = Group.new("GridCartesian:X{0.x_divs}:Y{0.y_divs}".format(self.options)) (pos_x, pos_y) = self.svg.namedview.center grid.transform.add_translate(pos_x - xmax / 2.0, pos_y - ymax / 2.0) # Group for major x gridlines majglx = grid.add(Group.new("MajorXGridlines")) # Group for major y gridlines majgly = grid.add(Group.new("MajorYGridlines")) # Group for minor x gridlines if self.options.x_subdivs > 1: # if there are any minor x gridlines minglx = grid.add(Group.new("MinorXGridlines")) # Group for subminor x gridlines if self.options.x_subsubdivs > 1: # if there are any minor minor x gridlines mminglx = grid.add(Group.new("SubMinorXGridlines")) # Group for minor y gridlines if self.options.y_subdivs > 1: # if there are any minor y gridlines mingly = grid.add(Group.new("MinorYGridlines")) # Group for subminor y gridlines if self.options.y_subsubdivs > 1: # if there are any minor minor x gridlines mmingly = grid.add(Group.new("SubMinorYGridlines")) draw_rect( 0, 0, xmax, ymax, self.options.border_th, "none", "Border", grid ) # border rectangle # DO THE X DIVISIONS====================================== sd = self.options.x_subdivs # sub divs per div ssd = self.options.x_subsubdivs # subsubdivs per subdiv for i in range(0, self.options.x_divs): # Major x divisions if i > 0: # don't draw first line (we made a proper border) draw_line( self.options.dx * i, 0, self.options.dx * i, ymax, self.options.x_divs_th, "MajorXDiv" + str(i), majglx, ) if self.options.x_log: # log x subdivs for j in range(1, sd): if j > 1: # the first loop is only for subsubdivs draw_line( self.options.dx * (i + log(j, sd)), 0, self.options.dx * (i + log(j, sd)), ymax, self.options.x_subdivs_th, "MinorXDiv" + str(i) + ":" + str(j), minglx, ) for k in range(1, ssd): # subsub divs if (j <= self.options.x_half_freq) or ( k % 2 == 0 ): # only draw half the subsubdivs past the half-freq point if (ssd % 2 > 0) and ( j > self.options.y_half_freq ): # half frequency won't work with odd numbers of subsubdivs, ssd2 = ssd + 1 # make even else: ssd2 = ssd # no change draw_line( self.options.dx * (i + log(j + k / float(ssd2), sd)), 0, self.options.dx * (i + log(j + k / float(ssd2), sd)), ymax, self.options.x_subsubdivs_th, "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k), mminglx, ) else: # linear x subdivs for j in range(0, sd): if ( j > 0 ): # not for the first loop (this loop is for the subsubdivs before the first subdiv) draw_line( self.options.dx * (i + j / float(sd)), 0, self.options.dx * (i + j / float(sd)), ymax, self.options.x_subdivs_th, "MinorXDiv" + str(i) + ":" + str(j), minglx, ) for k in range(1, ssd): # subsub divs draw_line( self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)), 0, self.options.dx * (i + (j * ssd + k) / (float(sd) * ssd)), ymax, self.options.x_subsubdivs_th, "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k), mminglx, ) # DO THE Y DIVISIONS======================================== sd = self.options.y_subdivs # sub divs per div ssd = self.options.y_subsubdivs # subsubdivs per subdiv for i in range(0, self.options.y_divs): # Major y divisions if i > 0: # don't draw first line (we will make a border) draw_line( 0, self.options.dy * i, xmax, self.options.dy * i, self.options.y_divs_th, "MajorYDiv" + str(i), majgly, ) if self.options.y_log: # log y subdivs for j in range(1, sd): if j > 1: # the first loop is only for subsubdivs draw_line( 0, self.options.dy * (i + 1 - log(j, sd)), xmax, self.options.dy * (i + 1 - log(j, sd)), self.options.y_subdivs_th, "MinorXDiv" + str(i) + ":" + str(j), mingly, ) for k in range(1, ssd): # subsub divs if (j <= self.options.y_half_freq) or ( k % 2 == 0 ): # only draw half the subsubdivs past the half-freq point if (ssd % 2 > 0) and ( j > self.options.y_half_freq ): # half frequency won't work with odd numbers of subsubdivs, ssd2 = ssd + 1 else: ssd2 = ssd # no change draw_line( 0, self.options.dx * (i + 1 - log(j + k / float(ssd2), sd)), xmax, self.options.dx * (i + 1 - log(j + k / float(ssd2), sd)), self.options.y_subsubdivs_th, "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k), mmingly, ) else: # linear y subdivs for j in range(0, self.options.y_subdivs): if ( j > 0 ): # not for the first loop (this loop is for the subsubdivs before the first subdiv) draw_line( 0, self.options.dy * (i + j / float(sd)), xmax, self.options.dy * (i + j / float(sd)), self.options.y_subdivs_th, "MinorXYiv" + str(i) + ":" + str(j), mingly, ) for k in range(1, ssd): # subsub divs draw_line( 0, self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)), xmax, self.options.dy * (i + (j * ssd + k) / (float(sd) * ssd)), self.options.y_subsubdivs_th, "SubminorXDiv" + str(i) + ":" + str(j) + ":" + str(k), mmingly, ) return grid if __name__ == "__main__": GridCartesian().run()