rpw.utils

Coerce / Type Casting

Type Casting Utilities

rpw.utils.coerce.to_category(category_reference, fuzzy=True)

Coerces a category, category name or category id to a BuiltInCategory.

>>> from rpw.utils.coerce import to_category
>>> to_category('OST_Walls')
BuiltInCategory.OST_Walls
>>> to_category('Walls')
BuiltInCategory.OST_Walls
>>> to_category(BuiltInCategory.OST_Walls)
BuiltInCategory.OST_Walls
Parameters:cateagory_reference ([DB.BuiltInCategory, str, CategoryId]) – Category Reference or Name
Returns:BuiltInCategory
Return type:[BuiltInCategory]
rpw.utils.coerce.to_category_id(category_reference, fuzzy=True)

Coerces a category, category name or category id to a Category Id.

>>> from rpw.utils.coerce import to_category_id
>>> to_category_id('OST_Walls')
<ElementId>
>>> to_category_id('Wall')
<ElementId>
>>> to_category_id(BuiltInCategory.OST_Walls)
<ElementId>
Parameters:cateagory_reference ([DB.BuiltInCategory, str, CategoryId]) – Category Reference or Name
Returns:ElementId of Category
Return type:[DB.ElementId]
rpw.utils.coerce.to_class(class_reference)

Coerces a class or class reference to a Class.

>>> from rpw.utils.coerce import to_class
>>> to_class('Wall')
[ DB.Wall ]
>>> to_class(Wall)
[ DB.Wall ]
Parameters:class_reference ([DB.Wall, str]) – Class Reference or class name
Returns:Class
Return type:[type]
rpw.utils.coerce.to_element(element_reference, doc=Document)

Same as to_elements but for a single object

rpw.utils.coerce.to_element_id(element_reference)

Coerces Element References (Element, ElementId, ...) to Element Id

>>> from rpw.utils.coerce import to_element_id
>>> to_element_id(SomeElement)
<Element Id>
rpw.utils.coerce.to_element_ids(element_references)

Coerces an element or list of elements into element ids. Elements remain unchanged. This will always return a list, even if only one element is passed.

>>> from rpw.utils.coerce import to_element_ids
>>> to_element_ids(DB.Element)
[ DB.ElementId ]
>>> to_element_ids(20001)
[ DB.ElementId ]
>>> to_element_ids([20001, 20003])
[ DB.ElementId, DB.ElementId ]
Parameters:elements (DB.Element) – Iterable list (list or set) or single of Element, int.
Returns:List of Element Ids.
Return type:[DB.ElementId, ... ]
rpw.utils.coerce.to_elements(element_references, doc=Document)

Coerces element reference (int, or ElementId) into DB.Element. Remains unchanged if it’s already DB.Element. Accepts single object or lists.

>>> from rpw.utils.coerce import to_elements
>>> to_elements(DB.ElementId)
[ DB.Element ]
>>> to_elements(20001)
[ DB.Element ]
>>> to_elements([20001, 20003])
[ DB.Element, DB.Element ]
Parameters:element_references ([DB.ElementId, int, DB.Element]) – Element Reference, single or list
Returns:Elements
Return type:[DB.Element]
rpw.utils.coerce.to_iterable(item_or_iterable)

Ensures input is iterable

>>> from rpw.utils.coerce import to_iterable
>>> to_iterable(SomeElement)
[SomeElement]
Parameters:any (iterable, non-iterable) –
Returns:Same as input
Return type:(iterable)
rpw.utils.coerce.to_pascal_case(snake_str)

Converts Snake Case to Pascal Case

>>> to_pascal_case('family_name')
'FamilyName'

Mixins

Collection of Class Mixins

class rpw.utils.mixins.ByNameCollectMixin

Adds name, by_name(), and by_name_or_element_ref() methods. This is for class inheritance only, used to reduce duplication

classmethod by_name(name)

Mixin to provide instantiating by a name for classes that are collectible. This is a mixin so specifi usage will vary for each for. This method will call the rpw.db.Element.collect method of the class, and return the first element with a matching .name property.

>>> LinePatternElement.by_name('Dash')
<rpw:LinePatternElement name:Dash>
>>> FillPatternElement.by_name('Solid')
<rpw:FillPatternElement name:Solid>
classmethod by_name_or_element_ref(reference)

Mixin for collectible elements. This is to help cast elements from name, elemente, or element_id

name

Returns object’s Name attribute

class rpw.utils.mixins.CategoryMixin

Adds category and get_category methods.

_category

Default Category Access Parameter. Overwrite on wrapper as needed. See Family Wrapper for an example.

category

Wrapped DB.Category

get_category(wrapped=True)

Wrapped DB.Category

Logger

Rpw Logger

Usage:

>>> from rpw.utils.logger import logger
>>> logger.info('My logger message')
>>> logger.error('My error message')
class rpw.utils.logger.LoggerWrapper

Logger Wrapper to extend loggers functionality. The logger is called in the same as the regular python logger, but also as a few extra features.

>>> logger.info('Message')
[INFO]  Message

Log Title

>>> logger.title('Message')
=========
Message
=========

Disable logger

>>> logger.disable()

Log Errors: This method appends errmsg to self.errors. This allows you to check if an error occured, and if it did not, close console window.

>>> logger.error('Message')
[ERROR]  Message
>>> print(logger.errors)
['Message']
critical(msg)

Log Message on logging.CRITICAL level

debug(msg)

Log Message on logging.DEBUG level

disable()

Sets logger level to logging.CRICITAL

error(msg)

Log Message on logging.ERROR level

info(msg)

Log Message on logging.INFO level

title(msg)

Log Message on logging.INFO level with lines above and below

verbose(verbose)

Sets logger to Verbose.

Parameters:(bool) – True to set logger.DEBUG, False to set to logging.INFO.
Usage:
>>> logger.verbose(True)
warning(msg)

Log Message on logging.WARNING level

.Net

.NET imports

This module ensures most commonly used .NET classes are loaded for you.for

>>> from rpw.utils.dotnet import List, Enum, Process

Implementation

Coerce

"""
Type Casting Utilities

"""

import rpw
from rpw import revit, DB
from rpw.base import BaseObjectWrapper
from rpw.db.builtins import BicEnum
from rpw.utils.dotnet import List
from rpw.exceptions import RpwTypeError


def to_element_id(element_reference):
    """
    Coerces Element References (Element, ElementId, ...) to Element Id

    >>> from rpw.utils.coerce import to_element_id
    >>> to_element_id(SomeElement)
    <Element Id>

    """
    if hasattr(element_reference, 'Id'):
        element_id = element_reference.Id
    elif isinstance(element_reference, DB.Reference):
        element_id = element_reference.ElementId
    elif isinstance(element_reference, int):
        element_id = DB.ElementId(element_reference)
    elif isinstance(element_reference, DB.ElementId):
        element_id = element_reference
    elif element_reference == DB.ElementId.InvalidElementId:
        element_id = element_reference
    else:
        raise RpwTypeError('Element, ElementId, or int', type(element_reference))
    return element_id


def to_element_ids(element_references):
    """
    Coerces an element or list of elements into element ids.
    Elements remain unchanged.
    This will always return a list, even if only one element is passed.

    >>> from rpw.utils.coerce import to_element_ids
    >>> to_element_ids(DB.Element)
    [ DB.ElementId ]
    >>> to_element_ids(20001)
    [ DB.ElementId ]
    >>> to_element_ids([20001, 20003])
    [ DB.ElementId, DB.ElementId ]

    Args:
        elements (``DB.Element``): Iterable list (``list`` or ``set``)
                                   or single of ``Element``, ``int``.

    Returns:
        [``DB.ElementId``, ... ]: List of Element Ids.
    """
    element_references = to_iterable(element_references)
    return [to_element_id(e_ref) for e_ref in element_references]

# TODO: Add case to unwrap rpw elements
def to_element(element_reference, doc=revit.doc):
    """ Same as to_elements but for a single object """
    if isinstance(element_reference, DB.Element):
        element = element_reference
    elif isinstance(element_reference, DB.ElementId):
        element = doc.GetElement(element_reference)
    elif isinstance(element_reference, DB.Reference):
        element = doc.GetElement(element_reference)
    elif isinstance(element_reference, int):
        element = doc.GetElement(DB.ElementId(element_reference))
    elif hasattr(element_reference, 'unwrap'):
        element = element_reference.unwrap()
    else:
        raise RpwTypeError('Element, ElementId, or int', type(element_reference))
    return element


def to_elements(element_references, doc=revit.doc):
    """
    Coerces element reference (``int``, or ``ElementId``) into ``DB.Element``.
    Remains unchanged if it's already ``DB.Element``.
    Accepts single object or lists.

    >>> from rpw.utils.coerce import to_elements
    >>> to_elements(DB.ElementId)
    [ DB.Element ]
    >>> to_elements(20001)
    [ DB.Element ]
    >>> to_elements([20001, 20003])
    [ DB.Element, DB.Element ]

    Args:
        element_references ([``DB.ElementId``, ``int``, ``DB.Element``]): Element Reference,
                                                                          single or list

    Returns:
        [``DB.Element``]: Elements
    """
    element_references = to_iterable(element_references)
    return [to_element(e_ref) for e_ref in element_references]


def to_class(class_reference):
    """ Coerces a class or class reference to a Class.

    >>> from rpw.utils.coerce import to_class
    >>> to_class('Wall')
    [ DB.Wall ]
    >>> to_class(Wall)
    [ DB.Wall ]

    Args:
        class_reference ([``DB.Wall``, ``str``]): Class Reference or class name

    Returns:
        [``type``]: Class
    """
    if isinstance(class_reference, str):
        return getattr(DB, class_reference)
    if isinstance(class_reference, type):
        return class_reference
    raise RpwTypeError('Class Type, Class Type Name', type(class_reference))


def to_category(category_reference, fuzzy=True):
    """ Coerces a category, category name or category id to a BuiltInCategory.

    >>> from rpw.utils.coerce import to_category
    >>> to_category('OST_Walls')
    BuiltInCategory.OST_Walls
    >>> to_category('Walls')
    BuiltInCategory.OST_Walls
    >>> to_category(BuiltInCategory.OST_Walls)
    BuiltInCategory.OST_Walls

    Args:
        cateagory_reference ([``DB.BuiltInCategory``, ``str``, ``CategoryId``]): Category Reference
                                                                                 or Name

    Returns:
        [``BuiltInCategory``]: BuiltInCategory
    """
    if isinstance(category_reference, DB.BuiltInCategory):
        return category_reference
    if isinstance(category_reference, str):
        if fuzzy:
            return BicEnum.fuzzy_get(category_reference)
        else:
            return BicEnum.get(category_reference)
    if isinstance(category_reference, DB.ElementId):
        return BicEnum.from_category_id(category_reference)
    raise RpwTypeError('Category Type, Category Type Name',
                       type(category_reference))


def to_category_id(category_reference, fuzzy=True):
    """
    Coerces a category, category name or category id to a Category Id.

    >>> from rpw.utils.coerce import to_category_id
    >>> to_category_id('OST_Walls')
    <ElementId>
    >>> to_category_id('Wall')
    <ElementId>
    >>> to_category_id(BuiltInCategory.OST_Walls)
    <ElementId>

    Args:
        cateagory_reference ([``DB.BuiltInCategory``, ``str``, ``CategoryId``]): Category Reference
                                                                                 or Name

    Returns:
        [``DB.ElementId``]: ElementId of Category
    """
    category_enum = to_category(category_reference)
    return DB.ElementId(category_enum)


def to_iterable(item_or_iterable):
    """
    Ensures input is iterable

    >>> from rpw.utils.coerce import to_iterable
    >>> to_iterable(SomeElement)
    [SomeElement]

    Args:
        any (iterable, non-iterable)

    Returns:
        (`iterable`): Same as input
    """
    if hasattr(item_or_iterable, '__iter__'):
        return item_or_iterable
    else:
        return [item_or_iterable]


def to_pascal_case(snake_str):
    """ Converts Snake Case to Pascal Case

    >>> to_pascal_case('family_name')
    'FamilyName'
    """
    components = snake_str.split('_')
    return "".join(x.title() for x in components)


# def dictioary_to_string(dictionary):
#     """ Makes a string with key:value pairs from a dictionary

#     >>> dictionary_to_string({'name': 'value'})
#     'name:value'
#     >>> dictionary_to_string({'name': 'value', 'id':5})
#     'name:value id:5'
#     """
#     return ' '.join(['{0}:{1}'.format(k, v) for k, v in dictionary.iteritems()])

Mixins

""" Collection of Class Mixins """

import rpw
# from rpw import revit, db, DB # Fixes Circular Import
from rpw.exceptions import RpwCoerceError
from rpw.utils.logger import deprecate_warning


class ByNameCollectMixin():

    """ Adds name, by_name(), and by_name_or_element_ref() methods.
    This is for class inheritance only, used to reduce duplication
    """

    @property
    def name(self):
        """ Returns object's Name attribute """
        return self._revit_object.Name

    @classmethod
    def by_name(cls, name):
        """
        Mixin to provide instantiating by a name for classes that are
        collectible. This is a mixin so specifi usage will vary for each for.
        This method will call the :any:`rpw.db.Element.collect`
        method of the class, and return the first element with a
        matching ``.name`` property.

        >>> LinePatternElement.by_name('Dash')
        <rpw:LinePatternElement name:Dash>

        >>> FillPatternElement.by_name('Solid')
        <rpw:FillPatternElement name:Solid>

        """
        e = cls.collect(where=lambda e: e.name.lower() == name.lower()).get_first()
        if e:
            return e
        raise RpwCoerceError('by_name({})'.format(name), cls)

    @classmethod
    def by_name_or_element_ref(cls, reference):
        """
        Mixin for collectible elements.
        This is to help cast elements from name, elemente, or element_id
        """
        if isinstance(reference, str):
            return cls.by_name(reference)
        elif isinstance(reference, rpw.DB.ElementId):
            return rpw.db.Element.from_id(reference)
        else:
            return cls(reference)



class CategoryMixin():

    """ Adds category and get_category methods.
    """

    @property
    def _category(self):
        """
        Default Category Access Parameter. Overwrite on wrapper as needed.
        See Family Wrapper for an example.
        """
        return self._revit_object.Category

    @property
    def category(self):
        """ Wrapped ``DB.Category`` """
        deprecate_warning('.category', 'get_category()')
        return rpw.db.Category(self._category)

    def get_category(self, wrapped=True):
        """ Wrapped ``DB.Category``"""
        return rpw.db.Category(self._category) if wrapped else self._category

Logger

"""
Rpw Logger

Usage:

    >>> from rpw.utils.logger import logger
    >>> logger.info('My logger message')
    >>> logger.error('My error message')

"""

import sys


class mockLoggerWrapper():
    def __init__(*args, **kwargs):
        pass

    def __getattr__(self, *args, **kwargs):
        return mockLoggerWrapper(*args, **kwargs)

    def __call__(self, *args, **kwargs):
        pass


class LoggerWrapper():
    """
    Logger Wrapper to extend loggers functionality.
    The logger is called in the same as the regular python logger,
    but also as a few extra features.

    >>> logger.info('Message')
    [INFO]  Message

    Log Title

    >>> logger.title('Message')
    =========
    Message
    =========

    Disable logger

    >>> logger.disable()

    Log Errors: This method appends errmsg to self.errors.
    This allows you to check  if an error occured, and if it did not,
    close console window.

    >>> logger.error('Message')
    [ERROR]  Message
    >>> print(logger.errors)
    ['Message']

    """

    def __init__(self):

        handler = logging.StreamHandler(sys.stdout)
        formatter = logging.Formatter("[%(levelname)s] %(message)s")
        # TODO: Show Module
        # formatter = logging.Formatter("[%(levelname)s] %(message)s [%(module)s:%(lineno)s]")
        handler.setFormatter(formatter)

        logger = logging.getLogger('rpw_logger')
        logger.addHandler(handler)
        logger.setLevel(logging.INFO)

        handler_title = logging.StreamHandler(sys.stdout)
        formatter_title = logging.Formatter("%(message)s")
        handler_title.setFormatter(formatter_title)

        logger_title = logging.getLogger('rpw_logger_title')
        logger_title.addHandler(handler_title)
        logger_title.setLevel(logging.INFO)

        self._logger = logger
        self._logger_title = logger_title
        self.errors = []

    def disable(self):
        """ Sets logger level to logging.CRICITAL """
        self._logger.setLevel(logging.CRITICAL)

    def verbose(self, verbose):
        """
        Sets logger to Verbose.

        Args:
            (bool): True to set `logger.DEBUG`, False to set to `logging.INFO`.

        Usage:
            >>> logger.verbose(True)

        """
        if verbose:
            self._logger.setLevel(logging.DEBUG)
        else:
            self._logger.setLevel(logging.INFO)

    def title(self, msg):
        """ Log Message on logging.INFO level with lines above and below """
        print('=' * 100)
        self._logger_title.info(msg)
        print('=' * 100)

    def info(self, msg):
        """ Log Message on logging.INFO level """
        self._logger.info(msg)

    def debug(self, msg):
        """ Log Message on logging.DEBUG level """
        self._logger.debug(msg)

    def warning(self, msg):
        """ Log Message on logging.WARNING level """
        self._logger.warning(msg)

    def error(self, msg):
        """ Log Message on logging.ERROR level """
        self._logger.error(msg)
        self.errors.append(msg)

    def critical(self, msg):
        """ Log Message on logging.CRITICAL level """
        self._logger.critical(msg)

    def setLevel(self, level):
        self._logger.setLevel(level)

def deprecate_warning(depracated, replaced_by=None):
    msg = '{} has been depracated and will be removed soon.'.format(depracated)
    if replaced_by:
        msg += ' Use {} instead'.format(replaced_by)
    logger.warning(msg)

try:
    import logging
except ImportError:
    # In Dynamo, Use Mock Logger
    logger = mockLoggerWrapper()
else:
    # In PyRevit, Use Logger
    logger = LoggerWrapper()

.NET

"""
.NET imports

This module ensures most commonly used .NET classes are loaded for you.for

>>> from rpw.utils.dotnet import List, Enum, Process

"""

import sys

from rpw.utils.logger import logger
from rpw.utils.sphinx_compat import MockImporter

# Attempt to Import clr
try:
    import clr
except ImportError:
    # Running Sphinx. Import MockImporter
    logger.warning('Error Importing CLR. Loading Mock Importer')
    sys.meta_path.append(MockImporter())

################
# .NET IMPORTS #
################

import clr
clr.AddReference('System')                 # Enum, Diagnostics
clr.AddReference('System.Collections')     # List

# Core Imports
from System import Enum
from System.Collections.Generic import List
from System.Diagnostics import Process