bradyajohnston/molecularnodes/mn-render
- community
Checking that the SKILL.md file is still in the repository.
How to render images and animations of molecules from a Python script with the molecularnodes package (Canvas, engines, framing, styles, materials, colour, trajectories, deterministic renders for tests). Load before writing or debugging any script that calls mn.Canvas, snapshot, animation or record.
Install
npx vskill@latest install bradyajohnston/molecularnodes/mn-renderSKILL.md
Rendering with Molecular Nodes from a Python script
1. Mental model
- One `Canvas`, many entities.
mn.Canvaswraps the active Blender scene: engine,
resolution, camera, world lighting, compositor and output. Molecules are mn.Molecule entities that you load, style and then frame with canvas.look_at.
- Create the Canvas before loading anything. It loads the Molecular Nodes scene
preset (camera, lights, world, compositor) into an empty scene. Calling mn.Canvas() again is safe: it binds to the existing scene and only reloads the preset when the scene holds no molecules.
- Styles are node trees.
add_stylebuilds a small node branch per call; the
render shows whatever the evaluated geometry is. look_at frames that evaluated geometry, so a molecule with one chain styled is framed on that chain.
- Units. Positions are Å times
world_scale = 0.1, so 1 Å is 0.1 Blender units. - There is no
mn.Trajectoryany more; MD topologies and trajectories load into
mn.Molecule as well.
2. Environment for a script
Always via uv run (never bare python). In a script that imports bpy directly:
Without the BLENDER_USER_EXTENSIONS line, an installed MN extension's bundled wheels shadow the venv. tests/conftest.py does the same thing. Keep scratch scripts outside the repo.
Inside Blender with the installed extension (blender -b --python script.py), the package is import bl_ext.blender_org.molecularnodes as mn; import molecularnodes fails there. Install headless with blender --online-mode --command extension install --enable molecularnodes (without --online-mode Blender refuses to reach the platform). Do not call bpy.ops.wm.read_factory_settings() to reset between renders: it has been reported to strip the extension's bundled wheels mid-run, so the next Molecule.load() fails on a missing biotite file. Delete the default objects by hand, or use one Blender process per render. Don't name a scratch script after a standard-library module (inspect.py, code.py, types.py, ...). Importing bpy imports that module, finds the script instead, and Blender initialises twice and aborts with Check failed: !IsGoogleLoggingInitialized().
3. Minimal still
snapshot(path=None, frame=None, file_format="PNG", render_scale=100). With no path it renders to a temp file and returns the image (notebooks display it). frame= renders one scene frame and restores the current frame afterwards. TIFF/EXR write the file but return None. The canonical docs versions are in docs/api/index.qmd and docs/api/rendering.qmd.
4. Canvas settings
| member | meaning | |
|---|---|---|
engine | mn.scene.EEVEE(samples=64, raytracing=True) or mn.scene.Cycles(samples=256, device="GPU", denoise=True, denoise_gpu=True); also accepts "EEVEE" / "CYCLES". Constructor values are written to the scene on assignment. | |
resolution | (x, y) pixels. render_scale is the percentage. | |
samples | samples on the active engine. | |
transparent | film transparency (alpha background). | |
background | world background RGBA; shortcut into the MN_world_shader node. | |
world.hdri_strength | lighting strength of the preset HDRI. | |
view_transform, exposure, gamma, look | colour management. Blender defaults to AgX; use "Standard" when colours must come out as specified. mn.Canvas(view_transform="Standard") sets it on construction. | |
passes | render passes: combined, z, mist, normal, position, vector, diffuse_color, emit, environment, ambient_occlusion. | |
frame, frame_start, frame_end, frame_range, fps | timeline. Setting frame uses frame_set, so trajectories update. | |
camera | lens, clip_start, clip_end, rotation (XYZ Euler in degrees), basis, set_viewpoint(...), frame_points(points, margin). | |
compositor | device, precision, denoise settings, reset(), clear(), add_annotations(). |
canvas.clear()deletes every object except cameras and lights (including the
preset backdrop), purges orphans recursively, and keeps engine, world, compositor and render settings. canvas.load_preset() reloads the whole preset.
Cycles(device="GPU")tries OPTIX, CUDA, METAL, HIP, ONEAPI and falls back to CPU
with a warning. EEVEE needs a GPU; the GitHub runners do not have one, so CI-facing scripts use Cycles CPU.
world.reset()andcompositor.reset()remove the preset nodes: after them
canvas.background raises ValueError and the annotation overlay is gone (compositor.add_annotations() restores it).
5. Loading entities
fetch(code, format=".bcif", cache=download.CACHE_DIR, database="rcsb"). load routes single files through biotite and topology plus coordinates through MDAnalysis; style= is optional and defaults to no style, leaving the tree empty. In tests use the fetch fixture instead, which loads from tests/data and never downloads.
6. Styles, materials, colour
- Style names:
spheres,cartoon,ribbon,surface,sticks,ball_and_stick.
Extra kwargs go to the style node's inputs (quality, scale, loop_radius, sphere, ...); unknown names raise TypeError, unknown style strings ValueError. add_style returns the molecule, so calls chain.
- Callable style (
lambda: mg.StyleX(...)) builds the node itself, so it cannot be
combined with selection, material or kwargs (TypeError); color, assembly and name are fine.
- Selection is an existing boolean attribute name first, then an MDAnalysis phrase
(stored as a managed sel_N attribute), an AtomGroup, or a callable returning a boolean socket (lambda: mg.IsPeptide() & mg.IsSideChain()).
- `assembly=True` instances the style over the file's biological assembly.
- Materials. Presets in
mn.material:Default(roughness, ao_distance, ao_exponent),
AmbientOcclusion(distance, exponent), Flat(outline, threshold, thickness) (no outline unless outline=True), Squishy(subsurface_scale, roughness), Transparent(transparency, fresnel, outline_color). Each instantiation builds an independent datablock ("Flat.001"), and its parameters are live: mat.distance = 0.15 changes the next render. Passing the string material="Flat" reuses one shared datablock, so edits affect every style using it. "Fresnel Outline" exists only as mn.material.append_material("Fresnel Outline"). Transparent needs the blended surface method, which its recipe sets. To swap the material on an existing style: style.i.material.default_value = mat.material.
- Colour.
color=takes"common"/"default"(elements, carbons random per
chain), "plddt", the name of an existing colour attribute, an RGBA sequence, or a callable returning a colour socket. Any other string warns and applies nothing (this replaced a silent black render). Arrays go on directly with mol["Color"] = rgba_array.
- Colouring part of a representation. Do not add the same style once per selection
to colour regions (a cartoon per section, sticks per nucleotide): each style builds its own geometry, so a cartoon or ribbon is cut at every boundary (blocky helix ends, stubs at the cuts). Add each style once and colour it from a per-atom attribute: ``python mol["Color"][mol.universe.select_atoms("chainID A and resid 19-26").ix, :3] = ( 0.1, 0.7, 0.7, ) # color=None reads Color; or store under another name and pass color="name" mol.add_style("cartoon") mol.add_style("sticks", selection="chainID A and resid 19-26") ` A new attribute needs atype="FLOAT_COLOR": mol["name"] = rgba infers a FLOAT4, which add_style` rejects (it warns and applies nothing). Cartoon colour follows the backbone atoms, so colouring only side-chain atoms leaves the cartoon unchanged.
- Spheres in EEVEE. Point clouds are ray-traced only by Cycles; on other engines
add_style switches StyleSpheres from sphere="Point" to "Instance" for you. If you build the tree by hand, set sphere="Instance" or "Mesh" yourself, and use instances whenever point clouds with different materials are joined.
7. Framing
look_at(target, viewpoint=None, margin=0.05)fits the camera to the evaluated
geometry without changing where it points; margin=0 is exact, negative crops.
camera.lenschanges afterlook_atinvalidate the fit: set the lens first, or call
look_at again.
- Stale camera basis.
set_viewpointwritesrotation_euler, but the fit reads
matrix_world, which only updates with the depsgraph. look_at(entity_or_object, viewpoint=...) is safe because reading the evaluated geometry updates it first. look_at(points_array, viewpoint=...), or set_viewpoint(...) followed by a separate look_at, fits against the previous orientation and renders empty or off-centre (measured 2026-09-18). Call bpy.context.view_layer.update() between the two until look_at does it itself.
frame_pointsextendsclip_endwhen the subject would be clipped; if a hand-placed
camera renders nothing, check camera.clip_end first.
- Rotating the subject instead of the camera: leave the camera at
"default"and
rotate the object, or use the viewpoint tuple in a loop for orbits (section 9).
8. Trajectories
Per-molecule playback properties, all stored on the Blender object: frame, subframes, offset, average, correct_periodic, interpolate. A trajectory with n universe frames and subframes = s spans n * (s + 1) - 1 scene frames.
Molecule.frames_to_collection(start=0, stop=None, step=1) bakes frames into a collection for the Animate Frames node when the timeline route is not wanted. set_frame is what the frame-change handler calls; user code sets canvas.frame.
9. Animations and image sequences
Three routes, pick by what changes between frames:
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingskills/mn-render/SKILL.md
Popularity
- Repository stars
- 1,378
- Repository forks
- 133
- 7-day trend score
- 41
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/bradyajohnston/molecularnodes/mn-render)