Profitons de ce début d'année pour re-explorer les frontières entre le monde
physique et le numérique. Aujourd'hui, nous ressortons le contrôleur
UltraLeap avec Dynamo
Contexte
A l'occasion d'un
Challenge Dynamo sur le forum, voici une carte de vœux 2026 un peu spéciale.
Pour faire
suite à
un article précédent, voici une version modifiée dans laquelle on crée un solide de type «
Texte ».
- Génération du Texte 3D
Pour transformer un texte en solide exploitable, nous contournons les
limitations standard en utilisant la classe GraphicsPath du namespace
System.Drawing.Drawing2D.
L'idée est de récupérer les contours vectoriels de la police (ici Arial),
de les convertir en points DS.Point, puis de reconstruire des PolyCurves
pour enfin générer un solide ( Thicken).
import clr
import sys
import System
clr.AddReference("System.Numerics")
#
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS
clr.AddReference('System.Drawing')
import System.Drawing
from System.Drawing import *
from System.Drawing.Drawing2D import *
text_value = "2026"
height_mm = 10
view_scale = 50
draw_contours = True
#
height = (height_mm * view_scale) * 0.00328083989501312 # convert for text 3d
#
filregionTypeName = "Uni2 - Noir"
class CManager:
"""
a custom context manager for Disposable Object
"""
def __init__(self, obj):
self.obj = obj
def __enter__(self):
return self.obj
def __exit__(self, exc_type, exc_value, exc_tb):
self.obj.Dispose()
if exc_type:
error = f"{exc_value} at line {exc_tb.tb_lineno}"
raise ValueError( error)
return self
points = []
types = []
with CManager(System.Drawing.Font("Arial", height, FontStyle.Regular)) as font:
with CManager(GraphicsPath()) as gp:
with CManager(StringFormat()) as sf:
sf.Alignment = StringAlignment.Center
sf.LineAlignment = StringAlignment.Center
gp.AddString(text_value, font.FontFamily, System.Convert.ToInt32(font.Style), font.Size, PointF(0, 0), sf)
# convert to loop array
points = list(gp.PathPoints)
types = list(gp.PathTypes)
# get all indice at 0 value
indices = [i for i, value in enumerate(types) if value == 0]
# split points at indices
array_pt = [points[i:j] for i, j in zip([0]+indices, indices+[None])]
# convert to XYZ
array_pt_rvt = [[DS.Point.ByCoordinates(p.X, -p.Y, 0) for p in subpoints] for subpoints in array_pt]
# purge point and create PolyCurve then Solid by letter
lst_curvloop = []
lst_solid = []
for idx, lstpt in enumerate(array_pt_rvt):
if len(lstpt) > 0:
ptToPull = [lstpt.pop(0)]
curves = []
for pt in lstpt:
if all(pt.DistanceTo(p) > 0.01 for p in ptToPull):
ptToPull.append(pt)
loop = DS.PolyCurve.ByPoints(ptToPull, True)
lst_curvloop.append(loop)
solid = loop.Patch().Thicken(0.7)
if idx in [3, 6]:
lst_solid[-1] = lst_solid[-1].Difference(solid)
else:
lst_solid.append(solid)
geo = DS.Solid.ByUnion(lst_solid)
# align solid to same coordinate system of UltraLeap
geo = geo.Rotate(DS.Point.ByCoordinates(0, 0, 0), DS.Vector.XAxis(), 90)
geo = geo.Rotate(DS.Point.ByCoordinates(0, 0, 0), DS.Vector.YAxis(), 180)
geo = geo.Rotate(DS.Point.ByCoordinates(0, 0, 0), DS.Vector.ZAxis(), 180)
OUT = geo
- L'Interaction : La "Magie" UltraLeap
- Logique de détection
- Pincement simple : Détection de la position dans l'espace.
-
Double pincement (deux mains) : En utilisant la distance entre les
deux mains, on définit une
BoundingBoxdynamique qui ajuste l'échelle (Scale) du texte "2026".
# Load the Python Standard and DesignScript Libraries
import sys
import clr
import System
clr.AddReference('ProtoGeometry')
import Autodesk.DesignScript.Geometry as DS
import sysconfig
# standard library path
sys.path.append(sysconfig.get_path("platstdlib"))
# site-package library path
sys.path.append(sysconfig.get_path("platlib"))
import leap
from leap import datatypes as ldt
import time
from io import StringIO
sys.stdout = StringIO()
class MyListener(leap.Listener):
def __init__(self):
self.hands_format = "Skeleton"
self.lst_points = []
self.lst_cuboid = []
self.radius = 2
def on_connection_event(self, event):
pass
#print("Connected")
def on_device_event(self, event):
try:
with event.device.open():
info = event.device.get_info()
except leap.LeapCannotOpenDeviceError:
info = event.device.get_info()
#print(f"Found device {info.serial}")
def location_end_of_finger(self, hand: ldt.Hand, digit_idx: int) -> ldt.Vector:
digit = hand.digits[digit_idx]
return digit.distal.next_joint
def sub_vectors(self, v1: ldt.Vector, v2: ldt.Vector) -> list:
return map(float.__sub__, v1, v2)
def fingers_pinching(self, thumb: ldt.Vector, index: ldt.Vector):
diff = list(map(abs, self.sub_vectors(thumb, index)))
if diff[0] < 20 and diff[1] < 20 and diff[2] < 20:
return True, diff
else:
return False, diff
def get_joint_position(self, bone):
if bone:
#return int(bone.x + (self.screen_size[1] / 2)), int(bone.z + (self.screen_size[0] / 2))
return bone.x , bone.y , bone.z
else:
return None
def on_tracking_event(self, event):
global solid_2026
global bbx_2026
geometries = []
pinch_pts = []
pinch_hand_type = set()
#print(f"Frame {event.tracking_frame_id} with {len(event.hands)} hands.")
for i in range(0, len(event.hands)):
hand = event.hands[i]
hand_type = "left" if str(hand.type) == "HandType.Left" else "right"
for index_digit in range(0, 5):
digit = hand.digits[index_digit]
for index_bone in range(0, 4):
bone = digit.bones[index_bone]
if self.hands_format == "Skeleton":
#print("Skeleton")
wrist = self.get_joint_position(hand.arm.next_joint)
elbow = self.get_joint_position(hand.arm.prev_joint)
if wrist:
#print("wrist")
pta = DS.Point.ByCoordinates(*wrist)
geometries.append(DS.Sphere.ByCenterPointRadius(pta, self.radius ))
if elbow:
#print("elbow")
ptb = DS.Point.ByCoordinates(*elbow)
geometries.append(DS.Sphere.ByCenterPointRadius(ptb, self.radius ))
if wrist and elbow:
#print("wrist and elbow")
try:
geometries.append(DS.Line.ByStartPointEndPoint(pta, ptb))
except Exception as ex:
pass
bone_start = self.get_joint_position(bone.prev_joint)
bone_end = self.get_joint_position(bone.next_joint)
#print(f"{bone_start=}")
#print(f"{bone_end=}")
ptc = DS.Point.ByCoordinates(*bone_start)
geometries.append(DS.Sphere.ByCenterPointRadius(ptc, self.radius ))
ptd = DS.Point.ByCoordinates(*bone_end)
geometries.append(DS.Sphere.ByCenterPointRadius(ptd, self.radius ))
try:
geometries.append(DS.Line.ByStartPointEndPoint(ptc, ptd))
except Exception as ex:
pass
#
# test if pinching
thumb = self.location_end_of_finger(hand, 0)
index = self.location_end_of_finger(hand, 1)
pinching, array = self.fingers_pinching(thumb, index)
#print(f"{pinching=}", array)
if pinching:
pinch_pts.append(DS.Point.ByCoordinates(*array))
pinch_hand_type.add(hand_type)
#
print(len(geometries))
if len(pinch_hand_type) == 2 and len(geometries) >= 141:
bbx = DS.BoundingBox.ByGeometry([geometries[21], geometries[141]])
#
try:
center = (
(bbx.MinPoint.X + bbx.MaxPoint.X) / 2,
(bbx.MinPoint.Y + bbx.MaxPoint.Y) / 2,
(bbx.MinPoint.Z + bbx.MaxPoint.Z) / 2
)
#
factorX = (bbx.MaxPoint.X - bbx.MinPoint.X) / (bbx_2026.MaxPoint.X - bbx_2026.MinPoint.X)
factorY = (bbx.MaxPoint.Y - bbx.MinPoint.Y) / (bbx_2026.MaxPoint.Y - bbx_2026.MinPoint.Y)
factorZ = (bbx.MaxPoint.Z - bbx.MinPoint.Z) / (bbx_2026.MaxPoint.Z - bbx_2026.MinPoint.Z)
#
new_solid_2026 = solid_2026.Scale(factorX, factorY, factorZ)
new_solid_2026 = new_solid_2026.Translate(*center)
#
self.lst_cuboid.append(new_solid_2026)
except Exception as ex:
print(ex)
#
if len(geometries) > 0:
self.lst_points.append(geometries)
def main():
global connection
my_listener = MyListener()
#connection = leap.Connection()
connection.add_listener(my_listener)
with connection.open():
connection.set_tracking_mode(leap.TrackingMode.Desktop)
time.sleep(0.1)
connection.remove_listener(my_listener)
if len(my_listener.lst_points) > 0 and len(my_listener.lst_cuboid) > 0:
return my_listener.lst_points[-1], my_listener.lst_cuboid[-1]
elif len(my_listener.lst_points) > 0 and len(my_listener.lst_cuboid) == 0:
return my_listener.lst_points[-1], None
else:
return [], None
connection = IN[0]
solid_2026 = IN[1]
bbx_2026 = solid_2026.BoundingBox
diagonal_original_2026 = bbx_2026.MaxPoint.DistanceTo(bbx_2026.MinPoint)
geoms, new_cuboid = main()
datam = []
dataKey = "LstGeoms"
if len(geoms) == 0:
System.AppDomain.CurrentDomain.SetData("_Dyn_Wireless_{}".format(dataKey), [])
print(new_cuboid)
if isinstance(new_cuboid, DS.Solid):
# set Data to memory
data = new_cuboid
# get Data in memory
datam = System.AppDomain.CurrentDomain.GetData("_Dyn_Wireless_{}".format(dataKey))
if datam is None and isinstance(data, DS.Cuboid):
System.AppDomain.CurrentDomain.SetData("_Dyn_Wireless_{}".format(dataKey), [data])
elif isinstance(data, DS.Solid):
try:
datam.append(data)
# clean solids
temp = []
for solid in datam[::-1]:
if all(not solid.DoesIntersect(s) for s in temp):
temp.append(solid)
System.AppDomain.CurrentDomain.SetData("_Dyn_Wireless_{}".format(dataKey), temp)
except Exception as ex:
print(ex)
else:
pass
#
datam = System.AppDomain.CurrentDomain.GetData("_Dyn_Wireless_{}".format(dataKey))
sys.stdout.seek(0)
printResult = sys.stdout.read()
OUT = geoms, printResult, datam
# reset the sys.stdout
sys.stdout = sys.__stdout__
sys.stderr = sys.__stderr__
Meilleurs vœux et une excellente année 2026 à tous !





0 commentaires:
Enregistrer un commentaire