Exemplo n.º 1
0
def test_exception():
    with pytest.raises(ArithmeticError):
        # orthogonal axis
        rytz_axis_construction(Vec3(5, 0, 0), Vec3(0, 3, 0))
    with pytest.raises(ArithmeticError):
        # colinear axis
        rytz_axis_construction(Vec3(5, 0, 0), Vec3(4, 0, 0))
Exemplo n.º 2
0
def virtual_block_reference_entities(
    block_ref: 'Insert',
    uniform_scaling_factor: float = None,
    skipped_entity_callback: Optional[Callable[['DXFGraphic', str],
                                               None]] = None
) -> Iterable['DXFGraphic']:
    """
    Yields 'virtual' parts of block reference `block_ref`. This method is meant to examine the the block reference
    entities without the need to explode the block reference. The `skipped_entity_callback()` will be called for all
    entities which are not processed, signature: :code:`skipped_entity_callback(entity: DXFEntity, reason: str)`,
    `entity` is the original (untransformed) DXF entity of the block definition, the `reason` string is an
    explanation why the entity was skipped.

    This entities are located at the 'exploded' positions, but are not stored in the entity database, have no handle
    and are not assigned to any layout.

    Args:
        block_ref: Block reference entity (INSERT)
        uniform_scaling_factor: override uniform scaling factor for text entities (TEXT, ATTRIB, MTEXT)  and
                                HATCH pattern, default is ``max(abs(xscale), abs(yscale),  abs(zscale))``
        skipped_entity_callback: called whenever the transformation of an entity is not supported and so was skipped.

    .. warning::

        **Non uniform scaling** returns incorrect results for text entities (TEXT, MTEXT, ATTRIB) and
        some other entities like ELLIPSE, SHAPE, HATCH with arc or ellipse path segments and
        POLYLINE/LWPOLYLINE with arc segments.

    (internal API)

    """
    assert block_ref.dxftype() == 'INSERT'
    Ellipse = cast('Ellipse', factory.cls('ELLIPSE'))
    if skipped_entity_callback is None:

        def skipped_entity_callback(entity, reason):
            logger.debug(
                f'(Virtual Block Reference Entities) Ignoring {str(entity)}: "{reason}"'
            )

    def disassemble(layout) -> Generator['DXFGraphic', None, None]:
        for entity in layout:
            dxftype = entity.dxftype()
            if dxftype == 'ATTDEF':  # do not explode ATTDEF entities. Already available in Insert.attribs
                continue

            if has_non_uniform_scaling:
                if dxftype in {'ARC', 'CIRCLE'}:
                    # convert ARC to ELLIPSE
                    yield Ellipse.from_arc(entity)
                    continue
                if dxftype in {'LWPOLYLINE', 'POLYLINE'} and entity.has_arc:
                    # disassemble (LW)POLYLINE into LINE and ARC segments
                    for segment in entity.virtual_entities():
                        # convert ARC to ELLIPSE
                        if segment.dxftype() == 'ARC':
                            yield Ellipse.from_arc(segment)
                        else:
                            yield segment
                    continue

            # Copy entity with all DXF attributes
            try:
                copy = entity.copy()
            except DXFTypeError:
                skipped_entity_callback(entity, 'non copyable')
                continue  # non copyable entities will be ignored

            if copy.dxftype() == 'HATCH':
                if copy.dxf.associative:
                    # remove associations
                    copy.dxf.associative = 0
                    for path in copy.paths:
                        path.source_boundary_objects = []

                if has_non_uniform_scaling and copy.paths.has_critical_elements(
                ):
                    # None uniform scaling produces incorrect results for the arc and ellipse transformations.
                    # This causes an DXF structure error for AutoCAD.
                    # todo: requires testing
                    skipped_entity_callback(entity,
                                            'unsupported non-uniform scaling')
                    continue

                    # For the case that arc and ellipse transformation works correct someday:
                    # copy.paths.arc_edges_to_ellipse_edges()

            yield copy

    brcs = block_ref.brcs()
    block_layout = block_ref.block()
    if block_layout is None:
        raise DXFStructureError(
            f'Required block definition for "{block_ref.dxf.name}" does not exist.'
        )

    has_scaling = block_ref.has_scaling
    if has_scaling:
        # Non uniform scaling will produce incorrect results for some entities!
        # Mirroring about an axis is handled like non uniform scaling! (-1, 1, 1)
        # (-1, -1, -1) is uniform scaling!
        has_non_uniform_scaling = not block_ref.has_uniform_scaling

        xscale = block_ref.dxf.xscale
        yscale = block_ref.dxf.yscale
        zscale = block_ref.dxf.zscale

        if block_ref.has_uniform_scaling and xscale < 0:
            # handle reflection about all three axis -x, -y, -z explicit as non uniform scaling
            has_non_uniform_scaling = True

        if uniform_scaling_factor is not None:
            uniform_scaling_factor = float(uniform_scaling_factor)
        else:
            uniform_scaling_factor = block_ref.text_scaling
    else:
        xscale, yscale, zscale = (1, 1, 1)
        uniform_scaling_factor = 1
        has_non_uniform_scaling = False

    for entity in disassemble(block_layout):

        dxftype = entity.dxftype()

        original_ellipse: Optional[Ellipse] = None
        if has_non_uniform_scaling and dxftype == 'ELLIPSE':
            original_ellipse = entity.copy()

        # Basic transformation from BRCS to WCS
        try:
            entity.transform_to_wcs(brcs)
        except NotImplementedError:  # entities without 'transform_to_wcs' support will be ignored
            skipped_entity_callback(entity, 'non transformable')
            continue

        if has_scaling:
            # Apply DXF attribute scaling:
            # Simple entities without properties to scale
            if dxftype in {
                    'LINE', 'POINT', 'LWPOLYLINE', 'POLYLINE', 'MESH',
                    'SPLINE', 'SOLID', '3DFACE', 'TRACE', 'IMAGE', 'WIPEOUT',
                    'XLINE', 'RAY', 'LIGHT', 'HELIX'
            }:
                pass  # nothing else to do
            elif dxftype in {'CIRCLE', 'ARC'}:
                # Non uniform scaling: ARC and CIRCLE converted to ELLIPSE
                # TODO: since non uniform scale => ellipse, scaling by (-s, -s, -s) is the only possible reflection here
                # TODO: handle reflections about z
                entity.dxf.radius = entity.dxf.radius * uniform_scaling_factor
            elif dxftype == 'ELLIPSE':
                # TODO: handle reflections about z
                if has_non_uniform_scaling:
                    open_ellipse = not math.isclose(
                        normalize_angle(original_ellipse.dxf.start_param),
                        normalize_angle(original_ellipse.dxf.end_param),
                    )
                    minor_axis = brcs.direction_to_wcs(
                        original_ellipse.minor_axis)

                    ellipse = cast('Ellipse', entity)
                    # Transform axis
                    major_axis = ellipse.dxf.major_axis
                    if not math.isclose(
                            major_axis.dot(minor_axis), 0, abs_tol=1e-9):
                        try:
                            major_axis, _, ratio = rytz_axis_construction(
                                major_axis, minor_axis)
                        except ArithmeticError:  # axis construction error - skip entity
                            skipped_entity_callback(
                                entity,
                                'axis construction error - please send a bug report.'
                            )
                            continue
                    else:
                        ratio = minor_axis.magnitude / major_axis.magnitude

                    ellipse.dxf.major_axis = major_axis
                    # AutoCAD does not accept a ratio < 1e-6 -> invalid DXF file
                    ellipse.dxf.ratio = max(ratio, 1e-6)
                    if open_ellipse:
                        original_start_param = original_ellipse.dxf.start_param
                        original_end_param = original_ellipse.dxf.end_param
                        start_point, end_point = brcs.points_to_wcs(
                            original_ellipse.vertices(
                                (original_start_param, original_end_param)))

                        # adjusting start- and end parameter
                        center = ellipse.dxf.center  # transformed center point
                        extrusion = ellipse.dxf.extrusion  # transformed extrusion vector, default is (0, 0, 1)

                        start_angle = extrusion.angle_about(
                            major_axis, start_point - center)
                        end_angle = extrusion.angle_about(
                            major_axis, end_point - center)
                        start_param = angle_to_param(ratio, start_angle)
                        end_param = angle_to_param(ratio, end_angle)

                        # if drawing the wrong side of the ellipse
                        if (start_param > end_param) != (original_start_param >
                                                         original_end_param):
                            start_param, end_param = end_param, start_param

                        ellipse.dxf.start_param = start_param
                        ellipse.dxf.end_param = end_param

                    if ellipse.dxf.ratio > 1:
                        ellipse.swap_axis()
            elif dxftype == 'MTEXT':
                # TODO: handle reflections. Note that the entity does store enough information to represent being
                #  reflected. This can be seen by reflecting then exploding in Autocad.
                #  The text will no longer be reflected.
                # Scale MTEXT height/width just by uniform_scaling.
                entity.dxf.char_height *= uniform_scaling_factor
                entity.dxf.width *= uniform_scaling_factor
            elif dxftype in {'TEXT', 'ATTRIB'}:
                # TODO: handle reflections. Note that the entity does store enough information to represent being
                #  reflected. This can be seen by reflecting then exploding in Autocad.
                #  The text will no longer be reflected.
                # Scale TEXT height just by uniform_scaling.
                entity.dxf.height *= uniform_scaling_factor
            elif dxftype == 'INSERT':
                # Set scaling of child INSERT to scaling of parent INSERT
                entity.dxf.xscale *= xscale
                entity.dxf.yscale *= yscale
                entity.dxf.zscale *= zscale
                # Scale attached ATTRIB entities:
                for attrib in entity.attribs:
                    attrib.dxf.height *= uniform_scaling_factor
            elif dxftype == 'SHAPE':
                # Scale SHAPE size just by uniform_scaling.
                entity.dxf.size *= uniform_scaling_factor
            elif dxftype == 'HATCH':
                # Non uniform scaling produces incorrect results for boundary paths containing ARC or ELLIPSE segments.
                # Scale HATCH pattern:
                hatch = cast('Hatch', entity)
                if uniform_scaling_factor != 1 and hatch.has_pattern_fill and hatch.pattern is not None:
                    hatch.dxf.pattern_scale *= uniform_scaling_factor
                    # hatch.pattern is already scaled by the stored pattern_scale value
                    hatch.set_pattern_definition(hatch.pattern.as_list(),
                                                 uniform_scaling_factor)
            else:  # unsupported entity will be ignored
                skipped_entity_callback(entity, 'unsupported entity')
                continue

        yield entity
Exemplo n.º 3
0
def virtual_block_reference_entities(
        block_ref: 'Insert') -> Iterable['DXFGraphic']:
    """
    Yields 'virtual' parts of block reference `block_ref`. This method is meant to examine the the block reference
    entities without the need to explode the block reference.

    This entities are located at the 'exploded' positions, but are not stored in the entity database, have no handle
    and are not assigned to any layout.

    Args:
        block_ref: Block reference entity (INSERT)

    .. warning::

        **Non uniform scaling** returns incorrect results for text entities (TEXT, MTEXT, ATTRIB) and
        some other entities like ELLIPSE, SHAPE, HATCH with arc or ellipse path segments and
        POLYLINE/LWPOLYLINE with arc segments.

    (internal API)

    """
    assert block_ref.dxftype() == 'INSERT'

    brcs = block_ref.brcs()
    # Non uniform scaling will produce incorrect results for some entities!
    xscale = block_ref.dxf.xscale
    yscale = block_ref.dxf.yscale
    uniform_scaling = max(abs(xscale), abs(yscale))
    non_uniform_scaling = xscale != yscale

    block_layout = block_ref.block()
    if block_layout is None:
        raise DXFStructureError(
            f'Required block definition for "{block_ref.dxf.name}" does not exist.'
        )

    for entity in block_layout:
        dxftype = entity.dxftype()
        if dxftype == 'ATTDEF':  # do not explode ATTDEF entities
            continue

        # Copy entity with all DXF attributes
        try:
            copy = entity.copy()
        except DXFTypeError:
            continue  # non copyable entities will be ignored

        if non_uniform_scaling and dxftype in {'ARC', 'CIRCLE'}:
            from ezdxf.entities import Ellipse
            copy = Ellipse.from_arc(entity)
            dxftype = copy.dxftype()

        # Basic transformation from BRCS to WCS
        try:
            copy.transform_to_wcs(brcs)
        except NotImplementedError:  # entities without 'transform_to_ucs' support will be ignored
            continue

        # Apply DXF attribute scaling:
        # 1. simple entities without properties to scale
        if dxftype in {
                'LINE', 'POINT', 'LWPOLYLINE', 'POLYLINE', 'MESH', 'HATCH',
                'SPLINE', 'SOLID', '3DFACE', 'TRACE', 'IMAGE', 'WIPEOUT',
                'XLINE', 'RAY', 'LIGHT', 'HELIX'
        }:
            pass  # nothing else to do
        elif dxftype in {'CIRCLE', 'ARC'}:
            # simple uniform scaling of radius
            # Non uniform scaling: ARC, CIRCLE -> ELLIPSE
            copy.dxf.radius = entity.dxf.radius * uniform_scaling
        elif dxftype == 'ELLIPSE':
            if non_uniform_scaling:
                if entity.dxftype(
                ) == 'ELLIPSE':  # original entity is an ELLIPSE
                    ellipse = cast('Ellipse', entity)

                    # transform axis
                    conjugated_major_axis = brcs.direction_to_wcs(
                        ellipse.dxf.major_axis)
                    conjugated_minor_axis = brcs.direction_to_wcs(
                        ellipse.minor_axis)
                    major_axis, _, ratio = rytz_axis_construction(
                        conjugated_major_axis, conjugated_minor_axis)
                    copy.dxf.major_axis = major_axis
                    copy.dxf.ratio = max(ratio, 1e-6)

                    # adjusting start- and end parameter
                    center = copy.dxf.center  # transformed center point
                    start_point, end_point = ellipse.vertices(
                        (ellipse.dxf.start_param, ellipse.dxd.end_param))
                    start_vec = brcs.to_wcs(start_point) - center
                    end_vec = brcs.to_wcs(end_point) - center
                    # The dot product (scalar product) is the angle between two vectors.
                    # https://en.wikipedia.org/wiki/Dot_product
                    # Not sure if this is the correct way to adjust start- and end parameter
                    copy.dxf.start_param = normalize_angle(
                        major_axis.dot(start_vec))
                    copy.dxf.end_param = normalize_angle(
                        major_axis.dot(end_vec))

                    if copy.dxf.ratio > 1:
                        copy.swap_axis()
                else:  # converted from ARC to ELLIPSE
                    ellipse = cast('Ellipse', copy)
                    ellipse.dxf.ratio = max(yscale / xscale, 1e-6)
                    if ellipse.dxf.ratio > 1:
                        ellipse.swap_axis()
        elif dxftype == 'MTEXT':
            # Scale MTEXT height/width just by uniform_scaling, how to handle non uniform scaling?
            copy.dxf.char_height *= uniform_scaling
            copy.dxf.width *= uniform_scaling
        elif dxftype in {'TEXT', 'ATTRIB'}:
            # Scale TEXT height just by uniform_scaling, how to handle non uniform scaling?
            copy.dxf.height *= uniform_scaling
        elif dxftype == 'INSERT':
            # Set scaling of child INSERT to scaling of parent INSERT
            for scale in ('xscale', 'yscale', 'zscale'):
                if block_ref.dxf.hasattr(scale):
                    original_scale = copy.dxf.get_default(scale)
                    block_ref_scale = block_ref.dxf.get(scale)
                    copy.dxf.set(scale, original_scale * block_ref_scale)
            if uniform_scaling != 1:
                # Scale attached ATTRIB entities:
                # Scale height just by uniform_scaling, how to handle non uniform scaling?
                for attrib in copy.attribs:
                    attrib.dxf.height *= uniform_scaling
        elif dxftype == 'SHAPE':
            # Scale SHAPE size just by uniform_scaling, how to handle non uniform scaling?
            copy.dxf.size *= uniform_scaling
        else:  # unsupported entity will be ignored
            continue
        yield copy
Exemplo n.º 4
0
def test_simple_case():
    a, b, ratio = rytz_axis_construction(Vec3(3, 0, 0), Vec3(-2, 2, 0))
    assert ratio < 1
Exemplo n.º 5
0
def virtual_block_reference_entities(
        block_ref: 'Insert',
        uniform_scaling_factor: float = None) -> Iterable['DXFGraphic']:
    """
    Yields 'virtual' parts of block reference `block_ref`. This method is meant to examine the the block reference
    entities without the need to explode the block reference.

    This entities are located at the 'exploded' positions, but are not stored in the entity database, have no handle
    and are not assigned to any layout.

    Args:
        block_ref: Block reference entity (INSERT)
        uniform_scaling_factor: override uniform scaling factor for text entities (TEXT, ATTRIB, MTEXT)  and
                                HATCH pattern, default is ``max(abs(xscale), abs(yscale),  abs(zscale))``

    .. warning::

        **Non uniform scaling** returns incorrect results for text entities (TEXT, MTEXT, ATTRIB) and
        some other entities like ELLIPSE, SHAPE, HATCH with arc or ellipse path segments and
        POLYLINE/LWPOLYLINE with arc segments.

    (internal API)

    """
    assert block_ref.dxftype() == 'INSERT'
    Ellipse = cast('Ellipse', factory.cls('ELLIPSE'))

    def disassemble(layout):
        for entity in layout:
            dxftype = entity.dxftype()
            if dxftype == 'ATTDEF':  # do not explode ATTDEF entities
                continue

            if has_non_uniform_scaling:
                if dxftype in {'ARC', 'CIRCLE'}:
                    # convert ARC to ELLIPSE
                    yield Ellipse.from_arc(entity)
                    continue
                if dxftype in {'LWPOLYLINE', 'POLYLINE'} and entity.has_arc:
                    # disassemble (LW)POLYLINE into LINE and ARC segments
                    for segment in entity.virtual_entities():
                        # convert ARC to ELLIPSE
                        if segment.dxftype() == 'ARC':
                            yield Ellipse.from_arc(segment)
                        else:
                            yield segment
                    continue

            # Copy entity with all DXF attributes
            try:
                copy = entity.copy()
            except DXFTypeError:
                logger.debug(
                    f'(Virtual Block Reference Entities) Ignoring non copyable entity {str(entity)}'
                )
                continue  # non copyable entities will be ignored

            if copy.dxftype() == 'HATCH':
                if copy.dxf.associative:
                    # remove associations
                    copy.dxf.associative = 0
                    for path in copy.paths:
                        path.source_boundary_objects = []

                if has_non_uniform_scaling and copy.paths.has_critical_elements(
                ):
                    # None uniform scaling produces incorrect results for the arc and ellipse transformations.
                    # This causes an DXF structure error for AutoCAD.
                    # todo: requires testing
                    logger.debug(
                        f'(Virtual Block Reference Entities) Ignoring {str(entity)} for non uniform scaling.'
                    )
                    continue

                    # For the case that arc and ellipse transformation works correct someday:
                    # copy.paths.arc_edges_to_ellipse_edges()

            yield copy

    brcs = block_ref.brcs()
    block_layout = block_ref.block()
    if block_layout is None:
        raise DXFStructureError(
            f'Required block definition for "{block_ref.dxf.name}" does not exist.'
        )

    has_scaling = block_ref.has_scaling
    if has_scaling:
        xscale = block_ref.dxf.xscale
        yscale = block_ref.dxf.yscale
        zscale = block_ref.dxf.zscale

        if uniform_scaling_factor is not None:
            uniform_scaling_factor = float(uniform_scaling_factor)
        else:
            uniform_scaling_factor = block_ref.text_scaling

        # Non uniform scaling will produce incorrect results for some entities!
        if xscale == yscale == zscale:
            has_non_uniform_scaling = False
            if xscale == 1:  # yscale == 1, zscale == 1
                has_scaling = False
        else:
            has_non_uniform_scaling = True
    else:
        xscale, yscale, zscale = (1, 1, 1)
        uniform_scaling_factor = 1
        has_non_uniform_scaling = False

    for entity in disassemble(block_layout):
        dxftype = entity.dxftype()

        if has_non_uniform_scaling and dxftype == 'ELLIPSE':
            # transform start- and end location before main transformation
            ellipse = cast('Ellipse', entity)
            open_ellipse = not math.isclose(
                normalize_angle(ellipse.dxf.start_param),
                normalize_angle(ellipse.dxf.end_param),
            )
            if open_ellipse:
                # transformed start- and end point
                start_param = ellipse.dxf.start_param
                end_param = ellipse.dxf.end_param
                start_point, end_point = brcs.points_to_wcs(
                    ellipse.vertices((start_param, end_param)))
            minor_axis = brcs.direction_to_wcs(ellipse.minor_axis)

        # Basic transformation from BRCS to WCS
        try:
            entity.transform_to_wcs(brcs)
        except NotImplementedError:  # entities without 'transform_to_wcs' support will be ignored
            logger.debug(
                f'(Virtual Block Reference Entities) Ignoring non transformable entity {str(entity)}'
            )
            continue

        if has_scaling:
            # Apply DXF attribute scaling:
            # Simple entities without properties to scale
            if dxftype in {
                    'LINE', 'POINT', 'LWPOLYLINE', 'POLYLINE', 'MESH',
                    'SPLINE', 'SOLID', '3DFACE', 'TRACE', 'IMAGE', 'WIPEOUT',
                    'XLINE', 'RAY', 'LIGHT', 'HELIX'
            }:
                pass  # nothing else to do
            elif dxftype in {'CIRCLE', 'ARC'}:
                # Non uniform scaling: ARC and CIRCLE converted to ELLIPSE
                entity.dxf.radius = entity.dxf.radius * uniform_scaling_factor
            elif dxftype == 'ELLIPSE' and has_non_uniform_scaling:
                ellipse = cast('Ellipse', entity)
                # Transform axis
                major_axis = ellipse.dxf.major_axis
                if not math.isclose(major_axis.dot(minor_axis), 0):
                    major_axis, _, ratio = rytz_axis_construction(
                        major_axis, minor_axis)
                else:
                    ratio = minor_axis.magnitude / major_axis.magnitude

                ellipse.dxf.major_axis = major_axis
                ellipse.dxf.ratio = max(ratio, 1e-6)
                if open_ellipse:
                    # adjusting start- and end parameter
                    center = ellipse.dxf.center  # transformed center point
                    start_angle = major_axis.angle_between(start_point -
                                                           center)
                    end_angle = major_axis.angle_between(end_point - center)
                    # todo: quadrant detection may fail if the rytz's axis construction algorithm is applied
                    ellipse.dxf.start_param = angle_to_param(
                        ratio, start_angle, quadrant(start_param))
                    ellipse.dxf.end_param = angle_to_param(
                        ratio, end_angle, quadrant(end_param))

                if ellipse.dxf.ratio > 1:
                    ellipse.swap_axis()
            elif dxftype == 'MTEXT':
                # Scale MTEXT height/width just by uniform_scaling.
                entity.dxf.char_height *= uniform_scaling_factor
                entity.dxf.width *= uniform_scaling_factor
            elif dxftype in {'TEXT', 'ATTRIB'}:
                # Scale TEXT height just by uniform_scaling.
                entity.dxf.height *= uniform_scaling_factor
            elif dxftype == 'INSERT':
                # Set scaling of child INSERT to scaling of parent INSERT
                entity.dxf.xscale *= xscale
                entity.dxf.yscale *= yscale
                entity.dxf.zscale *= zscale
                # Scale attached ATTRIB entities:
                for attrib in entity.attribs:
                    attrib.dxf.height *= uniform_scaling_factor
            elif dxftype == 'SHAPE':
                # Scale SHAPE size just by uniform_scaling.
                entity.dxf.size *= uniform_scaling_factor
            elif dxftype == 'HATCH':
                # Non uniform scaling produces incorrect results for boundary paths containing ARC or ELLIPSE segments.
                # Scale HATCH pattern:
                hatch = cast('Hatch', entity)
                if uniform_scaling_factor != 1 and hatch.has_pattern_fill and hatch.pattern is not None:
                    hatch.dxf.pattern_scale *= uniform_scaling_factor
                    # hatch.pattern is already scaled by the stored pattern_scale value
                    hatch.set_pattern_definition(hatch.pattern.as_list(),
                                                 uniform_scaling_factor)
            else:  # unsupported entity will be ignored
                continue
        yield entity