Repository: lifelike/hexmapextension
Branch: master
Commit: 8faee5fe96a2
Files: 10
Total size: 42.6 KB
Directory structure:
gitextract_1axyjuqj/
├── .gitignore
├── README.org
├── USER GUIDE.md
├── documentation/
│ ├── Alternate Hex Centers.md
│ └── Calculated Hex Size.md
├── hexmap.inx
├── hexmap.py
└── svgtests/
├── .gitignore
├── run.py
└── run.sh
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
*~
*.pyc
================================================
FILE: README.org
================================================
* Inkscape Hex Map Extension
Copyright 2008-2024 Pelle Nilsson and contributors
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 St, Fifth Floor, Boston, MA 02110-1301 USA
** Introduction
This is an extension for creating hex grids in [[http://www.inkscape.org][Inkscape.]] It can also be used to
make brick patterns of staggered rectangles.
You need Inkscape 1.0 or later installed. Older versions of Inkscape
are NOT supported (you can use older versions of this extensions
if you must create some hexgrids using older Inkscape versions,
or just upgrade Inkscape).
** Installing
To install the extension you need to copy *hexmap.inx*
and *hexmap.py*
into the *extensions* folder (directory) of your
Inkscape installation. In Linux this will typically be
*/usr/share/inkscape/extensions*
or *~/.config/inkscape/extensions*.
If Inkscape was installed on Linux from [[https://flathub.org/apps/details/org.inkscape.Inkscape][Flatpak]]
the path is more likely *~/.var/app/org.inkscape.Inkscape/config/inkscape/extensions/*.
In Windows it will be something like
*C:\Program Files\Inkscape\share\extensions*. After (re)starting
Inkscape you should now have *Boardgames* submenu in the
Extensions menu, and in that an entry for running
*Generate Hex Map*.
** Running The Extension
Creates a grid of hexagons over the document. Up to six layers
are created:
- *Hex Grid* The hexgrid itself. Each hex border is a separate line object.
- *Hex Centerdot* A small circle in the center of each hex, as is needed for many games.
- *Hex Fill* One polygon object for each hex. Can be used to quickly add a color or pattern to hexes.
- *Hex Coordinates* The coordinate label of each hex. The format of each label is controlled by parameters in the effect's dialog window.
- *Hex Corners* Corners/Vertices of each hex only. Use as alternative to the normal hexgrid if you prefer a map with less visible grid. Some people call these caltrops.
- *Hex Circles* Circles the size of each hex.
** User Guide
There is a separate [[https://github.com/lifelike/hexmapextension/blob/master/USER%20GUIDE.md][User Guide]] document with much more information and help
that is recommended reading for anyone about to use this extension or that
is considering to use it.
================================================
FILE: USER GUIDE.md
================================================
# Inkscape Hex Map Extension User Guide
To use the Hex Map Extension, open "Extensions" in the Inkscape top menu and select "Boardgames", "Create Hexmap…" A dialog box should open with tabs for gathering parameters and "Apply" and "Close" buttons
## Size tab
### Size Units
The drop-down here sets the units (e.g. "in" or "mm") to use.
### number of rows, columns
The number of rows (a row extends side-to-side) and columns (a column extends top-to-bottom) to be genrated.
### Hex Size
The Hex Size field allows the user to specify the side-to-side height of the hexagon.
If the hex size is 0.0, the extension fits the specified number of hexes in so that they do not overflow in any direction. The lines have non-zero thickness, and are positioned using points in the middle of the line. When the extension caluculates the size based on the page size, it moves the hex vertices in so the line thickness fits inside the page limits. Depending on line thickness, the dimension of the "open" area of the hex will be slightly or substantially different from the hex size. The details of the calculation of the hex size are given in a [separate document](documentation/Calculated%20Hex%20Size.md).
If the hex size is specified, the generated hexes can overflow the page.
The width of a hex is (Hex Size)/(√3/2). The length of one of the sides of a hexagon is ½*(Hex Width).

This example is a 5cm x 7cm page, with the extension set to create five rows and five columns. The hex size was left blank. As you can see the hexagons fill the width of the document before they fill the height. The hexagons are sized by the width of the page, and the rest of the page below the hexes is left blank.
In the example the extension has been run once to create hexes, then run a second time to create "bricks" (re-colored green). Unless the "bricks" are set to be square (on the [style tab](#force-square-bricks)), the width of the "bricks" is three-quarters of the "hex size" parameter, and the "brick" height is the same as the hex height, i.e. √3/2*(Hex Size). The "bricks" are centered at the same point as a similarly-sized hex.
### Stroke width
The width of the grid/vertices line segments. The stroke width also affects the size of the hex center dot, which will have a diameter of slightly more than twice the stroke width.
### Size of vertices (%)
Some games have partially drawn hexsides extending from the vertices (see picture below). This parameter determines how far the hexside extends out from the vertex, with the length of the hexside being 100%. Since the partial hexside extends out from both ends, the two partial hexsides meet in the middle when the size is 50%. If the size is 50% the result is not distinguishable from the regular grid. See also the [Layers tab](#layers-tab).

## Style tab
### Bricks
Instead of hexagons draw rectangular "bricks." The "bricks" have their center in the same place as the hex center (unless )
### Force Square Bricks
Instead of generating rectangles with centers where the hexagons are, generate squares that are the "hex size".
Note: if the hex size is calculated automatically, square "bricks" will overflow the page.
### Rotate
Generate the hexes pointy sides up-and-down instead of left-and-right. The coordinates are not rotated but zig-zag up and down. The result is not the same as generating the grid normally and rotating it in Inkscape.
### Half hexes at top and bottom
Generates a hex center for the half-hexes at the top and bottom. Generates a coordinate label for the half-hexes at the bottom.
### Shift grid to side and wrap
Instead of beginning with the point of the first hex column on the left-hand side, begin with the center of the hex. Also end with the center of the last column. The two halves at beginning and end are considered one column when calculating the number of columns. If four columns were specified, there would be a first half column labeled "A" (alpha column labeling), three full columns labeled "B"-"D" and a final half column labeled "E".
### First column half-hex down
By default the first column begins in the top left-hand of the page and the next column is shifted down a half hex. Checking this shifts the first column down a half hex instead.
## Coords tab
Various ways of structuring the hex coordinates.
## Layers tab
This determines which artifacts are generated. You might generate them all then turn the ones you don't need off by making them hidden in Inkscape.
### Grid
The border line segments of the hexagons.
### Fill
Layer containing solid hexagon fills for each hex. The fills are generated as white fills with no stroke, but can be colored or otherwise altered using Inkscape tools. If you are planning to overlay the hexagon grid over your own map, hide or don't generate this layer.
### Coordinates
Layer containing the hex coordinates, structured per the [Coords tab](#coords-tab). They are positioned in the center near the bottom of the hex. You can use Inkscape tools to reposition
### Center Dots
Layer containing the hex centers. These are always solid black circles. See [Alternate Hex Centers](documentation/Alternate%20Hex%20Centers.md) for steps to place different centers.
### Vertices
Some games print partial hexsides rather than full hexagons to mark the grid, as shown [previously](#size-of-vertices-). This layer is a redundant with the grid layer (although you could change the color or thickness of one/both layers for some interesting effects).
### Circles
Layer containing solid circles drawn with their centers where the hexagon centers are. These are generated as solid black circles with no stroke.
### Layers in Group
This option generates a parent layer that contains the other generated layers. It has no visible elements itself. Having a parent layer allows the child layers to be hidden/shown together as well as making it easy to rename or delete all the generated layers.
## Debug tab
If the log file field is filled in, the extension will output debug text to the file. This probably should not be filled in unless you are fixing a problem or developing a new feature.
### Generate Log File
Log file is only created if this is checked.
### Log File (optional)
Set this to the file to create (the button can be clicked to open a file chooser). WARNING! THIS FILE WILL BE OVERWRITTEN IF IT EXISTS (NO QUESTIONS ASKED).
## "Apply" button
Generates the hexagon grid. The code will not overwrite any existing layer that match the names the extension uses. If for some reason you need to generate the hexagon grid again (e.g. you made a mistake in the parameters or you want to overlay a second grid over the first) you have to rename or delete the prior generation.
## "Close" button
This button closes the Hex Map Dialog Box.
## Alternate Hex Centers
The hex center created by the Hex Map Extension is a single solid black circle, the size of which is determined by the stroke width. If you need a different shape for hex centers, [here](documentation/Alternate%20Hex%20Centers.md) is a detailed list of steps that will allow you to create them.
## The Real World!
The Hex Map Extension creates regular hexagons, that is all the sides and angles are the same. If you are trying to create a hexmap that is compatible with an existing printed boardgame, be aware that it may be possible that the printed map is not made of regular hexagons. One very popular board game has hexes that are around 1.06% taller than they should be if they were regular. For a single hex this is not very apparent, but the cumulative error for ten hexes (the boards for this game are ten hexes wide) is near eleven percent, which is hard to miss. The fix for this is to scale the hexgrid in one dimension using Inkscape, but the discrepancy can be very confusing if you are unaware.
================================================
FILE: documentation/Alternate Hex Centers.md
================================================
# Alternate Hex Centers
The Hex Map Extension creates hex center markers as black, solid circles with a size determined by the stroke width. Simple changes to these shapes (e.g. changing color) can be made in Inkscape using standard tools. If you want to have hex centers that are different shapes (e.g. rectangles, triangles, stars, etc.), this page will show you how you can create a single object then clone it into the right positions. The procedure is not difficult, but does require some (simple) math. The procedure tries to be detailed about each step, so don't be discouraged if it looks a bit long.
Make sure all measurements are in the same units. The type of units chosen will not matter because the final calculated values are percents. As long as the units are consistent any unit should produce the same answer.
## Creating a set of alternate set of center objects
1. Lock all layers except the center dots (and the group layer if you have the hex map grouped).
1. Get the size of one existing hex center by selecting it using the select tool (arrow). The width and height are given in the top toolbar. This value will be refered to as size_old_center_x (the width in the toolbar; should be the same as size_old_center_y or height because it is a circle).
1. If you specified the hex size when creating the hex grid, that is the center_distance_x.
1. If you did not specify the hex size or don't know it, you can measure it in the image.
- Select two hex centers that are in adjacent columns in adjacent hexes
- The width of the two selected objects minus the size_old_center_x is the center_distance_x
1. The center_distance_y = center_distance_x*√3/2
1. Create a new layer
1. Create a new hex center object on that new layer
1. Get the size of the new hex center object by selecting it, recording the size_new_center_x and size_new_center_y
1. Select the upper left existing hex center then the new hex center object. Use Inkscape to align to first object vertical and horizontal to position the new center over the old upper-left object.
1. Lock the old hex centers layer
1. Unselect the pair, then select the new hex center object
1. top menu, edit, create tiled clones
- symmetry=simple translation
- shift: as many rows as needed, two columns
- per row: shift x=0, shift y=100*(center_distance_y - size_new_center_y)/size_new_center_y
- per col: shift x=100*(center_distance_x - size_new_center_x)/size_new_center_x; shift y=100*(center_distance_y - size_new_center_y/2)/2*size_new_center_y
1. Click the "Create" button
1. [If something goes wrong, remove. Do not keep applying without removing. Bad things happen. Otherwise, at this point the first two columns should be correct.]
1. Select all the new hex center objects, original and clones
1. Record the width (group_size_x)
1. top menu, edit, create tiled clones
- symmetry=simple translation
- shift: one row, as many cols as needed divided by two
- shift per row: shift x=0, shift y=0
- per col: shift x=100*(2*center_distance_x - group_size_x)/group_size_x; shift y=0
1. Click the "Create" button
1. Hide old hex centers, lock new hex centers
================================================
FILE: documentation/Calculated Hex Size.md
================================================
# Calculated Hex Size
When the hex size parameter is left blank, the size of the hexes is calculated so the number of rows and columns will fit on the page without overflowing. The program finds the smallest dimension (height or width) page that will fit the number of rows and columns specified. The grid lines are not of zero thickness, so half a grid line thickness is subtracted from each edge (total of one grid line thickness). Thicker grid lines will result in (slightly) smaller hex sizes because of the margin to allow the full lines at the edges to fit inside the page. The remaining space is divided evenly by the number of rows or columns, depending on which would be filled first.
For example, if the page size is 5 cm. by 7 cm., regular hexes will fill out the width of the page before they fill out the height. If the stroke width is 1.5 mm. (0.15 cm.), the calculated hex size will be (5 - 0.15)/5 = 1.2125 cm.
================================================
FILE: hexmap.inx
================================================
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Create Hexmap</name>
<id>pelles.effect.hexmap</id>
<param name="tab" type="notebook">
<page name="page_1" gui-text="Size">
<param name="units" type="optiongroup" appearance="combo" gui-text="Size units">
<option value="mm">mm</option>
<option value="cm">cm</option>
<option value="in">in</option>
<option value="pt">pt</option>
<option value="px">px</option>
</param>
<param name="cols" type="int" gui-text="Columns" min="1" max="1000000">7</param>
<param name="rows" type="int" gui-text="Rows" min="1" max="1000000">7</param>
<param name="hexsize" type="float" min="0" max="9999" precision="2" gui-text="Hex Size (optional)">30</param>
<param name="orientation" type="optiongroup" appearance="combo" gui-text="Hex Size Orientation">
<option value="flattoflat">Flat to Flat</option>
<option value="peektopeek">Peek to Peek</option>
</param>
<param name="strokewidth" type="float" min="0.0" max="9999.0" precision="3" gui-text="Stroke Width">1.000</param>
<param name="verticesize" type="float" min="0.0" max="50.0" gui-text="Size of vertices (%)">10.0</param>
</page>
<page name="page_2" gui-text="Style">
<param name="bricks" type="bool" gui-text="Bricks">false</param>
<param name="squarebricks" type="bool" gui-text="Force Square Bricks">false</param>
<param name="rotate" type="bool" gui-text="Rotate">false</param>
<param name="halfhexes" type="bool" gui-text="Half hexes at top and bottom">true</param>
<param name="xshift" type="bool" gui-text="Shift grid to side and wrap">false</param>
<param name="firstcoldown" type="bool" gui-text="First column half-hex down">false</param>
</page>
<page name="page_3" gui-text="Coords">
<param name="fontfamily" type="string" gui-text="Font Family">sans-serif</param>
<param name="fontsize" type="float" gui-text="Font Size" min="0" max="10000" precision="1">10.0</param>
<param name="coordseparator" type="string" gui-text="Coordinate Separator">.</param>
<param name="coordalphacol" type="bool" gui-text="Column Alpha Coordinates">false</param>
<param name="coordrevrow" type="bool" gui-text="Row Coordinates Reversed">false</param>
<param name="coordrowfirst" type="bool" gui-text="Row Coordinate First">false</param>
<param name="coordzeros" type="bool" gui-text="Zero-Padded Coordinates">true</param>
<param name="coordrows" type="int" min="1" max="100" gui-text="Coordinates Every ... Rows">1</param>
<param name="coordcolstart" type="int" gui-text="First Col Nr" min="0" max="1000">1</param>
<param name="coordrowstart" type="int" gui-text="First Row Nr" min="0" max="1000">1</param>
</page>
<page name="page_4" gui-text="Layers">
<param name="layer_grid" type="bool" gui-text="Grid">true</param>
<param name="layer_fill" type="bool" gui-text="Fill (#ffffff)">true</param>
<param name="layer_coordinates" type="bool" gui-text="Coordinates">true</param>
<param name="layer_centerdots" type="bool" gui-text="Centerdots">true</param>
<param name="layer_vertices" type="bool" gui-text="Vertices">false</param>
<param name="layer_circles" type="bool" gui-text="Circles">false</param>
<param name="layersingroup" type="bool" gui-text="Layers in Group">false</param>
</page>
<page name="page_5" gui-text="Debug">
<param name="generatelog" type="bool" gui-text="Generate log file">false</param>
<param name="logfilepath" type="path" gui-text="Log File (optional)" mode="file_new" filetypes="txt">debug.txt</param>
</page>
</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu _name="Boardgames"/>
</effects-menu>
</effect>
<script>
<command location="inx" interpreter="python">hexmap.py</command>
</script>
</inkscape-extension>
================================================
FILE: hexmap.py
================================================
#!/usr/bin/env python3
import inkex
import sys
from inkex import NSS
import math
from lxml import etree
class Point:
def __init__(self, x, y):
self.x = x
self.y = y
def __str__(self):
return '%f,%f' % (self.x, self.y)
def y_mirror(self, h):
return Point(self.x, h - self.y);
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
def __mul__(self, k):
return Point(self.x * k, self.y * k)
def rotated(self, total_width):
return Point(self.y, total_width - self.x)
def nrdigits(f):
return int(math.floor(math.log10(f))) + 1
def alphacol(c):
d = c / 26
r = c % 26
return ('%c' % (r + 65)) * (int(d) + 1)
COORD_YOFFSET_PART = 75
CENTERDOT_SIZE_FACTOR = 1.1690625
# This is the ratio between the flat-to-flat size
# of a hexagon vs the point-to-point size.
HEX_RATIO = math.sqrt(3.0) / 2.0
class HexmapEffect(inkex.Effect):
def __init__(self):
inkex.Effect.__init__(self)
self.arg_parser.add_argument('--tab')
self.arg_parser.add_argument('--generatelog', type = inkex.Boolean,
default = False)
self.arg_parser.add_argument('--logfilepath', default = "debug.txt")
self.arg_parser.add_argument("--units", default='mm',
help="Units this dialog is using")
self.arg_parser.add_argument('--cols', type = int, default = '10',
help = 'Number of columns')
self.arg_parser.add_argument('--rows', type = int, default = '10',
help = 'Number of columns')
self.arg_parser.add_argument('--hexsize', type = float, default = 0.0)
self.arg_parser.add_argument('--orientation', default = 'flattoflat')
self.arg_parser.add_argument('--strokewidth', type = float,
default = 1.0)
self.arg_parser.add_argument('--coordrows', type = int, default = '1')
self.arg_parser.add_argument('--coordcolstart', type = int,
default = '1')
self.arg_parser.add_argument('--coordrowstart', type = int,
default = '1')
self.arg_parser.add_argument('--bricks', type = inkex.Boolean,
default = False)
self.arg_parser.add_argument('--squarebricks', type = inkex.Boolean,
default = False)
self.arg_parser.add_argument('--rotate', type = inkex.Boolean,
default = False)
self.arg_parser.add_argument("--fontfamily", default='sans-serif')
self.arg_parser.add_argument('--fontsize', type = float,
default = 10.0)
self.arg_parser.add_argument('--coordseparator', default = '')
self.arg_parser.add_argument('--layersingroup', type = inkex.Boolean,
default = False,
help = 'All layers in a layer group')
self.arg_parser.add_argument('--coordalphacol', type = inkex.Boolean,
default = False,
help = 'Reverse row coordinates')
self.arg_parser.add_argument('--coordrevrow', type = inkex.Boolean,
default = False,
help = 'Reverse row coordinates')
self.arg_parser.add_argument('--coordzeros', type = inkex.Boolean,
default = True)
self.arg_parser.add_argument('--coordrowfirst', type = inkex.Boolean,
default = False,
help = 'Reverse row coordinates')
self.arg_parser.add_argument('--xshift', type = inkex.Boolean,
default = False,
help = 'Shift grid half hex and wrap')
self.arg_parser.add_argument('--firstcoldown', type = inkex.Boolean,
default = False,
help = 'First column half-hex down')
self.arg_parser.add_argument('--halfhexes', type = inkex.Boolean,
default = False)
self.arg_parser.add_argument('--verticesize', type = float,
default = 1.0)
self.arg_parser.add_argument('--layer_grid', type = inkex.Boolean,
default = True)
self.arg_parser.add_argument('--layer_fill', type = inkex.Boolean,
default = True)
self.arg_parser.add_argument('--layer_coordinates',
type = inkex.Boolean, default = True)
self.arg_parser.add_argument('--layer_centerdots',
type = inkex.Boolean, default = True)
self.arg_parser.add_argument('--layer_vertices',
type = inkex.Boolean, default = False)
self.arg_parser.add_argument('--layer_circles',
type = inkex.Boolean, default = False)
def createLayer(self, name):
layer = etree.Element(inkex.addNS('g', 'svg'))
layer.set(inkex.addNS('label', 'inkscape'), name)
layer.set(inkex.addNS('groupmode', 'inkscape'), 'layer')
return layer
def logwrite(self, msg):
if self.options.generatelog:
self.log.write(msg)
def svg_line(self, p1, p2):
line = etree.Element('line')
line.set('x1', str(p1.x + self.xoffset))
line.set('y1', str(p1.y + self.yoffset))
line.set('x2', str(p2.x + self.xoffset))
line.set('y2', str(p2.y + self.yoffset))
line.set('style', 'stroke:#000000; stroke-width:'
+ str(self.stroke_width) + ';stroke-linecap:round')
return line
def svg_circle(self, p, radius):
circle = etree.Element('circle')
circle.set('cx', str(p.x + self.xoffset))
circle.set('cy', str(p.y + self.yoffset))
circle.set('r', str(radius))
circle.set('fill', 'black')
return circle
def svg_polygon(self, points):
poly = etree.Element('polygon')
pointsdefa = []
for p in points:
offset_p = Point(p.x + self.xoffset, p.y + self.yoffset)
pointsdefa.append(str(offset_p))
pointsdef = ' '.join(pointsdefa)
poly.set('points', pointsdef)
poly.set('style',
'stroke:none;fill:#ffffff;fill-opacity:1;stroke-width:'
+ str(self.stroke_width) + ';stroke-linecap:round')
return poly
def svg_coord(self, p, col, row, cols, rows, anchor='middle'):
if self.coordrevrow:
row = rows - row
else:
row = row + 1
if self.coordrevcol:
col = cols - col
else:
col = col + 1
# Apply configured start offsets (allow start values of 0)
start_row = int(self.options.coordrowstart)
start_col = int(self.options.coordcolstart)
row = row + start_row - 1
col = col + start_col - 1
# Only create coordinates that are within the configured start
# and follow the coordrows stepping. This permits start values of 0.
if ((row - start_row) % self.coordrows != 0) or row < start_row or col < start_col:
return None
if self.coordrowfirst:
col,row = [row,col]
if self.coordalphacol:
acol = alphacol(col - 1)
if self.coordzeros:
zrow = str(row).zfill(self.rowdigits)
coord = acol + self.coordseparator + zrow
else:
coord = acol + self.coordseparator + str(row)
elif self.coordzeros:
zcol = str(col).zfill(self.coldigits)
zrow = str(row).zfill(self.rowdigits)
coord = zcol + self.coordseparator + zrow
else:
coord = str(col) + self.coordseparator + str(row)
self.logwrite(" coord-> '%s'\n" % (coord))
text = etree.Element('text')
text.set('x', str(p.x + self.xoffset))
text.set('y', str(p.y + self.yoffset))
style = ('text-align:center;text-anchor:%s;font-family:"%s";font-size:%fpt;'
% (anchor, self.options.fontfamily, self.options.fontsize / 3.78))
text.set('style', style)
text.text = coord
return text
def add_hexline(self, gridlayer, verticelayer, p1, p2):
if gridlayer is not None:
gridlayer.append(self.svg_line(p1, p2))
if verticelayer is not None:
verticelayer.append(self.svg_line(p1, (p2 - p1)
* self.verticesize + p1))
verticelayer.append(self.svg_line(p2, p2 - (p2 - p1)
* self.verticesize))
def effect(self):
if self.options.generatelog:
self.log = open(self.options.logfilepath, 'w')
strokewidth = self.options.strokewidth
cols = self.options.cols
rows = self.options.rows
halves = self.options.halfhexes
xshift = self.options.xshift
firstcoldown = self.options.firstcoldown
bricks = self.options.bricks
squarebricks = self.options.squarebricks
rotate = self.options.rotate
layersingroup = self.options.layersingroup
self.coordseparator = self.options.coordseparator
if self.coordseparator == None:
self.coordseparator = ''
self.coordrevrow = self.options.coordrevrow
self.coordrevcol = False
self.coordalphacol = self.options.coordalphacol
self.coordrows = self.options.coordrows
self.coordrowfirst = self.options.coordrowfirst
self.coordzeros = self.options.coordzeros
if rotate:
self.coordrowfirst = not self.coordrowfirst
self.coordrevcol = not self.coordrevrow
self.coordrevrow = False
self.verticesize = self.options.verticesize / 100.0
self.logwrite('verticesize: %f\n' % self.verticesize)
if self.verticesize < 0.01 or self.verticesize > 0.5:
self.logwrite('verticesize out of range\n')
self.verticesize = 0.15
self.coldigits = nrdigits(cols + self.options.coordcolstart)
self.rowdigits = nrdigits(rows + self.options.coordrowstart)
if self.coldigits < 2:
self.coldigits = 2
if self.rowdigits < 2:
self.rowdigits = 2
if self.coordrowfirst:
self.coldigits,self.rowdigits = [self.rowdigits,self.coldigits]
self.logwrite('cols: %d, rows: %d\n' % (cols, rows))
self.logwrite('xshift: %s, halves: %s\n' % (str(xshift), str(halves)))
svg = self.document.xpath('//svg:svg' , namespaces=NSS)[0]
self.stroke_width = self.svg.unittouu(str(self.options.strokewidth)
+ self.options.units)
width = (float(self.svg.unittouu(svg.get('width')))
- self.stroke_width)
height = (float(self.svg.unittouu(svg.get('height')))
- self.stroke_width)
# So I was a bit lazy and only added an offset to all the
# svg_* functions to compensate for the stroke width.
# There should be a better way.
self.xoffset = self.stroke_width * 0.5
self.yoffset = self.stroke_width * 0.5
if self.options.layer_grid:
hexgrid = self.createLayer('Hex Grid')
else:
hexgrid = None
if self.options.layer_fill:
hexfill = self.createLayer('Hex Fill')
else:
hexfill = None
if self.options.layer_coordinates:
hexcoords = self.createLayer('Hex Coordinates')
else:
hexcoords = None
if self.options.layer_centerdots:
hexdots = self.createLayer('Hex Centerdots')
else:
hexdots = None
if self.options.layer_vertices:
hexvertices = self.createLayer('Hex Vertices')
else:
hexvertices = None
if self.options.layer_circles:
hexcircles = self.createLayer('Hex Circles')
else:
hexcircles = None
if hexvertices is not None and hexgrid is not None:
hexgrid.set('style', 'display:none')
self.logwrite('w, h : %f, %f\n' % (width, height))
if xshift:
hex_cols = (cols * 3.0) * 0.25
else:
hex_cols = (cols * 3.0 + 1.0) * 0.25
if halves:
hex_rows = rows
else:
hex_rows = rows + 0.5
# Size-calculation here assumes un-rotated grid with
# flat side up. If the grid is rotated the widths
# and heights will be swapped later in the code.
if self.options.hexsize > 0:
hex_height = (self.svg.unittouu(str(self.options.hexsize)
+ self.options.units))
hex_width = hex_height / HEX_RATIO
else:
hex_width = width / hex_cols
hex_height = hex_width * HEX_RATIO
if self.options.orientation == 'peektopeek':
hex_width = hex_width * HEX_RATIO
hex_height = hex_height * HEX_RATIO
# square bricks workaround
if bricks and squarebricks:
hex_height = hex_width
hex_width = hex_width / 0.75
hexes_height = hex_height * hex_rows
hexes_width = hex_width * 0.75 * cols + hex_width * 0.25
self.centerdotsize = self.stroke_width * CENTERDOT_SIZE_FACTOR
self.circlesize = hex_height / 2
self.logwrite('hex_width: %f, hex_height: %f\n' %(hex_width,
hex_height))
# FIXME try to remember what 0.005 is for
coord_yoffset = COORD_YOFFSET_PART * hex_height * 0.005
for col in range(cols + 1):
cx = (2.0 + col * 3.0) * 0.25 * hex_width
if xshift:
cx = cx - hex_width * 0.5
coldown = col % 2
if firstcoldown:
coldown = not coldown
for row in range(rows + 1):
cy = (0.5 + coldown * 0.5 + row) * hex_height
self.logwrite('col: %d, row: %d, c: %f %f\n' % (col, row,
cx, cy))
c = Point(cx, cy)
if rotate:
c = c.rotated(hexes_width)
if (hexcoords is not None
and (col < cols or xshift) and row < rows):
cc = c + Point(0, coord_yoffset)
anchor = 'middle'
if xshift and col == 0:
anchor = 'start'
elif xshift and col == cols:
anchor = 'end'
if not (halves and coldown and row == rows-1):
coord = self.svg_coord(cc, col, row, cols, rows, anchor)
if coord != None:
hexcoords.append(coord)
if (hexdots is not None
and (col < cols or xshift) and row < rows):
cd = self.svg_circle(c, self.centerdotsize)
cd.set('id', 'hexcenter_%d_%d'
% (col + self.options.coordcolstart,
row + self.options.coordrowstart))
hexdots.append(cd)
#FIXME make half-circles in half hexes
if (hexcircles is not None and (col < cols or xshift)
and row < rows):
el = self.svg_circle(c, self.circlesize)
el.set('id', 'hexcircle_%d_%d'
% (col + self.options.coordcolstart,
row + self.options.coordrowstart))
hexcircles.append(el)
x = [cx - hex_width * 0.5,
cx - hex_width * 0.25,
cx + hex_width * 0.25,
cx + hex_width * 0.5]
y = [cy - hex_height * 0.5,
cy,
cy + hex_height * 0.5]
if bricks and xshift:
sys.exit('No support for bricks with x shift.')
if xshift and col == 0:
x[0] = cx
x[1] = cx
elif xshift and col == cols:
x[2] = cx
x[3] = cx
if halves and coldown and row == rows-1:
y[2] = cy
# with bricks pattern, shift some coordinates a bit
# to make correct shape
if bricks:
brick_adjust = hex_width * 0.125
else:
brick_adjust = 0
p = [Point(x[2] + brick_adjust, y[0]),
Point(x[3] - brick_adjust, y[1]),
Point(x[2] + brick_adjust, y[2]),
Point(x[1] - brick_adjust, y[2]),
Point(x[0] + brick_adjust, y[1]),
Point(x[1] - brick_adjust, y[0])]
if rotate:
p = [point.rotated(hexes_width) for point in p]
if (hexfill is not None
and (col < cols or xshift) and row < rows):
if row < rows or (halves and coldown):
sp = self.svg_polygon(p)
if halves and coldown and row == rows - 1:
p2 = [x.y_mirror(hexes_height) for x in p]
sp = self.svg_polygon(p)
sp.set('id', 'hexfill_%d_%d'
% (col + self.options.coordcolstart,
row + self.options.coordrowstart))
hexfill.append(sp)
if ((col < cols and (not halves or row < rows
or not coldown))
or (xshift and col == cols
and not (halves and row == rows))):
self.add_hexline(hexgrid, hexvertices, p[5], p[0])
self.logwrite('line 0-5\n')
if row < rows:
if ((coldown or row > 0 or col < cols
or halves or xshift)
and not (xshift and col == 0)):
self.add_hexline(hexgrid, hexvertices, p[5], p[4])
self.logwrite('line 4-5\n')
if not coldown and row == 0 and col < cols:
self.add_hexline(hexgrid, hexvertices, p[0], p[1])
self.logwrite('line 0-1\n')
if not (halves and coldown and row == rows-1):
if (not (xshift and col == 0)
and not (not xshift and col == cols
and row == rows-1 and coldown)):
self.add_hexline(hexgrid, hexvertices, p[4], p[3])
self.logwrite('line 3-4\n')
if coldown and row == rows - 1 and col < cols:
self.add_hexline(hexgrid, hexvertices, p[1], p[2])
self.logwrite('line 1-2\n')
parent = svg
if layersingroup:
parent = self.createLayer('Hex Map')
self.append_if_new_name(svg, parent)
self.append_if_new_name(parent, hexfill)
self.append_if_new_name(parent, hexcircles)
self.append_if_new_name(parent, hexgrid)
self.append_if_new_name(parent, hexvertices)
self.append_if_new_name(parent, hexcoords)
self.append_if_new_name(parent, hexdots)
if self.options.generatelog:
self.log.close()
def append_if_new_name(self, svg, layer):
if layer is not None:
name = layer.get(inkex.addNS('label', 'inkscape'))
if not name in [c.get(inkex.addNS('label', 'inkscape'), 'name')
for c in svg.iterchildren()]:
svg.append(layer)
HexmapEffect().run()
================================================
FILE: svgtests/.gitignore
================================================
output
================================================
FILE: svgtests/run.py
================================================
#!/usr/bin/env python3
import difflib
import glob
import os
import os.path
import shutil
import subprocess
import sys
def add_countersheets_paths():
sys.path.insert(0, os.getcwd())
add_countersheets_paths()
inputdir = os.path.join('svgtests', 'input')
outputdir = os.path.join('svgtests', 'output')
expecteddir = os.path.join('svgtests', 'expected')
logdir = os.path.join('svgtests', 'log')
if not os.path.exists(outputdir):
os.mkdir(outputdir)
chosen = [a for a in sys.argv[1:]
if not a.startswith("-")]
GRID = ["--layer_grid=true"]
FILL = ["--layer_fill=true"]
COORDINATES = ["--layer_coordinates=true"]
CENTERDOTS = ["--layer_centerdots=true"]
VERTICES = ["--layer_vertices=true"]
CIRCLES = ["--layer_circles=true"]
GROUP_LAYERS = ["--layers-in-group=true"]
BRICKS = ["--bricks=true"]
SQUAREBRICKS = ["--squarebricks=true"]
ROTATE = ["--rotate=true"]
HALFHEXES= ["--halfhexes=true"]
XSHIFT= ["--xshift=true"]
FIRSTCOLDOWN= ["--firstcoldown=true"]
DEFAULT_LAYERS = GRID + FILL + COORDINATES + CENTERDOTS
ALL_LAYERS = DEFAULT_LAYERS + VERTICES + CIRCLES
def cr(c, r):
return ["--cols=" + str(c),
"--rows=" + str(r)]
def hexsize(s):
return ['--hexsize=%f' % s]
def units(u):
return ['--units=%s' % u]
def verticesize(s):
return ["--verticesize=%d" % s]
def strokewidth(w):
return ['--strokewidth=%f' % w]
def coordseparator(s):
return ['--coordseparator="%s"' % s]
COORDALPHACOL = ["--coordalphacol=true"]
COORDREVROW = ["--coordrevrow=true"]
COORDROWFIRST = ["--coordrowfirst=true"]
COORDZEROS = ["--coordzeros=true"]
def coordrows(r):
return ["--coordrows=%d" % r]
def coordcolstart(r):
return ["--coordcolstart=%d" % r]
def coordrowstart(r):
return ["--coordrowstart=%d" % r]
DEFAULTS = (ALL_LAYERS
+ cr(4,4)
+ verticesize(5)
+ hexsize(1)
+ strokewidth(1.0 / 25.4)
+ units("in"))
tests = {
"simple_1x1" : cr(1, 1) + DEFAULT_LAYERS,
"all_layers_1x1" : cr(1, 1) + ALL_LAYERS,
"4x4" : cr(4, 4) + ALL_LAYERS,
"defaults" : DEFAULTS
}
successes = 0
fails = 0
skipped = 0
for name,options in tests.items():
if chosen:
matches = False
for c in chosen:
if c in name:
matches = True
break
if not matches:
skipped += 1
continue
svgoutbasename = "%s.svg" % name
svgoutfile = os.path.join(outputdir, svgoutbasename)
svginfile = os.path.join(inputdir, "test.svg")
svgout = open(svgoutfile, "w")
command = os.path.join('.', 'hexmap.py')
logfile = os.path.join(logdir, 'cs_svgtests-%s.txt' % name)
commandline = [command]
commandline.extend(options)
commandline.append(svginfile)
if ['-v' in sys.argv]:
print((' '.join(commandline), sys.stderr))
effect = subprocess.Popen(commandline,
stdout=svgout)
effect.wait()
svgout.close()
if effect.returncode:
sys.exit('Failed to run svgtest %s.'
% (name))
expectedfile = os.path.join(expecteddir, svgoutbasename)
outputsvg = open(svgoutfile).read()
expectedsvg = open(expectedfile).read()
if outputsvg == expectedsvg:
successes += 1
else:
print(f"FAIL: diff {svgoutfile} {expectedfile}")
fails += 1
print(f"{successes}/{len(tests)} tests OK ({skipped} skipped, {fails} FAILED)\n")
================================================
FILE: svgtests/run.sh
================================================
#!/bin/sh
PYTHONPATH=/usr/share/inkscape/extensions/ svgtests/run.py "$@"
gitextract_1axyjuqj/
├── .gitignore
├── README.org
├── USER GUIDE.md
├── documentation/
│ ├── Alternate Hex Centers.md
│ └── Calculated Hex Size.md
├── hexmap.inx
├── hexmap.py
└── svgtests/
├── .gitignore
├── run.py
└── run.sh
SYMBOL INDEX (31 symbols across 2 files)
FILE: hexmap.py
class Point (line 9) | class Point:
method __init__ (line 10) | def __init__(self, x, y):
method __str__ (line 14) | def __str__(self):
method y_mirror (line 17) | def y_mirror(self, h):
method __sub__ (line 20) | def __sub__(self, other):
method __add__ (line 23) | def __add__(self, other):
method __mul__ (line 26) | def __mul__(self, k):
method rotated (line 29) | def rotated(self, total_width):
function nrdigits (line 32) | def nrdigits(f):
function alphacol (line 35) | def alphacol(c):
class HexmapEffect (line 47) | class HexmapEffect(inkex.Effect):
method __init__ (line 48) | def __init__(self):
method createLayer (line 116) | def createLayer(self, name):
method logwrite (line 122) | def logwrite(self, msg):
method svg_line (line 126) | def svg_line(self, p1, p2):
method svg_circle (line 136) | def svg_circle(self, p, radius):
method svg_polygon (line 144) | def svg_polygon(self, points):
method svg_coord (line 157) | def svg_coord(self, p, col, row, cols, rows, anchor='middle'):
method add_hexline (line 204) | def add_hexline(self, gridlayer, verticelayer, p1, p2):
method effect (line 213) | def effect(self):
method append_if_new_name (line 469) | def append_if_new_name(self, svg, layer):
FILE: svgtests/run.py
function add_countersheets_paths (line 11) | def add_countersheets_paths():
function cr (line 46) | def cr(c, r):
function hexsize (line 50) | def hexsize(s):
function units (line 53) | def units(u):
function verticesize (line 56) | def verticesize(s):
function strokewidth (line 59) | def strokewidth(w):
function coordseparator (line 62) | def coordseparator(s):
function coordrows (line 70) | def coordrows(r):
function coordcolstart (line 72) | def coordcolstart(r):
function coordrowstart (line 74) | def coordrowstart(r):
Condensed preview — 10 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (45K chars).
[
{
"path": ".gitignore",
"chars": 9,
"preview": "*~\n*.pyc\n"
},
{
"path": "README.org",
"chars": 2842,
"preview": "* Inkscape Hex Map Extension\n\n Copyright 2008-2024 Pelle Nilsson and contributors\n\n This program is free software; you c"
},
{
"path": "USER GUIDE.md",
"chars": 8051,
"preview": "# Inkscape Hex Map Extension User Guide\n\nTo use the Hex Map Extension, open \"Extensions\" in the Inkscape top menu and se"
},
{
"path": "documentation/Alternate Hex Centers.md",
"chars": 3168,
"preview": "# Alternate Hex Centers\n\nThe Hex Map Extension creates hex center markers as black, solid circles with a size determined"
},
{
"path": "documentation/Calculated Hex Size.md",
"chars": 928,
"preview": "# Calculated Hex Size\n\nWhen the hex size parameter is left blank, the size of the hexes is calculated so the number of r"
},
{
"path": "hexmap.inx",
"chars": 4388,
"preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<inkscape-extension xmlns=\"http://www.inkscape.org/namespace/inkscape/extension\">"
},
{
"path": "hexmap.py",
"chars": 20710,
"preview": "#!/usr/bin/env python3\n\nimport inkex\nimport sys\nfrom inkex import NSS\nimport math\nfrom lxml import etree\n\nclass Point:\n "
},
{
"path": "svgtests/.gitignore",
"chars": 7,
"preview": "output\n"
},
{
"path": "svgtests/run.py",
"chars": 3460,
"preview": "#!/usr/bin/env python3\n\nimport difflib\nimport glob\nimport os\nimport os.path\nimport shutil\nimport subprocess\nimport sys\n\n"
},
{
"path": "svgtests/run.sh",
"chars": 75,
"preview": "#!/bin/sh\n\nPYTHONPATH=/usr/share/inkscape/extensions/ svgtests/run.py \"$@\"\n"
}
]
About this extraction
This page contains the full source code of the lifelike/hexmapextension GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 10 files (42.6 KB), approximately 10.6k tokens, and a symbol index with 31 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.