Pour en bénéficier dans Dymamo, il suffit d'utiliser les nœuds Python CPython3 ou encore mieux, PythonNet3 (Dynamo 3+)
Module | Description | Exemples d'utilisation |
---|---|---|
scipy.linalg
|
Algèbre linéaire | Résolution de systèmes d'équations linéaires, calcul de valeurs et vecteurs propres, décompositions de matrices (SVD, LU, etc.). |
scipy.optimize
|
Optimisation | Recherche de minima et de maxima de fonctions, ajustement de courbes (curve fitting), résolution de problèmes de programmation linéaire. |
scipy.stats
|
Statistiques | Fonctions de densité de probabilité, tests statistiques (test t, ANOVA), statistiques descriptives, génération de nombres aléatoires. |
scipy.integrate
|
Intégration numérique | Calcul d'intégrales définies, résolution d'équations différentielles ordinaires (EDO). |
scipy.interpolate
|
Interpolation | Estimation de nouvelles valeurs à partir de points de données existants. |
scipy.fft
|
Transformée de Fourier | Analyse de signaux dans le domaine fréquentiel, filtrage. |
scipy.signal
|
Traitement du signal | Filtrage de signaux, analyse spectrale, conception de filtres. |
scipy.sparse
|
Matrices creuses | Stockage et manipulation efficaces de matrices contenant une grande proportion de zéros. |
scipy.spatial
|
Algorithmes spatiaux | Calcul de distances, triangulation de Delaunay, enveloppes convexes. |
scipy.ndimage
|
Traitement d'images | Filtrage, morphologie, segmentation d'images n-dimensionnelles. |
-
Courbe de Bézier
code
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import numpy as np
from scipy.special import comb
def bernstein(n, k, t):
"""compute Bernstein polynomial at t using scipy comb."""
return comb(n, k) * (t ** k) * ((1 - t) ** (n - k))
def bezier_curve(points, num_points=100):
"""compute a Bézier curve from control points using Bernstein polynomials."""
n = len(points) - 1 # Degree of the Bézier curve
t_values = np.linspace(0, 1, num_points)
curve = np.zeros((num_points, 2))
for i in range(n + 1):
curve += np.outer(bernstein(n, i, t_values), points[i])
return curve
ds_points = IN[0]
control_points = [[p.X, p.Y] for p in ds_points]
curve = bezier_curve(control_points)
out_points =[Point.ByCoordinates(x, y) for x, y in curve]
OUT = NurbsCurve.ByPoints(out_points)
- k-dimensional tree (KDTree)
L'objectif ici est de trouver (sur un plan 2D) le point le plus près d'une ligne grâce à l'algorithme k-dimensional tree.
Note :
scipy.spatial.KDTree
ne fonctionne qu'en 2D
code
import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import sysconfig
# standard library path
sys.path.append(sysconfig.get_path("platstdlib"))
# site-package library path
sys.path.append(sysconfig.get_path("platlib"))
import numpy as np
from scipy import spatial
def get_points_onCurve(curve, step = 0.2):
out = []
i = step
while i <= curve.Length :
try:
po = curve.PointAtSegmentLength(i)
i += step
out.append([po.X, po.Y, po.Z])
except:
break
return out
def get_groupIdx_by_flatIdx(idx):
curr = 0
for i, grp in enumerate(lstgroup_pts):
for j , pt in enumerate(grp):
if curr == idx:
return i
else:
curr += 1
lstgroup_pts = IN[0]
lstLines = IN[1]
a_arr = np.array([[p.X, p.Y, p.Z] for group in lstgroup_pts for p in group])
ktree = spatial.KDTree(a_arr)
out = []
for l in lstLines:
pts_arr = get_points_onCurve(l)
distances, lstindex = ktree.query(pts_arr, k=1)
counts = np.bincount(lstindex)
idx = np.argmax(counts)
# search the group index
gpIdx = get_groupIdx_by_flatIdx(idx)
out.append(gpIdx)
OUT = out
-
Trouver la courbe médiane entre 2 courbes
- identifier des clusters de points
L'objectif ici est de regrouper des points par proximité (clusters).
Nous créons un modèle de "clustering" en utilisant la méthode
cKDTree.query_ball_point
(méthode similaire à DBSCAN de Scikit-learn).
code
import sys
import clr
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
clr.AddReference('GeometryColor')
from Modifiers import GeometryColor
clr.AddReference('DSCoreNodes')
import DSCore
from DSCore import Color as DSColor
import numpy as np
import random
from scipy.spatial import cKDTree
random.seed(40)
inputs_points = IN[0]
array_pt = np.array([[p.X, p.Y] for p in inputs_points])
groups = []
def dbscan(x_pts, eps, min_samples):
"""
dbscan “from scratch” with scipy and numpy
returns
-------
labels : ndarray[int]
each integer represent a label
-2 = unvisited, -1 = noise, >=0 = cluster id
"""
x_pts = np.asarray(x_pts)
n = x_pts.shape[0]
# precompute neighbors for each point
tree = cKDTree(x_pts)
neighbors = tree.query_ball_point(x_pts, eps)
# initialization
labels = np.full(n, -2, dtype=int) # -2 = unvisited
cluster_id = 0
# scan through all points
for i in range(n):
if labels[i] != -2:
continue # already visited
if len(neighbors[i]) < min_samples:
labels[i] = -1 # mark as noise
continue
# i is a core point → start a new cluster
labels[i] = cluster_id
stack = neighbors[i].copy() # points to expand in this cluster
# expand cluster
while stack:
j = stack.pop()
if labels[j] == -1:
labels[j] = cluster_id # reclassify noise to border
if labels[j] != -2:
continue # already processed
labels[j] = cluster_id # assign to cluster
if len(neighbors[j]) >= min_samples:
# j is a core point → add its neighbors
stack.extend(neighbors[j])
cluster_id += 1
return labels
###### MAIN #####
eps = 3
min_samples = 15
labels = dbscan(array_pt, eps, min_samples)
#print(labels)
cluster_indices = {c: np.where(labels==c)[0].tolist()
for c in np.unique(labels) if c>=0}
noise_indices = np.where(labels==-1)[0].tolist()
# color results
for lab, idxs in cluster_indices.items():
subpts = np.take(inputs_points, idxs, axis=0)
col = DSColor.ByARGB(
255,
random.randint(0, 255),
random.randint(0, 255),
random.randint(0, 255)
)
for p in subpts:
groups.append( GeometryColor.ByGeometryColor(p, col) )
# (optionnel) on affiche aussi les points bruit en gris
grey = DSColor.ByARGB(128, 180, 180, 180)
for i in noise_indices:
groups.append( GeometryColor.ByGeometryColor(inputs_points[i], grey) )
OUT = groups
.....
- alpha-shape
Ici, nous utilisons la classe
Delaunay
pour générer une alpha-shape (une enveloppe polygonale 2D d'un ensemble
de points).
Voir mon
article précédent
import clr
import sys
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import numpy as np
from scipy.spatial import Delaunay
# --- helper to compute all triangle circumradii ---
def circumradii(pts, simplices):
pa, pb, pc = [pts[s] for s in simplices.T]
a = np.linalg.norm(pb - pc, axis=1)
b = np.linalg.norm(pa - pc, axis=1)
c = np.linalg.norm(pa - pb, axis=1)
# vectorized triangle area
area = 0.5 * np.abs(
(pb[:,0] - pa[:,0]) * (pc[:,1] - pa[:,1]) -
(pb[:,1] - pa[:,1]) * (pc[:,0] - pa[:,0])
)
# avoid div-by-zero → inf circumradius
return np.where(area > 0, a * b * c / (4.0 * area), np.inf)
def alpha_shape(pts, alpha_norm, percentile=99):
# triangulate & get radii
tri = Delaunay(pts)
simp = tri.simplices
R = circumradii(pts, simp)
# pick 99th percentile as the “max radius” and clamp alpha_norm to [0,1]
maxR = np.nanpercentile(R[np.isfinite(R)], percentile)
alpha = np.clip(float(alpha_norm), 0.0, 1.0) * maxR
# collect “small” triangles’ edges and count occurrences
edges = {}
for simplex, r in zip(simp, R):
if r < alpha:
for i,j in ((0,1),(1,2),(2,0)):
e = tuple(sorted((simplex[i], simplex[j])))
edges[e] = edges.get(e, 0) + 1
# boundary edges appear only once
boundary = [e for e,c in edges.items() if c == 1]
# build adjacency and walk the loop
adj = {}
for i,j in boundary:
adj.setdefault(i, []).append(j)
adj.setdefault(j, []).append(i)
start = boundary[0][0]
loop = [start]
prev = None
curr = start
j = 0
while j < len(pts): # avoid infinite loop
j += 1
nbrs = adj[curr]
nxt = nbrs[0] if nbrs[0] != prev else nbrs[1]
if nxt == start:
break
loop.append(nxt)
prev, curr = curr, nxt
return loop
# read
dynamo_pts = IN[0]
alpha_norm = IN[1]
pts = np.array([[p.X, p.Y] for p in dynamo_pts])
loop_idxs = alpha_shape(pts, alpha_norm)
contour_pts = np.take(dynamo_pts, loop_idxs, axis=0)
# output Dynamo points in the right order
OUT = contour_pts # [dynamo_pts[i] for i in loop_idxs]
...
- Résolution d'équations
Dans ce dernier exemple, nous utilisons la classe
scipy.linalg.solve
pour calculer l'intersection projetée de deux lignes consécutive.
Chaque ligne est convertie en sa forme générale d'équation cartésienne, qui est ax+by=c.
Pour une ligne passant par les points (x1, y1) et (x2, y2), les coefficients sont :
a=y1 − y2
b=x2 − x 1
c=x2*y1 − y2*x1
Le point d'intersection (x, y) est le point unique qui satisfait les deux équations simultanément. On a donc un système de deux équations à deux inconnues (x et y).
Ce système peut être écrit sous forme matricielle comme Ax=b :
# Load the Python Standard and DesignScript Libraries
import sys
import clr
import numpy as np
from scipy.linalg import solve
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
def compute_intersection_scipy(lineA, lineB):
"""Compute intersection point of 2 lines using SciPy's linear solver."""
x1, y1 = lineA.StartPoint.X, lineA.StartPoint.Y
x2, y2 = lineA.EndPoint.X, lineA.EndPoint.Y
x3, y3 = lineB.StartPoint.X, lineB.StartPoint.Y
x4, y4 = lineB.EndPoint.X, lineB.EndPoint.Y
# linear equation of lines
# Line A: (y1 - y2)x + (x2 - x1)y = x2*y1 - y2*x1
# Line B: (y3 - y4)x + (x4 - x3)y = x4*y3 - y4*x3
# create equilatent of matrix system : Ax = b
A_matrix = np.array([
[y1 - y2, x2 - x1],
[y3 - y4, x4 - x3]
])
b_vector = np.array([
x2*y1 - y2*x1,
x4*y3 - y4*x3
])
#
# check if lines are parallals / coincident
determinant = np.linalg.det(A_matrix)
if abs(determinant) < 0.01:
return None, None
else:
# Solve the linear system Ax = b
px, py = solve(A_matrix, b_vector ) # intersection_point
return px, py
def compute_intersection(lineA, lineB):
"""alternative computation intersection point of 2 lines without scipy"""
x1, y1 = lineA.StartPoint.X, lineA.StartPoint.Y
x2, y2 = lineA.EndPoint.X, lineA.EndPoint.Y
x3, y3 = lineB.StartPoint.X, lineB.StartPoint.Y
x4, y4 = lineB.EndPoint.X, lineB.EndPoint.Y
denom = (x1 - x2)*(y3 - y4) - (y1 - y2)*(x3 - x4)
print(denom)
if abs(denom) < 0.001:
return None, None # Lines are parallel
px = ((x1*y2 - y1*x2)*(x3 - x4) - (x1 - x2)*(x3*y4 - y3*x4)) / denom
py = ((x1*y2 - y1*x2)*(y3 - y4) - (y1 - y2)*(x3*y4 - y3*x4)) / denom
return float(px), float(py)
def ordered_curves(lines):
ordered_curves = [lines.pop(0)]
n = 1
while lines and n < 200:
n += 1
lines.sort(key = lambda x : ordered_curves[-1].DistanceTo(x))
ordered_curves.append(lines.pop(0))
return ordered_curves
lines = IN[0]
perimeter_points = []
lines_ordered = ordered_curves(lines)
lines_ordered.append(lines_ordered[0])
for idx, lineB in enumerate(lines_ordered):
if idx > 0:
lineA = lines_ordered[idx - 1]
x, y = compute_intersection_scipy(lineA, lineB)
if x is None:
perimeter_points.extend([lineA.StartPoint, lineA.EndPoint, lineB.StartPoint, lineB.EndPoint])
else:
perimeter_points.append(Point.ByCoordinates(x, y, lineB.StartPoint.Z))
polygon = Polygon.ByPoints(perimeter_points)
OUT = polygon, perimeter_points
...
0 commentaires:
Enregistrer un commentaire