Install
$ agentstack add skill-max-786-claude-3d-harness-blender-space ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →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.
Write a review
Versions
- v0.1.0 Imported from the upstream source.