AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Blender Space

skill-max-786-claude-3d-harness-blender-space · by MAX-786

>

— No reviews yet
0 installs
0 views
— view→install

Install

$ agentstack add skill-max-786-claude-3d-harness-blender-space

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ✓ Network access No
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ✓ Environment & secrets No
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-max-786-claude-3d-harness-blender-space)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 4d ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Blender Space? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Skill: Blender 3D Space & Geometry

Sei un esperto di geometria 3D in Blender. Questa skill fornisce strumenti precisi per orientarsi nello spazio 3D, posizionare oggetti correttamente, e debuggare problemi di coordinate.


IL PRINCIPIO PIÙ IMPORTANTE

> obj.location ≠ posizione dei vertici nel mondo. > > Blender ha due sistemi separati: > - Object origin: obj.location — il "perno" dell'oggetto nel mondo > - Mesh vertices: coordinate LOCAL rispetto all'origin > > Posizione mondo di un vertice = obj.matrix_world @ vertex.co > > Se l'origin non è al centro della geometria (succede con bmesh manuale, > transform_apply, o meshes importate), obj.location è fuorviante.

Il bug classico (esempio reale):

# Oggetto creato con bmesh, vertici a (0, 0, z=0.285) in local space
# obj.location = (0, 0, 0)  ← origin all'origine
# ERRORE: pensare che obj.location.z=0.1 metta l'oggetto a z=0.1
#         in realtà l'oggetto è a z = 0 + 0.285 = 0.285 nel mondo!

# CORRETTO: misura prima, poi calcola
offset = get_world_center(obj).z - obj.location.z
# Poi: obj.location.z = target_world_z - offset

FUNZIONI DI DEBUG SPAZIALE

# ── MISURA POSIZIONE REALE (WORLD SPACE) ────────────────────────────────────
import bpy

def world_bounds(obj):
    '''Bounding box reale in world space. USA SEMPRE QUESTA.'''
    verts_world = [obj.matrix_world @ v.co for v in obj.data.vertices]
    if not verts_world:
        # Fallback per oggetti senza vertices (curve, empty, light)
        loc = obj.matrix_world.translation
        return {'center': list(loc), 'min': list(loc), 'max': list(loc), 'size': [0,0,0]}
    xs = [v.x for v in verts_world]
    ys = [v.y for v in verts_world]
    zs = [v.z for v in verts_world]
    return {
        'min':    [min(xs), min(ys), min(zs)],
        'max':    [max(xs), max(ys), max(zs)],
        'center': [(min(xs)+max(xs))/2, (min(ys)+max(ys))/2, (min(zs)+max(zs))/2],
        'size':   [max(xs)-min(xs), max(ys)-min(ys), max(zs)-min(zs)],
        'origin_offset': [           # quanto l'origin è spostato dal centro geometria
            obj.location.x - (min(xs)+max(xs))/2,
            obj.location.y - (min(ys)+max(ys))/2,
            obj.location.z - (min(zs)+max(zs))/2,
        ]
    }

# Esempio: misura tutti gli oggetti mesh nella scena
info = {}
for obj in bpy.data.objects:
    if obj.type == 'MESH':
        b = world_bounds(obj)
        info[obj.name] = {
            'origin': [round(obj.location.x,3), round(obj.location.y,3), round(obj.location.z,3)],
            'world_center': [round(v,3) for v in b['center']],
            'world_zmin': round(b['min'][2],3),
            'world_zmax': round(b['max'][2],3),
            'size': [round(v,3) for v in b['size']],
            'origin_offset_z': round(b['origin_offset'][2],3)  # chiave! se ≠0 c'è mismatch
        }
result = info

SISTEMI DI COORDINATE

Gerarchia dei sistemi in Blender

WORLD SPACE          ← sistema assoluto, quello che vediamo nel render
    │
    └── LOCAL SPACE   ← relativo all'object origin (obj.location + rotation)
            │
            └── UV SPACE     ← 2D per textures (u,v in [0,1])
            └── CAMERA SPACE ← relativo alla camera (z = depth)
            └── SCREEN SPACE ← pixel sullo schermo

Conversioni fondamentali

import bpy
from mathutils import Vector, Matrix

# LOCAL → WORLD
def local_to_world(obj, local_point):
    return obj.matrix_world @ Vector(local_point)

# WORLD → LOCAL
def world_to_local(obj, world_point):
    return obj.matrix_world.inverted() @ Vector(world_point)

# WORLD → CAMERA SPACE
def world_to_camera(scene, camera, world_point):
    from bpy_extras.object_utils import world_to_camera_space
    return world_to_camera_space(scene, camera, Vector(world_point))
    # restituisce (x, y, z) dove x,y ∈ [0,1] = posizione sullo schermo
    # z = profondità (distanza dalla camera)

# Esempi
obj    = bpy.data.objects["Apple"]
cam    = bpy.data.objects["Camera"]
scene  = bpy.context.scene

# Dove sono i vertici del oggetto nel mondo?
world_verts = [obj.matrix_world @ v.co for v in obj.data.vertices]

# Dove appare un punto del mondo sullo schermo?
screen_pos = world_to_camera(scene, cam, (0, 0, 0.2))
print(f"Il punto (0,0,0.2) appare a {screen_pos.x:.2f}, {screen_pos.y:.2f} dello schermo")

POSIZIONAMENTO PRECISO

Pattern corretto per posizionare un oggetto

def place_object_world(obj, target_world_pos, anchor='center'):
    """
    Posiziona obj in modo che la sua geometria sia centrata (o allineata)
    a target_world_pos nel mondo, indipendentemente dall'origin offset.

    anchor:
      'center' → centro geometria a target_world_pos
      'bottom' → base geometria a target_world_pos (z del vertice più basso)
      'top'    → cima geometria a target_world_pos
    """
    # Calcola bounds attuali in world space
    verts_world = [obj.matrix_world @ v.co for v in obj.data.vertices]
    zs = [v.z for v in verts_world]
    xs = [v.x for v in verts_world]
    ys = [v.y for v in verts_world]

    # Centro geometrico in world space
    geo_center = Vector([(min(xs)+max(xs))/2,
                         (min(ys)+max(ys))/2,
                         (min(zs)+max(zs))/2])

    # Offset tra origin e geo center (in world space)
    origin_to_geo = geo_center - obj.matrix_world.translation

    target = Vector(target_world_pos)

    if anchor == 'center':
        obj.location = target - origin_to_geo
    elif anchor == 'bottom':
        bottom_offset = geo_center.z - min(zs)   # distanza centro→base
        obj.location.z = target.z + bottom_offset - origin_to_geo.z
        obj.location.x = target.x - origin_to_geo.x
        obj.location.y = target.y - origin_to_geo.y
    elif anchor == 'top':
        top_offset = max(zs) - geo_center.z      # distanza centro→cima
        obj.location.z = target.z - top_offset - origin_to_geo.z
        obj.location.x = target.x - origin_to_geo.x
        obj.location.y = target.y - origin_to_geo.y

# Esempio: metti la mela con la base a z=0.05
place_object_world(apple, (−0.08, 0.05, 0.05), anchor='bottom')

Stack verticale di oggetti

def stack_on_top(base_obj, new_obj, gap=0.002):
    """Posiziona new_obj sopra base_obj con gap di separazione."""
    # Top del base in world space
    base_verts = [base_obj.matrix_world @ v.co for v in base_obj.data.vertices]
    base_top   = max(v.z for v in base_verts)

    # Bottom del nuovo oggetto
    new_verts  = [new_obj.matrix_world @ v.co for v in new_obj.data.vertices]
    new_bot    = min(v.z for v in new_verts)

    # Sposta in modo che il bottom sia appena sopra il base_top
    delta_z = (base_top + gap) - new_bot
    new_obj.location.z += delta_z

# Esempio: impila pera sopra mela
stack_on_top(apple, pear, gap=0.005)

Allineamento orizzontale

def align_objects(objects, axis='x', mode='center'):
    """
    Allinea oggetti lungo un asse.
    axis: 'x', 'y', 'z'
    mode: 'center' (centra i centri), 'min' (allinea basi), 'max' (allinea cime)
    """
    idx = {'x':0, 'y':1, 'z':2}[axis]
    targets = []
    for obj in objects:
        verts = [obj.matrix_world @ v.co for v in obj.data.vertices]
        vals  = [getattr(v, axis) for v in verts]
        if mode == 'center': targets.append((min(vals)+max(vals))/2)
        elif mode == 'min':  targets.append(min(vals))
        elif mode == 'max':  targets.append(max(vals))

    avg = sum(targets) / len(targets)

    for obj, t in zip(objects, targets):
        diff = avg - t
        if axis == 'x': obj.location.x += diff
        elif axis == 'y': obj.location.y += diff
        elif axis == 'z': obj.location.z += diff

ROTAZIONI

Euler vs Quaternion

import math
from mathutils import Euler, Quaternion, Matrix

# ── EULER ANGLES (rotation_euler) ─────────────────────────────
# Blender usa XYZ Euler di default
# rotation_euler = (rx, ry, rz) in RADIANTI

obj.rotation_euler = (math.radians(45), 0, math.radians(30))
#                      ↑ tilt avanti     ↑ no roll    ↑ girata

# ATTENZIONE: ordine degli assi conta!
# 'XYZ' = prima ruota X, poi Y nel nuovo sistema, poi Z
obj.rotation_mode = 'XYZ'   # default
obj.rotation_mode = 'ZYX'   # per alcuni casi (aeronautica)
obj.rotation_mode = 'QUATERNION'  # senza gimbal lock

# ── QUATERNION ─────────────────────────────────────────────────
# Per rotazioni complesse senza gimbal lock
obj.rotation_mode = 'QUATERNION'
obj.rotation_quaternion = Quaternion((0, 0, 1), math.radians(45))
#                                     ↑ asse Z   ↑ angolo

# ── MATRIX ROTATION ────────────────────────────────────────────
# Ruota intorno a un asse arbitrario
axis   = Vector((1, 0.5, 0)).normalized()   # asse diagonale
angle  = math.radians(30)
rot    = Matrix.Rotation(angle, 4, axis)    # matrice 4x4
obj.matrix_world = rot @ obj.matrix_world   # applica

# ── LOOK-AT (punta verso un punto) ─────────────────────────────
def point_at(obj, target_world, track_axis='-Z', up_axis='Y'):
    """Orienta obj verso target nel mondo."""
    direction = Vector(target_world) - obj.location
    rot_quat  = direction.to_track_quat(track_axis, up_axis)
    obj.rotation_euler = rot_quat.to_euler()

# Esempio: punta la camera verso il cestino
point_at(camera, (0, 0, 0.2), track_axis='-Z', up_axis='Y')

Angoli tra oggetti

def angle_between_objects(obj_a, obj_b):
    """Angolo (in gradi) che obj_a deve ruotare per puntare a obj_b."""
    direction = (obj_b.location - obj_a.location).normalized()
    forward   = Vector((0, -1, 0))  # forward di default in Blender
    return math.degrees(forward.angle(direction))

def azimuth_elevation(observer, target):
    """
    Restituisce (azimuth, elevation) in gradi.
    Azimuth = angolo orizzontale (0=Nord/+Y, 90=Est/+X)
    Elevation = angolo verticale (0=orizzontale, 90=zenith)
    """
    d = Vector(target) - Vector(observer)
    horiz = Vector((d.x, d.y, 0)).length
    elevation = math.degrees(math.atan2(d.z, horiz))
    azimuth   = math.degrees(math.atan2(d.x, d.y))
    return azimuth, elevation

BOUNDING BOX E DISTANZE

# ── DISTANZA TRA OGGETTI ────────────────────────────────────────
def distance_between(obj_a, obj_b):
    """Distanza tra i centri geometrici nel mondo."""
    ca = sum([obj_a.matrix_world @ v.co for v in obj_a.data.vertices],
             Vector()) / len(obj_a.data.vertices)
    cb = sum([obj_b.matrix_world @ v.co for v in obj_b.data.vertices],
             Vector()) / len(obj_b.data.vertices)
    return (ca - cb).length

def surface_distance(obj_a, obj_b):
    """Distanza approssimata tra superfici (non centri) lungo Z."""
    va = [obj_a.matrix_world @ v.co for v in obj_a.data.vertices]
    vb = [obj_b.matrix_world @ v.co for v in obj_b.data.vertices]
    top_a  = max(v.z for v in va)
    bot_b  = min(v.z for v in vb)
    return bot_b - top_a   # positivo = gap, negativo = overlap

# ── OVERLAP CHECK ───────────────────────────────────────────────
def objects_overlap_z(obj_a, obj_b):
    """Controlla se i bounding box si sovrappongono in Z."""
    va = [obj_a.matrix_world @ v.co for v in obj_a.data.vertices]
    vb = [obj_b.matrix_world @ v.co for v in obj_b.data.vertices]
    a_min, a_max = min(v.z for v in va), max(v.z for v in va)
    b_min, b_max = min(v.z for v in vb), max(v.z for v in vb)
    return a_min  0]
    if not visible: return 0, 0
    xs = [p.x for p in visible]; ys = [p.y for p in visible]
    return max(xs)-min(xs), max(ys)-min(ys)

# ── AUTO-FRAME: posiziona camera per inquadrare tutti gli oggetti ──
def auto_frame_camera(cam, objects, margin=1.3):
    """
    Posiziona la camera per inquadrare tutti gli oggetti con margin.
    margin > 1 = più spazio intorno.
    """
    # Bounding box di tutti gli oggetti
    all_verts = []
    for obj in objects:
        all_verts.extend([obj.matrix_world @ v.co for v in obj.data.vertices])
    if not all_verts: return

    center = Vector([
        (max(v.x for v in all_verts) + min(v.x for v in all_verts)) / 2,
        (max(v.y for v in all_verts) + min(v.y for v in all_verts)) / 2,
        (max(v.z for v in all_verts) + min(v.z for v in all_verts)) / 2,
    ])
    radius = max((Vector(v) - center).length for v in all_verts) * margin

    # Distanza necessaria perché radius sia nel frame
    fov_rad = math.radians(camera_fov(cam, 'v') / 2)
    dist    = radius / math.tan(fov_rad) if fov_rad > 0 else radius * 5

    # Mantieni direzione corrente, scala la distanza
    direction = (cam.location - center).normalized()
    cam.location = center + direction * dist
    cam.data.dof.focus_distance = dist

PARENT-CHILD E MATRIX

# ── PARENT SENZA SPOSTARE L'OGGETTO ─────────────────────────────
def parent_keep_transform(child, parent):
    """
    Aggiunge child come figlio di parent SENZA spostarlo nel mondo.
    Fondamentale: child mantiene la sua posizione visiva.
    """
    child.parent = parent
    child.matrix_parent_inverse = parent.matrix_world.inverted()
    # matrix_parent_inverse compensa la trasformazione del parent

# ── WORLD MATRIX: capire tutto in un colpo ──────────────────────
def explain_matrix(obj):
    """Spiega la matrix_world di un oggetto."""
    m = obj.matrix_world
    loc, rot, scale = m.decompose()
    return {
        'world_position': list(loc),
        'world_rotation_euler_deg': [math.degrees(a) for a in rot.to_euler()],
        'world_scale': list(scale),
    }

# ── MATRIX COMPOSIZIONE ──────────────────────────────────────────
# matrix_world = matrix_parent_world @ matrix_local
# matrix_local = matrix_basis @ matrix_parent_inverse
#
# Per spostare un figlio nel mondo senza toccarne il local:
child.matrix_world = Matrix.Translation((new_x, new_y, new_z)) @ \
                     Matrix.Rotation(angle, 4, 'Z') @ \
                     child.matrix_world

PATTERN DI LAVORO CONSIGLIATO

Workflow sicuro per posizionamento

# STEP 1: Misura SEMPRE prima di spostare
def safe_place(obj, target_x, target_y, target_z, anchor='center'):
    """Posiziona obj con correzione automatica dell'origin offset."""
    verts = [obj.matrix_world @ v.co for v in obj.data.vertices]
    if not verts: return

    xs = [v.x for v in verts]; ys = [v.y for v in verts]; zs = [v.z for v in verts]
    cx = (min(xs)+max(xs))/2;  cy = (min(ys)+max(ys))/2;  cz = (min(zs)+max(zs))/2

    # Offset: origin rispetto al centro geometrico
    ox = obj.location.x - cx
    oy = obj.location.y - cy
    oz = obj.location.z - cz

    if anchor == 'center':
        obj.location = (target_x + ox, target_y + oy, target_z + oz)
    elif anchor == 'bottom':
        bottom_from_center = cz - min(zs)
        obj.location = (target_x + ox, target_y + oy, target_z + oz + bottom_from_center)
    elif anchor == 'top':
        top_from_center = max(zs) - cz
        obj.location = (target_x + ox, target_y + oy, target_z + oz - top_from_center)

# Uso:
# apple = bpy.data.objects["Apple"]
# safe_place(apple, -0.08, 0.05, 0.05, anchor='bottom')
# → la BASE della mela è ora a z=0.05 nel mondo, indipendentemente dall'origin

Normalizza una scena problematica

# Per risolvere problemi di origin in blocco:
def normalize_origins(obj_names):
    """Sposta gli origins al centro di tutti gli oggetti in lista."""
    for name in obj_names:
        obj = bpy.data.objects.get(name)
        if obj:
            bpy.context.view_layer.objects.active = obj
            bpy.ops.object.origin_set(type='ORIGIN_GEOMETRY', center='BOUNDS')
            # Da questo punto: obj.location = centro mondo dell'oggetto ✓

# Chiama all'inizio di ogni script che modificherà le posizioni
normalize_origins(["Apple", "Pear", "Banana", "Pineapple"])

NORMALI IN WORLD SPACE

Le normali non si trasformano con matrix_world — serve la normal matrix (transposta dell'inversa).

from mathutils import Vector

# ── P

…

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [MAX-786](https://github.com/MAX-786)
- **Source:** [MAX-786/claude-3d-harness](https://github.com/MAX-786/claude-3d-harness)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.