3 mars 2024

[Dynamo += Python] Comparaison de Solides via Sérialisation

 






Un exemple de sérialisation pour comparer des éléments par topologie (solides)

#DynamoBIM #Revit #Python #Solid #Serialization #AutodeskExpertElite #AutodeskCommunity 

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


Contexte

Nous avons plusieurs Éléments de Fabrication et nous souhaitons les regrouper par leur forme (topologie) sans se préoccuper du nom de leur type.



    Pour ce faire, nous allons sérialiser ces solides avec les propriétés suivantes :

  • Le Volume
  • La somme vectorielle des vecteurs (centroïde → sommets)
  • La somme de la distance entre tous les points
  • Et Éventuellement le type de matériels 



Définition


La sérialisation est un procédé d'entrée-sortie permettant de sauvegarder et recharger l'état d'un objet. Cette fonctionnalité permet de faire abstraction du format de fichier utilisé.


L'état d'un objet correspond à l'ensemble des valeurs de ses champs. Les propriétés sont calculées en fonction des champs, et le code des méthodes ne change pas au cours de l'exécution.


Processus avec Python



Dans le code Python ci-après, la classe SolidsUtils a pour objectif principal est d'identifier et de regrouper des solides similaires.

Voici un aperçu des méthodes clés de cette classe :

  • GetSolid_Data : Cette méthode extrait des données essentielles à partir d'éléments Revit, telles que la géométrie du solide et l'ID du matériau.

  • DS_computePoints : Une fois les données du solide obtenues, cette méthode relève / calcule des points significatifs sur la plus grande face du solide.

  • Sum_Vector_fromCentroid : En calculant la somme des vecteurs allant du centroïde du solide aux points sur sa surface, cette méthode offre une mesure de la dispersion géométrique du solide, aidant à caractériser sa forme de manière unique.

  • Cross_distace_pt : Cette méthode calcule la distance globale entre tous les points significatifs sur la surface du solide.

  • __repr__ : Pour faciliter la sérialisation et la comparaison des solides, cette méthode génère une représentation string d'une instance SolidsUtils, à l'aide de la bibliothèque Newtonsoft.Json .

  • __eq__ : Cette méthode (opérateurs de comparaison Python) définit l'égalité entre deux instances de SolidsUtils basée sur leur représentation sérialisée.

  • GroupSolids : Finalement, cette méthode de classe regroupe les solides semblables en se basant sur leurs caractéristiques calculées.

Aperçu 




code Python


import sys
import clr
import System
clr.AddReference('ProtoGeometry')
from Autodesk.DesignScript.Geometry import *
import Autodesk.DesignScript.Geometry as DS

#import Revit API
clr.AddReference('RevitAPI')
import Autodesk
from Autodesk.Revit.DB import *
import Autodesk.Revit.DB as DB

clr.AddReference('RevitNodes')
import Revit
clr.ImportExtensions(Revit.GeometryConversion)

#import transactionManager and DocumentManager (RevitServices is specific to Dynamo)
clr.AddReference('RevitServices')
import RevitServices
from RevitServices.Persistence import DocumentManager
doc = DocumentManager.Instance.CurrentDBDocument

clr.AddReference('Newtonsoft.Json')
import Newtonsoft.Json
from Newtonsoft.Json import JsonConvert

class SolidsUtils:
	lst_solid = []
	def __init__(self, element_object):
		self.element_object = element_object
		self.DS_solid, self.matId_value = self.GetSolid_Data(element_object)
		#self._use_material = False if isinstance(element_object, DS.Solid) else use_material
		self.__class__.lst_solid.append(self)
		self._computePoints = []
	
	def GetSolid_Data(self, elem):
		""" get Proto Solid and MaterialId"""
		# sub function
		def get_Solid_MaterialId(geo_elem):
			solid = geo_elem.ToProtoType()
			max_face = max(geo_elem.Faces, key = lambda x : x.Area)
			matId_value = max_face.MaterialElementId.IntegerValue
			return solid, matId_value
		# Main Function
		if isinstance(elem, DS.Solid):
			return elem, matId_value
		else:
			opt = Options()
			for geo in elem.get_Geometry(opt):
				if isinstance(geo, DB.Solid) and geo.Volume > 0.1:
					return get_Solid_MaterialId(geo)
				#
				elif isinstance(geo, DB.GeometryInstance) :
					for gi in geo.GetInstanceGeometry():
						if isinstance(gi, DB.Solid) and gi.Volume > 0.1:
							return get_Solid_MaterialId(gi)
		return None, -1
		
	@property
	def DS_computePoints(self):
		""" get all points on largest Face with loops"""
		if len(self._computePoints) > 0:
			return self._computePoints
		#get the max face with loops and points on edges or centerpoints
		max_face = max(self.DS_solid.Faces , key = lambda f : (len(f.Loops), f.SurfaceGeometry().Area))
		curves = max_face.SurfaceGeometry().PerimeterCurves()
		pts_on_max_face = []
		for c in curves:
			endsPoints = [c.CenterPoint] if isinstance(c, DS.Arc) else [getattr(c, tp) for tp in ['StartPoint', 'EndPoint']] 
			for pta in endsPoints:
				if all(pta.DistanceTo(p) > 0.01 for p in pts_on_max_face ):
					pts_on_max_face.append(pta)
		# delete points located on 2 aligned curves
		self._computePoints = []
		for p in pts_on_max_face:
			tempNormal = []
			for c in curves:
				if c.DistanceTo(p) < 0.01:
					tempNormal.append(c.NormalAtParameter(0.5))
			if len(tempNormal) == 2 and tempNormal[0].Cross(tempNormal[1]).Length < 0.01:
				pass
			else:
				self._computePoints.append(p)
		return self._computePoints

	@property
	def Sum_Vector_fromCentroid(self):
		""" compute sum of Vectors: centroid -> points on surface """
		centroid = self.DS_solid.Centroid()
		sum_vector = Vector.ByCoordinates(0,0,0)
		for p in self.DS_computePoints:
			v =  Vector.ByTwoPoints(centroid, p)
			sum_vector = sum_vector.Add(v)
		sum_Vector_fromCentroid = sum_vector.Length
		return sum_Vector_fromCentroid
		
	@property
	def Cross_distace_pt(self):
		""" compute global distance between points """
		cross_distace_pt = 0.0
		for i in self.DS_computePoints:
			for j in self.DS_computePoints:
				cross_distace_pt += i.DistanceTo(j)
		return cross_distace_pt
		
		
	def __repr__(self):
		return JsonConvert.SerializeObject({"Type" : self.__class__.__name__, 
				"Volume": "{:.3f}".format(self.DS_solid.Volume),
				"Sum_Vector_fromCenter" : "{:.3f}".format(self.Sum_Vector_fromCentroid), 
				"Cross_Dist_Pts" : "{:.3f}".format(self.Cross_distace_pt), 
				"MaterialId" : self.matId_value})

		
	def __eq__(self, other):
		return repr(self) == repr(other)
	
	@classmethod
	def GroupSolids(cls):
		dictCount = {}
		for obj in cls.lst_solid:
			if repr(obj) not in dictCount:
				dictCount[repr(obj)] = [obj.DS_solid]
			else:
				dictCount[repr(obj)].append(obj.DS_solid)
		return dictCount

		
toList = lambda x : x if hasattr(x, '__iter__') else [x]
input_Objs = toList(UnwrapElement(IN[0]))

for s in input_Objs:
	o = SolidsUtils(element_object = s)
result = SolidsUtils.GroupSolids().items()
OUT = result
...

Note :

Pour information, il est possible aussi de sérialiser des solides avec le nœud (méthode)  Geometry.ToSolidDef incluant d'autre type de données comme la localisation des sommets, les courbes des arêtes, 



Aperçu du JSON



... 

0 commentaires:

Enregistrer un commentaire