5 mai 2024

[Dynamo += Python] Les joies du mode Périodique (épisode 2)

 




 Vous aimez les transformations et les géométries en mouvement… vous êtes bien tombé

#DynamoBIM #Revit #Python #PerodicMode #Fractale #Tesserac #Geometry #AutodeskExpertElite #AutodeskCommunity 

if this article is not in your language, use the Google Translate widget ⬈ (bottom of page for Mobile version )


En addition du mode périodique, nous nous concentrons sur l'utilisation de l'API DynamoCore pour des mises à jour périodiques des variables, un processus different que le stockage traditionnel en mémoire ou dans des fichiers.

  • Utilisation de l'API DynamoCore

L'API DynamoCore est utilisé ici pour manipuler les valeurs au sein des nœuds Dynamo en temps réel. 

La méthode UpdateValue de la classe ModelBase permet de mettre à jour les variables à chaque exécution du script, offrant une alternative aux méthodes telles que System.AppDomain.CurrentDomain.SetData / GetData et la sérialisation via le module pickle.

ModelBase.UpdateValue(UpdateValueParams updateValueParams)


Dynamo.Graph.ModelBase est la classe de base pour tous les objets avec lesquels l'utilisateur peut interagir dans Dynamo.


Utilisation de SetData / GetData entre 2 nœuds Python sans transfert de données via les fils (wires)


  • Exemple 1 : Un zoom au travers de la fractale Mandelbrot

En mathématiques, l'ensemble de Mandelbrot est une fractale définie comme l'ensemble des points c du plan complexe pour lesquels la suite de nombres complexes définie par récurrence. 

Ici le mode périodique est utilisé pour générer des zooms détaillés sur différents aspects de la fractale, rendus possibles par les ajustements périodiques des paramètres du script.

Code (IronPython)


import clr
import System
from System.Runtime.InteropServices import Marshal

clr.AddReference("System.Core")
clr.ImportExtensions(System.Linq)

clr.AddReference('DynamoRevitDS')
import Dynamo 
#reference dynamo core to update node values
clr.AddReference('DynamoCore')
from Dynamo.Graph import UpdateValueParams

#access to the current Dynamo instance and workspace
dynamoRevit = Dynamo.Applications.DynamoRevit()
engine = dynamoRevit.RevitDynamoModel.EngineController
currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
model = dynamoRevit.RevitDynamoModel

clr.AddReference('System.Drawing')
from System.Drawing import Color, Bitmap, Imaging, Rectangle, Image

import colorsys
import math
#import time

px, py = -0.7746806106269039, -0.1374168856037867 #Tante Renate
R = 3 
max_iteration = 170 #2500
w, h = 400, 400 #1024,1024
mfactor = 0.5


def Mandelbrot2(*args):
    """
    get Mandelbrot fractal 
    """
    x, y, max_iteration, minx, maxx, miny, maxy = args
    zx = 0
    zy = 0
    RX1, RX2, RY1, RY2 = px-R/2.0, px+R/2.0, py-R/2.0, py+R/2.0
    cx = (x-minx)/float(maxx-minx)*(RX2-RX1)+RX1
    cy = (y-miny)/float(maxy-miny)*(RY2-RY1)+RY1
    i=0
    while zx**2 + zy**2 <= 4 and i < max_iteration:
        temp = zx**2 - zy**2
        zy = 2*zx*zy + cy
        zx = temp + cx
        i += 1
    return x, y, i

def image_by_lockBits(data_array):
    global w
    global h
    global max_iteration
    global mfactor
    bmpOut = Bitmap(w, h, Imaging.PixelFormat.Format24bppRgb)
    newData = bmpOut.LockBits(
                            Rectangle(0, 0, w, h),
                            Imaging.ImageLockMode.WriteOnly, bmpOut.PixelFormat)
    newBytesPerPixel = int(Image.GetPixelFormatSize(bmpOut.PixelFormat) / 8)
    for x, y, c in data_array:
        v = c**mfactor / float(max_iteration**mfactor)
        hv = 0.67-v
        if hv < 0: 
            hv += 1
        r, g, b = colorsys.hsv_to_rgb(hv,1,1-(v-0.1)**2/0.9**2)
        r = min(255,round(r*255))
        g = min(255,round(g*255))
        b = min(255,round(b*255))
        
        newPixelIndex = y * newData.Stride + x * newBytesPerPixel
        Marshal.WriteByte(newData.Scan0, newPixelIndex, r)
        Marshal.WriteByte(newData.Scan0, newPixelIndex + 1, g)
        Marshal.WriteByte(newData.Scan0, newPixelIndex + 2, b)
    bmpOut.UnlockBits(newData)
    #
    return bmpOut
    
def image_by_setPixel(data_array):
    global w
    global h
    global max_iteration
    global mfactor
    #
    bmpOut = Bitmap(w, h)
    # color pixels
    for x, y, c in data_array:
        v = c**mfactor/float(max_iteration**mfactor)
        hv = 0.67-v
        if hv < 0: 
            hv+=1
        r,g,b = colorsys.hsv_to_rgb(hv,1,1-(v-0.1)**2/0.9**2)
        r = min(255,round(r*255))
        g = min(255,round(g*255))
        b = min(255,round(b*255))
        bmpOut.SetPixel(x, y, Color.FromArgb(r, g, b))
    #
    return bmpOut
    
def gen_Mandelbrot_image(sequence, useSetPixel=False):
    global w
    global h
    byteArr = []
    ziplst = []
    for x in range(w):
        for y in range(h):
            ziplst.append([x, y, max_iteration, 0, w-1, 0, h-1])
            
    # apply multiprocessing parrlalelisme
    threadResult = ziplst.AsParallel()\
        .WithDegreeOfParallelism(System.Environment.ProcessorCount)\
        .WithExecutionMode(System.Linq.ParallelExecutionMode.ForceParallelism)\
        .Select(lambda e : Mandelbrot2(*e))
    #
    if useSetPixel:
        return image_by_setPixel(threadResult)
    else:
        return image_by_lockBits(threadResult)

nodeK = next((i for i in currentWorkspace.Nodes if i.Name.Equals("K_R")), None)
if nodeK is not None:
    # get values
    k = int(nodeK.GetValue(0,engine).Data) 
    R = float(nodeK.GetValue(1,engine).Data) 

f = 0.80
RZF = 1/1000000000000
useSetPixel = IN[1]

if k < 40 :
    mfactor = 0.5 + (1/float(1000000000000))**0.1/float(R**0.1)
    imgM = gen_Mandelbrot_image(k, useSetPixel)
    R *= f
    k+=1
    params = UpdateValueParams("Code", "{0};\n{1};".format(k, R))
    nodeK.UpdateValue(params)

    OUT = imgM    
    

Note:

    • ici est utilisé IronPython pour profiter du multiprocessing avec la méthode Linq AsParallel()
    • l'image est construite à partir d'une des 2 méthodes :
      • SetPixel 
      • LockBits/UnLockBits 

il sera préférable d'utiliser la méthode LockBits si vous souhaitez générer une plus grande image

Résultats (avec DynanoBim 3.0.4 / Revit 2025)




  • Exemple 2 : Visualisation d'un tesseract (hypercube 4D) en mouvement 


En géométrie, le tesseract, appelé aussi 4-hypercube ou encore 8-cellules ou octachore, est l'analogue quadridimensionnel du cube (tri-dimensionnel), où le mouvement le long de la quatrième dimension est souvent une représentation pour des transformations liées du cube à travers le temps.

Grace au mode periodique, la représentation du tesseract montre comment les transformations dans la quatrième dimension peuvent être visualisées dans notre espace tridimensionnel.


Code (IronPython ou CPython3/PythonNet)


import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import math

clr.AddReference('DSCoreNodes')
from DSCore import Color as DSColor 
clr.AddReference('GeometryColor')
from Modifiers import GeometryColor


clr.AddReference('DynamoRevitDS')
import Dynamo 
clr.AddReference('DynamoCore')
from Dynamo.Graph import UpdateValueParams
#access to the current Dynamo instance and workspace
dynamoRevit = Dynamo.Applications.DynamoRevit()
engine = dynamoRevit.RevitDynamoModel.EngineController
currentWorkspace = dynamoRevit.RevitDynamoModel.CurrentWorkspace
model = dynamoRevit.RevitDynamoModel
net_clr_runtime_name = ".Net Runtime : " + str(System.Environment.Version)

# Define the function for matrix multiplication (matrix by vector)
def matrix_multiply(mat, vec):
    return [
        sum(mat[i][j] * vec[j] for j in range(len(vec)))
        for i in range(len(mat))
    ]

# Project 4D coordinates to 3D
def project_4d_to_3d(point, scale, center):
    w = 1.0 / (4 - point[3])  # Perspective projection
    return [scale * w * point[i] + center[i] for i in range(3)]

# Define a function to create lines between points
def create_lines(points, connections_colors):
    lines = []
    for start, end, ds_color in connections_colors:
        line = Line.ByStartPointEndPoint(points[start], points[end])
        objColor = GeometryColor.ByGeometryColor(line, ds_color)
        lines.append(objColor)
    return lines

# Define the tesseract's vertices in 4D
points_4d = [
    [-1, -1, -1, 1], [1, -1, -1, 1], [1, 1, -1, 1], [-1, 1, -1, 1],
    [-1, -1, 1, 1], [1, -1, 1, 1], [1, 1, 1, 1], [-1, 1, 1, 1],
    [-1, -1, -1, -1], [1, -1, -1, -1], [1, 1, -1, -1], [-1, 1, -1, -1],
    [-1, -1, 1, -1], [1, -1, 1, -1], [1, 1, 1, -1], [-1, 1, 1, -1]
]

# Define rotation angles and setup rotation matrices for 4D rotation
#find the specific node
nodeK = next((i for i in currentWorkspace.Nodes if i.Name =="angle"), None)
if nodeK is not None:
    # get values
    d = nodeK.GetValue(0,engine)
    angle = float(nodeK.GetValue(0,engine).Data) 

cos_a, sin_a = math.cos(angle), math.sin(angle)
rotation_xy = [[cos_a, -sin_a, 0, 0], [sin_a, cos_a, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]]
rotation_zw = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, cos_a, -sin_a], [0, 0, sin_a, cos_a]]

# Apply rotations
points_4d_rotated = [matrix_multiply(rotation_xy, p) for p in points_4d]
points_4d_rotated = [matrix_multiply(rotation_zw, p) for p in points_4d_rotated]

# Project points to 3D
scale = 200  # Scale factor for visualization
center = [400, 400, 0]  # Center of the tesseract projection
points_3d = [Point.ByCoordinates(*project_4d_to_3d(p, scale, center)) for p in points_4d_rotated]

# Define connections based on the tesseract's edges
ds_colorA = DSColor.ByARGB(255, 255,0,0)
ds_colorB = DSColor.ByARGB(255, 46, 134, 193)
ds_colorC = DSColor.ByARGB(255, 0,0,0)
connections_colors = [
    # first cube
    (0, 1, ds_colorA), (1, 2, ds_colorA), (2, 3, ds_colorA), (3, 0, ds_colorA),
    (4, 5, ds_colorA), (5, 6, ds_colorA), (6, 7, ds_colorA), (7, 4, ds_colorA),
    (0, 4, ds_colorA), (1, 5, ds_colorA), (2, 6, ds_colorA), (3, 7, ds_colorA),
    # second cube
    (8, 9, ds_colorB), (9, 10, ds_colorB), (10, 11, ds_colorB), (11, 8, ds_colorB),
    (12, 13, ds_colorB), (13, 14, ds_colorB), (14, 15, ds_colorB), (15, 12, ds_colorB),
    (8, 12, ds_colorB), (9, 13, ds_colorB), (10, 14, ds_colorB), (11, 15, ds_colorB),
    # sommets
    (0, 8, ds_colorC), (1, 9, ds_colorC), (2, 10, ds_colorC), (3, 11, ds_colorC),
    (4, 12, ds_colorC), (5, 13, ds_colorC), (6, 14, ds_colorC), (7, 15, ds_colorC)
]

# Create lines between connected points
lines = create_lines(points_3d, connections_colors)

# Output the lines to Dynamo to visualize the tesseract
OUT = net_clr_runtime_name, lines, points_3d

params = UpdateValueParams("Code", "{0};".format(angle + 0.01))
nodeK.UpdateValue(params)


Résultats (avec DynanoBim 3.0.4 / Revit 2025)



articles (épisodes) sur le mode Periodique

lien des scripts Dynamo (Github)


Cet article n'augmentera peut-être pas votre productivité BIM mais... 

'La connaissance est un trésor, mais la pratique est la clé pour y accéder.' — Thomas Fuller"

0 commentaires:

Enregistrer un commentaire