Source code for axisvm.com.axdomain

# -*- coding: utf-8 -*-
from typing import Iterable, Callable, Union, List, Tuple, Iterator
from functools import partial

import numpy as np
import awkward as ak
from numpy import ndarray

import matplotlib.tri as tri

from sigmaepsilon.core import issequence

from sigmaepsilon.math.linalg import Vector
from sigmaepsilon.math.linalg.sparse.utils import count_cols
from sigmaepsilon.mesh import PointCloud, CartesianFrame
from sigmaepsilon.mesh import TopologyArray
from sigmaepsilon.mesh.utils import decompose
from sigmaepsilon.mesh.utils.topology import rewire
from sigmaepsilon.mesh.plotting import triplot_mpl_data, triplot_mpl_mesh

import axisvm
from .core.wrap import AxisVMModelItem, AxisVMModelItems, AxItemCollection, AxWrapper
from .core.utils import (
    RMatrix3x3toNumPy,
    RMatrix2x2toNumPy,
    RSurfaceCoordinates2list,
    triangulate as triang,
    dcomp2int,
    fcomp2int,
    scomp2int,
    xlmscomp2int,
    RXLAMSurfaceStresses2list,
    RSurfaceStresses2list,
    get_xlam_strs_case,
    get_xlam_strs_comb,
    get_xlam_effs_crit,
    RXLAMSurfaceEfficiencies2list,
    _DisplacementSystem,
    _LoadLevelOrModeShapeOrTimeStep,
)
from .attr import AxisVMAttributes, squeeze_attributes as dsqueeze
from .axsurface import SurfaceMixin, get_surface_attributes, surface_attr_fields
from .axresult import AxisVMResultItem, IAxisVMStresses


__all__ = ["IAxisVMDomain", "IAxisVMDomains"]


class AxDomainCollection(AxItemCollection):
    def __getattribute__(self, attr):
        if hasattr(self[0], attr):
            _attr = getattr(self[0], attr)
            if isinstance(_attr, Callable):

                def getter(i):
                    return getattr(i, attr)

                funcs = map(getter, self)

                def inner(*args, **kwargs):
                    return list(map(lambda f: f(*args, **kwargs), funcs))

                return inner
            else:
                return list(map(lambda i: getattr(i, attr), self))
        else:
            return super().__getattribute__(attr)


[docs]class IAxisVMDomain(AxisVMModelItem, SurfaceMixin): """Wrapper for the `IAxisVMDomain` COM interface.""" @property def model(self) -> AxWrapper: """ Returns the model of the domain. """ return self.parent.model @property def attributes(self) -> dict: """ Returns the attributes. """ return dsqueeze(self.parent.get_attributes(i=self.Index)) @property def domain_attributes(self) -> dict: """ Returns the domain attributes. """ return dsqueeze(self.parent.get_domain_attributes(i=self.Index)) @property def tr(self) -> ndarray: """Returns the transformation matrix of the domain.""" return self.transformation_matrix() @property def IsXLAM(self) -> bool: """Returns True if the domain is an XLAM domain, False otherwise.""" return self.parent.IsXLAM[self.Index]
[docs] def transformation_matrix(self) -> ndarray: """Returns the transformation matrix of the domain.""" return np.array(RMatrix3x3toNumPy(self.GetTrMatrix()[0]), dtype=float)
[docs] def record(self) -> Iterable: """Returns the record of the domain.""" return self.parent.records(self.Index)
[docs] def ABDS(self, *args, compose=True, **kwargs) -> Union[ndarray, List[ndarray]]: """ Returns the ABDS matrix of the domain. Parameters ---------- compose: bool, Optional If True, the result is one matrix, otherwise 4 submatrices. Default is True. Returns ------- Union[numpy.ndarray, List[ndarray]] One or four NumPy arrays. """ A, B, D, S, *_ = self._wrapped.GetCustomStiffnessMatrix() A, B, D = [RMatrix3x3toNumPy(x) for x in (A, B, D)] S = RMatrix2x2toNumPy(S) if compose: res = np.zeros((8, 8), dtype=float) res[0:3, 0:3] = A res[0:3, 3:6] = B res[3:6, 0:3] = B res[3:6, 3:6] = D res[6:8, 6:8] = S return res else: return A, B, D, S
[docs] def GetCustomStiffnessMatrix( self, to_numpy: bool = False, compose: bool = True ) -> Iterable: """ Returns the custom stiffness matrix, optionally as a NumPy array. Paramters --------- to_numpy: bool, Optional If `True`, the result is a NumPy array, otherwise the raw results are returned. Default is False. compose: bool, Optional If True, the result is one matrix, otherwise 4 submatrices. Only if 'to_numpy' is `True`. Default is True. See also -------- :func:`ABDS` """ if to_numpy: return self.ABDS(compose=compose) else: return self._wrapped.GetCustomStiffnessMatrix()
[docs] def coordinates(self, frame: str = "global") -> PointCloud: """ Returns the coordinates of the points related to the domain as an instance of :class:`sigmaepsilon.mesh.space.PointCloud`. """ coords = self.model.coordinates() topo = self.topology() i = np.unique(topo) - 1 if frame == "global": return coords[i] elif frame == "local": source = CartesianFrame(dim=3) target = CartesianFrame(self.transformation_matrix()) return PointCloud(coords[i], frame=source).show(target) else: raise ValueError("'frame' must be 'local' or 'global'")
[docs] def topology(self) -> TopologyArray: """ Returns the topology of the domain as a :class:`sigmaepsilon.mesh.TopologyArray`. """ axms = self.model.Surfaces._wrapped sIDs = self.MeshSurfaceIds def fnc_corner(i): return list(axms.Item[i].GetContourPoints()[0]) def fnc_mid(i): return list(axms.Item[i].GetMidPoints()[0]) def fnc(i): return fnc_corner(i) + fnc_mid(i) return TopologyArray(ak.Array(list(map(fnc, sIDs))))
[docs] def surface_coordinates(self) -> ak.Array: """ Returns the coordinates for each surface of the domain as an :class:`awkward.Array` instance. """ recs = self.GetMeshSurfacesCoordinates()[0] return ak.Array(list(map(RSurfaceCoordinates2list, recs)))
[docs] def detach_mesh( self, triangulate: bool = False, return_indices: bool = False ) -> Tuple[PointCloud, TopologyArray]: """ Returns the coordinate and topology arrays of the domain, detached from the structure. Parameters ---------- triangulate: bool, Optional If `True` the domain is first triangulated. Default is `False`. return_indices: bool, Optional If `True` the original node indices are also returned. Default is `False`. Returns ------- Tuple[PointCloud, TopologyArray] The points and the topology as arrays. """ topo = self.topology() inds = np.unique(topo) - 1 topo = topo.to_array() - 1 ecoords = self.surface_coordinates() topo = rewire(topo, inds, invert=True) coords = np.zeros((np.max(topo) + 1, 3), dtype=float) decompose(ecoords, topo, coords) source = CartesianFrame(dim=3) target = CartesianFrame(self.transformation_matrix()) coords = PointCloud(coords, frame=source).show(target)[:, :2] if triangulate: topo = triang(topo) if return_indices: return coords, topo, inds + 1 return coords, topo
[docs] def plot(self, *_, **mpl_kw) -> None: """ Plots the surface using `matplotlib`. Parameters ---------- **mpl_kw: dict, Optional Parameters to pass to :func:`~sigmaepsilon.mesh.plotting.mpl.triplot_mpl_mesh` """ coords, topo, _ = self.detach_mesh(return_indices=True, triangulate=True) triobj = tri.Triangulation(coords[:, 0], coords[:, 1], triangles=topo) if mpl_kw is None: mpl_kw = dict(axis="on") triplot_mpl_mesh(triobj, **mpl_kw)
[docs] def plot_dof_solution( self, *args, component: str = "ez", mpl_kw: dict = None, displacement_system: Union[str, int] = 1, **kwargs, ) -> None: """ Plots the degree of freedom solution of a domain using `matplotlib`. Parameters ---------- component: str, Optional Possible options are: * 'ex' or 'ux' : displacement in X direction * 'ey' or 'uy' : displacement in Y direction * 'ez' or 'uz' : displacement in Z direction * 'fx' or 'rotx' : rotation around X * 'fy' or 'roty' : rotation around Y * 'fz' or 'rotz' : rotation around Z Default is 'ez'. displacement_system: Union[str, int], Optional Sets the displacement system in which results are to be returned. Possible values are 1 (or 'global) and 0 (or 'local'). Default is 1, which means the global system. **mpl_kw : dict, Optional Parameters to pass to :func:`~sigmaepsilon.mesh.plotting.mpl.triplot_mpl_data` *args Forwarded to :function:`~axisvm.com.axmodel.IAxisVMModel.dof_solution`. **kwargs Forwarded to :function:`~axisvm.com.axmodel.IAxisVMModel.dof_solution`. Notes ----- This call does not require AxisVM to be visible. See also -------- :function:`~axisvm.com.axmodel.IAxisVMModel.dof_solution` Example ------- Import the necessary stuff, >>> from axisvm.com.client import start_AxisVM >>> from axisvm import examples >>> import matplotlib.pyplot as plt load a sample model, >>> axvm = start_AxisVM(visible=False, daemon=True) >>> axvm.model = examples.download_plate_ss() run a linear analysis, >>> axm = axvm.model >>> axm.Calculation.LinearAnalysis() and pot the results. >>> fig, ax = plt.subplots(figsize=(20, 4)) >>> mpl_kw = dict(nlevels=15, cmap='rainbow', axis='on', offset=0., cbpad=0.5, cbsize=0.3, cbpos='right', fig=fig, ax=ax) >>> axm.Domains[1].plot_dof_solution(component='uz', mpl_kw=mpl_kw, case=1) Finally, close the application. >>> axvm.Quit() """ axm: AxWrapper = self.model dofsol = axm.dof_solution(*args, displacement_system=1, **kwargs) coords, topo, inds = self.detach_mesh(return_indices=True, triangulate=True) triobj = tri.Triangulation(coords[:, 0], coords[:, 1], triangles=topo) dofsol = dofsol[inds - 1] displacement_system = _DisplacementSystem(displacement_system) if displacement_system == 0: source = CartesianFrame(dim=3) target = CartesianFrame(self.transformation_matrix()) dofsol[:, :3] = Vector(dofsol[:, :3], frame=source).show(target) dofsol[:, 3:6] = Vector(dofsol[:, 3:6], frame=source).show(target) component_index = dcomp2int(component) if mpl_kw is None: mpl_kw = dict( nlevels=15, cmap="jet", axis="on", offset=0.0, cbpad="10%", cbsize="10%", cbpos="right", ) compstr = component.upper() params = [self.Index, compstr] tmpl = "Domain {} - {}" mpl_kw["title"] = tmpl.format(*params) triplot_mpl_data(triobj, data=dofsol[:, component_index], **mpl_kw)
[docs] def plot_forces( self, *args, component: str = "nx", mpl_kw: dict = None, smoothen: bool = False, **kwargs, ) -> None: """ Plots internal forces of a domain using `matplotlib`. Parameters ---------- component: str, Optional Possible options are: * 'nx' : :math:`n_x` normal force * 'ny' : :math:`n_y` normal force * 'nxy' : :math:`n_{xy}` in plane shear force * 'mx' : :math:`m_x` bending moment * 'my' : :math:`m_y` bending moment * 'mxy' : :math:`m_{xy}` twisting moment * 'vx' or 'vxz' : :math:`v_x` shear force * 'vy' or 'vyz' : :math:`v_y` shear force Default is 'nx'. smoothen: int, Optional If the values should be smoothened or not. Default is False. **mpl_kw: dict, Optional Parameters to pass to :func:`~sigmaepsilon.mesh.plotting.mpl.triplot_mpl_data` """ assert not smoothen, "Smoothing is not available at the moment." axm = self.model sids = np.array(self.MeshSurfaceIds) - 1 forces = axm.generalized_surface_forces(*args, **kwargs)[sids] coords, topo = self.detach_mesh(triangulate=False) topo, forces = triang(topo, data=forces) # triangulate with data triobj = tri.Triangulation(coords[:, 0], coords[:, 1], triangles=topo) if mpl_kw is None: mpl_kw = dict( nlevels=15, cmap="jet", axis="on", offset=0.0, cbpad="10%", cbsize="10%", cbpos="right", ) i = fcomp2int(component) compstr = component.upper() params = [self.Index, compstr] tmpl = "Domain {} - {}" mpl_kw["title"] = tmpl.format(*params) triplot_mpl_data(triobj, data=forces[:, :3, i], **mpl_kw)
[docs] def plot_stresses( self, *args, component: str = None, mpl_kw: dict = None, source: str = None, z: str = None, **kwargs, ): """ Plots internal forces of a domain using `matplotlib`. Parameters ---------- component: str, Optional Possible options are: * 'sxx' : :math:`\\sigma_{x}` stress * 'syy' : :math:`\\sigma_{y}` stress * 'sxy' : :math:`\\tau_{xy}` stress * 'sxz' : :math:`\\tau_{xz}` stress * 'syz' : :math:`\\tau_{yz}` stress * 'svm' : :math:`\\sigma_{VM}` Von-Mises stress * 's1' : :math:`\\sigma_{1}` 1st principal stress * 's2' : :math:`\\sigma_{2}` 2nd principal stress * 'as' : :math:`\\alpha` principal direction angle Default is 'sxx_m_t' for XLAM domains 'sxx' otherwise. source: str, Optional Specifies the source of the possibe agents. Possible values are 'm' for bending and 'n' for normal action. Only for XLAM domains. Default is None. z: str, Optional Specifies the location along the thickness. Possible values are 't' for top, 'b' for bottom, 'm' for middle and 'max' for the location where the value takes its maximum (only for shear stresses). Default is None. **mpl_kw: dict, Optional Parameters to pass to :func:`~sigmaepsilon.mesh.plotting.mpl.triplot_mpl_data` Import the necessary stuff, Examples -------- >>> from axisvm.com.client import start_AxisVM >>> from axisvm import examples >>> import matplotlib.pyplot as plt load a sample model, >>> axvm = start_AxisVM(visible=False, daemon=True) >>> axvm.model = examples.download_plate_ss() run a linear analysis, >>> axm = axvm.model >>> axm.Calculation.LinearAnalysis() and pot the results. >>> fig, ax = plt.subplots(figsize=(20, 4)) >>> mpl_kw = dict(cmap='rainbow', axis='on', offset=0., cbpad=0.5, cbsize=0.3, cbpos='right', fig=fig, ax=ax) >>> axm.Domains[1].plot_stresses(component='svm', mpl_kw=mpl_kw, case=1, z='t') Finally, close the application. >>> axvm.Quit() """ smoothen: bool = False assert not smoothen, "Smoothing is not available at the moment." stresses = self.surface_stresses(*args, z=z, **kwargs) coords, topo = self.detach_mesh(triangulate=False) topo, stresses = triang(topo, data=stresses) # triangulate with data triobj = tri.Triangulation(coords[:, 0], coords[:, 1], triangles=topo) if mpl_kw is None: mpl_kw = dict( nlevels=15, cmap="jet", axis="on", offset=0.0, cbpad="10%", cbsize="10%", cbpos="right", ) if self.IsXLAM: if component is not None and source is not None: component += "_" + "{}".format(source) if component is not None and z is not None: component += "_" + "{}".format(z) c = component.lower() if component is not None else "sxx_m_t" ci = xlmscomp2int(c) else: c = component if component is not None else "sxx" ci = scomp2int(c) compstr = component.upper() params = [self.Index, compstr, z.lower()] tmpl = "Domain {} - {} - {}" mpl_kw["title"] = tmpl.format(*params) triplot_mpl_data(triobj, data=stresses[:, :3, ci], **mpl_kw)
def surface_stresses( self, case: Union[str, int] = None, combination: Union[str, int] = None, z: str = "m", load_case_id: int = None, load_combination_id: int = None, displacement_system: int = 0, load_level: int = None, mode_shape: int = None, time_step: int = None, ) -> ak.Array: if self.IsXLAM: return self.xlam_surface_stresses( case=case, combination=combination, load_case_id=load_case_id, load_combination_id=load_combination_id, displacement_system=displacement_system, load_level=load_level, mode_shape=mode_shape, time_step=time_step, ) axm = self.model stresses: AxisVMResultItem = axm.Results.Stresses sids = self.MeshSurfaceIds load_case_id, load_combination_id = stresses._get_case_or_component( case=case, combination=combination, load_case_id=load_case_id, load_combination_id=load_combination_id, ) config = dict( load_case_id=load_case_id, load_combination_id=load_combination_id, load_level=load_level, mode_shape=mode_shape, time_step=time_step, displacement_system=displacement_system, ) stresses.config(**config) if load_case_id is not None: def getter(i): return stresses.SurfaceStressesByLoadCaseId(i)[0] elif load_combination_id is not None: def getter(i): return stresses.SurfaceStressesByLoadCombinationId(i)[0] foo = partial(RSurfaceStresses2list, mode=z) return ak.Array(list(map(foo, map(getter, sids))))
[docs] def xlam_surface_stresses( self, case: Union[str, int] = None, combination: Union[str, int] = None, load_case_id: int = None, load_combination_id: int = None, load_level: int = None, mode_shape: int = None, time_step: int = None, displacement_system: Union[str, int] = 0, analysis_type: int = 0, frmt="array", factor=None, ) -> Union[ak.Array, dict]: """ Returns XLAM stresses either as a :class:`numpy.ndarray` or as a dictionary. Parameters ---------- displacement_system: int, Optional 0 or 'local' for local, 1 or 'global' for global. Default is 1. load_case_id: int, Optional Default is None. load_level: int, Optional Default is None. mode_shape: int, Optional Default is None. time_step: int, Optional Default is None. load_combination_id: int, Optional Default is None. case: Union[str, Iterable], Optional The name of a loadcase or an iterable of indices. Default is None. combination: str, Optional The name of a load combination. Default is None. analysis_type: int, Optional Default is 0. frmt: str, Optional Controls the type of the result. With 'array' it is a 3d Awkward array, otherwise a dictionary. Default is 'array'. factor: Iterable, Optional Linear coefficients for the different load cases specified with 'case'. If 'case' is an Iterable, 'factor' must be an Iterable of the same shape. Default is None. Notes ----- 1) It is the user who has to make sure that this call is only called on surfaces, that belong to an XLAM domain. 2) The returned stresses do not belong to the same position. Returns ------- awkward.Array or dict If frmt is 'array', the result is a 2d float Awkward array of shape (nN, nX), where nN is the number of nodes of the surface and nX is the number of stress components, which are: * 0 : :math:`\\sigma_{x}` stress at the top, from bending * 1 : :math:`\\sigma_{y}` stress at the top, from bending * 2 : :math:`\\tau_{xy}` stress at the top, from bending * 3 : :math:`\\sigma_{x}` stress at the bottom, from bending * 4 : :math:`\\sigma_{y}` stress at the bottom, from bending * 5 : :math:`\\tau_{xy}` stress at the bottom, from bending * 6 : max :math:`\\sigma_{x}` stress from stretching * 7 : max :math:`\\sigma_{y}` stress from stretching * 8 : max :math:`\\tau_{xy}` stress from stretching * 9 : max :math:`\\tau_{xz}` shear stress * 10 : max :math:`\\tau_{yz}` shear stress * 11 : max :math:`\\tau_{xz,r}` rolling shear stress * 12 : max :math:`\\tau_{yz,r}` rolling shear stress If frmt is 'dict', the stresses are returned as a dictionary of 1d NumPy arrays, where indices from 0 to 12 are the keys of the values at the corders. """ assert self.IsXLAM, "This is not an XLAM domain!" def ak2np(r, i): return ak.flatten(r[:, :, i]).to_numpy() def ad2d(arr): return {i: ak2np(arr, i) for i in range(13)} if issequence(case): if factor is not None: assert issequence( factor ), "If 'case' is an Iterable, 'factor' must be an Iterable of the same shape." assert len(case) == len( factor ), "Lists 'case' and 'factor' must have equal lengths." res = sum( [ self.xlam_surface_stresses( case=c, frmt="array", factor=f, analysis_type=analysis_type, load_level=load_level, mode_shape=mode_shape, time_step=time_step, displacement_system=displacement_system, ) for c, f in zip(case, factor) ] ) else: res = [ self.xlam_surface_stresses( case=c, frmt=frmt, factor=1.0, analysis_type=analysis_type, load_level=load_level, mode_shape=mode_shape, time_step=time_step, displacement_system=displacement_system, ) for c in case ] if frmt == "dict": return ad2d(res) return res axm: AxWrapper = self.model stresses: IAxisVMStresses = axm.Results.Stresses sids = self.MeshSurfaceIds load_case_id, load_combination_id = stresses._get_case_or_component( case=case, combination=combination, load_case_id=load_case_id, load_combination_id=load_combination_id, ) config = dict( load_case_id=load_case_id, load_combination_id=load_combination_id, load_level=load_level, mode_shape=mode_shape, time_step=time_step, displacement_system=displacement_system, ) stresses.config(**config) LoadLevelOrModeShapeOrTimeStep = _LoadLevelOrModeShapeOrTimeStep( load_level=load_level, mode_shape=mode_shape, time_step=time_step, ) if load_case_id is not None: getter = partial( get_xlam_strs_case, stresses, load_case_id, LoadLevelOrModeShapeOrTimeStep, analysis_type, ) elif load_combination_id is not None: getter = partial( get_xlam_strs_comb, stresses, load_combination_id, LoadLevelOrModeShapeOrTimeStep, analysis_type, ) foo = partial(RXLAMSurfaceStresses2list) factor = 1.0 if factor is None else float(factor) res = factor * ak.Array(list(map(foo, map(getter, sids)))) if frmt == "dict": return ad2d(res) return res
def critical_xlam_surface_efficiencies( self, *, combination_type=None, analysis_type=None, component=None, minmax_type=None, **__, ): axm = self.model stresses = axm.Results.Stresses def inner(sid): params = dict( SurfaceId=sid, MinMaxType=minmax_type, CombinationType=combination_type, AnalysisType=analysis_type, Component=component, ) rec = get_xlam_effs_crit(stresses, **params) return np.array(RXLAMSurfaceEfficiencies2list(rec)) return ak.Array(list(map(inner, self.MeshSurfaceIds))) def critical_xlam_data(self, *, combination_type=None, analysis_type=0, **__): Surfaces = self.model.Surfaces dparams = dict( minmax_type=1, combination_type=combination_type, analysis_type=analysis_type, component=4, # xse_Max ) sid, nid, f, lc = (None,) * 4 data = self.critical_xlam_surface_efficiencies(**dparams) cuts = count_cols(data) eff_max = ak.flatten(data[:, :, -1]).to_numpy() imax = np.argmax(eff_max) csum = np.cumsum(cuts) iS = np.where(csum > imax)[0][0] iN = imax - csum[iS - 1] sid = self.MeshSurfaceIds[iS] nid = self.topology()[iS, iN] if cuts[iS] == 6: surface_vertex_type = 0 if iN < 3 else 1 elif cuts[iS] == 8: surface_vertex_type = 0 if iN < 4 else 1 else: raise NotImplementedError sparams = dict( surface_vertex_type=surface_vertex_type, surface_vertex_id=nid, minmax_type=1, combination_type=combination_type, analysis_type=analysis_type, component=4, ) _, f, lc = Surfaces[sid].critical_xlam_efficiency(**sparams) return f, lc, (sid, nid) def _get_attrs(self): """Return the representation methods (internal helper).""" attrs = [] attrs.append(("Name", self.Name, "{}")) attrs.append(("Index", self.Index, "{}")) attrs.append(("UID", self._wrapped.UID, "{}")) attrs.append(("N Surfaces", len(self.MeshSurfaceIds), "{}")) attrs.append(("Area", self.Area, axisvm.FLOAT_FORMAT)) attrs.append(("Volume", self.Volume, axisvm.FLOAT_FORMAT)) attrs.append(("Weight", self.Weight, axisvm.FLOAT_FORMAT)) return attrs
[docs]class IAxisVMDomains(AxisVMModelItems, SurfaceMixin): """Wrapper for the `IAxisVMDomains` COM interface.""" __itemcls__ = IAxisVMDomain __collectioncls__ = AxDomainCollection def __getitem__(self, *args) -> IAxisVMDomain: return super().__getitem__(*args) @property def tr(self) -> ndarray: return self.transformation_matrices() @property def frames(self) -> ndarray: return self.transformation_matrices() @property def attributes(self): return self.get_attributes() @property def domain_attributes(self): return self.get_domain_attributes() @property def XLAMItems(self) -> Iterator[IAxisVMDomain]: return filter(lambda i: self.IsXLAM[i.Index], self[:]) @property def XLAMCount(self) -> int: return len(self.XLAMItems) def topology(self, *args, i=None) -> TopologyArray: i = i if len(args) == 0 else args[0] if isinstance(i, int): return self[i].topology() if isinstance(i, ndarray): ids = i else: if isinstance(i, Iterable): ids = np.array(i, dtype=int) else: ids = np.array(list(range(self.Count))) + 1 return np.vstack(list(map(lambda i: self[i].topology(), ids))) def _get_attributes_raw(self, *args, i=None, **kwargs) -> Iterable: i = i if len(args) == 0 else args[0] if isinstance(i, int): return self.BulkGetDomains([i]) if isinstance(i, ndarray): ids = i else: if isinstance(i, Iterable): ids = np.array(i, dtype=int) else: ids = np.array(list(range(self.Count))) + 1 return self.BulkGetDomains(ids) def get_domain_attributes( self, *args, i=None, raw=False, fields=None, rec=None, **kwargs ) -> AxisVMAttributes: if fields is None: fields = surface_attr_fields + ["LineIdCounts", "BulkLineIds"] elif isinstance(fields, str): fields = [fields] elif isinstance(fields, Iterable): fields = list(filter(lambda i: i in surface_attr_fields, fields)) if rec is None: i = i if len(args) == 0 else args[0] rec = self._get_attributes_raw(i=i) if raw: return rec else: LineIdCounts, BulkLineIds, SurfaceAttrs, *_ = rec data = get_surface_attributes( self, *args, i=i, attr=SurfaceAttrs, fields=fields, raw=False, **kwargs ) if "LineIdCounts" in fields: data["LineIdCounts"] = LineIdCounts if "BulkLineIds" in fields: data["BulkLineIds"] = BulkLineIds return AxisVMAttributes(data) def get_attributes(self, *args, **kwargs) -> AxisVMAttributes: return self.get_domain_attributes(*args, **kwargs) def get_xlam_domain_indices(self): return list(map(lambda i: i.Index, self.XLAMItems)) def records(self, *args, **kwargs): return self.get_domain_attributes(*args, raw=True, **kwargs)