838 lines
29 KiB
Python
838 lines
29 KiB
Python
# Copyright (c) 2019-2024 Manfred Moitzi
|
|
# License: MIT License
|
|
from __future__ import annotations
|
|
from typing import TYPE_CHECKING, Iterable, Union, Optional, Sequence, Any
|
|
from typing_extensions import Self, override
|
|
import logging
|
|
|
|
from ezdxf.lldxf.attributes import (
|
|
DXFAttr,
|
|
DXFAttributes,
|
|
DefSubclass,
|
|
XType,
|
|
group_code_mapping,
|
|
)
|
|
from ezdxf.lldxf import const
|
|
from ezdxf.lldxf.tags import Tags, DXFTag
|
|
from ezdxf.math import Matrix44
|
|
from ezdxf.tools import crypt, guid
|
|
from ezdxf import msgtypes
|
|
|
|
from .dxfentity import base_class, SubclassProcessor
|
|
from .dxfgfx import DXFGraphic, acdb_entity
|
|
from .factory import register_entity
|
|
from .copy import default_copy
|
|
from .temporary_transform import TransformByBlockReference
|
|
|
|
if TYPE_CHECKING:
|
|
from ezdxf.entities import DXFNamespace
|
|
from ezdxf.lldxf.tagwriter import AbstractTagWriter
|
|
from ezdxf import xref
|
|
|
|
|
|
__all__ = [
|
|
"Body",
|
|
"Solid3d",
|
|
"Region",
|
|
"Surface",
|
|
"ExtrudedSurface",
|
|
"LoftedSurface",
|
|
"RevolvedSurface",
|
|
"SweptSurface",
|
|
]
|
|
|
|
logger = logging.getLogger("ezdxf")
|
|
acdb_modeler_geometry = DefSubclass(
|
|
"AcDbModelerGeometry",
|
|
{
|
|
"version": DXFAttr(70, default=1),
|
|
"flags": DXFAttr(290, dxfversion=const.DXF2013),
|
|
"uid": DXFAttr(2, dxfversion=const.DXF2013),
|
|
},
|
|
)
|
|
acdb_modeler_geometry_group_codes = group_code_mapping(acdb_modeler_geometry)
|
|
|
|
# with R2013/AC1027 Modeler Geometry of ACIS data is stored in the ACDSDATA
|
|
# section as binary encoded information detection:
|
|
# group code 70, 1, 3 is missing
|
|
# group code 290, 2 present
|
|
#
|
|
# 0
|
|
# ACDSRECORD
|
|
# 90
|
|
# 1
|
|
# 2
|
|
# AcDbDs::ID
|
|
# 280
|
|
# 10
|
|
# 320
|
|
# 19B <<< handle of associated 3DSOLID entity in model space
|
|
# 2
|
|
# ASM_Data
|
|
# 280
|
|
# 15
|
|
# 94
|
|
# 7197 <<< size in bytes ???
|
|
# 310
|
|
# 414349532042696E61727946696C6...
|
|
|
|
|
|
@register_entity
|
|
class Body(DXFGraphic):
|
|
"""DXF BODY entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "BODY"
|
|
DXFATTRIBS = DXFAttributes(base_class, acdb_entity, acdb_modeler_geometry)
|
|
MIN_DXF_VERSION_FOR_EXPORT = const.DXF2000
|
|
|
|
def __init__(self) -> None:
|
|
super().__init__()
|
|
# Store SAT data as immutable sequence of strings, so the data can be shared
|
|
# across multiple copies of an ACIS entity.
|
|
self._sat: Sequence[str] = tuple()
|
|
self._sab: bytes = b""
|
|
self._update = False
|
|
self._temporary_transformation = TransformByBlockReference()
|
|
|
|
@property
|
|
def acis_data(self) -> Union[bytes, Sequence[str]]:
|
|
"""Returns :term:`SAT` data for DXF R2000 up to R2010 and :term:`SAB`
|
|
data for DXF R2013 and later
|
|
"""
|
|
if self.has_binary_data:
|
|
return self.sab
|
|
return self.sat
|
|
|
|
@property
|
|
def sat(self) -> Sequence[str]:
|
|
"""Get/Set :term:`SAT` data as sequence of strings."""
|
|
return self._sat
|
|
|
|
@sat.setter
|
|
def sat(self, data: Sequence[str]) -> None:
|
|
"""Set :term:`SAT` data as sequence of strings."""
|
|
self._sat = tuple(data)
|
|
|
|
@property
|
|
def sab(self) -> bytes:
|
|
"""Get/Set :term:`SAB` data as bytes."""
|
|
if ( # load SAB data on demand
|
|
self.doc is not None and self.has_binary_data and len(self._sab) == 0
|
|
):
|
|
self._sab = self.doc.acdsdata.get_acis_data(self.dxf.handle)
|
|
return self._sab
|
|
|
|
@sab.setter
|
|
def sab(self, data: bytes) -> None:
|
|
"""Set :term:`SAB` data as bytes."""
|
|
self._update = True
|
|
self._sab = data
|
|
|
|
@property
|
|
def has_binary_data(self):
|
|
"""Returns ``True`` if the entity contains :term:`SAB` data and
|
|
``False`` if the entity contains :term:`SAT` data.
|
|
"""
|
|
if self.doc:
|
|
return self.doc.dxfversion >= const.DXF2013
|
|
else:
|
|
return False
|
|
|
|
@override
|
|
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
|
assert isinstance(entity, Body)
|
|
entity.sat = self.sat
|
|
entity.sab = self.sab # load SAB on demand
|
|
entity.dxf.uid = guid()
|
|
entity._temporary_transformation = self._temporary_transformation
|
|
|
|
@override
|
|
def map_resources(self, clone: Self, mapping: xref.ResourceMapper) -> None:
|
|
"""Translate resources from self to the copied entity."""
|
|
super().map_resources(clone, mapping)
|
|
clone.convert_acis_data()
|
|
|
|
def convert_acis_data(self) -> None:
|
|
if self.doc is None:
|
|
return
|
|
msg = ""
|
|
dxfversion = self.doc.dxfversion
|
|
if dxfversion < const.DXF2013:
|
|
if self._sab:
|
|
self._sab = b""
|
|
msg = "DXF version mismatch, can't convert ACIS data from SAB to SAT, SAB data removed."
|
|
else:
|
|
if self._sat:
|
|
self._sat = tuple()
|
|
msg = "DXF version mismatch, can't convert ACIS data from SAT to SAB, SAT data removed."
|
|
if msg:
|
|
logger.info(msg)
|
|
|
|
@override
|
|
def notify(self, message_type: int, data: Any = None) -> None:
|
|
if message_type == msgtypes.COMMIT_PENDING_CHANGES:
|
|
self._temporary_transformation.apply_transformation(self)
|
|
|
|
@override
|
|
def preprocess_export(self, tagwriter: AbstractTagWriter) -> bool:
|
|
msg = ""
|
|
if tagwriter.dxfversion < const.DXF2013:
|
|
valid = len(self.sat) > 0
|
|
if not valid:
|
|
msg = f"{str(self)} doesn't have SAT data, skipping DXF export"
|
|
else:
|
|
valid = len(self.sab) > 0
|
|
if not valid:
|
|
msg = f"{str(self)} doesn't have SAB data, skipping DXF export"
|
|
if not valid:
|
|
logger.info(msg)
|
|
if valid and self._temporary_transformation.get_matrix() is not None:
|
|
logger.warning(f"{str(self)} has unapplied temporary transformations.")
|
|
return valid
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
"""Loading interface. (internal API)"""
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(
|
|
dxf, acdb_modeler_geometry_group_codes, 2, log=False
|
|
)
|
|
if not self.has_binary_data:
|
|
self.load_sat_data(processor.subclasses[2])
|
|
return dxf
|
|
|
|
def load_sat_data(self, tags: Tags):
|
|
"""Loading interface. (internal API)"""
|
|
text_lines = tags2textlines(tag for tag in tags if tag.code in (1, 3))
|
|
self._sat = tuple(crypt.decode(text_lines))
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags. (internal API)"""
|
|
super().export_entity(tagwriter)
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_modeler_geometry.name)
|
|
if tagwriter.dxfversion >= const.DXF2013:
|
|
# ACIS data is stored in the ACDSDATA section as SAB
|
|
if self.doc and self._update:
|
|
# write back changed SAB data into AcDsDataSection or create
|
|
# a new ACIS record:
|
|
self.doc.acdsdata.set_acis_data(self.dxf.handle, self.sab)
|
|
if self.dxf.hasattr("version"):
|
|
tagwriter.write_tag2(70, self.dxf.version)
|
|
self.dxf.export_dxf_attribs(tagwriter, ["flags", "uid"])
|
|
else:
|
|
# DXF R2000 - R2010 stores the ACIS data as SAT in the entity
|
|
self.dxf.export_dxf_attribs(tagwriter, "version")
|
|
self.export_sat_data(tagwriter)
|
|
|
|
def export_sat_data(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export ACIS data as DXF tags. (internal API)"""
|
|
|
|
def cleanup(lines):
|
|
for line in lines:
|
|
yield line.rstrip().replace("\n", "")
|
|
|
|
tags = Tags(textlines2tags(crypt.encode(cleanup(self.sat))))
|
|
tagwriter.write_tags(tags)
|
|
|
|
def tostring(self) -> str:
|
|
"""Returns ACIS :term:`SAT` data as a single string if the entity has
|
|
SAT data.
|
|
"""
|
|
if self.has_binary_data:
|
|
return ""
|
|
else:
|
|
return "\n".join(self.sat)
|
|
|
|
@override
|
|
def destroy(self) -> None:
|
|
if self.has_binary_data:
|
|
self.doc.acdsdata.del_acis_data(self.dxf.handle) # type: ignore
|
|
super().destroy()
|
|
|
|
@override
|
|
def transform(self, m: Matrix44) -> Self:
|
|
self._temporary_transformation.add_matrix(m)
|
|
return self
|
|
|
|
def temporary_transformation(self) -> TransformByBlockReference:
|
|
return self._temporary_transformation
|
|
|
|
|
|
def tags2textlines(tags: Iterable) -> Iterable[str]:
|
|
"""Yields text lines from code 1 and 3 tags, code 1 starts a line following
|
|
code 3 tags are appended to the line.
|
|
"""
|
|
line = None
|
|
for code, value in tags:
|
|
if code == 1:
|
|
if line is not None:
|
|
yield line
|
|
line = value
|
|
elif code == 3:
|
|
line += value
|
|
if line is not None:
|
|
yield line
|
|
|
|
|
|
def textlines2tags(lines: Iterable[str]) -> Iterable[DXFTag]:
|
|
"""Yields text lines as DXFTags, splitting long lines (>255) int code 1
|
|
and code 3 tags.
|
|
"""
|
|
for line in lines:
|
|
text = line[:255]
|
|
tail = line[255:]
|
|
yield DXFTag(1, text)
|
|
while len(tail):
|
|
text = tail[:255]
|
|
tail = tail[255:]
|
|
yield DXFTag(3, text)
|
|
|
|
|
|
@register_entity
|
|
class Region(Body):
|
|
"""DXF REGION entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "REGION"
|
|
|
|
|
|
acdb_3dsolid = DefSubclass(
|
|
"AcDb3dSolid",
|
|
{
|
|
"history_handle": DXFAttr(350, default="0"),
|
|
},
|
|
)
|
|
acdb_3dsolid_group_codes = group_code_mapping(acdb_3dsolid)
|
|
|
|
|
|
@register_entity
|
|
class Solid3d(Body):
|
|
"""DXF 3DSOLID entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "3DSOLID"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class, acdb_entity, acdb_modeler_geometry, acdb_3dsolid
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(dxf, acdb_3dsolid_group_codes, 3)
|
|
return dxf
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
if tagwriter.dxfversion > const.DXF2004:
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_3dsolid.name)
|
|
self.dxf.export_dxf_attribs(tagwriter, "history_handle")
|
|
|
|
|
|
def load_matrix(subclass: Tags, code: int) -> Matrix44:
|
|
values = [tag.value for tag in subclass.find_all(code)]
|
|
if len(values) != 16:
|
|
raise const.DXFStructureError("Invalid transformation matrix.")
|
|
return Matrix44(values)
|
|
|
|
|
|
def export_matrix(tagwriter: AbstractTagWriter, code: int, matrix: Matrix44) -> None:
|
|
for value in list(matrix):
|
|
tagwriter.write_tag2(code, value)
|
|
|
|
|
|
acdb_surface = DefSubclass(
|
|
"AcDbSurface",
|
|
{
|
|
"u_count": DXFAttr(71),
|
|
"v_count": DXFAttr(72),
|
|
},
|
|
)
|
|
acdb_surface_group_codes = group_code_mapping(acdb_surface)
|
|
|
|
|
|
@register_entity
|
|
class Surface(Body):
|
|
"""DXF SURFACE entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "SURFACE"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class, acdb_entity, acdb_modeler_geometry, acdb_surface
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(dxf, acdb_surface_group_codes, 3)
|
|
return dxf
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_surface.name)
|
|
self.dxf.export_dxf_attribs(tagwriter, ["u_count", "v_count"])
|
|
|
|
|
|
acdb_extruded_surface = DefSubclass(
|
|
"AcDbExtrudedSurface",
|
|
{
|
|
"class_id": DXFAttr(90),
|
|
"sweep_vector": DXFAttr(10, xtype=XType.point3d),
|
|
# 16x group code 40: Transform matrix of extruded entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"draft_angle": DXFAttr(42, default=0.0), # in radians
|
|
"draft_start_distance": DXFAttr(43, default=0.0),
|
|
"draft_end_distance": DXFAttr(44, default=0.0),
|
|
"twist_angle": DXFAttr(45, default=0.0), # in radians?
|
|
"scale_factor": DXFAttr(48, default=0.0),
|
|
"align_angle": DXFAttr(49, default=0.0), # in radians
|
|
# 16x group code 46: Transform matrix of sweep entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
# 16x group code 47: Transform matrix of path entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"solid": DXFAttr(290, default=0), # bool
|
|
# 0=No alignment; 1=Align sweep entity to path:
|
|
"sweep_alignment_flags": DXFAttr(70, default=0),
|
|
"unknown1": DXFAttr(71, default=0),
|
|
# 2=Translate sweep entity to path; 3=Translate path to sweep entity:
|
|
"align_start": DXFAttr(292, default=0), # bool
|
|
"bank": DXFAttr(293, default=0), # bool
|
|
"base_point_set": DXFAttr(294, default=0), # bool
|
|
"sweep_entity_transform_computed": DXFAttr(295, default=0), # bool
|
|
"path_entity_transform_computed": DXFAttr(296, default=0), # bool
|
|
"reference_vector_for_controlling_twist": DXFAttr(11, xtype=XType.point3d),
|
|
},
|
|
)
|
|
acdb_extruded_surface_group_codes = group_code_mapping(acdb_extruded_surface)
|
|
|
|
|
|
@register_entity
|
|
class ExtrudedSurface(Surface):
|
|
"""DXF EXTRUDEDSURFACE entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "EXTRUDEDSURFACE"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class,
|
|
acdb_entity,
|
|
acdb_modeler_geometry,
|
|
acdb_surface,
|
|
acdb_extruded_surface,
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.transformation_matrix_extruded_entity = Matrix44()
|
|
self.sweep_entity_transformation_matrix = Matrix44()
|
|
self.path_entity_transformation_matrix = Matrix44()
|
|
|
|
@override
|
|
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
|
assert isinstance(entity, ExtrudedSurface)
|
|
super().copy_data(entity, copy_strategy)
|
|
entity.transformation_matrix_extruded_entity = (
|
|
self.transformation_matrix_extruded_entity.copy()
|
|
)
|
|
entity.sweep_entity_transformation_matrix = (
|
|
self.sweep_entity_transformation_matrix.copy()
|
|
)
|
|
entity.path_entity_transformation_matrix = (
|
|
self.path_entity_transformation_matrix.copy()
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(
|
|
dxf, acdb_extruded_surface_group_codes, 4, log=False
|
|
)
|
|
self.load_matrices(processor.subclasses[4])
|
|
return dxf
|
|
|
|
def load_matrices(self, tags: Tags):
|
|
self.transformation_matrix_extruded_entity = load_matrix(tags, code=40)
|
|
self.sweep_entity_transformation_matrix = load_matrix(tags, code=46)
|
|
self.path_entity_transformation_matrix = load_matrix(tags, code=47)
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_extruded_surface.name)
|
|
self.dxf.export_dxf_attribs(tagwriter, ["class_id", "sweep_vector"])
|
|
export_matrix(
|
|
tagwriter,
|
|
code=40,
|
|
matrix=self.transformation_matrix_extruded_entity,
|
|
)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"draft_angle",
|
|
"draft_start_distance",
|
|
"draft_end_distance",
|
|
"twist_angle",
|
|
"scale_factor",
|
|
"align_angle",
|
|
],
|
|
)
|
|
export_matrix(
|
|
tagwriter, code=46, matrix=self.sweep_entity_transformation_matrix
|
|
)
|
|
export_matrix(tagwriter, code=47, matrix=self.path_entity_transformation_matrix)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"solid",
|
|
"sweep_alignment_flags",
|
|
"unknown1",
|
|
"align_start",
|
|
"bank",
|
|
"base_point_set",
|
|
"sweep_entity_transform_computed",
|
|
"path_entity_transform_computed",
|
|
"reference_vector_for_controlling_twist",
|
|
],
|
|
)
|
|
|
|
|
|
acdb_lofted_surface = DefSubclass(
|
|
"AcDbLoftedSurface",
|
|
{
|
|
# 16x group code 40: Transform matrix of loft entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"plane_normal_lofting_type": DXFAttr(70),
|
|
"start_draft_angle": DXFAttr(41, default=0.0), # in radians
|
|
"end_draft_angle": DXFAttr(42, default=0.0), # in radians
|
|
"start_draft_magnitude": DXFAttr(43, default=0.0),
|
|
"end_draft_magnitude": DXFAttr(44, default=0.0),
|
|
"arc_length_parameterization": DXFAttr(290, default=0), # bool
|
|
"no_twist": DXFAttr(291, default=1), # true/false
|
|
"align_direction": DXFAttr(292, default=1), # bool
|
|
"simple_surfaces": DXFAttr(293, default=1), # bool
|
|
"closed_surfaces": DXFAttr(294, default=0), # bool
|
|
"solid": DXFAttr(295, default=0), # true/false
|
|
"ruled_surface": DXFAttr(296, default=0), # bool
|
|
"virtual_guide": DXFAttr(297, default=0), # bool
|
|
},
|
|
)
|
|
acdb_lofted_surface_group_codes = group_code_mapping(acdb_lofted_surface)
|
|
|
|
|
|
@register_entity
|
|
class LoftedSurface(Surface):
|
|
"""DXF LOFTEDSURFACE entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "LOFTEDSURFACE"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class,
|
|
acdb_entity,
|
|
acdb_modeler_geometry,
|
|
acdb_surface,
|
|
acdb_lofted_surface,
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.transformation_matrix_lofted_entity = Matrix44()
|
|
|
|
@override
|
|
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
|
assert isinstance(entity, LoftedSurface)
|
|
super().copy_data(entity, copy_strategy)
|
|
entity.transformation_matrix_lofted_entity = (
|
|
self.transformation_matrix_lofted_entity.copy()
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(
|
|
dxf, acdb_lofted_surface_group_codes, 4, log=False
|
|
)
|
|
self.load_matrices(processor.subclasses[4])
|
|
return dxf
|
|
|
|
def load_matrices(self, tags: Tags):
|
|
self.transformation_matrix_lofted_entity = load_matrix(tags, code=40)
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_lofted_surface.name)
|
|
export_matrix(
|
|
tagwriter, code=40, matrix=self.transformation_matrix_lofted_entity
|
|
)
|
|
self.dxf.export_dxf_attribs(tagwriter, acdb_lofted_surface.attribs.keys())
|
|
|
|
|
|
acdb_revolved_surface = DefSubclass(
|
|
"AcDbRevolvedSurface",
|
|
{
|
|
"class_id": DXFAttr(90, default=0.0),
|
|
"axis_point": DXFAttr(10, xtype=XType.point3d),
|
|
"axis_vector": DXFAttr(11, xtype=XType.point3d),
|
|
"revolve_angle": DXFAttr(40), # in radians
|
|
"start_angle": DXFAttr(41), # in radians
|
|
# 16x group code 42: Transform matrix of revolved entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"draft_angle": DXFAttr(43), # in radians
|
|
"start_draft_distance": DXFAttr(44, default=0),
|
|
"end_draft_distance": DXFAttr(45, default=0),
|
|
"twist_angle": DXFAttr(46, default=0), # in radians
|
|
"solid": DXFAttr(290, default=0), # bool
|
|
"close_to_axis": DXFAttr(291, default=0), # bool
|
|
},
|
|
)
|
|
acdb_revolved_surface_group_codes = group_code_mapping(acdb_revolved_surface)
|
|
|
|
|
|
@register_entity
|
|
class RevolvedSurface(Surface):
|
|
"""DXF REVOLVEDSURFACE entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "REVOLVEDSURFACE"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class,
|
|
acdb_entity,
|
|
acdb_modeler_geometry,
|
|
acdb_surface,
|
|
acdb_revolved_surface,
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.transformation_matrix_revolved_entity = Matrix44()
|
|
|
|
@override
|
|
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
|
assert isinstance(entity, RevolvedSurface)
|
|
super().copy_data(entity, copy_strategy)
|
|
entity.transformation_matrix_revolved_entity = (
|
|
self.transformation_matrix_revolved_entity.copy()
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(
|
|
dxf, acdb_revolved_surface_group_codes, 4, log=False
|
|
)
|
|
self.load_matrices(processor.subclasses[4])
|
|
return dxf
|
|
|
|
def load_matrices(self, tags: Tags):
|
|
self.transformation_matrix_revolved_entity = load_matrix(tags, code=42)
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_revolved_surface.name)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"class_id",
|
|
"axis_point",
|
|
"axis_vector",
|
|
"revolve_angle",
|
|
"start_angle",
|
|
],
|
|
)
|
|
export_matrix(
|
|
tagwriter,
|
|
code=42,
|
|
matrix=self.transformation_matrix_revolved_entity,
|
|
)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"draft_angle",
|
|
"start_draft_distance",
|
|
"end_draft_distance",
|
|
"twist_angle",
|
|
"solid",
|
|
"close_to_axis",
|
|
],
|
|
)
|
|
|
|
|
|
acdb_swept_surface = DefSubclass(
|
|
"AcDbSweptSurface",
|
|
{
|
|
"swept_entity_id": DXFAttr(90),
|
|
# 90: size of binary data (lost on saving)
|
|
# 310: binary data (lost on saving)
|
|
"path_entity_id": DXFAttr(91),
|
|
# 90: size of binary data (lost on saving)
|
|
# 310: binary data (lost on saving)
|
|
# 16x group code 40: Transform matrix of sweep entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
# 16x group code 41: Transform matrix of path entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"draft_angle": DXFAttr(42), # in radians
|
|
"draft_start_distance": DXFAttr(43, default=0),
|
|
"draft_end_distance": DXFAttr(44, default=0),
|
|
"twist_angle": DXFAttr(45, default=0), # in radians
|
|
"scale_factor": DXFAttr(48, default=1),
|
|
"align_angle": DXFAttr(49, default=0), # in radians
|
|
# don't know the meaning of this matrices
|
|
# 16x group code 46: Transform matrix of sweep entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
# 16x group code 47: Transform matrix of path entity (16 floats;
|
|
# row major format; default = identity matrix)
|
|
"solid": DXFAttr(290, default=0), # in radians
|
|
# 0=No alignment; 1= align sweep entity to path:
|
|
"sweep_alignment": DXFAttr(70, default=0),
|
|
"unknown1": DXFAttr(71, default=0),
|
|
# 2=Translate sweep entity to path; 3=Translate path to sweep entity:
|
|
"align_start": DXFAttr(292, default=0), # bool
|
|
"bank": DXFAttr(293, default=0), # bool
|
|
"base_point_set": DXFAttr(294, default=0), # bool
|
|
"sweep_entity_transform_computed": DXFAttr(295, default=0), # bool
|
|
"path_entity_transform_computed": DXFAttr(296, default=0), # bool
|
|
"reference_vector_for_controlling_twist": DXFAttr(11, xtype=XType.point3d),
|
|
},
|
|
)
|
|
acdb_swept_surface_group_codes = group_code_mapping(acdb_swept_surface)
|
|
|
|
|
|
@register_entity
|
|
class SweptSurface(Surface):
|
|
"""DXF SWEPTSURFACE entity - container entity for embedded ACIS data."""
|
|
|
|
DXFTYPE = "SWEPTSURFACE"
|
|
DXFATTRIBS = DXFAttributes(
|
|
base_class,
|
|
acdb_entity,
|
|
acdb_modeler_geometry,
|
|
acdb_surface,
|
|
acdb_swept_surface,
|
|
)
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.transformation_matrix_sweep_entity = Matrix44()
|
|
self.transformation_matrix_path_entity = Matrix44()
|
|
self.sweep_entity_transformation_matrix = Matrix44()
|
|
self.path_entity_transformation_matrix = Matrix44()
|
|
|
|
@override
|
|
def copy_data(self, entity: Self, copy_strategy=default_copy) -> None:
|
|
assert isinstance(entity, SweptSurface)
|
|
super().copy_data(entity, copy_strategy)
|
|
entity.transformation_matrix_sweep_entity = (
|
|
self.transformation_matrix_sweep_entity.copy()
|
|
)
|
|
entity.transformation_matrix_path_entity = (
|
|
self.transformation_matrix_path_entity.copy()
|
|
)
|
|
entity.sweep_entity_transformation_matrix = (
|
|
self.sweep_entity_transformation_matrix.copy()
|
|
)
|
|
entity.path_entity_transformation_matrix = (
|
|
self.path_entity_transformation_matrix.copy()
|
|
)
|
|
|
|
@override
|
|
def load_dxf_attribs(
|
|
self, processor: Optional[SubclassProcessor] = None
|
|
) -> DXFNamespace:
|
|
dxf = super().load_dxf_attribs(processor)
|
|
if processor:
|
|
processor.fast_load_dxfattribs(
|
|
dxf, acdb_swept_surface_group_codes, 4, log=False
|
|
)
|
|
self.load_matrices(processor.subclasses[4])
|
|
return dxf
|
|
|
|
def load_matrices(self, tags: Tags):
|
|
self.transformation_matrix_sweep_entity = load_matrix(tags, code=40)
|
|
self.transformation_matrix_path_entity = load_matrix(tags, code=41)
|
|
self.sweep_entity_transformation_matrix = load_matrix(tags, code=46)
|
|
self.path_entity_transformation_matrix = load_matrix(tags, code=47)
|
|
|
|
@override
|
|
def export_entity(self, tagwriter: AbstractTagWriter) -> None:
|
|
"""Export entity specific data as DXF tags."""
|
|
# base class export is done by parent class
|
|
super().export_entity(tagwriter)
|
|
# AcDbEntity export is done by parent class
|
|
# AcDbModelerGeometry export is done by parent class
|
|
tagwriter.write_tag2(const.SUBCLASS_MARKER, acdb_swept_surface.name)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"swept_entity_id",
|
|
"path_entity_id",
|
|
],
|
|
)
|
|
export_matrix(
|
|
tagwriter, code=40, matrix=self.transformation_matrix_sweep_entity
|
|
)
|
|
export_matrix(tagwriter, code=41, matrix=self.transformation_matrix_path_entity)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"draft_angle",
|
|
"draft_start_distance",
|
|
"draft_end_distance",
|
|
"twist_angle",
|
|
"scale_factor",
|
|
"align_angle",
|
|
],
|
|
)
|
|
|
|
export_matrix(
|
|
tagwriter, code=46, matrix=self.sweep_entity_transformation_matrix
|
|
)
|
|
export_matrix(tagwriter, code=47, matrix=self.path_entity_transformation_matrix)
|
|
self.dxf.export_dxf_attribs(
|
|
tagwriter,
|
|
[
|
|
"solid",
|
|
"sweep_alignment",
|
|
"unknown1",
|
|
"align_start",
|
|
"bank",
|
|
"base_point_set",
|
|
"sweep_entity_transform_computed",
|
|
"path_entity_transform_computed",
|
|
"reference_vector_for_controlling_twist",
|
|
],
|
|
)
|