Changelog¶
All notable changes to Blendmentation. The format follows Keep a Changelog, and versions follow Semantic Versioning.
Unreleased¶
0.11.0 - 2026-10-16¶
Added¶
generating.Compose(..., prefix="run1_")starts every file of the dataset with the prefix: images, masks, passes, AOVs, previews, label JSONs, and theAugmentationsgraph andskipped.jsonl. The index counts only the files with the same prefix, so runs with different prefixes can write to one folder, or be merged into one later.export.coco,yolo,vocandbopread the labels of every prefix and, when there are prefixes, number the images in order so the ids stay unique.export.augmentation_stats,skip_statsandaugmentation_reporttake aprefixto read one run.augmentations.ShadowCatcher(p=0.5, otherwise=False)makes an object a shadow catcher (Cycles) with probabilityp, and a normal object otherwise, e.g. a floor that renders only the shadows on it over a transparent film or agenerating.Background.- Shorter imports:
from blendmentation import augmentations as aug, export, generating as gen, state. The longerfrom blendmentation.augmentations import augmentationsstill works. Each module's__all__lists its public names. - Every label JSON and every line of
skipped.jsonlstarts with"version", the Blendmentation version that wrote it, andblendmentation.__version__gives the installed version.
Changed¶
- Blendmentation needs Blender 4.2 LTS or newer (it was 4.0), and Python 3.11 or newer when used as a Python module or for the export alone.
- Only the main arguments can be passed by position; the options are keyword-only, so
new ones can be added later without breaking scripts. For example
Translation(x, y, z),Number(data_path, value_range),Menu(data_path, options, weights),OneOf(augmentations, weights),KeepAbove(surface, margin),LookAt(target),Visibility(p),generating.Compose(steps, path, resolution),BBox(classes),Render(file_format),export.coco(path, output). Write the rest as keywords:Rotation(z=90, p=0.5),BBox(classes, max_truncation=0.3),export.yolo(path, bbox_from="mask"). export.voctakesoutputinstead ofoutput_dir, likecocoandbop. Theoutputofcoco,vocandbopcan be a//path, relative to the .blend file, likepath.
Removed¶
generating.RotationMatrixand therotation_matriceslabel key.generating.Posegives each instance's rotation relative to the camera (R, in OpenCV camera axes, scale removed) with its translation.
0.10.2 - 2026-10-15¶
Changed¶
- The "skips" bar of
export.augmentation_reportreads like a number line: more images skipped when the augmentation runs is red, to the right, with a plus (e.g. +38%), and fewer is green, to the left, with a minus. In 0.10.1 red was on the left.
0.10.1 - 2026-10-15¶
Added¶
generating.Augmentationswrites every datapointBBoxskips toskipped.jsonlin the output folder: the reason (max_truncation,iou_deconflictormax_occlusion), the class and objects, the measured value and the limit, and what each augmentation did.export.skip_stats(path)counts the skipped datapoints by reason and class, andexport.augmentation_statsalso counts each augmentation in them (skipped).export.augmentation_reportshows the skipped datapoints: their number in the header (click it for the reasons and classes) and, on the cards of augmentations that run for some objects and not for others, a "skips" bar centered on 0: red to the left when images were skipped more often when the augmentation ran than when it didn't (e.g. +38%), green to the right when less often, faded when within chance, so the augmentations that cause skips stand out.
0.10.0 - 2026-10-15¶
Added¶
augmentations.Compose.describe()returns the graph of what aComposecan do: every augmentation in it, nested ones inOneOf,Chain,NodeandModifierincluded, with the probability that it is reached and that it runs per object, itsotherwisevalue, and for aMenu,MaterialSlotorUVMapwith its options listed, aBooleanor aVisibility, the probability of each value. The branches of aOneOfhave their weight and share of its picks.generating.Augmentations({"name": compose, ...})records in every label what each augmentation did for each object (applied, whether itsplet it run, andvalue, what it set, theotherwisevalue included), underaugmentations, and saves the graph of theComposes toaugmentations.jsonin the output folder.export.augmentation_stats(path)compares how often each augmentation ran over a dataset, and how often it set each value, with the probabilities of its graph. The datapointsBBoxskipped are not in the dataset, so the gap shows what they changed.export.augmentation_report(path)writesaugmentations.html, a page that draws the graph as a flowchart (the steps of aComposeorChainin a row, aOneOfas a fork to its branches with their weight and share,NodeandModifierinputs as rows of their card), with bars of how often each augmentation runs and sets each value, expected and observed, and marks the differences larger than chance. It is one file, opened in any browser without internet.augmentations.graph_preview(composes, path, show=False)draws the graph of augmentationComposes on the same HTML page, before generating, with the probability of each augmentation and value, to check a pipeline before a long run.- Every augmentation and
augmentations.Composetakes an optionalname, e.g.Translation(x=1, name="slide sideways"), shown in the graph (describe()), inexport.augmentation_statsand as the card title inexport.augmentation_report.
Changed¶
resultsof an augmentation that doesn't use the object it is called with (an absolute data path inNumber,Vector,Boolean,Menu,NodeorModifier,Seed, or aOneOforChainof only those) holds one entry, underNone, with the last value set. In aComposeover several objects it runs once per object on the same value, andresultsused to list a value per object although only the last one was kept.
Fixed¶
augmentations.Chain'sactual(andresults) holds(x, y, z)for aTranslation,RotationorScalein it, as their ownresultsdo. It wasNonefor them.augmentations.Menu,MaterialSlotandUVMapreject negative weights. Before, they were accepted and picked a wrong option every time (weights=[-1, 2]always picked the second one). Weights that sum to 0, are infinite or aren't numbers now fail when the augmentation is created, inOneOftoo, instead of at the first call. AMenuwhose options are read from Blender says how many options it has when the number of weights differs.augmentations.Menu(["A", "B"], [3, 1])(inside aNodeorModifier, without a data path) explains that the weights must be given by name,weights=[3, 1], instead of saying the data path must be a string.generating.Backgroundrejects infinite weights before generating, instead of failing at the first datapoint.
0.9.6 - 2026-10-15¶
Fixed¶
generating.BBox(..., allow_empty=True)now ignores every instance out of view, not only when all of them are. Before, a datapoint with some instances in frame and others out of it (e.g. a geometry nodes scatter) was still skipped bymax_truncation, and an instance partly behind the camera counted as in frame even when it couldn't be seen.generating.BBoxno longer skips a datapoint for an instance with no geometry, such as an object whose geometry nodes output only instances (e.g. a Menu Switch between its own mesh and instances). It gets bbox None and never skips, like a hidden one; before,max_truncationcounted it as fully out of frame.
0.9.5 - 2026-10-15¶
Added¶
generating.BBox(..., allow_empty=True)keeps datapoints with no instance in frame (negative samples), whichmax_truncationused to skip because an instance out of frame counts as fully truncated.max_truncationstill skips when any instance is in frame.
0.9.4 - 2026-10-14¶
Changed¶
generating.Backgroundno longer requires Render Properties > Film > Transparent, only RGBA output. The background shows where the render is transparent and an opaque render hides it, so a pipeline can have transparent and opaque datapoints.
0.9.3 - 2026-10-14¶
Added¶
augmentations.UVMap(uv_maps=None, weights=None, p=1.0, otherwise=None, data_path=None)makes one of a mesh's UV maps, picked at random (by index or name, optionally weighted), the one it renders with (and the active one), e.g. to lay a texture out at different scales or angles.actualholds the UV map's name, andstate.restoring()sets the previous one back.
0.9.2 - 2026-10-14¶
Added¶
- The augmentations that change an object (
Translation,Rotation,Scale,Visibility,KeepAbove,PlaceOn,LookAt,PlaceOnCurve,FocalLength,DepthOfField,MaterialSlot,SimpleMaterial) take an optionaldata_path, an absolute path to an object, e.g.Translation(x=0.3, data_path='bpy.data.objects["Light"]'). They then change that object instead of the one they are called with, ignore the objects of aCompose, and can be called alone:lamp_shift().
0.9.1 - 2026-10-13¶
Added¶
DepthOfField(..., defocus=...)moves the focus off the target by a random percent of its distance, added like aTranslationoffset:defocus=10focuses anywhere from 10 % nearer to 10 % farther,(0, 20)only farther,(low, high, step)in steps. Without a target it works from the scene's focus, also a Focus on Object's distance (which is then replaced by the distance).actualholds thedefocuspercent.FocalLength(..., keep_size=True, size=(80, 120)): withkeep_size, the target gets a random size in the image instead of keeping it, in percent of its size before the call (the camera moves farther or closer).sizetakes a(min, max)range or an exact number; without it,keep_sizekeeps the size exactly as before.actualholds thesizeset.augmentations.Modifiersets the settings of any modifier, not only the inputs of a geometry nodes modifier: Subdivision Surface, Bevel, Array, Displace... by their Python name, e.g.Modifier('modifiers["Subdivision"]', {"render_levels": Number((1, 3)), "subdivision_type": Menu(["CATMULL_CLARK", "SIMPLE"])}). On a geometry nodes modifier, a key that is no input's name sets that setting, e.g."show_render"to turn it on and off.augmentations.Modifier.template(modifier, obj=None, prefix="augmentations.", relative=True)writes aModifierfor a modifier: every input of a geometry nodes modifier by name, with the Min and Max of the input in the node group's interface, or every setting of another modifier by its Python name, with the name shown in the UI and the slider's range;Boolean(p=0.5),Menuwith every option, and the type and current value in a comment. Inputs set to read an attribute are comments, and a last comment shows how to turn the modifier on and off ("show_render"). The path is relative to the object by default, or absolute withrelative=False. The add-on copies it for the active modifier of the active object from the 3D viewport's Sidebar (N) > Blendmentation > Copy Modifier Template > Relative or Absolute.
0.9.0 - 2026-10-12¶
Added¶
augmentations.Chain.shape_keys_template(obj, prefix="augmentations.", relative=True)writes aChainwith aNumberfor every shape key of an object, over its slider range (0 to 1 by default), with the current value in a comment; muted and driven shape keys are comments. The paths are relative to the object, or absolute through it withrelative=False. The add-on copies it for the active object from the 3D viewport's Sidebar (N) > Blendmentation > Copy Shape Keys Template > Relative or Absolute.Node.templatetakesrelative=True, which writes the node's path relative toobj(through its active material, another material slot, its data or a geometry nodes modifier; a world's node relative to the world) instead of absolute.- The add-on has a Blendmentation tab in the node editor's sidebar (N): Copy Node Template
Relative (from the active object) or Absolute. The Node menu, right click menu and F3 search have both too.
Changed¶
- The inputs of a
NodeorModifierare set withNumber,Vector,BooleanandMenuwritten without a data path, e.g."Rust strength": augmentations.Number((0, 1)): theNodeorModifiergives each one the path of its input (for aNode,<node path>.inputs["Rust strength"].default_value, relative or absolute as the node's path is). UseVectorfor vector, rotation and color inputs; on a color input it keeps alpha. An augmentation that doesn't fit the socket raises aTypeErrornaming the right one.Node.templateand the add-on write inputs this way. - The templates (
Node.template,Chain.shape_keys_templateand the add-on's) start with a comment that says whether the paths are relative or absolute, with the name of the object they start from or of what they change, and how to call the augmentation.
Fixed¶
Node.template(and the add-on's Copy Node Template) no longer writes an input that fails when run for a built-in node's menu input, e.g. Transform Geometry's Mode, whose options Python can't list. It is now a comment starting withWarning!, with the current option, to addMenu([...])yourself.
Removed¶
augmentations.Input, and the dict form of aNodeorModifierinput ({"value_range": ..., "p": ...}). Set each input withNumber,Vector,BooleanorMenuwritten without a data path instead, e.g.Input((0.5, 1), p=0.2, otherwise=0)becomesNumber((0.5, 1), p=0.2, otherwise=0),Input(p=0.3)becomesBoolean(p=0.3)andInput(options=["A", "B"])becomesMenu(["A", "B"]). A plain range or option list ("Rust": (0, 1)) still works.
0.8.2 - 2026-10-12¶
Changed¶
SimpleMaterialalso takes an exact value forhue,saturation,value,roughnessandmetallic, e.g.metallic=1, next to a(min, max)range.generating.Composeraises aTypeErrornaming the item whenstepsholds something that is not a generating step, e.g. an augmentation.export.coco(bbox_from="mask")with an instance that has no instance mask raises the same error asyoloandvoc, which says to addSegmentation(classes, per="instance").
Fixed¶
- A
Keypointspoint and thetargetofLookAt,FocalLengthandDepthOfFieldno longer accept booleans as coordinates:(True, 0, 1)raises aTypeErrorinstead of being taken as the point (1, 0, 1).
0.8.1 - 2026-10-09¶
Added¶
augmentations.PlaceOnCurve(curve, align=False, position=(0, 1), p=1.0)moves a camera (or any object) to a random point on a curve object, e.g. a camera path, picked evenly along its length. By default the object keeps its rotation; withalign=Trueit faces along the curve direction there, upright.positionlimits it to part of the curve, as a fraction of its length. PutTranslationandRotationafter it to move or turn the object from there, orLookAt(target)to aim it at something.actualholds thepositionand the worldlocation.- Blendmentation is also a Blender add-on: install
blendmentation-addon-<version>.zipfrom the GitHub release in Preferences > Add-ons, and scripts can import it withoutsys.pathsetup. It adds Node > Copy Blendmentation Template to the node editor (also in the right click menu and F3 search), which copies anaugmentations.Node(...)for the selected node to the clipboard: every input by its exact name, with the widest range the node allows (a group input's Min and Max), all menu options, and the type and current value in a comment. The prefix (e.g.aug.) is set in the add-on's preferences. augmentations.Node.template(node, obj=None, prefix="augmentations.")returns the same text without the add-on, for a node or its data path.
0.8.0 - 2026-10-09¶
Added¶
with state.restoring():sets back every value the augmentations changed inside the block when it ends, also when something in it raises. Nothing has to be listed or saved first: every augmentation records what it changes (transforms, render visibility, node and modifier inputs, material slots, camera settings, any data path), also inside aOneOf,Chain,NodeorModifier.state.set(owner, name, value)makes a change of your own code that is set back too. Blocks can be nested.augmentations.OneOf(augmentations, weights=None, p=1.0)applies one augmentation from a list, picked at random by weight, again for every object in aCompose. Itsactualis the picked index andresultsthe index per object. AOneOfcan contain anotherOneOf.Composeclears theresultsof the augmentations inside it.augmentations.Node(node, inputs, p=1.0)sets many inputs of one node, e.g. the group node of a smart material or of a geometry nodes setup, in one augmentation.inputsmaps each input name (or index) to a range, or to anaugmentations.Input(value_range, options, weights, p, otherwise)(a dict with these keys works too); each input is set like aNumber,Vector(vectors, colors and rotations, in radians),BooleanorMenu, picked by the socket type. A color range sets red, green and blue and keeps alpha.actualholds the value set to each input.augmentations.Modifier(modifier, inputs, p=1.0)sets the inputs of a geometry nodes modifier likeNodedoes for a node, e.g.Modifier('modifiers["Scatter"]', {"Density": Input((5, 20))}). It finds the inputs by their name in the node group, in every Blender version, so you don't need theSocket_2data paths that changed in Blender 5.augmentations.Menuworks on the menu inputs of a geometry nodes modifier in Blender 4.x, which Blender stores as numbers: it sets and reports the option names.augmentations.MaterialSlot(slots=None, weights=None, p=1.0, otherwise=None)gives an object one of the materials in its own material slots, picked at random: every face gets the picked slot, and it becomes the active slot, soactive_materialpaths in the sameComposereach it.actualis the material's name. Materials aren't copied, so objects that share a material or a mesh keep sharing it.augmentations.Chain(augmentations, p=1.0)applies a list of augmentations to one object as one augmentation, so aOneOfcan pick between whole sequences, e.g. aMaterialSlottogether with aNodethat augments only that material.Composeclears theresultsinside it.- Every augmentation call takes
p, which overrides the augmentation'spfor that call, like aComposecall does:rust(p=0.2),rotation(car, p=0.5). augmentations.Numbertakesvalue_range=(min, max, step), which sets one of min, min + step, ... max.augmentations.Menuwithoutoptionsreads them on a group node's menu input too, from the Menu Switch the group passes it to.
Changed¶
- The loop restores the scene with
with state.restoring():instead ofinitial = state.State(objects, fields=...)andinitial.restore(). Put the augmentations and the generatingComposeinside the block. augmentations.Materialis renamed toaugmentations.SimpleMaterial.augmentations.Numberraises aValueErrorfor avalue_rangethat is not(min, max)or(min, max, step), or whose min is above its max.
Removed¶
state.State, with itsobjectsandfields: usestate.restoring(). It only set back what was saved when it was built;restoring()sets back exactly what the augmentations changed.
0.7.3 - 2026-10-08¶
Added¶
generating.Instances(parent, of=None)labels the instances that geometry nodes make onparent(Instance on Points with Object Info or Collection Info, no Realize Instances needed): put it in a class's list inclasses, and every top-level instance becomes an instance of the class, with its own box (BBox, all skip settings included), pose (Pose) and masks (Segmentation, full masks too).ofpicks the instanced objects (an object, a list or a collection). In the labels, an instance's objects are named<parent>/<instance index>/<object>. Its masks come from a Cycles id render, whatever the engine. Geometry nodes instances blockKeypointseven when the object they instance is hidden.ofalso finds instances that geometry nodes turned into copies of the object's mesh (Object Info without As Instance, Smooth by Angle after the instancing), and raises when it finds none because the instances are meshes of no object.
Changed¶
- Mask files have the class in their name: instance masks are
<index>_mask_<class>_<n>.png(was<index>_mask_<n>.png), full masks<index>_mask_<class>_<n>_full.png, and class masks<index>_class_<class>.png(was<index>_mask_<class>.png). The label JSON lists every mask file as before, and the exports read them from it.
Fixed¶
- Objects in a collection excluded from the view layer or disabled in renders are treated
as hidden in the labels, like objects hidden with
Visibility:BBoxgives them no box (and they don't makemax_truncationskip the datapoint),Poseno pose, they don't blockKeypointsand a keypoint on one is not visible.
0.7.2 - 2026-10-07¶
Changed¶
- The README and the docs home page explain the approach: the variation lives in your Blender scene, in shader and geometry nodes, and Blendmentation changes their values. Their quick start is a short example.
- The step-by-step examples and the full example moved to their own Quick start page, which starts with installing in Blender.
0.7.1 - 2026-10-07¶
Changed¶
- The README's quick start is shorter: the chained transform setup and the full example. The step-by-step setups (a material with shader nodes, an HDRI world) are in the docs.
0.7.0 - 2026-10-07¶
Added¶
augmentations.Seed(node_group): gives every unlinked Seed input in a node group (Distribute Points, Random Value, Hash Value..., nested groups included) its own random value on every call, e.g.Seed(bpy.data.node_groups["Geometry Nodes"])for a new scatter in every image. Takespandotherwise(one seed for all of them).state.Statetakes node groups inobjectsand restores their nodes, e.g. the seedsSeedchanges.augmentations.PlaceOn(surface, margin=0): moves the object up or down along world Z until its lowest point restsmarginabove the surface, where they overlap seen from above. LikeKeepAbove, which only lifts, but it also lowers floating objects, e.g. one tipped over around an origin that is not at its bottom.- Stepped values in
Translation,RotationandScale: an axis can be(low, high, step), which picks one oflow,low + step, ... up tohigh, e.g.Rotation(z=(0, 270, 90))for 0, 90, 180 or 270 degrees. An invalid axis (low above high, a step of 0 or less) raises aValueErrorwhen the augmentation is created. otherwiseonNumber,Vector,Boolean,Menu,Visibility,FocalLengthandDepthOfField: the value to set when the augmentation doesn't run because of itsp, e.g.Number(path, (0.5, 1), p=0.2, otherwise=0),FocalLength((24, 85), p=0.3, otherwise=50)orDepthOfField(target, p=0.5, otherwise=False)(depth of field off). None keeps the value, the default except forBoolean(False) andVisibility(hidden), which work as before.Boolean(..., otherwise=None)andVisibility(..., otherwise=None)leave the value unchanged instead.state.Statetakes a World (or another datablock with a node tree, such as a material) inobjects: it saves and restores its node tree, and relative paths infieldsresolve on it. Pass the world to aComposeto augment it with relative data paths, e.g.world_aug([bpy.context.scene.world]).
Changed¶
state.Staterestores the whole node tree of materials (and of worlds), not only the unlinked input values: node settings such as a Math node's operation or muting, the values of Value, RGB and Vector nodes, color ramp stops, curve points, images, and the nodes inside node groups. Augmentations of these no longer needfields.
0.6.0 - 2026-10-07¶
Added¶
generating.Pose(classes): the 6D pose of every instance relative to the camera,Randtin OpenCV camera axes, plus the object'sscale. The label gets"poses".full_masks=Trueongenerating.Segmentation: also saves every instance's full mask, its whole silhouette ignoring occlusion, as<index>_mask_<n>_full.png. The label gets"full_masks". Instances that don't overlap share one extra render.export.bop(): writes the dataset as a BOP scene for 6D pose estimation:scene_gt.json,scene_camera.json,rgb/, and with masksmask/,mask_visib/andscene_gt_info.json, with aDepthpassdepth/(16-bit, mm).CameraDatasavesunit_scale, the scene's meters per Blender unit.bbox_from="mask"onexport.yoloandexport.voc, as onexport.coco: boxes of the visible pixels of each instance mask instead of theBBoxboxes around the whole object. NeedsSegmentationwithper="instance"or"both".
Changed¶
generating.Segmentationin Cycles makes the masks from the Object Index pass of the beauty render instead of a separate Workbench render, so they match the image: alpha-clipped materials (leaf cards, decals, fences) and shader displacement are right, and there is no extra render. Volumes get no mask. With EEVEE and Workbench it still uses the Workbench render. In Cycles, aPasses(["ObjectIndex"])step next toSegmentationnow holds the instance ids instead of the objects' pass indices.
Fixed¶
- Several
generating.Segmentationsteps in oneComposeoverwrote each other's masks: instance numbers now continue across the steps, and a class mask name that is already taken (the same class in two steps, or class names that differ only in characters not allowed in file names) gets_2,_3, ...
0.5.0 - 2026-10-07¶
Added¶
generating.Background: puts a random background behind a transparent render, in the image and the preview images. Modes: uniform color, white noise, color noise and images from a folder, mixed byweights, withnoise_size=(min, max). Needs Film > Transparent and RGBA output. The label gets"background".resultson every augmentation: what it did to each object of the lastComposecall, as{object name: value}((x, y, z)forTranslation,RotationandScale).actualandappliedonly hold the last object of aComposecall, which is now documented. Save it in the label withgenerator({"lift": keep_above.results}).
0.4.0 - 2026-10-06¶
Added¶
augmentations.Visibility: shows or hides an object in the render. Bboxes, masks, occlusion and keypoint visibility follow it.augmentations.KeepAbove: lifts an object until it rests above a surface, also on uneven surfaces.
Fixed¶
Statefailed on materials with an AOV Output node in Blender 4.0, where the AOV name shadows the node name.
0.3.0 - 2026-10-06¶
Added¶
BBox(max_occlusion=...): skips a datapoint when objects in no class hide too much of an instance.skip_emptyforSegmentation,AOVToImageandPasses: empty images aren't written, their file in the label isNone.- A
Composecall can override its probability:aug(objects, p=0.5). - An agent skill for using the library through Blender MCP (
skills/blendmentation).
Changed¶
- The docs moved to https://blendmentation.docs.csmx.eu/.
mainholds the latest release, development happens ondev.
0.2.0 - 2026-10-05¶
Added¶
BBox(max_truncation=...): skips a datapoint when an instance's box is too far out of frame.- Per-class skip settings: a class can be
{"instances": [...], "iou_deconflict": ..., "max_truncation": ...}, which win over theBBoxarguments. CameraDatasaves the depth of field settings.- A documentation site, built from
docs/and the docstrings.
Changed¶
NumberandVectortake avalue_range(now required), thepercentageargument was removed.
0.1.0 - 2026-10-05¶
Added¶
- First release.
- Augmentations:
Compose,Translation,Rotation,Scale,LookAt,FocalLength,DepthOfField,Material, andNumber,Vector,Boolean,Menufor any value by data path. Every augmentation takes a probabilityp. Stateto save and restore the scene after every datapoint.- Generating steps:
Render,BBoxwithiou_deconflict,Segmentation,AOVToImage,Passes,RotationMatrix,OutputField,CameraData,Keypoints, and the preview imagesBBoxImageandSegmentationImage. - Export to COCO, YOLO and Pascal VOC.
- Runs inside Blender and with
bpyas a Python module.