FlatternPattern is an Inkscape extension. It replaces a pattern fill or a pattern stroke with real geometry. The result looks the same as the pattern, and plotters, cutters and other editors can use it.
Inkscape 1.4 has "Objects to Pattern" and "Pattern to Objects". "Pattern to Objects" gives one tile only. FlatternPattern gives all the tiles that the object shows, cut to the shape of the object.
The two images show a rectangle before and after the extension, in the Inkscape split view. The top half shows the normal view. The bottom half shows the outline view.
The result looks the same as the pattern fill. Each tile is a copy that you can select and edit.
The result is one filled path.
For users:
Technical details:
- Inkscape 1.4. The tests use Inkscape 1.4.4 on Linux (see Tested with).
- No other packages. The extension uses the
inkexpackage that comes with Inkscape.
Version: 0.1.1. The change log lists the changes in each release.
- Download the zip file from the latest release.
- Open the user extensions folder for your system (see the table).
- Extract the files from the zip file into that folder.
- Restart Inkscape.
| System | User extensions folder |
|---|---|
| Linux | ~/.config/inkscape/extensions/ |
| Linux, Flatpak | ~/.var/app/org.inkscape.Inkscape/config/inkscape/extensions/ |
| Windows | %APPDATA%\inkscape\extensions\ |
| macOS | ~/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions/ |
Inkscape shows the correct folder at Edit > Preferences > System > User extensions.
- Select one or more objects with a pattern fill or a pattern stroke.
- Select Extensions > Modify Path > FlatternPattern.
- Set the options (see the table).
- Click Apply.
The extension also finds the objects in selected groups. To undo the change, push Ctrl+Z one time.
The extension replaces each object with a group. The group has the id
ID-flat, where ID is the id of the object. The object stays in the group
with its own id. Its pattern paint changes to none.
This is the default mode. The extension puts one copy of the tile content in each tile. A clip path with the shape of the object hides the rest. For a stroke, a mask with the shape of the stroke hides the rest.
The result is exact. You can edit each copy. Some cutters do not support masks. For these cutters, use merged outlines for the fill.
Shapes that meet at a tile edge can show a thin light line on the screen. The renderer smooths the edge of each copy, and the two edges do not cover the pixel fully. To hide these lines, select "Overlap clipped copies to hide seams". Each filled shape then grows by the seam bleed, 0.2 px by default. In tests with Inkscape and rsvg at 96 to 300 dpi, 0.2 px left at most 2 light pixels on a full page. Tile content with an opacity below 1 gets a slightly darker line at each edge.
The extension converts the strokes of the tile to paths and makes one filled path for the object. Use this mode for stencils, plotters and cutters.
This mode starts Inkscape in the background, so it needs more time. The merged path has the first color of the tile content and no transparency. The extension shows a message when the tile has more than one color. It also shows a message when a shape in the tile has an opacity below 0.99. Inkscape stock patterns use values such as 0.995, which look opaque, so they get no message.
The extension makes clipped copies, and shows a message, in these cases:
- The object has a pattern stroke. This mode merges only pattern fills.
- Inkscape does not start, or it gives no result.
- The tile has an image, text, a filter, a mask, a clip, markers, a gradient or a nested pattern.
| Option | Default | Function |
|---|---|---|
| Output mode | Clipped copies | Selects clipped copies or merged outlines. |
| Also flatten stroke patterns | On | Flattens pattern strokes too. Pattern strokes always get clipped copies. |
| Keep a hidden copy of the original | Off | Puts a hidden copy of each object after its group, with the id ID-original. |
| Remove unused pattern definitions | On | Removes the patterns, clips, masks and filters that nothing uses after the change. |
| Maximum number of tiles | 2000 | Stops the extension when the selection needs more tiles. |
| Seam overlap (merged outlines) | 0.01 | Makes each tile larger by this distance in user units. This prevents thin gaps between merged tiles. |
| Overlap clipped copies to hide seams | Off | Makes the shapes in each clipped copy larger by the seam bleed. |
| Seam bleed (px) | 0.2 | Sets the distance for the option above, in px (1/96 in). |
| Match the Inkscape view, not the SVG standard | Off | Keeps the Inkscape view of patterns with objectBoundingBox units. |
Inkscape 1.4 shows some patterns with objectBoundingBox units at another
place than browsers do. By default, the output follows the SVG standard. Thus
the output can move in Inkscape. The extension shows a message for each such
object. To keep the Inkscape view, select "Match the Inkscape view, not the
SVG standard".
The clip, mask, filter and opacity of the object move to the new group. The
tiles make the box of the group larger than the box of the object. Thus the
extension converts a clip, mask or filter in objectBoundingBox units to a
copy in userSpaceOnUse units.
The extension does not convert the primitiveUnits of a filter. When a
filter uses primitiveUnits="objectBoundingBox", the extension shows a
message. The filter result can look different.
The extension skips these objects and shows a message:
- Text. Use Path > Object to Path, then run the extension again.
- Clones. Use Edit > Clone > Unlink Clone, then run the extension again.
- Hatch fills and mesh gradients.
- Pattern strokes that do not scale with the object
(
vector-effect:non-scaling-stroke). - Patterns with a circular
hrefchain, a negative size or a reference to a missing element.
When the selection needs more tiles than the maximum, the extension stops and changes nothing. The message gives the number of tiles that the selection needs.
A pattern with no area, or a pattern with no content, paints nothing. The
extension then sets the fill or the stroke to none and shows a message.
Other limits, with the behavior of the extension:
- Merged outlines give one color and no transparency. Pattern strokes and some tile contents get clipped copies instead (see Merged outlines).
- Clipped copies of a pattern stroke use a mask. Some cutters do not support masks.
- Clipped copies can show thin light seams on the screen. The seam bleed option hides most of them (see Clipped copies).
- A pattern with
objectBoundingBoxunits can move in Inkscape, because the output follows the SVG standard. The extension shows a message (see Patterns with objectBoundingBox units). - The extension does not convert the
primitiveUnitsof a filter on the object. It shows a message (see Clips, masks and filters on the object).
Report problems and requests on the issue tracker. Attach a small SVG file that shows the problem, if possible.
The extension does these steps for each object with a pattern paint:
- Resolve the pattern.
fp_resolve.resolve()follows thehrefchain. Each attribute comes from the first pattern in the chain that sets it. The content comes from the first pattern with child elements. A circular chain stops the work for this object. - Find the tiles.
fp_tiling.tile_box()calculates the tile rectangle from the units and the box of the object.content_transform()adds theviewBoxor the content units.plan()moves the box of the object back through the inversepatternTransform. It then keeps only the tiles that overlap the object. The extension adds the tile counts of all objects before it changes the document. Over the limit, it stops. - Clipped copies:
fp_clipped.flatten_element()puts one copy of the tile content in each tile, with the transform fromtile_transform(). Each copy gets the full style that it had in the pattern. A tile clip cuts the content that goes out of the tile. A clip in the shape of the object cuts the full fill. A mask in the shape of the stroke cuts a stroke. - Merged outlines:
fp_merged.merge_fills()starts Inkscape two times. The first run converts the strokes of the tile to paths, joins them, and cuts the result to the tile. The seam overlap makes the tile slightly larger. The second run joins all tile copies and cuts them to the shape of the object. The result is one path.
- Inkscape 1.4.4 on Manjaro Linux, with the bundled
inkex1.4.0 and Python 3.13. - Not tested on Windows or macOS yet.
The automated test suite has 353 tests. The tests cover these items:
- The
hrefchain of a pattern: inherited attributes, cycles, missing targets. - Lengths with units, fractions and percentages.
patternUnits,patternContentUnits,patternTransform,viewBoxandpreserveAspectRatio.- The tile range. A test compares it with a brute-force overlap check for random transforms.
- The stroke area: caps, joins, miter limits, dashes and markers.
- Styles: inheritance from the pattern, not from the object, and stylesheets.
- Clips, masks, filters and opacity on the object, and
paint-order. - The selection: groups, text, clones, hatches and mesh gradients.
- The two output modes, the fallback to clipped copies, and the tile limit.
- The removal of unused definitions, and the
.inxfile.
105 of the tests are render comparisons. Each test renders the input and the
output to PNG with Inkscape or rsvg-convert. A pixel is different when one
color channel changes by more than 64 of 255. The test fails when more than
0.1 % of the pixels are different. The input files are the fixtures in
tests/data/flatten/ and five files made in the Inkscape GUI in
tests/data/real/. Some inputs show a known difference between the renderers
or a known limit of merged outlines. These tests are expected failures, each
with its reason in the test file. One more test checks that the seam bleed
removes at least 90 % of the light seam pixels.
GitHub Actions runs the linters and all tests for each push to main and
each pull request. The test job uses Ubuntu 24.04 and Inkscape 1.4.4 from the
Inkscape PPA. The developer also runs the tests before each push of a version
change.
A second workflow runs all tests with the nightly build of Inkscape 1.5 from
the Inkscape trunk PPA. It runs for each push to main, each pull request
and each Monday. Its result does not block a merge.
The coverage badge at the top of this file shows the part of the statements
in the extension code that the tests run. The value comes from the last CI
run on main.
To install the development version, copy flatternpattern.inx and all the
.py files in the top folder of this repository into the user extensions
folder (see Install).
The development tools use uv.
- Install the tools with
uv sync. - Install the Git hooks with
uv run pre-commit install. - Run the tests with
uv run pytest. - To measure the coverage, run
uv run coverage run -m pytest, thenuv run coverage report.
The tests import inkex from /usr/share/inkscape/extensions. To use
another folder, set the INKSCAPE_EXTENSIONS_DIR environment variable. The
render tests need the inkscape and rsvg-convert commands. If a command is
not available, pytest skips the tests that need it.
To make a release, push a tag vX.Y.Z on a commit on main. The tag must
match the version in pyproject.toml, and CHANGELOG.md must have a section
for it. The release workflow then makes a draft release with the zip file.
Read the draft and publish it on GitHub.
docs/spike-merged-mode.md records the test of the Inkscape actions that
the merged outlines mode uses.
I made this extension with AI coding help (Claude Code). I designed the extension, reviewed the code and tested it. 353 automated tests check it, and 105 of them compare renders. I maintain it and answer the issues myself.
MIT. See LICENSE.

