Esempio n. 1
0
class ImpConf(object):
    """Imperfection Configuration

    Created by default as one attribute of the :class:`.ConeCyl` object,
    accessed through::

        cc = ConeCyl()
        impconf = cc.impconf

    If one has the ``impconf`` object and wants to access the corresponding
    :class:`ConeCyl` object, the attribute ``conecyl`` can be used as
    examplified below.  Note that if no :class:`.ConeCyl` is assigned to this
    imperfection configuration a ``None`` value will be obtained::

        cc = impconf.conecyl

    The imperfections are grouped in the attributes detailed below.

    ================== ======================================================
    Attributes         Description
    ================== ======================================================
    uneven_bottom_edge :class:`.UnevenBottomEdge` object
    uneven_top_edge    :class:`.UnevenTopEdge` object
    ploads             ``list`` of :class:`.PLoad` (Perturbation Load) objects
    dimples            ``list`` of :class:`.Dimple` (Dimple Imperfection)
                       objects
    axisymmetrics      ``list`` of :class:`.Axisymmetric` (Axisymmetric
                       Imperfection) objects
    lbmis              ``list`` of :class:`.LBMI` (Linear Buckling Mode-Shaped
                       Imperfection) objects
    tis                ``list`` of :class:`.TI` (Thickness Imperfection)
                       objects
    msis               ``list`` of :class:`.MSI` (Mid-Surface Imperfection)
                       objects
    cutouts            ``list`` of :class:`.Cutout` objects
    ppi                :class:`.PPI` (Ply Piece Imperfection) object or
                       ``None`` if not set
    ffi                :class:`.FFI` (Fiber Fraction Imperfection) object or
                       ``None`` if not set
    ================== ======================================================

    """
    def __init__(self):
        self.uneven_bottom_edge = UnevenBottomEdge()
        self.uneven_bottom_edge.impconf = self
        self.uneven_top_edge = UnevenTopEdge()
        self.uneven_top_edge.impconf = self
        self.imperfections = []
        self.ploads = []
        self.dimples = []
        self.axisymmetrics = []
        self.lbmis = []
        self.msis = []
        self.tis = []
        self.cutouts = []
        self.ppi = None
        self.ffi = None
        self.rename = True
        self.name = ''
        self.conecyl = None


    def __setstate__(self, attrs):
        # Called during unpickling (i.e. loading)
        # Calling __init__ prevents problems with missing attributes,
        # when loading from older versions
        self.__init__()
        self.__dict__.update(attrs)


    def add_axisymmetric(self, pt, b, wb):
        """Add an Axisymmetric Imperfection (AI)

        Parameters
        ----------
        pt : float
            Normalized meridional position.
        b : float
            Half-wave length.
        wb : float
            Imperfection amplitude (amplitude of the half-wave).

        Returns
        -------
        ax : :class:`.Axisymmetric` object.

        """
        ax = Axisymmetric(pt, b, wb)
        ax.impconf = self
        self.axisymmetrics.append(ax)
        return ax


    def add_dimple(self, thetadeg, pt, a, b, wb):
        """Add a Dimple Imperfection (DI)

        Parameters
        ----------
        thetadeg : float
            Circumferential position of the dimple.
        a : float
            Circumferential half-wave length of the dimple.
        b : float
            Meridional half-wave length of the dimple.
        wb : float
            Imperfection amplitude.

        Returns
        -------
        d : :class:`.Dimple` object.

        """
        d = Dimple(thetadeg, pt, a, b, wb)
        d.impconf = self
        self.dimples.append(d)
        return d


    def add_lbmi(self, mode, scaling_factor):
        """Add a Linear Buckling Mode-shaped Imperfection (LBMI)

        Parameters
        ----------
        mode : int
            Mode number corresponding to this eigenvector.
        scaling_factor : float
            Amplitude of this eigenvector when applied as an imperfection.

        Returns
        -------
        lbmi : :class:`.LBMI` object.

        """
        lbmi = LBMI(mode, scaling_factor)
        lbmi.impconf = self
        self.lbmis.append(lbmi)
        return lbmi


    def add_measured_u3s_bottom_edge(self, thetadegs, u3s):
        r"""Add a measured uneven bottom edge

        Straightforward method to include measured data about the bottom edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect bottom edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_bottom_edge.add_measured_u3s(thetadegs, u3s)


    def add_measured_u3s_top_edge(self, thetadegs, u3s):
        r"""Add a measured uneven top edge

        Straightforward method to include measured data about the top edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect top edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_top_edge.add_measured_u3s(thetadegs, u3s)


    def add_msi(self, imp_ms='', scaling_factor=1., R_best_fit=None,
                H_measured=None, path=None, use_theta_z_format=True,
                rotatedeg=0., ignore_bot_h=True, ignore_top_h=True,
                stretch_H=False, c0=None,
                m0=None, n0=None, funcnum=None):
        r"""Add a Mid-Surface Imperfection (MSI)

        Also called geometric imperfection.

        If the imperfection is :ref:`already included in the database
        <tutorials_conecylDB>` only the corresponding entry ``imp_ms`` and
        the scaling factor need to be specified.

        If the imperfection is not in the database one can specify the
        full path for the file containing the imperfection, the measured
        radius and height, as detailed below.

        Parameters
        ----------
        imp_ms : str, optional
            Name of the imperfection in the database.
        scaling_factor : float, optional
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.
        R_best_fit : float, optional
            Best fit radius obtained with functions :func:`.best_fit_cylinder`
            or :func:`.best_fit_cone`.
        alphadeg_measured : float
            The semi-vertex angle of the measured sample (it is ``0.`` for a
            cylinder).
        H_measured : float, optional
            The total height of the measured test specimen, including eventual
            resin rings at the edges.
        path : str, optional
            Full path to the file containing the imperfection data.
        use_theta_z_format : bool, optional
            If the imperfection file is in the `\theta, Z, imp` format
            instead of the `X, Y, Z` format.
        rotatedeg : float, optional
            Rotation angle in degrees telling how much the imperfection
            pattern should be rotated about the `X_3` (or `Z`) axis.
        ignore_bot_h : float, optional
            Used to ignore nodes from the bottom resin ring. The default value
            ``True`` will use data from the bottom resin ring, if it exists.
        ignore_top_h : float, optional
            Used to ignore nodes from the top resin ring. The default value
            ``True`` will use data from the top resin ring, if it exists.
        stretch_H : bool, optional
            If the measured imperfection does not cover the whole height it
            will be stretched. If ``stretch_H==True``, ``ignore_bot_h`` and
            ``ignore_top_h`` are automatically set to ``False``.
        c0 : str or np.ndarray, optional
            The coefficients representing the imperfection pattern. If
            supplied will overwrite the imperfection data passed using the
            other parameters. For more details see :func:`.calc_c0`.
        m0 : int, optional
            Number of terms along the meridian (`z`) used to obtain ``c0``,
            see :func:`.calc_c0`.
        n0 : int, optional
            Number of terms along the circumference (`\theta`) used to obtain
            ``c0``, see :func:`.calc_c0`.
        funcnum : int, optional
            The base function used to obtain ``c0``, see :func:`.calc_c0`.

        Returns
        -------
        msi : :class:`.MSI` object.

        """
        msi = MSI()
        msi.impconf = self
        msi.imp_ms = imp_ms
        msi.scaling_factor = scaling_factor
        msi.use_theta_z_format = use_theta_z_format
        msi.R_best_fit = R_best_fit
        msi.H_measured = H_measured
        msi.path = path
        msi.rotatedeg = rotatedeg
        msi.ignore_bot_h = ignore_bot_h
        msi.ignore_top_h = ignore_top_h
        msi.stretch_H = stretch_H

        if c0 is not None and not (m0 and n0 and funcnum):
            raise ValueError('Parameter "c0" must be supplied with ' +
                             '"m0", "n0" and "funcnum"')
        elif c0 is not None and m0 and n0 and funcnum:
            if funcnum==1:
                size = 2
            elif funcnum==2:
                size = 2
            elif funcnum==3:
                size = 4
            else:
                raise ValueError('Invalid value for "funcnum"!')
            if isinstance(c0, str):
                if not os.path.isfile(c0):
                    raise ValueError('File {0} not found!'.format(c0))
                else:
                    c0 = np.loadtxt(c0)
                    if c0.ndim==1:
                        if c0.shape[0] != size*m0*n0:
                            raise ValueError(
                                'Invalid "c0" for the given "m0" and "n0"!')
                    else:
                        raise ValueError(
                                'Array for "c0" must be one-dimensional!')
        msi.c0 = c0
        msi.m0 = m0
        msi.n0 = n0
        msi.funcnum = funcnum
        self.msis.append(msi)
        return msi


    def add_pload(self, thetadeg, pt, pltotal, step=1):
        """Add a Perturbation Load

        Parameters
        ----------
        thetadeg : float
            Circumferential position.
        pt : float
            Normalized meridional position.
        pltotal : float
            The magnitude of the perturbation load (it is always applied
            normally to the shell surface).
        step : int
            The step in which the perturbation load will be included. In
            ``step=1`` the load is constant along the analysis while in
            ``step=2`` the load is incremented.

        Returns
        -------
        pload : :class:`.PLoad` object.

        """
        pload = PLoad(thetadeg, pt, pltotal, step)
        pload.impconf = self
        self.ploads.append(pload)
        return pload


    def add_shim_bottom_edge(self, thetadeg, thick, width):
        """Add a Shim to the bottom edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_bottom_edge)
        return shim


    def add_shim_top_edge(self, thetadeg, thick, width):
        """Add a Shim to the top edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_top_edge)
        return shim


    def add_ti(self, imp_thick, scaling_factor):
        """Add Thickness Imperfection (TI)

        The imperfection must be already included in the database (:ref:`check
        this tutorial <tutorials_conecylDB>`).

        Parameters
        ----------
        imp_thick : str
            Name of the thickness imperfection in the database.
        scaling_factor : float
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.

        Returns
        -------
        ti : :class:`.TI` object.

        """
        ti = TI()
        ti.impconf = self
        ti.imp_thick = imp_thick
        ti.scaling_factor = scaling_factor
        self.tis.append(ti)
        return ti


    def add_cutout(self, thetadeg, pt, d, drill_offset_deg=0.,
                   clearance_factor=0.75, numel_radial_edge=4,
                   prop_around_hole=None):
        r"""Add a cutout

        Parameters
        ----------
        thetadeg : float
            Circumferential position of the dimple.
        pt : float
            Normalized meridional position.
        d : float
            Diameter of the drilling machine.
        drill_offset_deg : float, optional
            Angular offset when the drilling is not normal to the shell
            surface. A positive offset means a positive rotation about the
            `\theta` axis, along the meridional plane.
        clearance_factor : float, optional
            Fraction of the diameter to apply as clearance around the cutout.
            This clearance is partitoned and meshed separately from the rest of
            the cone / cylinder.
        numel_radial_edge : int, optional
            Number of elements along the radial edges about the cutout center.
            This parameter affects the aspect ratio of the elements inside the
            cutout area.
        prop_around_hole : dict, optional
            Dictionary with keys:

            - radius : float
            - 'stack': list of floats
            - 'plyts': list of floats
            - 'mat_names': list of strings

            Example::

                prop_around_holes = {
                    'radius': 10.,
                    'stack': [0, 90, 0],
                    'plyts': [0.125, 0.125, 0.125],
                    'mat_names': ['Alum', 'Alum', 'Alum'],
                }

            .. note:: `mat_names` must be a list of materials already created in
                      the current model in Abaqus

        Returns
        -------
        cutout : :class:`.Cutout` object.

        """
        cutout = Cutout(thetadeg, pt, d, drill_offset_deg, clearance_factor,
                numel_radial_edge, prop_around_hole)
        cutout.impconf = self
        self.cutouts.append(cutout)
        return cutout


    def add_ppi(self, info, extra_height):
        """Adds Ply Piece Imperfection (PPI)

        There can be only one of these, so calling this function overrides the
        previous imperfection, if any.
        Note: Applicable for cones only!

        Parameters
        ----------
        info : list
            List of dictionaries with info about the layup of this cone.
            See :class:`.PPI` for more details
        extra_height : float
            Extra height above and below the cone height (`cc.H`) to consider
            in the ply placement model.
        Returns
        -------
        ppi : :class:`.PPI` object.
        """
        if self.ppi is not None:
            warn('PPI object already set, overriding...')
        self.ppi = PPI(info, extra_height)
        self.ppi.impconf = self
        return self.ppi


    def add_ffi(self, nominal_vf, E_matrix, nu_matrix, use_ti, global_sf=None):
        """Adds Fiber Fraction Imperfection (FFI)

        There can be only one of these, so calling this function overrides the
        previous imperfection, if any.

        Parameters
        ----------
        nominal_vf : float
            Nominal fiber volume fraction of the material
        E_matrix : float
            Young's modulus of the matrix material
        nu_matrix : float
            Poisson's ratio of the matrix material
        use_ti : bool
            If ``True``, create varying material properties according to the
            thickness imperfection data (if present).
        global_sf : float or ``None``
            Global scaling factor to apply to the material thickness.
            Set to ``None`` to disable. The global scaling may be overridden
            by a thickness imperfection, if ``use_ti`` (see above) is ``True``.

        Returns
        -------
        ffi : :class:`.FFI` object.

        """
        if self.ffi is not None:
            warn('FFI object already set, overriding...')
        self.ffi = FFI(nominal_vf, E_matrix, nu_matrix, use_ti, global_sf)
        self.ffi.impconf = self
        return self.ffi


    def rebuild(self):
        # TODO: Reduce the amount of code duplication?
        self.imperfections = []
        i = -1
        # uneven bottom edge
        ube = self.uneven_bottom_edge
        i += 1
        ube.index = i
        ube.rebuild()
        self.imperfections.append(ube)
        # uneven top edge
        ute = self.uneven_top_edge
        i += 1
        ute.index = i
        ute.rebuild()
        self.imperfections.append(ute)
        # ploads
        for pload in self.ploads:
            i += 1
            pload.index = i
            pload.rebuild()
            self.imperfections.append(pload)
        # dimples
        for sb in self.dimples:
            i += 1
            sb.index = i
            sb.rebuild()
            self.imperfections.append(sb)
        # axisymmetrics
        for ax in self.axisymmetrics:
            i += 1
            ax.index = i
            ax.rebuild()
            self.imperfections.append(ax)
        # linear buckling mode-shaped imperfection (LBMI)
        for lbmi in self.lbmis:
            i += 1
            lbmi.index = i
            lbmi.rebuild()
            self.imperfections.append(lbmi)
        # cutout
        for cutout in self.cutouts:
            i += 1
            cutout.index = i
            cutout.rebuild()
            self.imperfections.append(cutout)
        # ply piece imperfection
        if self.ppi is not None:
            i += 1
            self.ppi.index = i
            self.ppi.rebuild()
            self.imperfections.append(self.ppi)
        # fiber fraction imperfection
        if self.ffi is not None:
            i += 1
            self.ffi.index = i
            self.ffi.rebuild()
            self.imperfections.append(self.ffi)
        # thickness imperfection (TI)
        for ti in self.tis:
            i += 1
            ti.index = i
            ti.rebuild()
            self.imperfections.append(ti)
        # mid-surface imperfection (MSI)
        for msi in self.msis:
            i += 1
            msi.index = i
            msi.rebuild()
            self.imperfections.append(msi)
        # name
        if self.rename:
            self.name = ('PLs_%02d_dimples_%02d_axisym_%02d' +\
                        '_lbmis_%02d_MSIs_%02d_TIs_%02d') % \
                        (len(self.ploads), len(self.dimples),
                          len(self.axisymmetrics), len(self.lbmis),
                          len(self.msis), len(self.tis))


    def create(self):
        for imp in self.imperfections:
            valid = True
            for pt in imp.pts:
                if pt<0 or pt>1.:
                    valid = False
                    break
            if imp and valid:
                imp.create()
Esempio n. 2
0
class ImpConf(object):
    """Imperfection Configuration

    Created by default as one attribute of the :class:`.ConeCyl` object,
    accessed through::

        cc = ConeCyl()
        impconf = cc.impconf

    If one has the ``impconf`` object and wants to access the corresponding
    :class:`ConeCyl` object, the attribute ``conecyl`` can be used as
    examplified below.  Note that if no :class:`.ConeCyl` is assigned to this
    imperfection configuration a ``None`` value will be obtained::

        cc = impconf.conecyl

    The imperfections are grouped in the attributes detailed below.

    ================== ======================================================
    Attributes         Description
    ================== ======================================================
    uneven_bottom_edge :class:`.UnevenBottomEdge` object
    uneven_top_edge    :class:`.UnevenTopEdge` object
    ploads             ``list`` of :class:`.PLoad` (Perturbation Load) objects
    dimples            ``list`` of :class:`.Dimple` (Dimple Imperfection)
                       objects
    axisymmetrics      ``list`` of :class:`.Axisymmetric` (Axisymmetric
                       Imperfection) objects
    lbmis              ``list`` of :class:`.LBMI` (Linear Buckling Mode-Shaped
                       Imperfection) objects
    tis                ``list`` of :class:`.TI` (Thickness Imperfection)
                       objects
    msis               ``list`` of :class:`.MSI` (Mid-Surface Imperfection)
                       objects
    cutouts            ``list`` of :class:`.Cutout` objects
    ppi                :class:`.PPI` (Ply Piece Imperfection) object or
                       ``None`` if not set
    ffi                :class:`.FFI` (Fiber Fraction Imperfection) object or
                       ``None`` if not set
    ================== ======================================================

    """
    def __init__(self):
        self.uneven_bottom_edge = UnevenBottomEdge()
        self.uneven_bottom_edge.impconf = self
        self.uneven_top_edge = UnevenTopEdge()
        self.uneven_top_edge.impconf = self
        self.imperfections = []
        self.ploads = []
        self.cb = []
        self.dimples = []
        self.axisymmetrics = []
        self.lbmis = []
        self.msis = []
        self.tis = []
        self.cutouts = []
        self.ppi = None
        self.ffi = None
        self.rename = True
        self.name = ''
        self.conecyl = None

    def __setstate__(self, attrs):
        # Called during unpickling (i.e. loading)
        # Calling __init__ prevents problems with missing attributes,
        # when loading from older versions
        self.__init__()
        self.__dict__.update(attrs)

    def add_axisymmetric(self, pt, b, wb):
        """Add an Axisymmetric Imperfection (AI)

        Parameters
        ----------
        pt : float
            Normalized meridional position.
        b : float
            Half-wave length.
        wb : float
            Imperfection amplitude (amplitude of the half-wave).

        Returns
        -------
        ax : :class:`.Axisymmetric` object.

        """
        ax = Axisymmetric(pt, b, wb)
        ax.impconf = self
        self.axisymmetrics.append(ax)
        return ax

    def add_dimple(self, thetadeg, pt, a, b, wb):
        """Add a Dimple Imperfection (DI)

        Parameters
        ----------
        thetadeg : float
            Circumferential position of the dimple.
        a : float
            Circumferential half-wave length of the dimple.
        b : float
            Meridional half-wave length of the dimple.
        wb : float
            Imperfection amplitude.

        Returns
        -------
        d : :class:`.Dimple` object.

        """
        d = Dimple(thetadeg, pt, a, b, wb)
        d.impconf = self
        self.dimples.append(d)
        return d

    def add_lbmi(self, mode, scaling_factor):
        """Add a Linear Buckling Mode-shaped Imperfection (LBMI)

        Parameters
        ----------
        mode : int
            Mode number corresponding to this eigenvector.
        scaling_factor : float
            Amplitude of this eigenvector when applied as an imperfection.

        Returns
        -------
        lbmi : :class:`.LBMI` object.

        """
        lbmi = LBMI(mode, scaling_factor)
        lbmi.impconf = self
        self.lbmis.append(lbmi)
        return lbmi

    def add_measured_u3s_bottom_edge(self, thetadegs, u3s):
        r"""Add a measured uneven bottom edge

        Straightforward method to include measured data about the bottom edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect bottom edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_bottom_edge.add_measured_u3s(thetadegs, u3s)

    def add_measured_u3s_top_edge(self, thetadegs, u3s):
        r"""Add a measured uneven top edge

        Straightforward method to include measured data about the top edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect top edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_top_edge.add_measured_u3s(thetadegs, u3s)

    def add_msi(self,
                imp_ms='',
                scaling_factor=1.,
                R_best_fit=None,
                H_measured=None,
                path=None,
                use_theta_z_format=True,
                rotatedeg=0.,
                ignore_bot_h=True,
                ignore_top_h=True,
                stretch_H=False,
                c0=None,
                m0=None,
                n0=None,
                funcnum=None):
        r"""Add a Mid-Surface Imperfection (MSI)

        Also called geometric imperfection.

        If the imperfection is :ref:`already included in the database
        <tutorials_conecylDB>` only the corresponding entry ``imp_ms`` and
        the scaling factor need to be specified.

        If the imperfection is not in the database one can specify the
        full path for the file containing the imperfection, the measured
        radius and height, as detailed below.

        Parameters
        ----------
        imp_ms : str, optional
            Name of the imperfection in the database.
        scaling_factor : float, optional
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.
        R_best_fit : float, optional
            Best fit radius obtained with functions :func:`.best_fit_cylinder`
            or :func:`.best_fit_cone`.
        alphadeg_measured : float
            The semi-vertex angle of the measured sample (it is ``0.`` for a
            cylinder).
        H_measured : float, optional
            The total height of the measured test specimen, including eventual
            resin rings at the edges.
        path : str, optional
            Full path to the file containing the imperfection data.
        use_theta_z_format : bool, optional
            If the imperfection file is in the `\theta, Z, imp` format
            instead of the `X, Y, Z` format.
        rotatedeg : float, optional
            Rotation angle in degrees telling how much the imperfection
            pattern should be rotated about the `X_3` (or `Z`) axis.
        ignore_bot_h : float, optional
            Used to ignore nodes from the bottom resin ring. The default value
            ``True`` will use data from the bottom resin ring, if it exists.
        ignore_top_h : float, optional
            Used to ignore nodes from the top resin ring. The default value
            ``True`` will use data from the top resin ring, if it exists.
        stretch_H : bool, optional
            If the measured imperfection does not cover the whole height it
            will be stretched. If ``stretch_H==True``, ``ignore_bot_h`` and
            ``ignore_top_h`` are automatically set to ``False``.
        c0 : str or np.ndarray, optional
            The coefficients representing the imperfection pattern. If
            supplied will overwrite the imperfection data passed using the
            other parameters. For more details see :func:`.calc_c0`.
        m0 : int, optional
            Number of terms along the meridian (`z`) used to obtain ``c0``,
            see :func:`.calc_c0`.
        n0 : int, optional
            Number of terms along the circumference (`\theta`) used to obtain
            ``c0``, see :func:`.calc_c0`.
        funcnum : int, optional
            The base function used to obtain ``c0``, see :func:`.calc_c0`.

        Returns
        -------
        msi : :class:`.MSI` object.

        """
        msi = MSI()
        msi.impconf = self
        msi.imp_ms = imp_ms
        msi.scaling_factor = scaling_factor
        msi.use_theta_z_format = use_theta_z_format
        msi.R_best_fit = R_best_fit
        msi.H_measured = H_measured
        msi.path = path
        msi.rotatedeg = rotatedeg
        msi.ignore_bot_h = ignore_bot_h
        msi.ignore_top_h = ignore_top_h
        msi.stretch_H = stretch_H

        if c0 is not None and not (m0 and n0 and funcnum):
            raise ValueError('Parameter "c0" must be supplied with ' +
                             '"m0", "n0" and "funcnum"')
        elif c0 is not None and m0 and n0 and funcnum:
            if funcnum == 1:
                size = 2
            elif funcnum == 2:
                size = 2
            elif funcnum == 3:
                size = 4
            else:
                raise ValueError('Invalid value for "funcnum"!')
            if isinstance(c0, str):
                if not os.path.isfile(c0):
                    raise ValueError('File {0} not found!'.format(c0))
                else:
                    c0 = np.loadtxt(c0)
                    if c0.ndim == 1:
                        if c0.shape[0] != size * m0 * n0:
                            raise ValueError(
                                'Invalid "c0" for the given "m0" and "n0"!')
                    else:
                        raise ValueError(
                            'Array for "c0" must be one-dimensional!')
        msi.c0 = c0
        msi.m0 = m0
        msi.n0 = n0
        msi.funcnum = funcnum
        self.msis.append(msi)
        return msi

    def add_pload(self, thetadeg, pt, pltotal, step=1):
        """Add a Perturbation Load

        Parameters
        ----------
        thetadeg : float
            Circumferential position.
        pt : float
            Normalized meridional position.
        pltotal : float
            The magnitude of the perturbation load (it is always applied
            normally to the shell surface).
        step : int
            The step in which the perturbation load will be included. In
            ``step=1`` the load is constant along the analysis while in
            ``step=2`` the load is incremented.

        Returns
        -------
        pload : :class:`.PLoad` object.

        """
        pload = PLoad(thetadeg, pt, pltotal, step)
        pload.impconf = self
        self.ploads.append(pload)
        return pload

    def add_cb(self, thetadeg, pt, cbtotal, step=1):
        """Add a Constant Amplitude Perturbation Buckle Imperfection

        Parameters
        ----------
        thetadeg : float
            Circumferential position.
        pt : float
            Normalized meridional position.
        cbtotal : float
            The magnitude of the constant buckle (it is always applied
            normally to the shell surface).
        step : int
            The step in which the constant buckle will be included. In
            ``step=1`` the load is constant along the analysis while in
            ``step=2`` the load is incremented.

        Returns
        -------
        cb : :class:`.CBamp` object.

        """
        cb = CBamp(thetadeg, pt, cbtotal, step)
        cb.impconf = self
        self.cb.append(cb)
        return cb

    def add_shim_bottom_edge(self, thetadeg, thick, width):
        """Add a Shim to the bottom edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_bottom_edge)
        return shim

    def add_shim_top_edge(self, thetadeg, thick, width):
        """Add a Shim to the top edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_top_edge)
        return shim

    def add_ti(self, imp_thick, scaling_factor):
        """Add Thickness Imperfection (TI)

        The imperfection must be already included in the database (:ref:`check
        this tutorial <tutorials_conecylDB>`).

        Parameters
        ----------
        imp_thick : str
            Name of the thickness imperfection in the database.
        scaling_factor : float
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.

        Returns
        -------
        ti : :class:`.TI` object.

        """
        ti = TI()
        ti.impconf = self
        ti.imp_thick = imp_thick
        ti.scaling_factor = scaling_factor
        self.tis.append(ti)
        return ti

    def add_cutout(self,
                   thetadeg,
                   pt,
                   d,
                   drill_offset_deg=0.,
                   clearance_factor=0.75,
                   numel_radial_edge=4,
                   prop_around_cutout=None):
        r"""Add a cutout

        Parameters
        ----------
        thetadeg : float
            Circumferential position of the dimple.
        pt : float
            Normalized meridional position.
        d : float
            Diameter of the drilling machine.
        drill_offset_deg : float, optional
            Angular offset when the drilling is not normal to the shell
            surface. A positive offset means a positive rotation about the
            `\theta` axis, along the meridional plane.
        clearance_factor : float, optional
            Fraction of the diameter to apply as clearance around the cutout.
            This clearance is partitoned and meshed separately from the rest of
            the cone / cylinder.
        numel_radial_edge : int, optional
            Number of elements along the radial edges about the cutout center.
            This parameter affects the aspect ratio of the elements inside the
            cutout area.
        prop_around_cutout : dict, optional
            Dictionary with keys:

            - 'mode' : str ('radius' or 'partition')
            - 'radius' : float
            - 'stack': list of floats
            - 'plyts': list of floats
            - 'mat_names': list of strings
            .

            Examples:

            - Defining a property with ``'mode'='radius'``::

                prop_around_cutout = {
                    'mode': 'radius',
                    'radius': 10.,
                    'stack': [0, 90, 0],
                    'plyts': [0.125, 0.125, 0.125],
                    'mat_names': ['Alum', 'Alum', 'Alum'],
                }

            - Defining a property with ``'mode'='partition'``::

                prop_around_cutout = {
                    'mode': 'partition',
                    'stack': [0, 90, 0],
                    'plyts': [0.125, 0.125, 0.125],
                    'mat_names': ['Alum', 'Alum', 'Alum'],
                }

            .. note:: ``mat_names`` must be a list of materials already created in
                      the current model in Abaqus

        Returns
        -------
        cutout : :class:`.Cutout` object.

        """
        cutout = Cutout(thetadeg, pt, d, drill_offset_deg, clearance_factor,
                        numel_radial_edge, prop_around_cutout)
        cutout.impconf = self
        self.cutouts.append(cutout)
        return cutout

    def add_ppi(self, info, extra_height):
        """Adds Ply Piece Imperfection (PPI)

        There can be only one of these, so calling this function overrides the
        previous imperfection, if any.
        Note: Applicable for cones only!

        Parameters
        ----------
        info : list
            List of dictionaries with info about the layup of this cone.
            See :class:`.PPI` for more details
        extra_height : float
            Extra height above and below the cone height (`cc.H`) to consider
            in the ply placement model.
        Returns
        -------
        ppi : :class:`.PPI` object.
        """
        if self.ppi is not None:
            warn('PPI object already set, overriding...')
        self.ppi = PPI(info, extra_height)
        self.ppi.impconf = self
        return self.ppi

    def add_ffi(self, nominal_vf, E_matrix, nu_matrix, use_ti, global_sf=None):
        """Adds Fiber Fraction Imperfection (FFI)

        There can be only one of these, so calling this function overrides the
        previous imperfection, if any.

        Parameters
        ----------
        nominal_vf : float
            Nominal fiber volume fraction of the material
        E_matrix : float
            Young's modulus of the matrix material
        nu_matrix : float
            Poisson's ratio of the matrix material
        use_ti : bool
            If ``True``, create varying material properties according to the
            thickness imperfection data (if present).
        global_sf : float or ``None``
            Global scaling factor to apply to the material thickness.
            Set to ``None`` to disable. The global scaling may be overridden
            by a thickness imperfection, if ``use_ti`` (see above) is ``True``.

        Returns
        -------
        ffi : :class:`.FFI` object.

        """
        if self.ffi is not None:
            warn('FFI object already set, overriding...')
        self.ffi = FFI(nominal_vf, E_matrix, nu_matrix, use_ti, global_sf)
        self.ffi.impconf = self
        return self.ffi

    def rebuild(self):
        # TODO: Reduce the amount of code duplication?
        self.imperfections = []
        i = -1
        # uneven bottom edge
        ube = self.uneven_bottom_edge
        i += 1
        ube.index = i
        ube.rebuild()
        self.imperfections.append(ube)
        # uneven top edge
        ute = self.uneven_top_edge
        i += 1
        ute.index = i
        ute.rebuild()
        self.imperfections.append(ute)
        # ploads
        for pload in self.ploads:
            i += 1
            pload.index = i
            pload.rebuild()
            self.imperfections.append(pload)
        # CSBI
        for cb in self.cb:
            i += 1
            cb.index = i
            cb.rebuild()
            self.imperfections.append(cb)
        # dimples
        for sb in self.dimples:
            i += 1
            sb.index = i
            sb.rebuild()
            self.imperfections.append(sb)
        # axisymmetrics
        for ax in self.axisymmetrics:
            i += 1
            ax.index = i
            ax.rebuild()
            self.imperfections.append(ax)
        # linear buckling mode-shaped imperfection (LBMI)
        for lbmi in self.lbmis:
            i += 1
            lbmi.index = i
            lbmi.rebuild()
            self.imperfections.append(lbmi)
        # cutout
        for cutout in self.cutouts:
            i += 1
            cutout.index = i
            cutout.rebuild()
            self.imperfections.append(cutout)
        # ply piece imperfection
        if self.ppi is not None:
            i += 1
            self.ppi.index = i
            self.ppi.rebuild()
            self.imperfections.append(self.ppi)
        # fiber fraction imperfection
        if self.ffi is not None:
            i += 1
            self.ffi.index = i
            self.ffi.rebuild()
            self.imperfections.append(self.ffi)
        # thickness imperfection (TI)
        for ti in self.tis:
            i += 1
            ti.index = i
            ti.rebuild()
            self.imperfections.append(ti)
        # mid-surface imperfection (MSI)
        for msi in self.msis:
            i += 1
            msi.index = i
            msi.rebuild()
            self.imperfections.append(msi)
        # name
        if self.rename:
            self.name = ('PLs_%02d_dimples_%02d_axisym_%02d' +\
                        '_lbmis_%02d_MSIs_%02d_TIs_%02d') % \
                        (len(self.ploads), len(self.dimples),
                          len(self.axisymmetrics), len(self.lbmis),
                          len(self.msis), len(self.tis))

    def create(self):
        for imp in self.imperfections:
            valid = True
            for pt in imp.pts:
                if pt < 0 or pt > 1.:
                    valid = False
                    break
            if imp and valid:
                imp.create()
Esempio n. 3
0
class ImpConf(object):
    """Imperfection Configuration

    Created by default as one attribute of the :class:`.ConeCyl` object,
    accessed through::

        cc = ConeCyl()
        impconf = cc.impconf

    If one has the ``impconf`` object and wants to access the corresponding
    :class:`ConeCyl` object, the attribute ``conecyl`` can be used as
    examplified below.  Note that if no :class:`.ConeCyl` is assigned to this
    imperfection configuration a ``None`` value will be obtained::

        cc = impconf.conecyl

    The imperfections are grouped in the attributes detailed below.

    ================== ======================================================
    Attributes         Description
    ================== ======================================================
    uneven_bottom_edge :class:`.UnevenBottomEdge` object
    uneven_top_edge    :class:`.UnevenTopEdge` object
    ploads             ``list`` of :class:`.PLoad` (Perturbation Load) objects
    dimples            ``list`` of :class:`.Dimple` (Dimple Imperfection)
                       objects
    axisymmetrics      ``list`` of :class:`.Axisymmetric` (Axisymmetric
                       Imperfection) objects
    lbmis              ``list`` of :class:`.LBMI` (Linear Buckling Mode-Shaped
                       Imperfection) objects
    tis                ``list`` of :class:`.TI` (Thickness Imperfection)
                       objects
    msis               ``list`` of :class:`.MSI` (Mid-Surface Imperfection)
                       objects
    ================== ======================================================

    """
    def __init__(self):
        self.uneven_bottom_edge = UnevenBottomEdge()
        self.uneven_bottom_edge.impconf = self
        self.uneven_top_edge = UnevenTopEdge()
        self.uneven_top_edge.impconf = self
        self.imperfections = []
        self.ploads = []
        self.dimples = []
        self.axisymmetrics = []
        self.lbmis = []
        self.msis = []
        self.tis = []
        self.rename = True
        self.name = ''
        self.conecyl = None

    def __setstate__(self, attrs):
        # Called during unpickling (i.e. loading)
        # Calling __init__ prevents problems with missing attributes,
        # when loading from older versions
        self.__init__()
        self.__dict__.update(attrs)

    def add_axisymmetric(self, pt, b, wb):
        """Adds an Axisymmetric Imperfection (AI)

        Parameters
        ----------
        pt : float
            Normalized meridional position.
        b : float
            Half-wave length.
        wb : float
            Imperfection amplitude (amplitude of the half-wave).

        Returns
        -------
        ax : :class:`.Axisymmetric` object.

        """
        ax = Axisymmetric(pt, b, wb)
        ax.impconf = self
        self.axisymmetrics.append(ax)
        return ax

    def add_dimple(self, thetadeg, pt, a, b, wb):
        """Adds a Dimple Imperfection (DI)

        Parameters
        ----------
        thetadeg : float
            Circumferential position of the dimple.
        a : float
            Circumferential half-wave length of the dimple.
        b : float
            Meridional half-wave length of the dimple.
        wb : float
            Imperfection amplitude.

        Returns
        -------
        d : :class:`.Dimple` object.

        """
        d = Dimple(thetadeg, pt, a, b, wb)
        d.impconf = self
        self.dimples.append(d)
        return d

    def add_lbmi(self, mode, scaling_factor):
        """Adds a Linear Buckling Mode-shaped Imperfection (LBMI)

        Parameters
        ----------
        mode : int
            Mode number corresponding to this eigenvector.
        scaling_factor : float
            Amplitude of this eigenvector when applied as an imperfection.

        Returns
        -------
        lbmi : :class:`.LBMI` object.

        """
        lbmi = LBMI(mode, scaling_factor)
        lbmi.impconf = self
        self.lbmis.append(lbmi)
        return lbmi

    def add_measured_u3s_bottom_edge(self, thetadegs, u3s):
        r"""Adds a measured uneven bottom edge

        Straightforward method to include measured data about the bottom edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect bottom edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_bottom_edge.add_measured_u3s(thetadegs, u3s)

    def add_measured_u3s_top_edge(self, thetadegs, u3s):
        r"""Adds a measured uneven top edge

        Straightforward method to include measured data about the top edge
        imperfection.

        Adopts the coordinate system of :ref:`this figure <figure_conecyl>`
        when defining the `u_3` displacements for each `\theta` value.

        The edge imperfection that actually goes for each node is a linear
        interpolation of the measured values.

        Parameters
        ----------
        thetadegs : list
            The circumferential positions where the imperfect top edge was
            measured, in degrees.
        u3s : list
            The measured imperfections representing displacements along the
            `X_3` axis :ref:`of the adopted model <figure_conecyl>`.

        """
        self.uneven_top_edge.add_measured_u3s(thetadegs, u3s)

    def add_msi(self,
                imp_ms='',
                scaling_factor=1.,
                R_best_fit=None,
                H_measured=None,
                path=None,
                use_theta_z_format=True,
                rotatedeg=0.,
                ignore_bot_h=True,
                ignore_top_h=True,
                stretch_H=False,
                c0=None,
                m0=None,
                n0=None,
                funcnum=None):
        r"""Adds a Mid-Surface Imperfection (MSI)

        Also called geometric imperfection.

        If the imperfection is :ref:`already included in the database
        <tutorials_conecylDB>` only the corresponding entry ``imp_ms`` and
        the scaling factor need to be specified.

        If the imperfection is not in the database one can specify the
        full path for the file containing the imperfection, the measured
        radius and height, as detailed below.

        Parameters
        ----------
        imp_ms : str, optional
            Name of the imperfection in the database.
        scaling_factor : float, optional
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.
        R_best_fit : float, optional
            Best fit radius obtained with functions :func:`.best_fit_cylinder`
            or :func:`.best_fit_cone`.
        alphadeg_measured : float
            The semi-vertex angle of the measured sample (it is ``0.`` for a
            cylinder).
        H_measured : float, optional
            The total height of the measured test specimen, including eventual
            resin rings at the edges.
        path : str, optional
            Full path to the file containing the imperfection data.
        use_theta_z_format : bool, optional
            If the imperfection file is in the `\theta, Z, imp` format
            instead of the `X, Y, Z` format.
        rotatedeg : float, optional
            Rotation angle in degrees telling how much the imperfection
            pattern should be rotated about the `X_3` (or `Z`) axis.
        ignore_bot_h : float, optional
            Used to ignore nodes from the bottom resin ring. The default value
            ``True`` will use data from the bottom resin ring, if it exists.
        ignore_top_h : float, optional
            Used to ignore nodes from the top resin ring. The default value
            ``True`` will use data from the top resin ring, if it exists.
        stretch_H : bool, optional
            If the measured imperfection does not cover the whole height it
            will be stretched. If ``stretch_H==True``, ``ignore_bot_h`` and
            ``ignore_top_h`` are automatically set to ``False``.
        c0 : str or np.ndarray, optional
            The coefficients representing the imperfection pattern. If
            supplied will overwrite the imperfection data passed using the
            other parameters. For more details see :func:`.calc_c0`.
        m0 : int, optional
            Number of terms along the meridian (`z`) used to obtain ``c0``,
            see :func:`.calc_c0`.
        n0 : int, optional
            Number of terms along the circumference (`\theta`) used to obtain
            ``c0``, see :func:`.calc_c0`.
        funcnum : int, optional
            The base function used to obtain ``c0``, see :func:`.calc_c0`.

        Returns
        -------
        msi : :class:`.MSI` object.

        """
        msi = MSI()
        msi.impconf = self
        msi.imp_ms = imp_ms
        msi.scaling_factor = scaling_factor
        msi.use_theta_z_format = use_theta_z_format
        msi.R_best_fit = R_best_fit
        msi.H_measured = H_measured
        msi.path = path
        msi.rotatedeg = rotatedeg
        msi.ignore_bot_h = ignore_bot_h
        msi.ignore_top_h = ignore_top_h
        msi.stretch_H = stretch_H

        if c0 is not None and not (m0 and n0 and funcnum):
            raise ValueError('Parameter "c0" must be supplied with ' +
                             '"m0", "n0" and "funcnum"')
        elif c0 is not None and m0 and n0 and funcnum:
            if funcnum == 1:
                size = 2
            elif funcnum == 2:
                size = 2
            elif funcnum == 3:
                size = 4
            else:
                raise ValueError('Invalid value for "funcnum"!')
            if isinstance(c0, str):
                if not os.path.isfile(c0):
                    raise ValueError('File {0} not found!'.format(c0))
                else:
                    c0 = np.loadtxt(c0)
                    if c0.ndim == 1:
                        if c0.shape[0] != size * m0 * n0:
                            raise ValueError(
                                'Invalid "c0" for the given "m0" and "n0"!')
                    else:
                        raise ValueError(
                            'Array for "c0" must be one-dimensional!')
        msi.c0 = c0
        msi.m0 = m0
        msi.n0 = n0
        msi.funcnum = funcnum
        self.msis.append(msi)
        return msi

    def add_pload(self, thetadeg, pt, pltotal, step=1):
        """Adds a Perturbation Load

        Parameters
        ----------
        thetadeg : float
            Circumferential position.
        pt : float
            Normalized meridional position.
        pltotal : float
            The magnitude of the perturbation load (it is always applied
            normally to the shell surface).
        step : int
            The step in which the perturbation load will be included. In
            ``step=1`` the load is constant along the analysis while in
            ``step=2`` the load is incremented.

        Returns
        -------
        pload : :class:`.PLoad` object.

        """
        pload = PLoad(thetadeg, pt, pltotal, step)
        pload.impconf = self
        self.ploads.append(pload)
        return pload

    def add_shim_bottom_edge(self, thetadeg, thick, width):
        """Adds a Shim to the bottom edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_bottom_edge)
        return shim

    def add_shim_top_edge(self, thetadeg, thick, width):
        """Adds a Shim to the top edge

        Parameters
        ----------
        thetadeg : float
            Circumferential position where the shim starts.
        thick : float
            Thickness of the shim.
        width : float
            Perimetrical width of the shim (along the shell perimeter).

        Returns
        -------
        shim : :class:`.Shim` object.

        """
        shim = Shim(thetadeg, thick, width, self.uneven_top_edge)
        return shim

    def add_ti(self, imp_thick, scaling_factor):
        """Adds Thickness Imperfection (TI)

        The imperfection must be already included in the database (:ref:`check
        this tutorial <tutorials_conecylDB>`).

        Parameters
        ----------
        imp_thick : str
            Name of the thickness imperfection in the database.
        scaling_factor : float
            Scaling factor applied to the original imperfection amplitude,
            usually to allow imperfection sensitivity studies.

        Returns
        -------
        ti : :class:`.TI` object.
        """
        ti = TI()
        ti.impconf = self
        ti.imp_thick = imp_thick
        ti.scaling_factor = scaling_factor
        self.tis.append(ti)
        return ti

    def rebuild(self):
        self.imperfections = []
        i = -1
        # uneven bottom edge
        ube = self.uneven_bottom_edge
        i += 1
        ube.index = i
        ube.rebuild()
        self.imperfections.append(ube)
        # uneven top edge
        ute = self.uneven_top_edge
        i += 1
        ute.index = i
        ute.rebuild()
        self.imperfections.append(ute)
        # ploads
        for pload in self.ploads:
            i += 1
            pload.index = i
            pload.rebuild()
            self.imperfections.append(pload)
        # dimples
        for sb in self.dimples:
            i += 1
            sb.index = i
            sb.rebuild()
            self.imperfections.append(sb)
        # axisymmetrics
        for ax in self.axisymmetrics:
            i += 1
            ax.index = i
            ax.rebuild()
            self.imperfections.append(ax)
        # linear buckling mode-shaped imperfection (LBMI)
        for lbmi in self.lbmis:
            i += 1
            lbmi.index = i
            lbmi.rebuild()
            self.imperfections.append(lbmi)
        # mid-surface imperfection (MSI)
        for msi in self.msis:
            i += 1
            msi.index = i
            msi.rebuild()
            self.imperfections.append(msi)
        # thickness imperfection (TI)
        for ti in self.tis:
            i += 1
            ti.index = i
            ti.rebuild()
            self.imperfections.append(ti)
        # name
        if self.rename:
            self.name = ('PLs_%02d_dimples_%02d_axisym_%02d' +\
                        '_lbmis_%02d_MSIs_%02d_TIs_%02d') % \
                        (len(self.ploads), len(self.dimples),
                          len(self.axisymmetrics), len(self.lbmis),
                          len(self.msis), len(self.tis))

    def create(self):
        for imp in self.tis:
            if imp:
                imp.create()
        for imp in self.msis:
            if imp:
                imp.create()
        for imp in self.imperfections:
            valid = True
            for pt in imp.pts:
                if pt < 0 or pt > 1.:
                    valid = False
                    break
            if imp and valid:
                imp.create()