Changelog
v520.1.0 - UNRELEASED
Added
- Added a general handler that triggers after nodes are added to the tree in the GUI. Sets the default material of style nodes and generates data objects for biological assemblies. #1191
v520.0.1 - 2026-09-06
Fixed
- Text printing in a jupyter notebook crashed rendering #1185
v520.0.0 - 2026-09-05
Added
On using the import operators, the camera and viewport end clipping distance is increased to 10000 from 100 so users aren’t confused when importing larger structures and systems #1150
Molecule.add_style()accepts a callable forstyle,selectionandcolor. The callable is evaluated inside the node tree context, so it can build any nodes frommolecularnodes.nodes.geometrywithout having to write out a whole node tree:mol.add_style("cartoon", color=lambda: g.ColorSecondaryStructure()) mol.add_style("sticks", selection=lambda: g.IsPeptide() & g.IsSideChain()) mol.add_style(lambda: g.StyleSpheres(sphere="Instance", quality=4))A callable
styledefines the style node itself, soselection,materialand style keyword arguments cannot be passed alongside it - passing them raises aTypeErrorrather than silently dropping them.SelectionManager.node()returns aNamed Attributenode for a selection, creating the selection if it does not already exist. This is the bridge between selections and the node tree API, and is how an MDAnalysis selection phrase is used inside a callable style:mol.add_style(lambda: g.StyleSpheres(selection=mol.selections.node("not protein")))Existing selections are reused - matched on the selection string together with the
updatingandperiodicflags, or onname- so repeated calls do not pile up duplicate selections and mesh attributes.SelectionManager.find()looks up an existing selection by the selection string that created it, whereget()looks a selection up by its name.Canvas.look_at()takes amargin, the fraction of the frame to leave empty around the subject. It defaults to0.05, so renders get a little breathing room rather than the subject sitting against the edge of the frame. Set it to0to fit the subject exactly, or go negative to crop in past its edges. #1170molecularnodes.framingsolves camera framing on a set of positions.fit_camera_to_points()is the exact solve used bylook_at(), withfit_orthographic_to_points()for orthographic cameras andenclosing_sphere()for framing that does not change with viewing angle. It is plain numpy, with no dependency on Blender. #1170
Changed
add_style(color=...)now validates its argument. A string must be a known keyword ("common","default","plddt") or the name of an existing colour attribute on the molecule. Anything else raises aUserWarningand adds no colour, rather than silently rendering the geometry black. This includes names of attributes that exist but are not colours, such as"b_factor"or"lipophobicity".add_style(style=...)is validated against the styles that can actually be dispatched, and the error lists them.add_style("spheres")picks itssphereto suit the render engine. Point clouds are only ray-traced by Cycles, so under EEVEE the spheres came out as octahedra - invisible at whole-protein zoom and glaring on a close-up. EEVEE now gets"Instance", Cycles keeps"Point", and passingsphereyourself always wins. The engine is read when the style is added, so switching engines afterwards does not revisit it. #1172- Breaking:
Canvas.scene_reset()is renamedCanvas.load_preset(), which is what it does - it loads a whole preset scene, its lighting, camera, world shader and render settings included. Thekeep_settingsargument is gone, andenginenow defaults to the engine the preset defines rather than forcing EEVEE. #1169 - Creating a
Canvasno longer wipes the scene. Atemplategiven explicitly is always loaded, but left out the “Molecular Nodes” preset is only loaded into a scene with no molecules in it. A firstmn.Canvas()still gets the studio lighting, while re-running the same cell - which marimo does on its own - binds to the scene and leaves the work in it alone. Previously it destroyed every molecule in the scene with no warning. Passtemplate=Noneto bind without loading anything. #1169 - Breaking:
Canvas.look_at()accepts any number of positions, where it previously expected the 8 vertices of a bounding box.get_view()returns the positions making up a view rather than 8 bounding-box corners - still alist[tuple], so views combine with+as before.blender.utils.get_bounding_box(),look_at_object()andlook_at_bbox()are gone. #1170 Canvas.look_at()frames the geometry an entity renders rather than the entity’s bounds, so styling one chain of four frames that chain. Every component of the evaluated geometry is taken into account - a style can output a mesh, a point cloud and instances at once, and an object only ever exposes one of them through itsdata. Point clouds are grown by their radius and instances by the bounds of what they instance, so spheres are framed by their surface rather than their centres. #1170Canvas.clear()empties the scene of content rather than removing only Molecular Nodes entities. The camera, lights, render settings, world shader and compositor are kept, so the canvas is left ready to render whatever is added next, and only the canvas’s own scene is touched. #1169
Fixed
add_style()raised a bareKeyErrorfor style names that were accepted by validation but had no corresponding style node -"vdw","atoms","sphere"and"ball+stick"among them. These now raise aValueErrornaming the supported styles.- Saving a
.blendsilently lost the entire session if any entity’s object had been deleted, from the outliner or otherwise. The save handler raised before writing the.MNSessionfile while the.blenditself saved normally, so every other molecule and trajectory in the file lost its state on reload. #1169 - Opening a second
.blendleft the previous file’s entities in the session, where they raised aLinkedObjectErroron any access. #1169 MNSession.remove()raised aLinkedObjectErrorwhen the object behind an entity had already been deleted. The entity is now dropped either way, so removing is always safe to call - and a single stale entity can no longer abort a wholeCanvas.clear(). #1169Canvas.look_at()pulled the camera back further than the subject needed, by a different amount from each direction. It built a mesh from the 8 corners of an axis-aligned bounding box and handed that to Blender’scamera_to_view_selectedoperator; an axis-aligned box projects larger than the points inside it, and by an amount that changes with the viewing angle. Framing is now solved directly on the positions, so it is exact and consistent from any direction. #1170Canvas.look_at()silently emptied the frame when given more than 8 positions. It was annotatedlist[tuple]with no check on how many, and anything but a bounding box left the subject a few pixels wide in the middle of an empty frame. #1170get_view()readbound_boxbefore the depsgraph had caught up, so what it returned depended on whether anything had happened to trigger an evaluation - a molecule read straight afteradd_style()reported the bounds of its atoms rather than of the style built from them. It also mixed spaces, returning object-local bounds without a selection and world-space ones with. Both are now world-space positions read from evaluated geometry. #1170Canvas.clear()leaked data-blocks. A molecule left behind over a hundred of them - its mesh, material and the node groups backing its styles - which accumulated on every load-and-clear cycle, as only a recursive purge collects node groups hanging off an object’s modifier tree. #1169