Skip to main content

blender-atom-render

Render atomic structures and individual atom spheres using Blender's io_mesh_atomic addon. Two modes - (1) full-structure auto-frame rendering with multi-angle support, (2) single-atom sphere rendering for legends. Triggers - /blender-atom-render, "atom render", "원자 렌더링", "atom legend", "레전드 만들어", "blender로 렌더", "구 렌더링", "structure render blender"

설치로 이동

소스 정보

저장소
s-choung/Research-Skills
최근 소스 활동
2026년 6월 24일 05:22
감지된 SKILL.md 언어
영어
스타
32
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

파일 탐색기
3 개 파일

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
blender-atom-render
description
Render atomic structures and individual atom spheres using Blender's io_mesh_atomic addon. Two modes - (1) full-structure auto-frame rendering with multi-angle support, (2) single-atom sphere rendering for legends. Triggers - /blender-atom-render, "atom render", "원자 렌더링", "atom legend", "레전드 만들어", "blender로 렌더", "구 렌더링", "structure render blender"
# Blender Atom Render Render atomic structures (molecules, slabs, nanoparticles, MOFs) and individual atom spheres using Blender's `io_mesh_atomic` addon. ## Prerequisites - Blender installed at `/Applications/Blender.app/Contents/MacOS/Blender` - Python with ASE (`ase`) and Pillow (`PIL`) in base conda - For single-atom mode: a Blender `.blend` file with camera and lighting setup - For full-structure mode: no .blend file needed (auto-frame script creates camera+lights) ## Resource Management (CRITICAL - READ FIRST) Blender Cycles and OVITO Tachyon are both CPU/GPU-intensive. Parallel rendering WILL crash on laptops (M1/M2 Mac, 16-32GB RAM). ### Rules - **NEVER dispatch parallel subagents** for rendering. Each Blender/OVITO process uses 2-8GB RAM + full CPU. - Batch renders in a **single sequential Python script** instead. Write one .py file that loops through all inputs, then run it as one `conda run` or Blender call. - For Blender: max **1 process at a time**. Even 2 concurrent Blender instances can OOM on 16GB Mac. - For OVITO: max **2-3 concurrent** (lighter than Blender, but still heavy for 500+ atom structures). - Large structures (1000+ atoms, MOFs): set timeout=300000 and expect 30-60s per render. - If rendering 20+ files: write a batch script, run in background, monitor with `tail`. ### Memory estimation | Atoms | Blender RAM | OVITO RAM | Render time | |-------|------------|-----------|-------------| | < 50 | ~500 MB | ~200 MB | 1-3s | | 100-300 | ~1 GB | ~500 MB | 5-15s | | 500-1000 | ~2 GB | ~800 MB | 15-40s | | 1000+ | ~4 GB | ~1.5 GB | 30-120s | ## Mode 1: Full-Structure Auto-Frame Rendering Renders complete structures (.xyz, .cif) with automatic camera framing, multi-angle support, and 3-point lighting. No .blend file required. ### Anti-clipping rules (CRITICAL) - `ortho_scale = max_bounding_box_dim * 1.8` prevents edge clipping - Never use `* 1.4` or lower; atoms at slab edges will be cut off - For very flat slabs (z << x,y), use `max(size.x, size.y) * 1.6` instead of max_dim ### Ball radius rule (CRITICAL) - Always use `scale_ballradius=1.0`. NEVER reduce below 1.0. - Reducing ballradius (e.g., 0.35) makes atoms look disconnected, especially in MOFs/COFs where bond visualization depends on atom sphere overlap. - If structure looks too crowded, zoom out (increase ortho_scale) instead of shrinking atoms. ### Camera angles - **perspective**: 45-degree oblique view `(0.55, -0.55, 0.5)` normalized from center. Best for 3D structures (nanoparticles, MOFs). - **top**: near-vertical `(0.15, -0.15, 0.95)`. Best for surface slabs to show top-layer pattern. - Always render BOTH angles for each structure. ### Lighting: 3-point setup - **Key light** (SUN, energy=3.5, angle=8deg): main illumination from upper-right - **Fill light** (SUN, energy=1.2): softer from opposite side, reduces harsh shadows - **Rim light** (SUN, energy=1.5): backlight for edge definition and depth ### Auto-frame render script ```python # render_autoframe_v2.py # Usage: blender --background --python render_autoframe_v2.py -- input.xyz output.png [perspective|top] import bpy, sys, mathutils, math argv = sys.argv[sys.argv.index("--") + 1:] xyz_path = argv[0] output_path = argv[1] angle = argv[2] if len(argv) > 2 else "perspective" for obj in list(bpy.data.objects): bpy.data.objects.remove(obj, do_unlink=True) bpy.ops.outliner.orphans_purge(do_recursive=True) bpy.ops.preferences.addon_enable(module="io_mesh_atomic") bpy.ops.import_mesh.xyz( filepath=xyz_path, ball="1", mesh_azimuth=128, mesh_zenith=128, scale_ballradius=1.0, scale_distances=1.0, ) mesh_objs = [o for o in bpy.context.scene.objects if o.type == 'MESH'] if not mesh_objs: sys.exit(1) all_min = mathutils.Vector((1e9, 1e9, 1e9)) all_max = mathutils.Vector((-1e9, -1e9, -1e9)) for obj in mesh_objs: for corner in obj.bound_box: world_pt = obj.matrix_world @ mathutils.Vector(corner) all_min.x = min(all_min.x, world_pt.x) all_min.y = min(all_min.y, world_pt.y) all_min.z = min(all_min.z, world_pt.z) all_max.x = max(all_max.x, world_pt.x) all_max.y = max(all_max.y, world_pt.y) all_max.z = max(all_max.z, world_pt.z) center = (all_min + all_max) / 2 size = all_max - all_min max_dim = max(size.x, size.y, size.z) cam_data = bpy.data.cameras.new("AutoCam") cam_data.type = 'ORTHO' cam_data.ortho_scale = max_dim * 1.8 # 1.8x for safe margin cam_obj = bpy.data.objects.new("AutoCam", cam_data) bpy.context.collection.objects.link(cam_obj) cam_dist = max_dim * 3 if angle == "top": cam_obj.location = center + mathutils.Vector((cam_dist*0.15, -cam_dist*0.15, cam_dist*0.95)) else: # perspective cam_obj.location = center + mathutils.Vector((cam_dist*0.55, -cam_dist*0.55, cam_dist*0.5)) direction = center - cam_obj.location cam_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler() bpy.context.scene.camera = cam_obj # 3-point lighting for name, energy, loc, rot in [ ("Key", 3.5, (cam_dist, -cam_dist, cam_dist*1.5), (30, 0, -45)), ("Fill", 1.2, (-cam_dist, cam_dist*0.5, cam_dist*0.3), (60, 0, 135)), ("Rim", 1.5, (-cam_dist*0.3, cam_dist*0.8, cam_dist*0.6), (45, 0, 180)), ]: light = bpy.data.lights.new(name, type='SUN') light.energy = energy obj = bpy.data.objects.new(name, light) bpy.context.collection.objects.link(obj) obj.location = center + mathutils.Vector(loc) obj.rotation_euler = tuple(math.radians(a) for a in rot) bpy.context.scene.render.resolution_x = 2000 bpy.context.scene.render.resolution_y = 2000 bpy.context.scene.render.film_transparent = True bpy.context.scene.render.image_settings.file_format = 'PNG' bpy.context.scene.render.image_settings.color_mode = 'RGBA' bpy.context.scene.render.filepath = output_path bpy.ops.render.render(write_still=True) ``` ### Batch rendering workflow 1. Render each file as a SEPARATE Blender process (no loops within Blender) 2. Always render both angles: `_perspective.png` and `_top.png` 3. Large structures (1000+ atoms): timeout 120s per render, ~15s typical 4. MOFs (500+ atoms): may need 30-60s ## Mode 2: Single-Atom Sphere Rendering (for legends) ## Workflow ### Step 1: Extract unique atoms from structure files Use ASE to read all structure files and extract unique chemical symbols. Create single-atom XYZ files at (0,0,0). ```python from ase.io import read from ase import Atoms from ase.io import write import glob, os traj_files = glob.glob('**/*.traj', recursive=True) all_symbols = set() for f in traj_files: atoms = read(f, index=-1) all_symbols.update(atoms.get_chemical_symbols()) for symbol in sorted(all_symbols): atom = Atoms(symbol, positions=[(0, 0, 0)]) write(f'{symbol}.xyz', atom) ``` ### Step 2: Create a clean blend file (camera + lights only) Copy the original blend file, removing all mesh objects while preserving camera and lighting. Add a close-up orthographic camera for single-atom rendering. **Key: use `bpy.data.objects.remove()` instead of `select_set() + delete`** — some objects may not be in the active ViewLayer and will fail with select-based deletion. ```python # make_clean_blend.py — run with: blender original.blend --background --python make_clean_blend.py import bpy, mathutils OUTPUT_BLEND = "camera_light_clean.blend" # Delete all non-camera/light objects via data API (bypasses ViewLayer issues) keep_types = {'CAMERA', 'LIGHT', 'EMPTY'} to_delete = [obj for obj in bpy.data.objects if obj.type not in keep_types] for obj in to_delete: bpy.data.objects.remove(obj, do_unlink=True) bpy.ops.outliner.orphans_purge(do_recursive=True) # Add close-up orthographic camera for single atom cam_data = bpy.data.cameras.new("AtomCam") cam_data.type = 'ORTHO' cam_data.ortho_scale = 4.0 # adjust to frame atom nicely cam_obj = bpy.data.objects.new("AtomCam", cam_data) bpy.context.collection.objects.link(cam_obj) cam_obj.location = (5.0, -5.0, 3.5) direction = mathutils.Vector((0, 0, 0)) - cam_obj.location cam_obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler() bpy.context.scene.camera = cam_obj bpy.ops.wm.save_as_mainfile(filepath=OUTPUT_BLEND) ``` ### Step 3: Render each atom INDEPENDENTLY **Critical lesson learned:** Rendering in a loop within a single Blender session causes material contamination — `_ball` objects persist across iterations despite deletion attempts. **Always launch a separate Blender process per atom.** #### Default CPK colors ```python # render_single.py — run with: blender clean.blend --background --python render_single.py -- input.xyz output.png import bpy, sys argv = sys.argv[sys.argv.index("--") + 1:] xyz_path, output_path = argv[0], argv[1] bpy.ops.preferences.addon_enable(module="io_mesh_atomic") bpy.ops.import_mesh.xyz( filepath=xyz_path, ball="1", # '0'=NURBS, '1'=MESH, '2'=META mesh_azimuth=128, # high = smoother sphere mesh_zenith=128, scale_ballradius=1.0, scale_distances=1.0, ) bpy.context.scene.render.filepath = output_path bpy.ops.render.render(write_still=True) ``` #### Custom RGB colors ```python # render_single_color.py — run with: blender clean.blend --background --python render_single_color.py -- input.xyz output.png R G B import bpy, sys argv = sys.argv[sys.argv.index("--") + 1:] xyz_path, output_path = argv[0], argv[1] r, g, b = float(argv[2]), float(argv[3]), float(argv[4]) bpy.ops.preferences.addon_enable(module="io_mesh_atomic") bpy.ops.import_mesh.xyz( filepath=xyz_path, ball="1", mesh_azimuth=128, mesh_zenith=128, scale_ballradius=1.0, scale_distances=1.0, ) # Override material color on the ball object for obj in bpy.context.scene.objects: if "_ball" in obj.name: if obj.data.materials: mat = obj.data.materials[0] mat.diffuse_color = (r, g, b, 1.0) if mat.use_nodes: for node in mat.node_tree.nodes: if node.type == 'BSDF_PRINCIPLED': node.inputs['Base Color'].default_value = (r, g, b, 1.0) break bpy.context.scene.render.filepath = output_path bpy.ops.render.render(write_still=True) ``` Shell loop (custom colors via function): ```bash BLENDER=/Applications/Blender.app/Contents/MacOS/Blender render_atom() { local elem=$1 r=$2 g=$3 b=$4 "$BLENDER" "$CLEAN_BLEND" --background --python render_single_color.py -- \ "${elem}.xyz" "renders/${elem}.png" "$r" "$g" "$b" } render_atom Ba 0 0.78 0 render_atom La 0.502 0.922 0.973 # ... etc. RGB values are floats 0.0-1.0 ``` Shell loop (default CPK colors): ```bash for xyz in input_dir/*.xyz; do elem=$(basename "$xyz" .xyz) "$BLENDER" "$CLEAN_BLEND" --background --python render_single.py -- "$xyz" "renders/${elem}.png" done ``` ### Step 4: Create per-system legend images Use Pillow to compose horizontal legends with rendered 3D sphere PNGs as icons: `[rendered sphere] Name [rendered sphere] Name ...` ```python from PIL import Image, ImageDraw, ImageFont
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기