New DXF importer, based in DXFGrabber.

This addon replaces the old DXF importer.

Written by cnd (Lukas Treyer).

It uses dxfgrabber - copyright (C) 2012 by Manfred Moitzi (mozman) (MIT license).

Review and some cleanup by mont29 (Bastien Montagne).
This commit is contained in:
Lukas Treyer 2014-08-19 16:06:16 +02:00 committed by Bastien Montagne
parent ff4c009b18
commit 82a00ee2a0
37 changed files with 6628 additions and 2600 deletions

137
io_import_dxf/Readme.md Normal file
View File

@ -0,0 +1,137 @@
_Development Repository of DXF importer for Blender including DXF and .blend testfiles. Help out making the importer more realiable by posting your errors in the [issue tracker](https://bitbucket.org/treyerl/io_import_scene_dxf/issues?status=new&status=open) or in the [BlenderArtists thread](http://blenderartists.org/forum/showthread.php?323358-DXF-Importer)._
# Features v0.8.4
* __Blocks__ are being imported and are reflected in Blender as linked objects or optionally as group instances. For linked objects sub-blocks get parented to the main block. If a block contains mixed curve / mesh / surface / text / light entities, the different types are being imported to different objects that are being parented to the main block.
* __Layers__ are being reflected with "Blender-groups". Select an object and type Shift-G to select all objects within the same "Blender-Group" (they should call it category, because that's what it is). Anyhow as Blender supports "only" 20 layers and DXF files can have virtually an infinite amount of layers I think it's best users would select grouped objects as described and move them themselves to layers as they wish.
* __Speed__: Using as many generators instead of lists as possible minimizes memory consumption. Parts of the the underlying dxf library "dxfgrabber" are written in cython and can be compiled to platform specific modules.
* __DXF Attributes__: DXF specific attributes (e.g. `thickness`, `width` and `extrusion`) are taken into account to import geometry as precise as possible.
* __Geo Referencing__: If the pyproj library is available, the scene center will be converted to lat/lon taking into account re-centering of geometry. The origin/DXF coordinate system (SRID) must be specified. If you have a DXF file from QGIS or ArcGIS this option should be most likely set to WGS84. The destination/scene SRID is by default the same as the DXF SRID, but of course you can set it to your local coordinate system. If a scene has a SRID already, this option is not available and the DXF SRID MUST be specified, so that the DXF geometry can be aligned to the scene geometry. For the installation of pyproj see "Installation".
### [Options](https://bitbucket.org/repo/5M8eeg/images/616018241-0.8.4.jpg):
* import `TEXT` entities (`TEXT`, `MTEXT`)
* import `LIGHT` entity, incl. support for AutoCAD colors
* merge all entities of a layer into one object per a) Blender geometry type b) DXF geometry type
* export NURBS 3D geometry (`BODY`, `REGION`, `PLANESURFACE`, `SURFACE`, `3DSOLID`) to ACIS-Sat files, since this is the format AutoCAD stores to DXF. The user is being notified about the amount of stored .sat/.sab files (any comments on experience with other 3D packages than AutoCAD are very welcome).
* combine `Line` entities to Blender "POLY"-curves (= remove doubles)
* switch outliner display mode to GROUPS
* display BLOCK entities with bounding boxes (instead parenting them only to Empties)
* import BLOCK entities as linked objects or group instances (default = linked objects)
* import DXF file to a new scene
* center the imported geometry to the center of the scene; the offset information is stored as a custom property to the scene
### DXF entities being mapped to BLENDER CURVES:
* `LINE` as "POLYLINE"-curve
* `(LW)POLYLINE`, `(LW)POLYGON` as "POLYLINE"-curve if they have no bulges else as "BEZIER"-curve
* conversion to Blender's cubic "BEZIER"-curve of
* quadratic `SPLINE`s and `(LW)POLYLINE`s
* splines with degree > 3 are imported as polylines (so far).
* `ARC`s, `CIRCLE`s and `ELLIPSE`s
* polys with bulges (`(LW)POLYLINE`, `POLYGON`)
* `HELIX`es (__3D__)
### DXF entities being mapped to BLENDER MESHES:
* `MESH` is mapped to an mesh object with a SubD modifier, incl. edge crease
* `POLYFACE`s and `POLYMESH`es are imported to a mesh object
* `3DFACE`s, `SOLID`s, `POINT`s are imported into one combined mesh object per layer called _layername_3Dfaces_.
# Installation (User)
* download the latest `io_import_scene_dxf.zip` file from the [download section](https://bitbucket.org/treyerl/io_import_scene_dxf/downloads)
* in Blender go to File -> User Preferences -> Addons and click `Install from file` at the bottom and choose the downloaded zip file.
* optional 'pyproj': Download ([WIN](https://code.google.com/p/pyproj/downloads/list), [MAC](http://www.ia.arch.ethz.ch/wp-content/uploads/2013/11/pyproj.zip)) pyproj and copy it to *your AppData/ApplicationSupport Folder*/Blender/2.70/scripts/modules/
# Roadmap / Release Info
_version 1.0.0 should be able to import `ALL` DXF/AutoCAD information translatable in some way to Blender._
##0.9.x: Materials
_(surfaces attributes like color, hatches etc.)_
* color to material map
* hatches: dynamic generation of hatch-textures? procedural textures?
* line colors and width to freestyle?
##0.8.x: Geometry:
* blocks not reference by `INSERT`-entity but name starting with `*` (Dimensions)
* text alignment attributes
* bsplines with degree > 3? any test-files are welcome!
* update option (?):
* named entities (blocks) are imported but don't replace existing Blender objects with the same name
but only update their geometry if it changed
###0.8.4
* proper knot insertion: bsplines are now properly converted to cubic bezier splines
* geo-referencing: if pyproj is available, the scene center will be converted to lat/lon taking
into account re-centering of geometry. The origin/DXF coordinate system (SRID) must be specified. If you have a
DXF file from QGIS or ArcGIS this option should be most likely set to WGS84. The destination/scene SRID
is by default the same as the DXF SRID, but of course you can set it to your local coordinate system.
If a scene has a SRID already, this option is not available and the DXF SRID MUST be specified, so that the DXF
geometry can be aligned to the scene geometry.
###0.8.3
* many bug fixes (better testing with test-script)
* new option: switch display mode to "GROUPS" in outliner
* new option: BLOCK entities with bounding boxes
* new option: BLOCK representation as group instances (for INSERTs with sub-inserts and rows and cols)
* new option: import dxf to a new scene
* new option: center the imported geometry and set the offset information as custom properties in the scene
The offset is stored as a custom property to the scene. The key of the property is called like
"*name of the dxf file*_recenter" and the value is a [x,y,z] array. "lat"/"lon" georeferencing is not possible
since DXF does not store a EPSG coordinate system reference. But if the users know the coordinate system of the
DXF file they can convert the x,y,z offset to lat/lon/altitude ([duck it](https://duckduckgo.com)).
DXF does not store any information about the projection of x,y to lat,lon = the user needs to convert x, y to lat, lon.
* dxf file filter in the file browser
* POINT entities get imported as Empties if the merge option is turned off
* display errors as pop up message (for known errors)
* block objects get copied with obj.copy() = not only the geometry is being cached, but the whole object
= skips unnecessary calculation (especially for bounding boxes)
###0.8.2
* added support for "width" attribute on curve types.
* improved thickness attribute handling on curve types; tilt option is Z_UP (--> check for Blender bug), for better shading a Edge Split Modifier is being added if the curve has width AND thickness
* added thickness on solids and traces
* improved solids: upon self intersection two faces are created
* added support for TRACE entity
* better yet not perfect extrusion handling
* added option to merge connecting LINE entities into polygons
* INSERT col and row attributes are handled with array modifier (needs improvement on BLOCK entities: instead of Empty the parent should be a bounding box)
* TEXT entity now uses plain_text() filter (no %%u and %%d symbols anymore)
###0.8.1 (Start of version_info:):
* GUI options (for already existing functionality):
* import text `True/False`
* import lights `True/False`
* export ACIS code for NURB types `True/False`
* merge entities to Blender objects `True/False`
* by layer
* by layer and then by DXF entity type
* extrusion z-value for 2D types and `INSERT` to x-mirror the entity (which will be excluded from merged entities)
* using bmesh-layers/loops for crease information of `MESH`
* more clear code structure and doc strings
###0.8.0:
version of APR2014:
* Added 3D, text (including attributes, no style), light types. Code restructuring since it grew and grew.
* Much improved dxfgrabber library incl. some parts in Cython for speedup. Text, Style, Light, and some 3D types, especially ACIS geometry became feasible only because of the extended capabilities of dxfgrabber.
* Tackled DXF Sample Files from [CADKit](http://www.cadkit.net/2012/01/sample-dxf-files.html):
* introducing some hacks and bug-fixes; bsplines with order higher > 4 will be most likely be imported as straight polylines
* introduced merged entities; especially with `3DFACE`s this lead to massive speed improvements.
* added License information: GPL
###0.1.0:
version of JAN2014 was only able to import 2D curves. no 3D information. But Blocks got mapped to linked objects already and layers to groups.
# Installation (Development)
* copy/clone this repository to [Blender's addon folder](https://www.google.ch/search?client=safari&rls=en&q=blender+python+modules&ie=UTF-8&oe=UTF-8&gfe_rd=cr&ei=cvJpU6yAI6LC8gfB7IDICA#q=Configuration+%26+Data+Paths+-+Blender+Wiki&rls=en)
* download the latest version of [dxfgrabber](https://bitbucket.org/mozman/dxfgrabber) and copy its dxfgrabber folder into the this repository.
* in Blender go to File -> User Preferences -> Addons and search for "dxf" and activate the dxf import addon (there might be an old addon with an exlamation mark, don't activate that one)
* test it with the supplied testfiles and the [DXF sample files from cadkit.net](http://www.mediafire.com/?pcq6a8pbsiz6paw)
### Development on a mac
It helps to start Blender from the Terminal because thats where the python print statements go.
1. right-click on the Blender-icon and select show package contents
2. navigate to Contents/MacOS/
3. either double-click on "blender" or drag and drop it to a Terminal window

544
io_import_dxf/__init__.py Normal file
View File

@ -0,0 +1,544 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import bpy
import os
from bpy.props import StringProperty, BoolProperty, EnumProperty, IntProperty, FloatProperty
from .dxfimport.do import Do, Indicator
from .transverse_mercator import TransverseMercator
try:
from pyproj import Proj, transform
PYPROJ = True
except:
PYPROJ = False
bl_info = {
"name": "Import AutoCAD DXF Format (.dxf)",
"author": "Lukas Treyer, Manfred Moitzi (support + dxfgrabber library), Vladimir Elistratov, Bastien Montagne",
"version": (0, 8, 5),
"blender": (2, 7, 1),
"location": "File > Import > AutoCAD DXF",
"description": "Import files in the Autocad DXF format (.dxf)",
"wiki_url": "https://bitbucket.org/treyerl/io_import_scene_dxf/overview",
"tracker_url": "https://bitbucket.org/treyerl/io_import_scene_dxf/issues?status=new&status=open",
"category": "Import-Export",
}
proj_none_items = (
('NONE', "None", "No Coordinate System is available / will be set"),
)
proj_user_items = (
('USER', "User Defined", "Define the EPSG code"),
)
proj_tmerc_items = (
('TMERC', "Transverse Mercator", "Mercator Projection using a lat/lon coordinate as its geo-reference"),
)
proj_epsg_items = (
('EPSG:4326', "WGS84", "World Geodetic System 84; default for lat / lon; EPSG:4326"),
('EPSG:3857', "Spherical Mercator", "Webbrowser mapping service standard (Google, OpenStreetMap, ESRI); EPSG:3857"),
('EPSG:27700', "National Grid U.K",
"Ordnance Survey National Grid reference system used in Great Britain; EPSG:27700"),
('EPSG:2154', "France (Lambert 93)", "Lambert Projection for France; EPSG:2154"),
('EPSG:5514', "Czech Republic & Slovakia", "Coordinate System for Czech Republic and Slovakia; EPSG:5514"),
('EPSG:5243', "LLC Germany", "Projection for Germany; EPSG:5243"),
('EPSG:28992', "Amersfoort Netherlands", "Amersfoort / RD New -- Netherlands; EPSG:28992"),
('EPSG:21781', "Swiss CH1903 / LV03", "Switzerland and Lichtenstein; EPSG:21781"),
('EPSG:5880', "Brazil Polyconic", "Cartesian 2D; Central, South America; EPSG:5880 "),
('EPSG:42103', "LCC USA", "Lambert Conformal Conic Projection; EPSG:42103"),
('EPSG:3350', "Russia: Pulkovo 1942 / CS63 zone C0", "Russian Federation - onshore and offshore; EPSG:3350"),
('EPSG:22293', "Cape / Lo33 South Africa", "South Africa; EPSG:22293"),
('EPSG:27200', "NZGD49 / New Zealand Map Grid", "NZGD49 / New Zealand Map Grid; EPSG:27200"),
('EPSG:3112', "GDA94 Australia Lambert", "GDA94 / Geoscience Australia Lambert; EPSG:3112"),
('EPSG:24378', "India zone I", "Kalianpur 1975 / India zone I; EPSG:24378"),
('EPSG:2326', "Hong Kong 1980 Grid System", "Hong Kong 1980 Grid System; EPSG:2326"),
('EPSG:3414', "SVY21 / Singapore TM", "SVY21 / Singapore TM; EPSG:3414"),
)
proj_epsg_dict = {e[0]: e[1] for e in proj_epsg_items}
__version__ = '.'.join([str(s) for s in bl_info['version']])
BY_LAYER = 0
BY_DXFTYPE = 1
SEPARATED = 2
LINKED_OBJECTS = 3
GROUP_INSTANCES = 4
T_Merge = True
T_ImportText = True
T_ImportLight = True
T_ExportAcis = False
T_MergeLines = True
T_OutlinerGroups = True
T_Bbox = True
T_CreateNewScene = False
T_Recenter = False
T_ThicknessBevel = False
T_import_atts = True
RELEASE_TEST = False
DEBUG = False
def is_ref_scene(scene):
return "latitude" in scene and "longitude" in scene
def read(report, filename, obj_merge=BY_LAYER, import_text=True, import_light=True, export_acis=True, merge_lines=True,
do_bbox=True, block_rep=LINKED_OBJECTS, new_scene=None, recenter=False, projDXF=None, projSCN=None,
thicknessWidth=True, but_group_by_att=True, dxf_unit_scale=1.0):
# import dxf and export nurbs types to sat/sab files
# because that's how autocad stores nurbs types in a dxf...
do = Do(filename, obj_merge, import_text, import_light, export_acis, merge_lines, do_bbox, block_rep, recenter,
projDXF, projSCN, thicknessWidth, but_group_by_att, dxf_unit_scale)
errors = do.entities(os.path.basename(filename).replace(".dxf", ""), new_scene)
# display errors
for error in errors:
report('ERROR', error)
# inform the user about the sat/sab files
if len(do.acis_files) > 0:
report('INFO', "Exported %d NURBS objects to sat/sab files next to your DXF file" % len(do.acis_files))
def display_groups_in_outliner():
outliners = (a for a in bpy.context.screen.areas if a.type == "OUTLINER")
for outliner in outliners:
outliner.spaces[0].display_mode = "GROUPS"
# Update helpers (must be globals to be re-usable).
def _update_use_georeferencing_do(self, context):
if not self.create_new_scene:
scene = context.scene
# Try to get Scene SRID (ESPG) data from current scene.
srid = scene.get("SRID", None)
if srid is not None:
self.internal_using_scene_srid = True
srid = srid.upper()
if srid == 'TMERC':
self.proj_scene = 'TMERC'
self.merc_scene_lat = scene.get('latitude', 0)
self.merc_scene_lon = scene.get('longitude', 0)
else:
if srid in (p[0] for p in proj_epsg_items):
self.proj_scene = srid
else:
self.proj_scene = 'USER'
self.epsg_scene_user = srid
else:
self.internal_using_scene_srid = False
else:
self.internal_using_scene_srid = False
def _recenter_allowed(self):
scene = bpy.context.scene
return (not (self.use_georeferencing and (self.proj_scene == 'TMERC'
or (not self.create_new_scene and is_ref_scene(scene))))
or (not PYPROJ and self.dxf_indi == "EUCLIDEAN"))
def _set_recenter(self, value):
self.recenter = value if _recenter_allowed(self) else False
def _update_proj_scene_do(self, context):
# make sure scene EPSG is not None if DXF EPSG is not None
if self.proj_scene == 'NONE' and self.proj_dxf != 'NONE':
self.proj_scene = self.proj_dxf
def _update_import_atts_do(self, context):
if self.represent_thickness_and_width and self.merge:
self.import_atts = True
elif not self.merge:
self.import_atts = False
class IMPORT_OT_dxf(bpy.types.Operator):
"""Import from DXF file format (.dxf)"""
bl_idname = "import_scene.dxf"
bl_description = 'Import from DXF file format (.dxf)'
bl_label = "Import DXf v." + __version__
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
bl_options = {'UNDO'}
filepath = StringProperty(
name="input file",
subtype='FILE_PATH'
)
filename_ext = ".dxf"
filter_glob = StringProperty(
default="*.dxf",
options={'HIDDEN'},
)
def _update_merge(self, context):
_update_import_atts_do(self, context)
merge = BoolProperty(
name="Merged Objects",
description="Merge DXF entities to Blender objects",
default=T_Merge,
update=_update_merge
)
merge_options = EnumProperty(
name="Merge",
description="Merge multiple DXF entities into one Blender object",
items=[('BY_TYPE', "By Layer AND Dxf-Type", "Merge DXF entities by type AND layer"),
('BY_LAYER', "By Layer", "Merge DXF entities of a layer to an object")],
default='BY_LAYER',
)
merge_lines = BoolProperty(
name="Combine LINE entities to polygons",
description="Checks if lines are connect on start or end and merges them to a polygon",
default=T_MergeLines
)
import_text = BoolProperty(
name="Import Text",
description="Import DXF Text Entities MTEXT and TEXT",
default=T_ImportText,
)
import_light = BoolProperty(
name="Import Lights",
description="Import DXF Text Entity LIGHT",
default=T_ImportLight
)
export_acis = BoolProperty(
name="Export ACIS Entities",
description="Export Entities consisting of ACIS code to ACIS .sat/.sab files",
default=T_ExportAcis
)
outliner_groups = BoolProperty(
name="Display Groups in Outliner(s)",
description="Make all outliners in current screen layout show groups",
default=T_OutlinerGroups
)
do_bbox = BoolProperty(
name="Parent Blocks to Bounding Boxes",
description="Create a bounding box for blocks with more than one object (faster without)",
default=T_Bbox
)
block_options = EnumProperty(
name="Blocks As",
description="Select the representation of DXF blocks: linked objects or group instances",
items=[('LINKED_OBJECTS', "Linked Objects", "Block objects get imported as linked objects"),
('GROUP_INSTANCES', "Group Instances", "Block objects get imported as group instances")],
default='LINKED_OBJECTS',
)
def _update_create_new_scene(self, context):
_update_use_georeferencing_do(self, context)
_set_recenter(self, self.recenter)
create_new_scene = BoolProperty(
name="Import DXF to new scene",
description="Creates a new scene with the name of the imported file",
default=T_CreateNewScene,
update=_update_create_new_scene,
)
recenter = BoolProperty(
name="Center geometry to scene",
description="Moves geometry to the center of the scene",
default=T_Recenter,
)
def _update_thickness_width(self, context):
_update_import_atts_do(self, context)
represent_thickness_and_width = BoolProperty(
name="Represent line thickness/width",
description="Map thickness and width of lines to Bevel objects and extrusion attribute",
default=T_ThicknessBevel,
update=_update_thickness_width
)
import_atts = BoolProperty(
name="Merge by attributes",
description="If 'Merge objects' is on but thickness and width are not chosen to be represented, with this "
"option object still can be merged by thickness, with, subd and extrusion attributes "
"(extrusion = transformation matrix of DXF objects)",
default=T_import_atts
)
# geo referencing
def _update_use_georeferencing(self, context):
_update_use_georeferencing_do(self, context)
_set_recenter(self, self.recenter)
use_georeferencing = BoolProperty(
name="Geo Referencing",
description="Project coordinates to a given coordinate system or reference point",
default=True,
update=_update_use_georeferencing,
)
def _update_dxf_indi(self, context):
_set_recenter(self, self.recenter)
dxf_indi = EnumProperty(
name="DXF coordinate type",
description="Indication for spherical or euclidian coordinates",
items=[('EUCLIDEAN', "Euclidean", "Coordinates in x/y"),
('SPHERICAL', "Spherical", "Coordinates in lat/lon")],
default='EUCLIDEAN',
update=_update_dxf_indi,
)
# Note: FloatProperty is not precise enough, e.g. 1.0 becomes 0.999999999. Python is more precise here (it uses
# doubles internally), so we store it as string here and convert to number with py's float() func.
dxf_scale = StringProperty(
name="Unit Scale",
description="Coordinates are assumed to be in meters; deviation must be indicated here",
default="1.0"
)
def _update_proj(self, context):
_update_proj_scene_do(self, context)
_set_recenter(self, self.recenter)
if PYPROJ:
pitems = proj_none_items + proj_user_items + proj_epsg_items
proj_dxf = EnumProperty(
name="DXF SRID",
description="The coordinate system for the DXF file (check http://epsg.io)",
items=pitems,
default='NONE',
update=_update_proj,
)
epsg_dxf_user = StringProperty(name="EPSG-Code", default="EPSG")
merc_dxf_lat = FloatProperty(name="Geo-Reference Latitude", default=0.0)
merc_dxf_lon = FloatProperty(name="Geo-Reference Longitude", default=0.0)
pitems = proj_none_items + ((proj_user_items + proj_tmerc_items + proj_epsg_items) if PYPROJ else proj_tmerc_items)
proj_scene = EnumProperty(
name="Scn SRID",
description="The coordinate system for the Scene (check http://epsg.io)",
items=pitems,
default='NONE',
update=_update_proj,
)
epsg_scene_user = StringProperty(name="EPSG-Code", default="EPSG")
merc_scene_lat = FloatProperty(name="Geo-Reference Latitude", default=0.0)
merc_scene_lon = FloatProperty(name="Geo-Reference Longitude", default=0.0)
# internal use only!
internal_using_scene_srid = BoolProperty(default=False, options={'HIDDEN'})
def draw(self, context):
layout = self.layout
scene = context.scene
# merge options
layout.label("Merge Options:")
box = layout.box()
box.prop(self, "block_options")
box.prop(self, "do_bbox")
box.prop(self, "merge")
sub = box.row()
sub.enabled = self.merge
sub.prop(self, "merge_options")
box.prop(self, "merge_lines")
# general options
layout.label("Line thickness and width:")
box = layout.box()
box.prop(self, "represent_thickness_and_width")
sub = box.row()
sub.enabled = (not self.represent_thickness_and_width and self.merge)
sub.prop(self, "import_atts")
# optional objects
layout.label("Optional Objects:")
box = layout.box()
box.prop(self, "import_text")
box.prop(self, "import_light")
box.prop(self, "export_acis")
# view options
layout.label("View Options:")
box = layout.box()
box.prop(self, "outliner_groups")
box.prop(self, "create_new_scene")
sub = box.row()
sub.enabled = _recenter_allowed(self)
sub.prop(self, "recenter")
# geo referencing
layout.prop(self, "use_georeferencing", text="Geo Referencing:")
box = layout.box()
box.enabled = self.use_georeferencing
self.draw_pyproj(box, context.scene) if PYPROJ else self.draw_merc(box)
def draw_merc(self, box):
box.label("DXF File:")
box.prop(self, "dxf_indi")
box.prop(self, "dxf_scale")
sub = box.column()
sub.enabled = not _recenter_allowed(self)
sub.label("Geo Reference:")
sub = box.column()
sub.enabled = not _recenter_allowed(self) and self.create_new_scene
sub.prop(self, "merc_scene_lat", text="Lat")
sub.prop(self, "merc_scene_lon", text="Lon")
def draw_pyproj(self, box, scene):
valid_dxf_srid = True
# DXF SCALE
box.prop(self, "dxf_scale")
# EPSG DXF
box.alert = (self.proj_scene != 'NONE' and (not valid_dxf_srid or self.proj_dxf == 'NONE'))
box.prop(self, "proj_dxf")
box.alert = False
if self.proj_dxf == 'USER':
try:
Proj(init=self.epsg_dxf_user)
except:
box.alert = True
valid_dxf_srid = False
box.prop(self, "epsg_dxf_user")
box.alert = False
box.separator()
# EPSG SCENE
col = box.column()
# Only info in case of pre-defined EPSG from current scene.
if self.internal_using_scene_srid:
col.enabled = False
col.prop(self, "proj_scene")
if self.proj_scene == 'USER':
try:
Proj(init=self.epsg_scene_user)
except Exception as e:
col.alert = True
col.prop(self, "epsg_scene_user")
col.alert = False
col.label("") # Placeholder.
elif self.proj_scene == 'TMERC':
col.prop(self, "merc_scene_lat", text="Lat")
col.prop(self, "merc_scene_lon", text="Lon")
else:
col.label("") # Placeholder.
col.label("") # Placeholder.
# user info
if self.proj_scene != 'NONE':
if not valid_dxf_srid:
box.label("DXF SRID not valid", icon="ERROR")
if self.proj_dxf == 'NONE':
box.label("", icon='ERROR')
box.label("DXF SRID must be set, otherwise")
if self.proj_scene == 'USER':
code = self.epsg_scene_user
else:
code = self.proj_scene
box.label('Scene SRID %r is ignored!' % code)
def execute(self, context):
merge_map = {"BY_LAYER": BY_LAYER, "BY_TYPE": BY_DXFTYPE}
block_map = {"LINKED_OBJECTS": LINKED_OBJECTS, "GROUP_INSTANCES": GROUP_INSTANCES}
merge_options = SEPARATED
if self.merge:
merge_options = merge_map[self.merge_options]
scene = bpy.context.scene
if self.create_new_scene:
scene = bpy.data.scenes.new(os.path.basename(self.filepath).replace(".dxf", ""))
proj_dxf = None
proj_scn = None
dxf_unit_scale = 1.0
if self.use_georeferencing:
dxf_unit_scale = float(self.dxf_scale)
if PYPROJ:
if self.proj_dxf != 'NONE':
if self.proj_dxf == 'USER':
proj_dxf = Proj(init=self.epsg_dxf_user)
else:
proj_dxf = Proj(init=self.proj_dxf)
if self.proj_scene != 'NONE':
if self.proj_scene == 'USER':
proj_scn = Proj(init=self.epsg_scene_user)
elif self.proj_scene == 'TMERC':
proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon)
else:
proj_scn = Proj(init=self.proj_scene)
else:
proj_dxf = Indicator(self.dxf_indi)
proj_scn = TransverseMercator(lat=self.merc_scene_lat, lon=self.merc_scene_lon)
if RELEASE_TEST:
# for release testing
from . import test
test.test()
else:
read(self.report, self.filepath, merge_options, self.import_text, self.import_light, self.export_acis,
self.merge_lines, self.do_bbox, block_map[self.block_options], scene, self.recenter,
proj_dxf, proj_scn, self.represent_thickness_and_width, self.import_atts, dxf_unit_scale)
if self.outliner_groups:
display_groups_in_outliner()
return {'FINISHED'}
def invoke(self, context, event):
# Force first update...
self._update_use_georeferencing(context)
wm = context.window_manager
wm.fileselect_add(self)
return {'RUNNING_MODAL'}
def menu_func(self, context):
self.layout.operator(IMPORT_OT_dxf.bl_idname, text="AutoCAD DXF")
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_import.append(menu_func)
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_import.remove(menu_func)
if __name__ == "__main__":
register()

View File

@ -0,0 +1,67 @@
# dxfgrabber - copyright (C) 2012 by Manfred Moitzi (mozman)
# Purpose: grab information from DXF drawings - all DXF versions supported
# Created: 21.07.2012
# License: MIT License
version = (0, 7, 4)
VERSION = "%d.%d.%d" % version
__author__ = "mozman <mozman@gmx.at>"
__doc__ = """A Python library to grab information from DXF drawings - all DXF versions supported."""
# Python27/3x support should be done here
import sys
PYTHON3 = sys.version_info.major > 2
if PYTHON3:
tostr = str
else: # PYTHON27
tostr = unicode
# end of Python 2/3 adaption
# if tostr does not work, look at package 'dxfwrite' for escaping unicode chars
from .const import BYBLOCK, BYLAYER
import io
from .tags import dxfinfo
from .color import aci_to_true_color
def read(stream, options=None):
if hasattr(stream, 'readline'):
from .drawing import Drawing
return Drawing(stream, options)
else:
raise AttributeError('stream object requires a readline() method.')
def readfile(filename, options=None):
try: # is it ascii code-page encoded?
return readfile_as_asc(filename, options)
except UnicodeDecodeError: # try unicode and ignore errors
return readfile_as_utf8(filename, options, errors='ignore')
def readfile_as_utf8(filename, options=None, errors='strict'):
return _read_encoded_file(filename, options, encoding='utf-8', errors=errors)
def readfile_as_asc(filename, options=None):
def get_encoding():
with io.open(filename) as fp:
info = dxfinfo(fp)
return info.encoding
return _read_encoded_file(filename, options, encoding=get_encoding())
def _read_encoded_file(filename, options=None, encoding='utf-8', errors='strict'):
from .drawing import Drawing
with io.open(filename, encoding=encoding, errors=errors) as fp:
dwg = Drawing(fp, options)
dwg.filename = filename
return dwg

View File

@ -0,0 +1,88 @@
# Purpose: acdsdata section manager
# Created: 05.05.2014
# Copyright (C) 2014, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from itertools import islice
from .tags import TagGroups, DXFStructureError, Tags, binary_encoded_data_to_bytes
class AcDsDataSection(object):
name = 'acdsdata'
def __init__(self):
# Standard_ACIS_Binary (SAB) data store, key = handle of DXF Entity in the ENTITIES section: BODY, 3DSOLID
# SURFACE, PLANESURFACE, REGION
self.sab_data = {}
@classmethod
def from_tags(cls, tags, drawing):
data_section = cls()
data_section._build(tags, drawing.dxfversion)
return data_section
def _build(self, tags, dxfversion):
if len(tags) == 3: # empty entities section
return
for group in TagGroups(islice(tags, 2, len(tags)-1)):
data_record = AcDsDataRecord(Tags(group))
if data_record.dxftype == 'ACDSRECORD':
asm_data = data_record.get_section('ASM_Data', None)
if asm_data is not None:
self.add_asm_data(data_record)
def add_asm_data(self, acdsrecord):
""" Store SAB data as binary string in the sab_data dict, with handle to owner Entity as key.
"""
try:
asm_data = acdsrecord.get_section('ASM_Data')
entity_id = acdsrecord.get_section('AcDbDs::ID')
except ValueError:
return
else:
handle = entity_id[2].value
binary_data_text = (tag.value for tag in asm_data if tag.code == 310)
binary_data = binary_encoded_data_to_bytes(binary_data_text)
self.sab_data[handle] = binary_data
class Section(Tags):
@property
def name(self):
return self[0].value
@property
def type(self):
return self[1].value
@property
def data(self):
return self[2:]
class AcDsDataRecord(object):
def __init__(self, tags):
self.dxftype = tags[0].value
start_index = 2
while tags[start_index].code != 2:
start_index += 1
self.sections = [Section(tags) for tags in TagGroups(islice(tags, start_index, None), split_code=2)]
def has_section(self, name):
return self.get_section(name, default=None) is not None
def get_section(self, name, default=KeyError):
for section in self.sections:
if section.name == name:
return section
if default is KeyError:
raise KeyError(name)
else:
return default
def __getitem__(self, name):
return self.get_section(name)

View File

@ -0,0 +1,57 @@
# Purpose: blocks section
# Created: 09.08.2012, taken from my package ezdxf
# Copyright (C) 2011, Manfred Moitzi
# License: MIT-License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from itertools import islice
from .tags import TagGroups
from .entitysection import build_entities
class BlocksSection(object):
name = 'blocks'
def __init__(self):
self._blocks = dict()
@staticmethod
def from_tags(tags, drawing):
blocks_section = BlocksSection()
if drawing.grab_blocks:
blocks_section._build(tags, drawing.dxfversion)
return blocks_section
def _build(self, tags, dxfversion):
if len(tags) == 3: # empty block section
return
groups = list()
for group in TagGroups(islice(tags, 2, len(tags)-1)):
groups.append(group)
if group[0].value == 'ENDBLK':
entities = build_entities(groups, dxfversion)
block = entities[0]
block.set_entities(entities[1:-1])
self._add(block)
groups = list()
def _add(self, block):
self._blocks[block.name] = block
# start of public interface
def __len__(self):
return len(self._blocks)
def __iter__(self):
return iter(self._blocks.values())
def __contains__(self, name):
return name in self._blocks
def __getitem__(self, name):
return self._blocks[name]
def get(self, name, default=None):
return self._blocks.get(name, default)

View File

@ -0,0 +1,37 @@
# Purpose: codepage handling
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
codepages = {
'874': 'cp874', # Thai,
'932': 'cp932', # Japanese
'936': 'gbk', # UnifiedChinese
'949': 'cp949', # Korean
'950': 'cp950', # TradChinese
'1250': 'cp1250', # CentralEurope
'1251': 'cp1251', # Cyrillic
'1252': 'cp1252', # WesternEurope
'1253': 'cp1253', # Greek
'1254': 'cp1254', # Turkish
'1255': 'cp1255', # Hebrew
'1256': 'cp1256', # Arabic
'1257': 'cp1257', # Baltic
'1258': 'cp1258', # Vietnam
}
def toencoding(dxfcodepage):
for codepage, encoding in codepages.items():
if dxfcodepage.endswith(codepage):
return encoding
return 'cp1252'
def tocodepage(encoding):
for codepage, enc in codepages.items():
if enc == encoding:
return 'ANSI_'+codepage
return 'ANSI_1252'

301
io_import_dxf/dxfgrabber/color.py Executable file
View File

@ -0,0 +1,301 @@
__author__ = 'manfred'
class TrueColor(int):
def rgb(self):
return (self >> 16) & 0xFF, (self >> 8) & 0xFF, self & 0xFF
@property
def r(self):
return (self >> 16) & 0xFF
@property
def g(self):
return (self >> 8) & 0xFF
@property
def b(self):
return self & 0xFF
def __getitem__(self, item):
if item == 0:
return self.r
elif item == 1:
return self.g
elif item == 2:
return self.b
raise IndexError(item)
@staticmethod
def from_rgb(r, g, b):
return TrueColor(((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff))
@staticmethod
def from_aci(index):
if index < 1:
raise IndexError(index)
return dxf_default_colors[index]
def aci_to_true_color(index):
return TrueColor.from_aci(index)
dxf_default_colors = [
TrueColor(0x000000),
TrueColor(0xff0000),
TrueColor(0xffff00),
TrueColor(0x00ff00),
TrueColor(0x00ffff),
TrueColor(0x0000ff),
TrueColor(0xff00ff),
TrueColor(0xffffff),
TrueColor(0x414141),
TrueColor(0x808080),
TrueColor(0xff0000),
TrueColor(0xffaaaa),
TrueColor(0xbd0000),
TrueColor(0xbd7e7e),
TrueColor(0x810000),
TrueColor(0x815656),
TrueColor(0x680000),
TrueColor(0x684545),
TrueColor(0x4f0000),
TrueColor(0x4f3535),
TrueColor(0xff3f00),
TrueColor(0xffbfaa),
TrueColor(0xbd2e00),
TrueColor(0xbd8d7e),
TrueColor(0x811f00),
TrueColor(0x816056),
TrueColor(0x681900),
TrueColor(0x684e45),
TrueColor(0x4f1300),
TrueColor(0x4f3b35),
TrueColor(0xff7f00),
TrueColor(0xffd4aa),
TrueColor(0xbd5e00),
TrueColor(0xbd9d7e),
TrueColor(0x814000),
TrueColor(0x816b56),
TrueColor(0x683400),
TrueColor(0x685645),
TrueColor(0x4f2700),
TrueColor(0x4f4235),
TrueColor(0xffbf00),
TrueColor(0xffeaaa),
TrueColor(0xbd8d00),
TrueColor(0xbdad7e),
TrueColor(0x816000),
TrueColor(0x817656),
TrueColor(0x684e00),
TrueColor(0x685f45),
TrueColor(0x4f3b00),
TrueColor(0x4f4935),
TrueColor(0xffff00),
TrueColor(0xffffaa),
TrueColor(0xbdbd00),
TrueColor(0xbdbd7e),
TrueColor(0x818100),
TrueColor(0x818156),
TrueColor(0x686800),
TrueColor(0x686845),
TrueColor(0x4f4f00),
TrueColor(0x4f4f35),
TrueColor(0xbfff00),
TrueColor(0xeaffaa),
TrueColor(0x8dbd00),
TrueColor(0xadbd7e),
TrueColor(0x608100),
TrueColor(0x768156),
TrueColor(0x4e6800),
TrueColor(0x5f6845),
TrueColor(0x3b4f00),
TrueColor(0x494f35),
TrueColor(0x7fff00),
TrueColor(0xd4ffaa),
TrueColor(0x5ebd00),
TrueColor(0x9dbd7e),
TrueColor(0x408100),
TrueColor(0x6b8156),
TrueColor(0x346800),
TrueColor(0x566845),
TrueColor(0x274f00),
TrueColor(0x424f35),
TrueColor(0x3fff00),
TrueColor(0xbfffaa),
TrueColor(0x2ebd00),
TrueColor(0x8dbd7e),
TrueColor(0x1f8100),
TrueColor(0x608156),
TrueColor(0x196800),
TrueColor(0x4e6845),
TrueColor(0x134f00),
TrueColor(0x3b4f35),
TrueColor(0x00ff00),
TrueColor(0xaaffaa),
TrueColor(0x00bd00),
TrueColor(0x7ebd7e),
TrueColor(0x008100),
TrueColor(0x568156),
TrueColor(0x006800),
TrueColor(0x456845),
TrueColor(0x004f00),
TrueColor(0x354f35),
TrueColor(0x00ff3f),
TrueColor(0xaaffbf),
TrueColor(0x00bd2e),
TrueColor(0x7ebd8d),
TrueColor(0x00811f),
TrueColor(0x568160),
TrueColor(0x006819),
TrueColor(0x45684e),
TrueColor(0x004f13),
TrueColor(0x354f3b),
TrueColor(0x00ff7f),
TrueColor(0xaaffd4),
TrueColor(0x00bd5e),
TrueColor(0x7ebd9d),
TrueColor(0x008140),
TrueColor(0x56816b),
TrueColor(0x006834),
TrueColor(0x456856),
TrueColor(0x004f27),
TrueColor(0x354f42),
TrueColor(0x00ffbf),
TrueColor(0xaaffea),
TrueColor(0x00bd8d),
TrueColor(0x7ebdad),
TrueColor(0x008160),
TrueColor(0x568176),
TrueColor(0x00684e),
TrueColor(0x45685f),
TrueColor(0x004f3b),
TrueColor(0x354f49),
TrueColor(0x00ffff),
TrueColor(0xaaffff),
TrueColor(0x00bdbd),
TrueColor(0x7ebdbd),
TrueColor(0x008181),
TrueColor(0x568181),
TrueColor(0x006868),
TrueColor(0x456868),
TrueColor(0x004f4f),
TrueColor(0x354f4f),
TrueColor(0x00bfff),
TrueColor(0xaaeaff),
TrueColor(0x008dbd),
TrueColor(0x7eadbd),
TrueColor(0x006081),
TrueColor(0x567681),
TrueColor(0x004e68),
TrueColor(0x455f68),
TrueColor(0x003b4f),
TrueColor(0x35494f),
TrueColor(0x007fff),
TrueColor(0xaad4ff),
TrueColor(0x005ebd),
TrueColor(0x7e9dbd),
TrueColor(0x004081),
TrueColor(0x566b81),
TrueColor(0x003468),
TrueColor(0x455668),
TrueColor(0x00274f),
TrueColor(0x35424f),
TrueColor(0x003fff),
TrueColor(0xaabfff),
TrueColor(0x002ebd),
TrueColor(0x7e8dbd),
TrueColor(0x001f81),
TrueColor(0x566081),
TrueColor(0x001968),
TrueColor(0x454e68),
TrueColor(0x00134f),
TrueColor(0x353b4f),
TrueColor(0x0000ff),
TrueColor(0xaaaaff),
TrueColor(0x0000bd),
TrueColor(0x7e7ebd),
TrueColor(0x000081),
TrueColor(0x565681),
TrueColor(0x000068),
TrueColor(0x454568),
TrueColor(0x00004f),
TrueColor(0x35354f),
TrueColor(0x3f00ff),
TrueColor(0xbfaaff),
TrueColor(0x2e00bd),
TrueColor(0x8d7ebd),
TrueColor(0x1f0081),
TrueColor(0x605681),
TrueColor(0x190068),
TrueColor(0x4e4568),
TrueColor(0x13004f),
TrueColor(0x3b354f),
TrueColor(0x7f00ff),
TrueColor(0xd4aaff),
TrueColor(0x5e00bd),
TrueColor(0x9d7ebd),
TrueColor(0x400081),
TrueColor(0x6b5681),
TrueColor(0x340068),
TrueColor(0x564568),
TrueColor(0x27004f),
TrueColor(0x42354f),
TrueColor(0xbf00ff),
TrueColor(0xeaaaff),
TrueColor(0x8d00bd),
TrueColor(0xad7ebd),
TrueColor(0x600081),
TrueColor(0x765681),
TrueColor(0x4e0068),
TrueColor(0x5f4568),
TrueColor(0x3b004f),
TrueColor(0x49354f),
TrueColor(0xff00ff),
TrueColor(0xffaaff),
TrueColor(0xbd00bd),
TrueColor(0xbd7ebd),
TrueColor(0x810081),
TrueColor(0x815681),
TrueColor(0x680068),
TrueColor(0x684568),
TrueColor(0x4f004f),
TrueColor(0x4f354f),
TrueColor(0xff00bf),
TrueColor(0xffaaea),
TrueColor(0xbd008d),
TrueColor(0xbd7ead),
TrueColor(0x810060),
TrueColor(0x815676),
TrueColor(0x68004e),
TrueColor(0x68455f),
TrueColor(0x4f003b),
TrueColor(0x4f3549),
TrueColor(0xff007f),
TrueColor(0xffaad4),
TrueColor(0xbd005e),
TrueColor(0xbd7e9d),
TrueColor(0x810040),
TrueColor(0x81566b),
TrueColor(0x680034),
TrueColor(0x684556),
TrueColor(0x4f0027),
TrueColor(0x4f3542),
TrueColor(0xff003f),
TrueColor(0xffaabf),
TrueColor(0xbd002e),
TrueColor(0xbd7e8d),
TrueColor(0x81001f),
TrueColor(0x815660),
TrueColor(0x680019),
TrueColor(0x68454e),
TrueColor(0x4f0013),
TrueColor(0x4f353b),
TrueColor(0x333333),
TrueColor(0x505050),
TrueColor(0x696969),
TrueColor(0x828282),
TrueColor(0xbebebe),
TrueColor(0xffffff),
]

110
io_import_dxf/dxfgrabber/const.py Executable file
View File

@ -0,0 +1,110 @@
# Purpose: constant values
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
ENV_CYTHON = 'DXFGRABBER_CYTHON'
BYBLOCK = 0
BYLAYER = 256
XTYPE_NONE = 0
XTYPE_2D = 1
XTYPE_3D = 2
XTYPE_2D_3D = 3
acadrelease = {
'AC1009': 'R12',
'AC1012': 'R13',
'AC1014': 'R14',
'AC1015': 'R2000',
'AC1018': 'R2004',
'AC1021': 'R2007',
'AC1024': 'R2010',
}
dxfversion = {
acad: dxf for dxf, acad in acadrelease.items()
}
# Entity: Polyline, Polymesh
# 70 flags
POLYLINE_CLOSED = 1
POLYLINE_MESH_CLOSED_M_DIRECTION = POLYLINE_CLOSED
POLYLINE_CURVE_FIT_VERTICES_ADDED = 2
POLYLINE_SPLINE_FIT_VERTICES_ADDED = 4
POLYLINE_3D_POLYLINE = 8
POLYLINE_3D_POLYMESH = 16
POLYLINE_MESH_CLOSED_N_DIRECTION = 32
POLYLINE_POLYFACE = 64
POLYLINE_GENERATE_LINETYPE_PATTERN =128
# Entity: Polymesh
# 75 surface smooth type
POLYMESH_NO_SMOOTH = 0
POLYMESH_QUADRIC_BSPLINE = 5
POLYMESH_CUBIC_BSPLINE = 6
POLYMESH_BEZIER_SURFACE = 8
#Entity: Vertex
# 70 flags
VERTEXNAMES = ('vtx0', 'vtx1', 'vtx2', 'vtx3')
VTX_EXTRA_VERTEX_CREATED = 1 ## Extra vertex created by curve-fitting
VTX_CURVE_FIT_TANGENT = 2 ## Curve-fit tangent defined for this vertex.
## A curve-fit tangent direction of 0 may be omitted from the DXF output, but is
## significant if this bit is set.
## 4 = unused, never set in dxf files
VTX_SPLINE_VERTEX_CREATED = 8 ##Spline vertex created by spline-fitting
VTX_SPLINE_FRAME_CONTROL_POINT = 16
VTX_3D_POLYLINE_VERTEX = 32
VTX_3D_POLYGON_MESH_VERTEX = 64
VTX_3D_POLYFACE_MESH_VERTEX = 128
VERTEX_FLAGS = {
'polyline2d': 0,
'polyline3d': VTX_3D_POLYLINE_VERTEX,
'polymesh': VTX_3D_POLYGON_MESH_VERTEX,
'polyface': VTX_3D_POLYGON_MESH_VERTEX | VTX_3D_POLYFACE_MESH_VERTEX,
}
POLYLINE_FLAGS = {
'polyline2d': 0,
'polyline3d': POLYLINE_3D_POLYLINE,
'polymesh': POLYLINE_3D_POLYMESH,
'polyface': POLYLINE_POLYFACE,
}
#---block-type flags (bit coded values, may be combined):
# Entity: BLOCK
# 70 flags
BLK_ANONYMOUS = 1 # This is an anonymous block generated by hatching, associative dimensioning, other internal operations, or an application
BLK_NON_CONSTANT_ATTRIBUTES = 2 # This block has non-constant attribute definitions (this bit is not set if the block has any attribute definitions that are constant, or has no attribute definitions at all)
BLK_XREF = 4 # This block is an external reference (xref)
BLK_XREF_OVERLAY = 8 # This block is an xref overlay
BLK_EXTERNAL = 16 # This block is externally dependent
BLK_RESOLVED = 32 # This is a resolved external reference, or dependent of an external reference (ignored on input)
BLK_REFERENCED = 64 # This definition is a referenced external reference (ignored on input)
LWPOLYLINE_CLOSED = 1
LWPOLYLINE_PLINEGEN = 128
SPLINE_CLOSED = 1
SPLINE_PERIODIC = 2
SPLINE_RATIONAL = 4
SPLINE_PLANAR = 8
SPLINE_LINEAR = 16 # planar bit is also set
MTEXT_TOP_LEFT = 1
MTEXT_TOP_CENTER = 2
MTEXT_TOP_RIGHT = 3
MTEXT_MIDDLE_LEFT = 4
MTEXT_MIDDLE_CENTER = 5
MTEXT_MIDDLE_RIGHT = 6
MTEXT_BOTTOM_LEFT = 7
MTEXT_BOTTOM_CENTER = 8
MTEXT_BOTTOM_RIGHT = 9
MTEXT_LEFT_TO_RIGHT = 1
MTEXT_TOP_TO_BOTTOM = 2
MTEXT_BY_STYLE = 5

View File

@ -0,0 +1,7 @@
def __bootstrap__():
global __bootstrap__, __loader__, __file__
import sys, pkg_resources, imp
__file__ = pkg_resources.resource_filename(__name__,'cydxfentity.so')
__loader__ = None; del __bootstrap__, __loader__
imp.load_dynamic(__name__,__file__)
__bootstrap__()

View File

@ -0,0 +1,7 @@
def __bootstrap__():
global __bootstrap__, __loader__, __file__
import sys, pkg_resources, imp
__file__ = pkg_resources.resource_filename(__name__,'cytags.so')
__loader__ = None; del __bootstrap__, __loader__
imp.load_dynamic(__name__,__file__)
__bootstrap__()

View File

@ -0,0 +1,38 @@
# Purpose: decode DXF proprietary data
# Created: 01.05.2014
# Copyright (C) 2014, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from . import PYTHON3
_replacement_table = {
0x20: ' ',
0x40: '_',
0x5F: '@',
}
for c in range(0x41, 0x5F):
_replacement_table[c] = chr(0x41 + (0x5E - c)) # 0x5E -> 'A', 0x5D->'B', ...
def decode(text_lines):
def _decode(text):
s = []
skip = False
if PYTHON3:
text = bytes(text, 'ascii')
else:
text = map(ord, text)
for c in text:
if skip:
skip = False
continue
if c in _replacement_table:
s += _replacement_table[c]
skip = (c == 0x5E) # skip space after 'A'
else:
s += chr(c ^ 0x5F)
return ''.join(s)
return [_decode(line) for line in text_lines]

View File

@ -0,0 +1,38 @@
# Purpose: handle default chunk
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .tags import Tags, DXFTag
class DefaultChunk(object):
def __init__(self, tags, drawing):
assert isinstance(tags, Tags)
self.tags = tags
self._drawing = drawing
@staticmethod
def from_tags(tags, drawing):
return DefaultChunk(tags, drawing)
@property
def name(self):
return self.tags[1].value.lower()
def iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'):
while True:
tag = next(tagreader)
if tag == DXFTag(0, stoptag):
return
tags = Tags([tag])
append = tags.append
end_tag = DXFTag(0, endofchunk)
while tag != end_tag:
tag = next(tagreader)
append(tag)
yield tags

View File

@ -0,0 +1,65 @@
# Purpose: handle drawing data of DXF files
# Created: 21.07.12
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
__author__ = "mozman <mozman@gmx.at>"
from .tags import TagIterator
from .sections import Sections
DEFAULT_OPTIONS = {
"grab_blocks": True, # import block definitions True=yes, False=No
"assure_3d_coords": False, # guarantees (x, y, z) tuples for ALL coordinates
"resolve_text_styles": True, # Text, Attrib, Attdef and MText attributes will be set by the associated text style if necessary
}
class Drawing(object):
def __init__(self, stream, options=None):
if options is None:
options = DEFAULT_OPTIONS
self.grab_blocks = options.get('grab_blocks', True)
self.assure_3d_coords = options.get('assure_3d_coords', False)
self.resolve_text_styles = options.get('resolve_text_styles', True)
tagreader = TagIterator(stream, self.assure_3d_coords)
self.dxfversion = 'AC1009'
self.encoding = 'cp1252'
self.filename = None
sections = Sections(tagreader, self)
self.header = sections.header
self.layers = sections.tables.layers
self.styles = sections.tables.styles
self.linetypes = sections.tables.linetypes
self.blocks = sections.blocks
self.entities = sections.entities
self.objects = sections.objects if ('objects' in sections) else []
if 'acdsdata' in sections:
self.acdsdata = sections.acdsdata
# sab data introduced with DXF version AC1027 (R2013)
if self.dxfversion >= 'AC1027':
self.collect_sab_data()
if self.resolve_text_styles:
resolve_text_styles(self.entities, self.styles)
for block in self.blocks:
resolve_text_styles(block, self.styles)
def modelspace(self):
return (entity for entity in self.entities if not entity.paperspace)
def paperspace(self):
return (entity for entity in self.entities if entity.paperspace)
def collect_sab_data(self):
for entity in self.entities:
if hasattr(entity, 'set_sab_data'):
sab_data = self.acdsdata.sab_data[entity.handle]
entity.set_sab_data(sab_data)
def resolve_text_styles(entities, text_styles):
for entity in entities:
if hasattr(entity, 'resolve_text_style'):
entity.resolve_text_style(text_styles)

202
io_import_dxf/dxfgrabber/dxf12.py Executable file
View File

@ -0,0 +1,202 @@
# Purpose: DXF12 tag wrapper
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
from .dxfentity import DXFEntity
from . import const
from .const import XTYPE_3D, XTYPE_2D_3D
def make_attribs(additional=None):
dxfattribs = {
'handle': DXFAttr(5),
'layer': DXFAttr(8), # layername as string, default is '0'
'linetype': DXFAttr(6), # linetype as string, special names BYLAYER/BYBLOCK, default is BYLAYER
'thickness': DXFAttr(39),
'color': DXFAttr(62), # dxf color index, 0 .. BYBLOCK, 256 .. BYLAYER, default is 256
'paperspace': DXFAttr(67), # 0 .. modelspace, 1 .. paperspace, default is 0
'extrusion': DXFAttr(210, XTYPE_3D),
}
if additional:
dxfattribs.update(additional)
return DXFAttributes(DefSubclass(None, dxfattribs))
class Line(DXFEntity):
DXFATTRIBS = make_attribs({
'start': DXFAttr(10, XTYPE_2D_3D),
'end': DXFAttr(11, XTYPE_2D_3D),
})
class Point(DXFEntity):
DXFATTRIBS = make_attribs({
'point': DXFAttr(10, XTYPE_2D_3D),
})
class Circle(DXFEntity):
DXFATTRIBS = make_attribs({
'center': DXFAttr(10, XTYPE_2D_3D),
'radius': DXFAttr(40),
})
class Arc(DXFEntity):
DXFATTRIBS = make_attribs({
'center': DXFAttr(10, XTYPE_2D_3D),
'radius': DXFAttr(40),
'startangle': DXFAttr(50),
'endangle': DXFAttr(51),
})
class Trace(DXFEntity):
DXFATTRIBS = make_attribs({
'vtx0': DXFAttr(10, XTYPE_2D_3D),
'vtx1': DXFAttr(11, XTYPE_2D_3D),
'vtx2': DXFAttr(12, XTYPE_2D_3D),
'vtx3': DXFAttr(13, XTYPE_2D_3D),
})
Solid = Trace
class Face(DXFEntity):
DXFATTRIBS = make_attribs({
'vtx0': DXFAttr(10, XTYPE_2D_3D),
'vtx1': DXFAttr(11, XTYPE_2D_3D),
'vtx2': DXFAttr(12, XTYPE_2D_3D),
'vtx3': DXFAttr(13, XTYPE_2D_3D),
'invisible_edge': DXFAttr(70),
})
class Text(DXFEntity):
DXFATTRIBS = make_attribs({
'insert': DXFAttr(10, XTYPE_2D_3D),
'height': DXFAttr(40),
'text': DXFAttr(1),
'rotation': DXFAttr(50), # in degrees (circle = 360deg)
'oblique': DXFAttr(51), # in degrees, vertical = 0deg
'style': DXFAttr(7), # text style
'width': DXFAttr(41), # width FACTOR!
'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
'halign': DXFAttr(72), # horizontal justification
'valign': DXFAttr(73), # vertical justification
'alignpoint': DXFAttr(11, XTYPE_2D_3D),
})
class Insert(DXFEntity):
DXFATTRIBS = make_attribs({
'attribsfollow': DXFAttr(66),
'name': DXFAttr(2),
'insert': DXFAttr(10, XTYPE_2D_3D),
'xscale': DXFAttr(41),
'yscale': DXFAttr(42),
'zscale': DXFAttr(43),
'rotation': DXFAttr(50),
'colcount': DXFAttr(70),
'rowcount': DXFAttr(71),
'colspacing': DXFAttr(44),
'rowspacing': DXFAttr(45),
})
class SeqEnd(DXFEntity):
DXFATTRIBS = DXFAttributes(DefSubclass(None, {'handle': DXFAttr(5), 'paperspace': DXFAttr(67), }))
class Attrib(DXFEntity): # also ATTDEF
DXFATTRIBS = make_attribs({
'insert': DXFAttr(10, XTYPE_2D_3D),
'height': DXFAttr(40),
'text': DXFAttr(1),
'prompt': DXFAttr(3), # just in ATTDEF not ATTRIB
'tag': DXFAttr(2),
'flags': DXFAttr(70),
'fieldlength': DXFAttr(73),
'rotation': DXFAttr(50),
'oblique': DXFAttr(51),
'width': DXFAttr(41), # width factor
'style': DXFAttr(7),
'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
'halign': DXFAttr(72), # horizontal justification
'valign': DXFAttr(74), # vertical justification
'alignpoint': DXFAttr(11, XTYPE_2D_3D),
})
class Polyline(DXFEntity):
DXFATTRIBS = make_attribs({
'elevation': DXFAttr(10, XTYPE_2D_3D),
'flags': DXFAttr(70),
'defaultstartwidth': DXFAttr(40),
'defaultendwidth': DXFAttr(41),
'mcount': DXFAttr(71),
'ncount': DXFAttr(72),
'msmoothdensity': DXFAttr(73),
'nsmoothdensity': DXFAttr(74),
'smoothtype': DXFAttr(75),
})
def get_vertex_flags(self):
return const.VERTEX_FLAGS[self.get_mode()]
@property
def flags(self):
return self.get_dxf_attrib('flags', 0)
def get_mode(self):
flags = self.flags
if flags & const.POLYLINE_SPLINE_FIT_VERTICES_ADDED:
return 'spline2d'
elif flags & const.POLYLINE_3D_POLYLINE:
return 'polyline3d'
elif flags & const.POLYLINE_3D_POLYMESH:
return 'polymesh'
elif flags & const.POLYLINE_POLYFACE:
return 'polyface'
else:
return 'polyline2d'
def is_mclosed(self):
return bool(self.flags & const.POLYLINE_MESH_CLOSED_M_DIRECTION)
def is_nclosed(self):
return bool(self.flags & const.POLYLINE_MESH_CLOSED_N_DIRECTION)
class Vertex(DXFEntity):
DXFATTRIBS = make_attribs({
'location': DXFAttr(10, XTYPE_2D_3D),
'startwidth': DXFAttr(40),
'endwidth': DXFAttr(41),
'bulge': DXFAttr(42),
'flags': DXFAttr(70),
'tangent': DXFAttr(50),
'vtx0': DXFAttr(71),
'vtx1': DXFAttr(72),
'vtx2': DXFAttr(73),
'vtx3': DXFAttr(74),
})
class Block(DXFEntity):
DXFATTRIBS = make_attribs({
'name': DXFAttr(2),
'name2': DXFAttr(3),
'flags': DXFAttr(70),
'basepoint': DXFAttr(10, XTYPE_2D_3D),
'xrefpath': DXFAttr(1),
})
class EndBlk(SeqEnd):
DXFATTRIBS = DXFAttributes(DefSubclass(None, {'handle': DXFAttr(5)}))

546
io_import_dxf/dxfgrabber/dxf13.py Executable file
View File

@ -0,0 +1,546 @@
# Purpose: DXF13 tag wrapper
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from . import dxf12
from .dxfentity import DXFEntity
from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
from . import const
from .const import XTYPE_2D, XTYPE_3D, XTYPE_2D_3D
from .tags import Tags
from .decode import decode
none_subclass = DefSubclass(None, {
'handle': DXFAttr(5),
'block_record': DXFAttr(330), # Soft-pointer ID/handle to owner BLOCK_RECORD object
})
entity_subclass = DefSubclass('AcDbEntity', {
'paperspace': DXFAttr(67), # 0 .. modelspace, 1 .. paperspace, default is 0
'layer': DXFAttr(8), # layername as string, default is '0'
'linetype': DXFAttr(6), # linetype as string, special names BYLAYER/BYBLOCK, default is BYLAYER
'ltscale': DXFAttr(48), # linetype scale, default is 1.0
'invisible': DXFAttr(60), # invisible .. 1, visible .. 0, default is 0
'color': DXFAttr(62), # dxf color index, 0 .. BYBLOCK, 256 .. BYLAYER, default is 256
'true_color': DXFAttr(420), # true color as 0x00RRGGBB 24-bit value (since AC1018)
'transparency': DXFAttr(440), # transparency value 0x020000TT (since AC1018) 0 = fully transparent / 255 = opaque
'shadow_mode': DXFAttr(284), # shadow_mode (since AC1021)
# 0 = Casts and receives shadows
# 1 = Casts shadows
# 2 = Receives shadows
# 3 = Ignores shadows
})
line_subclass = DefSubclass('AcDbLine', {
'start': DXFAttr(10, XTYPE_2D_3D),
'end': DXFAttr(11, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Line(dxf12.Line):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, line_subclass)
point_subclass = DefSubclass('AcDbPoint', {
'point': DXFAttr(10, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Point(dxf12.Point):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, point_subclass)
circle_subclass = DefSubclass('AcDbCircle', {
'center': DXFAttr(10, XTYPE_2D_3D),
'radius': DXFAttr(40),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Circle(dxf12.Circle):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass)
arc_subclass = DefSubclass('AcDbArc', {
'startangle': DXFAttr(50),
'endangle': DXFAttr(51),
})
class Arc(dxf12.Arc):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, circle_subclass, arc_subclass)
trace_subclass = DefSubclass('AcDbTrace', {
'vtx0': DXFAttr(10, XTYPE_2D_3D),
'vtx1': DXFAttr(11, XTYPE_2D_3D),
'vtx2': DXFAttr(12, XTYPE_2D_3D),
'vtx3': DXFAttr(13, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Trace(dxf12.Trace):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, trace_subclass)
Solid = Trace
face_subclass = DefSubclass('AcDbFace', {
'vtx0': DXFAttr(10, XTYPE_2D_3D),
'vtx1': DXFAttr(11, XTYPE_2D_3D),
'vtx2': DXFAttr(12, XTYPE_2D_3D),
'vtx3': DXFAttr(13, XTYPE_2D_3D),
'invisible_edge': DXFAttr(70),
})
class Face(dxf12.Face):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, face_subclass)
text_subclass = (
DefSubclass('AcDbText', {
'insert': DXFAttr(10, XTYPE_2D_3D),
'height': DXFAttr(40),
'text': DXFAttr(1),
'rotation': DXFAttr(50), # in degrees (circle = 360deg)
'oblique': DXFAttr(51), # in degrees, vertical = 0deg
'style': DXFAttr(7), # text style
'width': DXFAttr(41), # width FACTOR!
'textgenerationflag': DXFAttr(71), # 2 = backward (mirr-x), 4 = upside down (mirr-y)
'halign': DXFAttr(72), # horizontal justification
'alignpoint': DXFAttr(11, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
}),
DefSubclass('AcDbText', {'valign': DXFAttr(73)}))
class Text(dxf12.Text):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *text_subclass)
polyline_subclass = DefSubclass('AcDb2dPolyline', {
'elevation': DXFAttr(10, XTYPE_3D),
'flags': DXFAttr(70),
'defaultstartwidth': DXFAttr(40),
'defaultendwidth': DXFAttr(41),
'mcount': DXFAttr(71),
'ncount': DXFAttr(72),
'msmoothdensity': DXFAttr(73),
'nsmoothdensity': DXFAttr(74),
'smoothtype': DXFAttr(75),
'thickness': DXFAttr(39),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Polyline(dxf12.Polyline):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, polyline_subclass)
vertex_subclass = (
DefSubclass('AcDbVertex', {}), # subclasses[2]
DefSubclass('AcDb2dVertex', { # subclasses[3]
'location': DXFAttr(10, XTYPE_2D_3D),
'startwidth': DXFAttr(40),
'endwidth': DXFAttr(41),
'bulge': DXFAttr(42),
'flags': DXFAttr(70),
'tangent': DXFAttr(50),
'vtx0': DXFAttr(71),
'vtx1': DXFAttr(72),
'vtx2': DXFAttr(73),
'vtx3': DXFAttr(74),
})
)
EMPTY_SUBCLASS = Tags()
class Vertex(dxf12.Vertex):
VTX3D = const.VTX_3D_POLYFACE_MESH_VERTEX | const.VTX_3D_POLYGON_MESH_VERTEX | const.VTX_3D_POLYLINE_VERTEX
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *vertex_subclass)
def post_read_correction(self):
if self.tags.subclasses[2][0].value != 'AcDbVertex':
self.tags.subclasses.insert(2, EMPTY_SUBCLASS) # create empty AcDbVertex subclass
class SeqEnd(dxf12.SeqEnd):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass)
lwpolyline_subclass = DefSubclass('AcDbPolyline', {
'elevation': DXFAttr(38),
'thickness': DXFAttr(39),
'flags': DXFAttr(70),
'const_width': DXFAttr(43),
'count': DXFAttr(90),
'extrusion': DXFAttr(210, XTYPE_3D),
})
LWPOINTCODES = (10, 20, 40, 41, 42)
class LWPolyline(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, lwpolyline_subclass)
def __iter__(self):
subclass = self.tags.subclasses[2] # subclass AcDbPolyline
def get_vertex():
point.append(attribs.get(40, 0))
point.append(attribs.get(41, 0))
point.append(attribs.get(42, 0))
return tuple(point)
point = None
attribs = {}
for tag in subclass:
if tag.code in LWPOINTCODES:
if tag.code == 10:
if point is not None:
yield get_vertex()
point = list(tag.value)
attribs = {}
else:
attribs[tag.code] = tag.value
if point is not None:
yield get_vertex() # last point
def data(self):
full_points = list(self)
points = []
width = []
bulge = []
for point in full_points:
x = 2 if len(point) == 5 else 3
points.append(point[:x])
width.append((point[-3], point[-2]))
bulge.append(point[-1])
return points, width, bulge
@property
def flags(self):
return self.get_dxf_attrib('flags', 0)
def is_closed(self):
return bool(self.flags & const.LWPOLYLINE_CLOSED)
insert_subclass = DefSubclass('AcDbBlockReference', {
'attribsfollow': DXFAttr(66),
'name': DXFAttr(2),
'insert': DXFAttr(10, XTYPE_2D_3D),
'xscale': DXFAttr(41),
'yscale': DXFAttr(42),
'zscale': DXFAttr(43),
'rotation': DXFAttr(50),
'colcount': DXFAttr(70),
'rowcount': DXFAttr(71),
'colspacing': DXFAttr(44),
'rowspacing': DXFAttr(45),
'extrusion': DXFAttr(210, XTYPE_3D),
})
class Insert(dxf12.Insert):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, insert_subclass)
attrib_subclass = (
DefSubclass('AcDbText', {
'insert': DXFAttr(10, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'height': DXFAttr(40),
'text': DXFAttr(1),
'style': DXFAttr(7), # DXF-specs: 'AcDbAttribute'; AutoCAD: 'AcDbText'
}),
DefSubclass('AcDbAttribute', {
'tag': DXFAttr(2),
'flags': DXFAttr(70),
'fieldlength': DXFAttr(73),
'rotation': DXFAttr(50),
'width': DXFAttr(41),
'oblique': DXFAttr(51),
'textgenerationflag': DXFAttr(71),
'halign': DXFAttr(72),
'valign': DXFAttr(74),
'alignpoint': DXFAttr(11, XTYPE_2D_3D),
'extrusion': DXFAttr(210, XTYPE_3D),
})
)
class Attrib(dxf12.Attrib):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *attrib_subclass)
attdef_subclass = (
DefSubclass('AcDbText', {
'insert': DXFAttr(10, XTYPE_2D_3D),
'thickness': DXFAttr(39),
'height': DXFAttr(40),
'text': DXFAttr(1),
'rotation': DXFAttr(50),
'width': DXFAttr(41),
'oblique': DXFAttr(51),
'style': DXFAttr(7),
'textgenerationflag': DXFAttr(71),
'halign': DXFAttr(72),
'alignpoint': DXFAttr(11),
'extrusion': DXFAttr(210),
}),
DefSubclass('AcDbAttributeDefinition', {
'prompt': DXFAttr(3),
'tag': DXFAttr(2),
'flags': DXFAttr(70),
'fieldlength': DXFAttr(73),
'valign': DXFAttr(74),
}))
class Attdef(dxf12.Attrib):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, *attdef_subclass)
ellipse_subclass = DefSubclass('AcDbEllipse', {
'center': DXFAttr(10, XTYPE_2D_3D),
'majoraxis': DXFAttr(11, XTYPE_2D_3D), # relative to the center
'extrusion': DXFAttr(210, XTYPE_3D),
'ratio': DXFAttr(40),
'startparam': DXFAttr(41), # this value is 0.0 for a full ellipse
'endparam': DXFAttr(42), # this value is 2*pi for a full ellipse
})
class Ellipse(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, ellipse_subclass)
ray_subclass = DefSubclass('AcDbRay', {
'start': DXFAttr(10, XTYPE_3D),
'unitvector': DXFAttr(11, XTYPE_3D),
})
class Ray(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, ray_subclass)
xline_subclass = DefSubclass('AcDbXline', {
'start': DXFAttr(10, XTYPE_3D),
'unitvector': DXFAttr(11, XTYPE_3D),
})
class XLine(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, xline_subclass)
spline_subclass = DefSubclass('AcDbSpline', {
'normalvector': DXFAttr(210, XTYPE_3D), # omitted if spline is not planar
'flags': DXFAttr(70),
'degree': DXFAttr(71),
'nknots': DXFAttr(72),
'ncontrolpoints': DXFAttr(73),
'nfitcounts': DXFAttr(74),
'knot_tolerance': DXFAttr(42), # default 0.0000001
'controlpoint_tolerance': DXFAttr(43), # default 0.0000001
'fit_tolerance': DXFAttr(44), # default 0.0000000001
'starttangent': DXFAttr(12, XTYPE_3D), # optional
'endtangent': DXFAttr(13, XTYPE_3D), # optional
})
class Spline(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, spline_subclass)
def knots(self):
# groupcode 40, multiple values: nknots
subclass = self.tags.subclasses[2] # subclass AcDbSpline
return (tag.value for tag in subclass if tag.code == 40)
def weights(self):
# groupcode 41, multiple values
subclass = self.tags.subclasses[2] # subclass AcDbSpline
return (tag.value for tag in subclass if tag.code == 41)
def controlpoints(self):
# groupcode 10,20,30, multiple values: ncontrolpoints
return self._get_points(10)
def fitpoints(self):
# groupcode 11,21,31, multiple values: nfitpoints
return self._get_points(11)
def _get_points(self, code):
return (tag.value for tag in self.tags.subclasses[2] if tag.code == code)
helix_subclass = DefSubclass('AcDbHelix', {
'helix_major_version': DXFAttr(90),
'helix_maintainance_version': DXFAttr(91),
'axis_base_point': DXFAttr(10, XTYPE_3D),
'start_point': DXFAttr(11, XTYPE_3D),
'axis_vector': DXFAttr(12, XTYPE_3D),
'radius': DXFAttr(40),
'turns': DXFAttr(41),
'turn_height': DXFAttr(42),
'handedness': DXFAttr(290), # 0 = left, 1 = right
'constrain': DXFAttr(280), # 0 = Constrain turn height; 1 = Constrain turns; 2 = Constrain height
})
class Helix(Spline):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, spline_subclass, helix_subclass)
mtext_subclass = DefSubclass('AcDbMText', {
'insert': DXFAttr(10, XTYPE_3D),
'height': DXFAttr(40),
'reference_rectangle_width': DXFAttr(41),
'horizontal_width': DXFAttr(42),
'vertical_height': DXFAttr(43),
'attachmentpoint': DXFAttr(71),
'text': DXFAttr(1), # also group code 3, if more than 255 chars
'style': DXFAttr(7), # text style
'extrusion': DXFAttr(210, XTYPE_3D),
'xdirection': DXFAttr(11, XTYPE_3D),
'rotation': DXFAttr(50), # xdirection beats rotation
'linespacing': DXFAttr(44), # valid from 0.25 to 4.00
})
class MText(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mtext_subclass)
def rawtext(self):
subclass = self.tags.subclasses[2]
lines = [tag.value for tag in subclass.find_all(3)]
lines.append(self.get_dxf_attrib('text'))
return ''.join(lines)
block_subclass = (
DefSubclass('AcDbEntity', {'layer': DXFAttr(8)}),
DefSubclass('AcDbBlockBegin', {
'name': DXFAttr(2),
'name2': DXFAttr(3),
'description': DXFAttr(4),
'flags': DXFAttr(70),
'basepoint': DXFAttr(10, XTYPE_2D_3D),
'xrefpath': DXFAttr(1),
})
)
class Block(dxf12.Block):
DXFATTRIBS = DXFAttributes(none_subclass, *block_subclass)
endblock_subclass = (
DefSubclass('AcDbEntity', {'layer': DXFAttr(8)}),
DefSubclass('AcDbBlockEnd', {}),
)
class EndBlk(dxf12.EndBlk):
DXFATTRIBS = DXFAttributes(none_subclass, *endblock_subclass)
sun_subclass = DefSubclass('AcDbSun', {
'version': DXFAttr(90),
'status': DXFAttr(290),
'sun_color': DXFAttr(63), # ??? DXF Color Index = (1 .. 255), 256 by layer
'intensity': DXFAttr(40),
'shadows': DXFAttr(291),
'date': DXFAttr(91), # Julian day
'time': DXFAttr(92), # Time (in seconds past midnight)
'daylight_savings_time': DXFAttr(292),
'shadow_type': DXFAttr(70), # 0 = Ray traced shadows; 1 = Shadow maps
'shadow_map_size': DXFAttr(71), # 0 = Ray traced shadows; 1 = Shadow maps
'shadow_softness': DXFAttr(280),
})
# SUN resides in the objects section and has no AcDbEntity subclass
class Sun(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, sun_subclass)
mesh_subclass = DefSubclass('AcDbSubDMesh', {
'version': DXFAttr(71),
'blend_crease': DXFAttr(72), # 0 = off, 1 = on
'subdivision_levels': DXFAttr(91), # int >= 1
})
class Mesh(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, mesh_subclass)
light_subclass = DefSubclass('AcDbLight', {
'version': DXFAttr(90),
'name': DXFAttr(1),
'light_type': DXFAttr(70), # distant = 1; point = 2; spot = 3
'status': DXFAttr(290),
'light_color': DXFAttr(63), # DXF Color Index = (1 .. 255), 256 by layer
'true_color': DXFAttr(421), # 24-bit color 0x00RRGGBB
'plot_glyph': DXFAttr(291),
'intensity': DXFAttr(40),
'position': DXFAttr(10, XTYPE_3D),
'target': DXFAttr(11, XTYPE_3D),
'attenuation_type': DXFAttr(72), # 0 = None; 1 = Inverse Linear; 2 = Inverse Square
'use_attenuation_limits': DXFAttr(292), # bool
'attenuation_start_limit': DXFAttr(41),
'attenuation_end_limit': DXFAttr(42),
'hotspot_angle': DXFAttr(50),
'fall_off_angle': DXFAttr(51),
'cast_shadows': DXFAttr(293),
'shadow_type': DXFAttr(73), # 0 = Ray traced shadows; 1 = Shadow maps
'shadow_map_size': DXFAttr(91),
'shadow_softness': DXFAttr(280),
})
class Light(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, light_subclass)
modeler_geometry_subclass = DefSubclass('AcDbModelerGeometry', {
'version': DXFAttr(70),
})
class Body(DXFEntity):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass)
def get_acis_data(self):
# for AC1027 and later - ACIS data is stored in the ACDSDATA section in Standard ACIS Binary format
geometry = self.tags.subclasses[2] # AcDbModelerGeometry
return decode([tag.value for tag in geometry if tag.code in (1, 3)])
solid3d_subclass = DefSubclass('AcDb3dSolid', {
'handle_to_history_object': DXFAttr(350),
})
# Region == Body
class Solid3d(Body):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass, solid3d_subclass)
surface_subclass = DefSubclass('AcDbSurface', {
'u_isolines': DXFAttr(71),
'v_isolines': DXFAttr(72),
})
class Surface(Body):
DXFATTRIBS = DXFAttributes(none_subclass, entity_subclass, modeler_geometry_subclass, surface_subclass)

View File

@ -0,0 +1,47 @@
# Purpose: define dxf attributes
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from collections import namedtuple
from .const import XTYPE_NONE
def DXFAttr(code, xtype=XTYPE_NONE):
# assert type(xtype) is int
return _DXFAttr(code, xtype)
_DXFAttr = namedtuple('DXFAttr', 'code xtype')
DXFAttr3 = namedtuple('DXFAttr3', 'code xtype subclass')
DefSubclass = namedtuple('DefSubclass', 'name attribs')
class DXFAttributes(object):
def __init__(self, *subclassdefs):
self._subclasses = []
self._attribs = {}
for subclass in subclassdefs:
self.add_subclass(subclass)
def add_subclass(self, subclass):
subclass_index = len(self._subclasses)
self._subclasses.append(subclass)
self._add_subclass_attribs(subclass, subclass_index)
def _add_subclass_attribs(self, subclass, subclass_index):
for name, dxfattrib in subclass.attribs.items():
self._attribs[name] = DXFAttr3(dxfattrib.code, dxfattrib.xtype, subclass_index)
def __getitem__(self, name):
return self._attribs[name]
def __contains__(self, name):
return name in self._attribs
def keys(self):
return iter(self._attribs.keys())
def subclasses(self):
return iter(self._subclasses)

View File

@ -0,0 +1,84 @@
# Purpose: generic tag wrapper
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
import os
from .const import ENV_CYTHON, XTYPE_NONE, XTYPE_2D, XTYPE_3D, XTYPE_2D_3D
cyDXFEntity = None
OPTIMIZE = True
if ENV_CYTHON in os.environ:
if os.environ[ENV_CYTHON].upper() in ('1', 'ON', 'TRUE'):
OPTIMIZE = True
else:
OPTIMIZE = False
try:
if OPTIMIZE:
from.cydxfentity import cyDXFEntity
except ImportError:
pass
class pyDXFEntity(object):
DXFATTRIBS = {}
def __init__(self, tags):
self.tags = tags
def dxftype(self):
return self.tags.noclass[0].value
def get_dxf_attrib(self, key, default=ValueError):
# core function - every optimization is useful
try:
dxfattr = self.DXFATTRIBS[key]
except KeyError:
# attribute is not defined - returning the default value is useful
# to query newer DXF attributes on older DXF files.
# !! Problem: misspelled attributes with default values do not
# raise an Exception !!
if default is ValueError:
raise ValueError("DXFAttrib '%s' is not defined." % key)
else:
return default
try:
return self._get_dxf_attrib(dxfattr)
except ValueError: # attribute is defined but no value is present
if default is ValueError:
raise ValueError("DXFAttrib '%s': value is not present." % key)
else:
return default
def _get_dxf_attrib(self, dxfattr):
# no subclass is subclass index 0
subclass_tags = self.tags.subclasses[dxfattr.subclass]
xtype = dxfattr.xtype
if xtype != XTYPE_NONE and xtype != XTYPE_2D_3D:
return self._get_extented_type(subclass_tags, dxfattr.code, xtype)
else:
return subclass_tags.get_value(dxfattr.code)
def paperspace(self):
return self.get_dxf_attrib('paperspace', default=0) == 1
def post_read_correction(self):
pass
@staticmethod
def _get_extented_type(tags, code, xtype):
value = tags.get_value(code)
if len(value) == 2:
if xtype == XTYPE_3D:
return value[0], value[1], 0.
elif xtype == XTYPE_2D:
return value[0], value[1]
return value
if cyDXFEntity is not None:
DXFEntity = cyDXFEntity
else:
DXFEntity = pyDXFEntity

View File

@ -0,0 +1,930 @@
# encoding: utf-8
# Purpose: entity classes
# Created: 21.07.2012, parts taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from . import dxf12, dxf13
from . import const
from .juliandate import calendar_date
from datetime import datetime
from .color import TrueColor
import math
from .styles import default_text_style
SPECIAL_CHARS = {
'd': '°'
}
class SeqEnd(object):
def __init__(self, wrapper):
self.dxftype = wrapper.dxftype()
class Entity(SeqEnd):
def __init__(self, wrapper):
super(Entity, self).__init__(wrapper)
self.paperspace = bool(wrapper.paperspace())
class Shape(Entity):
def __init__(self, wrapper):
super(Shape, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.layer = get_dxf('layer', '0')
self.linetype = get_dxf('linetype', None) # None=BYLAYER
self.thickness = get_dxf('thickness', 0.0)
self.extrusion = get_dxf('extrusion', (0., 0., 1.))
self.ltscale = get_dxf('ltscale', 1.0)
self.invisible = get_dxf('invisible', 0) # 0=visible
self.color = get_dxf('color', const.BYLAYER) # 256=BYLAYER, 0=BYBLOCK
self.true_color = get_dxf('true_color', None) # 0x00RRGGBB
if self.true_color is not None:
self.true_color = TrueColor(self.true_color)
self.transparency = get_dxf('transparency', None) # 0x020000TT
if self.transparency is not None:
# 0.0 = opaque & 1.0 if fully transparent
self.transparency = 1. - float(self.transparency & 0xFF) / 255.
self.shadow_mode = get_dxf('shadow_mode', None)
# 0 = Casts and receives shadows
# 1 = Casts shadows
# 2 = Receives shadows
# 3 = Ignores shadows
# if adding additional DXF attributes, do it also for PolyShape
class PolyShape(object):
""" Base class for Polyface and Polymesh, both are special cases of POLYLINE.
"""
def __init__(self, polyline, dxftype):
self.dxftype = dxftype
self.paperspace = polyline.paperspace
self.layer = polyline.layer
self.linetype = polyline.linetype
self.ltscale = polyline.ltscale
self.invisible = polyline.invisible
self.color = polyline.color
self.true_color = polyline.true_color
self.transparency = polyline.transparency
self.shadow_mode = polyline.shadow_mode
class Line(Shape):
def __init__(self, wrapper):
super(Line, self).__init__(wrapper)
self.start = wrapper.get_dxf_attrib('start')
self.end = wrapper.get_dxf_attrib('end')
class Point(Shape):
def __init__(self, wrapper):
super(Point, self).__init__(wrapper)
self.point = wrapper.get_dxf_attrib('point')
class Circle(Shape):
def __init__(self, wrapper):
super(Circle, self).__init__(wrapper)
self.center = wrapper.get_dxf_attrib('center')
self.radius = wrapper.get_dxf_attrib('radius')
class Arc(Shape):
def __init__(self, wrapper):
super(Arc, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.center = get_dxf('center')
self.radius = get_dxf('radius')
self.startangle = get_dxf('startangle')
self.endangle = get_dxf('endangle')
class Trace(Shape):
def __init__(self, wrapper):
super(Trace, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.points = [
get_dxf(vname) for vname in const.VERTEXNAMES
]
Solid = Trace
class Face(Trace):
def __init__(self, wrapper):
super(Face, self).__init__(wrapper)
self.invisible_edge = wrapper.get_dxf_attrib('invisible_edge', 0)
def is_edge_invisible(self, edge):
# edges 0 .. 3
return bool(self.invisible_edge & (1 << edge))
class Text(Shape):
def __init__(self, wrapper):
super(Text, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.insert = get_dxf('insert')
self.text = get_dxf('text')
self.height = get_dxf('height', 0)
self.width = get_dxf('width', 0)
self.oblique = get_dxf('oblique', None)
self.rotation = get_dxf('rotation', 0.)
self.style = get_dxf('style', "")
self.halign = get_dxf('halign', 0)
self.valign = get_dxf('valign', 0)
self.alignpoint = get_dxf('alignpoint', None)
if get_dxf('textgenerationflag', None) is not None:
self.is_backwards = bool(get_dxf('textgenerationflag', 0) & 2)
self.is_upside_down = bool(get_dxf('textgenerationflag', 0) & 4)
else:
self.is_backwards = None
self.is_upside_down = None
self.font = ""
self.bigfont = ""
def resolve_text_style(self, text_styles):
style = text_styles.get(self.style, None)
if style is None:
style = default_text_style
if self.height == 0:
self.height = style.height
if self.width == 0:
self.width = style.width
if self.oblique is None:
self.oblique = style.oblique
if self.is_backwards is None:
self.is_backwards = style.is_backwards
if self.is_upside_down is None:
self.is_upside_down = style.is_upside_down
if self.font is None:
self.font = style.font
if self.bigfont is None:
self.bigfont = style.bigfont
def plain_text(self):
chars = []
raw_chars = list(reversed(self.text)) # text splitted into chars, in reversed order for efficient pop()
while len(raw_chars):
char = raw_chars.pop()
if char == '%': # formatting codes and special characters
if len(raw_chars) and raw_chars[-1] == '%':
raw_chars.pop() # '%'
if len(raw_chars):
special_char = raw_chars.pop() # command char
chars.append(SPECIAL_CHARS.get(special_char, ""))
else: # char is just a single '%'
chars.append(char)
else: # char is what it is, a character
chars.append(char)
return "".join(chars)
class Insert(Shape):
def __init__(self, wrapper):
super(Insert, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.name = get_dxf('name')
self.insert = get_dxf('insert')
self.rotation = get_dxf('rotation', 0.)
self.scale = get_dxf('xscale', 1.), get_dxf('yscale', 1.), get_dxf('zscale', 1.)
self.row_count = get_dxf('rowcount', 1)
self.row_spacing = get_dxf('rowspacing', 0.)
self.col_count = get_dxf('colcount', 1)
self.col_spacing = get_dxf('colspacing', 0.)
self.attribsfollow = bool(get_dxf('attribsfollow', 0))
self.attribs = []
def find_attrib(self, attrib_tag):
for attrib in self.attribs:
if attrib.tag == attrib_tag:
return attrib
return None
def append_data(self, attribs):
self.attribs = attribs
class Attrib(Text): # also ATTDEF
def __init__(self, wrapper):
super(Attrib, self).__init__(wrapper)
self.tag = wrapper.get_dxf_attrib('tag')
_LINE_TYPES = frozenset(('spline2d', 'polyline2d', 'polyline3d'))
class Polyline(Shape):
def __init__(self, wrapper):
super(Polyline, self).__init__(wrapper)
self.vertices = [] # set in append data
self.points = [] # set in append data
self.controlpoints = [] # set in append data
self.width = [] # set in append data
self.bulge = [] # set in append data
self.tangents = [] # set in append data
self.flags = wrapper.flags
self.mode = wrapper.get_mode()
get_dxf = wrapper.get_dxf_attrib
self.mcount = get_dxf('mcount', 0)
self.ncount = get_dxf('ncount', 0)
self.default_start_width = get_dxf('defaultstartwidth', 0.)
self.default_end_width = get_dxf('defaultendwidth', 0.)
self.is_mclosed = wrapper.is_mclosed()
self.is_nclosed = wrapper.is_nclosed()
self.elevation = get_dxf('elevation', (0., 0., 0.))
self.m_smooth_density = get_dxf('msmoothdensity', 0.)
self.n_smooth_density = get_dxf('nsmoothdensity', 0.)
self.smooth_type = get_dxf('smoothtype', 0)
self.spline_type = None
if self.mode == 'spline2d':
if self.smooth_type == const.POLYMESH_CUBIC_BSPLINE:
self.spline_type = 'cubic_bspline'
elif self.smooth_type == const.POLYMESH_QUADRIC_BSPLINE:
self.spline_type = 'quadratic_bspline'
elif self.smooth_type == const.POLYMESH_BEZIER_SURFACE:
self.spline_type = 'bezier_curve' # is this a valid spline type for DXF12?
def __len__(self):
return len(self.vertices)
def __getitem__(self, item):
return self.vertices[item]
def __iter__(self):
return iter(self.vertices)
@property
def is_closed(self):
return self.is_mclosed
@is_closed.setter
def is_closed(self, status):
self.is_mclosed = status
def append_data(self, vertices):
def default_width(start_width, end_width):
if start_width == 0.:
start_width = self.default_start_width
if end_width == 0.:
end_width = self.default_end_width
return start_width, end_width
self.vertices = vertices
if self.mode in _LINE_TYPES:
for vertex in self.vertices:
if vertex.flags & const.VTX_SPLINE_FRAME_CONTROL_POINT:
self.controlpoints.append(vertex.location)
else:
self.points.append(vertex.location)
self.width.append(default_width(vertex.start_width, vertex.end_width))
self.bulge.append(vertex.bulge)
self.tangents.append(vertex.tangent if vertex.flags & const.VTX_CURVE_FIT_TANGENT else None)
def cast(self):
if self.mode == 'polyface':
return Polyface(self)
elif self.mode == 'polymesh':
return Polymesh(self)
else:
return self
class SubFace(object):
def __init__(self, face_record, vertices):
self._vertices = vertices
self.face_record = face_record
def __len__(self):
return len(self.face_record.vtx)
def __getitem__(self, item):
return self._vertices[self._vertex_index(item)]
def __iter__(self):
return (self._vertices[index].location for index in self.indices())
def _vertex_index(self, pos):
return abs(self.face_record.vtx[pos]) - 1
def indices(self):
return tuple(abs(i)-1 for i in self.face_record.vtx if i != 0)
def is_edge_visible(self, pos):
return self.face_record.vtx[pos] > 0
class Polyface(PolyShape):
def __init__(self, polyline):
VERTEX_FLAGS = const.VTX_3D_POLYFACE_MESH_VERTEX + const.VTX_3D_POLYGON_MESH_VERTEX
def is_vertex(flags):
return flags & VERTEX_FLAGS == VERTEX_FLAGS
super(Polyface, self).__init__(polyline, 'POLYFACE')
vertices = []
face_records = []
for vertex in polyline.vertices:
(vertices if is_vertex(vertex.flags) else face_records).append(vertex)
self.vertices = vertices
self._face_records = face_records
def __getitem__(self, item):
return SubFace(self._face_records[item], self.vertices)
def __len__(self):
return len(self._face_records)
def __iter__(self):
return (SubFace(f, self.vertices) for f in self._face_records)
class Polymesh(PolyShape):
def __init__(self, polyline):
super(Polymesh, self).__init__(polyline, 'POLYMESH')
self.mcount = polyline.mcount
self.ncount = polyline.ncount
self.is_mclosed = polyline.is_mclosed
self.is_nclosed = polyline.is_nclosed
self._vertices = polyline.vertices
self.m_smooth_density = polyline.m_smooth_density
self.n_smooth_density = polyline.n_smooth_density
self.smooth_type = polyline.smooth_type
def __iter__(self):
return iter(self._vertices)
def get_location(self, pos):
return self.get_vertex(pos).location
def get_vertex(self, pos):
mcount = self.mcount
ncount = self.ncount
m, n = pos
if 0 <= m < mcount and 0 <= n < ncount:
pos = m * ncount + n
return self._vertices[pos]
else:
raise IndexError(repr(pos))
class Vertex(Shape):
def __init__(self, wrapper):
super(Vertex, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.location = get_dxf('location')
self.flags = get_dxf('flags', 0)
self.start_width = get_dxf('startwidth', 0)
self.end_width = get_dxf('endwidth', 0)
self.bulge = get_dxf('bulge', 0)
self.tangent = get_dxf('tangent', None)
self.vtx = self._get_vtx(wrapper)
def _get_vtx(self, wrapper):
vtx = []
get_dxf = wrapper.get_dxf_attrib
for vname in const.VERTEXNAMES:
try:
vtx.append(get_dxf(vname))
except ValueError:
pass
return tuple(vtx)
class LWPolyline(Shape):
def __init__(self, wrapper):
super(LWPolyline, self).__init__(wrapper)
self.points, self.width, self.bulge = wrapper.data()
self.const_width = wrapper.get_dxf_attrib('const_width', 0)
self.is_closed = wrapper.is_closed()
self.elevation = wrapper.get_dxf_attrib('elevation', (0., 0., 0.))
def __len__(self):
return len(self.points)
def __getitem__(self, item):
return self.points[item]
def __iter__(self):
return iter(self.points)
class Ellipse(Shape):
def __init__(self, wrapper):
super(Ellipse, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.center = get_dxf('center')
self.majoraxis = get_dxf('majoraxis')
self.ratio = get_dxf('ratio', 1.0) # circle
self.startparam = get_dxf('startparam', 0.)
self.endparam = get_dxf('endparam', 6.283185307179586) # 2*pi
class Ray(Shape):
def __init__(self, wrapper):
super(Ray, self).__init__(wrapper)
self.start = wrapper.get_dxf_attrib('start')
self.unitvector = wrapper.get_dxf_attrib('unitvector')
XLine = Ray
class Spline(Shape):
def __init__(self, wrapper):
super(Spline, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.normalvector = get_dxf('normalvector', None)
self.flags = get_dxf('flags', 0)
self.degree = get_dxf('degree', 3)
self.starttangent = get_dxf('starttangent', None)
self.endtangent = get_dxf('endtangent', None)
self.knots = tuple(wrapper.knots())
self.weights = tuple(wrapper.weights())
self.tol_knot = get_dxf('knot_tolernace', .0000001)
self.tol_controlpoint = get_dxf('controlpoint_tolerance', .0000001)
self.tol_fitpoint = get_dxf('fitpoint_tolerance', .0000000001)
self.controlpoints = tuple(wrapper.controlpoints())
self.fitpoints = tuple(wrapper.fitpoints())
if len(self.weights) == 0:
self.weights = tuple([1.0] * len(self.controlpoints))
@property
def is_closed(self):
return bool(self.flags & const.SPLINE_CLOSED)
@property
def is_periodic(self):
return bool(self.flags & const.SPLINE_PERIODIC)
@property
def is_rational(self):
return bool(self.flags & const.SPLINE_RATIONAL)
@property
def is_planar(self):
return bool(self.flags & const.SPLINE_PLANAR)
@property
def is_linear(self):
return bool(self.flags & const.SPLINE_LINEAR)
class Helix(Spline):
def __init__(self, wrapper):
super(Helix, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.helix_version = (get_dxf('helix_major_version', 1),
get_dxf('helix_maintainance_version', 1))
self.axis_base_point = get_dxf('axis_base_point', None)
self.start_point = get_dxf('start_point', None)
self.axis_vector = get_dxf('axis_vector', None)
self.radius = get_dxf('radius', 0)
self.turns = get_dxf('turns', 0)
self.turn_height = get_dxf('turn_height', 0)
self.handedness = get_dxf('handedness', 0) # 0 = left, 1 = right
self.constrain = get_dxf('constrain', 0)
# 0 = Constrain turn height;
# 1 = Constrain turns;
# 2 = Constrain height
def deg2vec(deg):
rad = float(deg) * math.pi / 180.0
return math.cos(rad), math.sin(rad), 0.
def normalized(vector):
x, y, z = vector
m = (x**2 + y**2 + z**2)**0.5
return x/m, y/m, z/m
##################################################
# MTEXT inline codes
# \L Start underline
# \l Stop underline
# \O Start overstrike
# \o Stop overstrike
# \K Start strike-through
# \k Stop strike-through
# \P New paragraph (new line)
# \pxi Control codes for bullets, numbered paragraphs and columns
# \X Paragraph wrap on the dimension line (only in dimensions)
# \Q Slanting (obliquing) text by angle - e.g. \Q30;
# \H Text height - e.g. \H3x;
# \W Text width - e.g. \W0.8x;
# \F Font selection
#
# e.g. \Fgdt;o - GDT-tolerance
# e.g. \Fkroeger|b0|i0|c238|p10 - font Kroeger, non-bold, non-italic, codepage 238, pitch 10
#
# \S Stacking, fractions
#
# e.g. \SA^B:
# A
# B
# e.g. \SX/Y:
# X
# -
# Y
# e.g. \S1#4:
# 1/4
#
# \A Alignment
#
# \A0; = bottom
# \A1; = center
# \A2; = top
#
# \C Color change
#
# \C1; = red
# \C2; = yellow
# \C3; = green
# \C4; = cyan
# \C5; = blue
# \C6; = magenta
# \C7; = white
#
# \T Tracking, char.spacing - e.g. \T2;
# \~ Non-wrapping space, hard space
# {} Braces - define the text area influenced by the code
# \ Escape character - e.g. \\ = "\", \{ = "{"
#
# Codes and braces can be nested up to 8 levels deep
ESCAPED_CHARS = "\\{}"
GROUP_CHARS = "{}"
ONE_CHAR_COMMANDS = "PLlOoKkX"
class MText(Shape):
def __init__(self, wrapper):
super(MText, self).__init__(wrapper)
self.insert = wrapper.get_dxf_attrib('insert')
self.rawtext = wrapper.rawtext()
get_dxf = wrapper.get_dxf_attrib
self.height = get_dxf('height', 0)
self.rect_width = get_dxf('reference_rectangle_width', None)
self.horizontal_width = get_dxf('horizontal_width', None)
self.vertical_height = get_dxf('vertical_height', None)
self.linespacing = get_dxf('linespacing', 1.0)
self.attachmentpoint = get_dxf('attachmentpoint', 1)
self.style = get_dxf('style', 'STANDARD')
self.extrusion = get_dxf('extrusion', (0., 0., 1.))
try:
xdir = wrapper.get_dxf_attrib('xdirection')
except ValueError:
xdir = deg2vec(get_dxf('rotation', 0.0))
self.xdirection = normalized(xdir)
self.font = None
self.bigfont = None
def lines(self):
return self.rawtext.split('\P')
def plain_text(self, split=False):
chars = []
raw_chars = list(reversed(self.rawtext)) # text splitted into chars, in reversed order for efficient pop()
while len(raw_chars):
char = raw_chars.pop()
if char == '\\': # is a formatting command
try:
char = raw_chars.pop()
except IndexError:
break # premature end of text - just ignore
if char in ESCAPED_CHARS: # \ { }
chars.append(char)
elif char in ONE_CHAR_COMMANDS:
if char == 'P': # new line
chars.append('\n')
# discard other commands
else: # more character commands are terminated by ';'
stacking = char == 'S' # stacking command surrounds user data
try:
while char != ';': # end of format marker
char = raw_chars.pop()
if stacking and char != ';':
chars.append(char) # append user data of stacking command
except IndexError:
break # premature end of text - just ignore
elif char in GROUP_CHARS: # { }
pass # discard group markers
elif char == '%': # special characters
if len(raw_chars) and raw_chars[-1] == '%':
raw_chars.pop() # discard next '%'
if len(raw_chars):
special_char = raw_chars.pop()
# replace or discard formatting code
chars.append(SPECIAL_CHARS.get(special_char, ""))
else: # char is just a single '%'
chars.append(char)
else: # char is what it is, a character
chars.append(char)
plain_text = "".join(chars)
return plain_text.split('\n') if split else plain_text
def resolve_text_style(self, text_styles):
style = text_styles.get(self.style, None)
if style is None:
style = default_text_style
if self.height == 0:
self.height = style.height
if self.font is None:
self.font = style.font
if self.bigfont is None:
self.bigfont = style.font
class Block(Shape):
def __init__(self, wrapper):
super(Block, self).__init__(wrapper)
self.basepoint = wrapper.get_dxf_attrib('basepoint')
self.name = wrapper.get_dxf_attrib('name')
self.flags = wrapper.get_dxf_attrib('flags', 0)
self.xrefpath = wrapper.get_dxf_attrib('xrefpath', "")
self._entities = list()
@property
def is_xref(self):
return bool(self.flags & const.BLK_XREF)
@property
def is_xref_overlay(self):
return bool(self.flags & const.BLK_XREF_OVERLAY)
@property
def is_anonymous(self):
return bool(self.flags & const.BLK_ANONYMOUS)
def set_entities(self, entities):
self._entities = entities
def __iter__(self):
return iter(self._entities)
def __getitem__(self, item):
return self._entities[item]
def __len__(self):
return len(self._entities)
class BlockEnd(SeqEnd):
pass
def unpack_seconds(seconds):
seconds = int(seconds / 1000) # remove 1/1000 part
hours = int(seconds / 3600)
seconds = int(seconds % 3600)
minutes = int(seconds / 60)
seconds = int(seconds % 60)
return hours, minutes, seconds
class Sun(Entity):
def __init__(self, wrapper):
super(Sun, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.version = get_dxf('version', 1)
self.status = bool(get_dxf('status', 0)) # on/off ?
self.sun_color = get_dxf('sun_color', None) # None is unset
self.intensity = get_dxf('intensity', 0)
self.shadows = bool(get_dxf('shadows', 0))
julian_date = get_dxf('date', 0.)
if julian_date > 0.:
date = calendar_date(julian_date)
else:
date = datetime.now()
hours, minutes, seconds = unpack_seconds(get_dxf('time', 0))
self.date = datetime(date.year, date.month, date.day, hours, minutes, seconds)
self.daylight_savings_time = bool(get_dxf('daylight_savings_time', 0))
self.shadow_type = get_dxf('shadows_type', 0)
self.shadow_map_size = get_dxf('shadow_map_size', 0)
self.shadow_softness = get_dxf('shadow_softness', 0)
class Mesh(Shape):
def __init__(self, wrapper):
super(Mesh, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.version = get_dxf('version', 2)
self.blend_crease = bool(get_dxf('blend_crease', 0))
self.subdivision_levels = get_dxf('subdivision_levels', 1)
# rest are mostly positional tags
self.vertices = []
self.faces = []
self.edges = []
self.edge_crease_list = []
subdmesh_tags = wrapper.tags.get_subclass('AcDbSubDMesh')
# for all blocks I ignore the count values, perhaps they are wrong,
# but I use the count tags as indicator for the begin of the list
try:
pos = subdmesh_tags.tag_index(92)
except ValueError: # no vertices???
return
else:
self.vertices = Mesh.get_vertices(subdmesh_tags, pos+1)
try:
pos = subdmesh_tags.tag_index(93)
except ValueError: # no faces???
pass
else:
self.faces = Mesh.get_faces(subdmesh_tags, pos+1)
try:
pos = subdmesh_tags.tag_index(94)
except ValueError: # no edges
pass
else:
self.edges = Mesh.get_edges(subdmesh_tags, pos+1)
try:
pos = subdmesh_tags.tag_index(95)
except ValueError: # no edges crease values
pass
else:
self.edge_crease_list = Mesh.get_edge_crease_list(subdmesh_tags, pos+1)
def get_face(self, index):
return tuple(self.vertices[vertex_index] for vertex_index in self.faces[index])
def get_edge(self, index):
return tuple(self.vertices[vertex_index] for vertex_index in self.edges[index])
@staticmethod
def get_vertices(tags, pos):
vertices = []
itags = iter(tags[pos:])
while True:
try:
tag = next(itags)
except StopIteration: # premature end of tags, return what you got
break
if tag.code == 10:
vertices.append(tag.value)
else:
break
return vertices
@staticmethod
def get_faces(tags, pos):
faces = []
face = []
itags = iter(tags[pos:])
try:
while True:
tag = next(itags)
# loop until first tag.code != 90
if tag.code != 90:
break
count = tag.value # count of vertex indices
while count > 0:
tag = next(itags)
face.append(tag.value)
count -= 1
faces.append(tuple(face))
del face[:]
except StopIteration: # premature end of tags, return what you got
pass
return faces
@staticmethod
def get_edges(tags, pos):
edges = []
start_index = None
for index in Mesh.get_raw_list(tags, pos, code=90):
if start_index is None:
start_index = index
else:
edges.append((start_index, index))
start_index = None
return edges
@staticmethod
def get_edge_crease_list(tags, pos):
return Mesh.get_raw_list(tags, pos, code=140)
@staticmethod
def get_raw_list(tags, pos, code):
raw_list = []
itags = iter(tags[pos:])
while True:
try:
tag = next(itags)
except StopIteration:
break
if tag.code == code:
raw_list.append(tag.value)
else:
break
return raw_list
class Light(Shape):
def __init__(self, wrapper):
super(Light, self).__init__(wrapper)
get_dxf = wrapper.get_dxf_attrib
self.version = get_dxf('version', 1)
self.name = get_dxf('name', "")
self.light_type = get_dxf('light_type', 1) # distant = 1; point = 2; spot = 3
self.status = bool(get_dxf('status', 0)) # on/off ?
self.light_color = get_dxf('light_color', None) # 0 is unset
self.true_color = get_dxf('true_color', None) # None is unset
self.plot_glyph = bool(get_dxf('plot_glyph', 0))
self.intensity = get_dxf('intensity', 0)
self.position = get_dxf('position', (0, 0, 1))
self.target = get_dxf('target', (0, 0, 0))
self.attenuation_type = get_dxf('attenuation_type', 0) # 0 = None; 1 = Inverse Linear; 2 = Inverse Square
self.use_attenuation_limits = bool(get_dxf('use_attenuation_limits', 0))
self.attenuation_start_limit = get_dxf('attenuation_start_limit', 0)
self.attenuation_end_limit = get_dxf('attenuation_end_limit', 0)
self.hotspot_angle = get_dxf('hotspot_angle', 0)
self.fall_off_angle = get_dxf('fall_off_angle', 0)
self.cast_shadows = bool(get_dxf('cast_shadows', 0))
self.shadow_type = get_dxf('shadow_type', 0) # 0 = Ray traced shadows; 1 = Shadow maps
self.shadow_map_size = get_dxf('shadow_map_size', 0)
self.shadow_softness = get_dxf('shadow_softness', 0)
class Body(Shape):
def __init__(self, wrapper):
super(Body, self).__init__(wrapper)
# need handle to get SAB data in DXF version AC1027 and later
self.handle = wrapper.get_dxf_attrib('handle', None)
self.version = wrapper.get_dxf_attrib('version', 1)
self.acis = wrapper.get_acis_data()
def set_sab_data(self, sab_data):
self.acis = sab_data
@property
def is_sat(self):
return isinstance(self.acis, list) # but could be an empty list
@property
def is_sab(self):
return not self.is_sat # has binary encoded ACIS data
Solid3d = Body
# perhaps reading creation history is needed
class Surface(Body):
def __init__(self, wrapper):
super(Surface, self).__init__(wrapper)
self.u_isolines = wrapper.get_dxf_attrib('u_isolines', 0)
self.v_isolines = wrapper.get_dxf_attrib('v_isolines', 0)
EntityTable = {
'LINE': (Line, dxf12.Line, dxf13.Line),
'POINT': (Point, dxf12.Point, dxf13.Point),
'CIRCLE': (Circle, dxf12.Circle, dxf13.Circle),
'ARC': (Arc, dxf12.Arc, dxf13.Arc),
'TRACE': (Trace, dxf12.Trace, dxf13.Trace),
'SOLID': (Solid, dxf12.Solid, dxf13.Solid),
'3DFACE': (Face, dxf12.Face, dxf13.Face),
'TEXT': (Text, dxf12.Text, dxf13.Text),
'INSERT': (Insert, dxf12.Insert, dxf13.Insert),
'SEQEND': (SeqEnd, dxf12.SeqEnd, dxf13.SeqEnd),
'ATTRIB': (Attrib, dxf12.Attrib, dxf13.Attrib),
'ATTDEF': (Attrib, dxf12.Attrib, dxf13.Attdef),
'POLYLINE': (Polyline, dxf12.Polyline, dxf13.Polyline),
'VERTEX': (Vertex, dxf12.Vertex, dxf13.Vertex),
'BLOCK': (Block, dxf12.Block, dxf13.Block),
'ENDBLK': (BlockEnd, dxf12.EndBlk, dxf13.EndBlk),
'LWPOLYLINE': (LWPolyline, None, dxf13.LWPolyline),
'ELLIPSE': (Ellipse, None, dxf13.Ellipse),
'RAY': (Ray, None, dxf13.Ray),
'XLINE': (XLine, None, dxf13.XLine),
'SPLINE': (Spline, None, dxf13.Spline),
'HELIX': (Helix, None, dxf13.Helix),
'MTEXT': (MText, None, dxf13.MText),
'SUN': (Sun, None, dxf13.Sun),
'MESH': (Mesh, None, dxf13.Mesh),
'LIGHT': (Light, None, dxf13.Light),
'BODY': (Body, None, dxf13.Body),
'REGION': (Body, None, dxf13.Body),
'3DSOLID': (Solid3d, None, dxf13.Solid3d),
'SURFACE': (Surface, None, dxf13.Surface),
'PLANESURFACE': (Surface, None, dxf13.Surface),
}
def entity_factory(tags, dxfversion):
dxftype = tags.get_type()
cls, dxf12wrapper, dxf13wrapper = EntityTable[dxftype]
wrapper = dxf12wrapper(tags) if dxfversion == "AC1009" else dxf13wrapper(tags)
wrapper.post_read_correction()
shape = cls(wrapper)
return shape

View File

@ -0,0 +1,94 @@
# Purpose: handle entity section
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from itertools import islice
from .tags import TagGroups, DXFStructureError
from .tags import ClassifiedTags
from .entities import entity_factory
class EntitySection(object):
name = 'entities'
def __init__(self):
self._entities = list()
@classmethod
def from_tags(cls, tags, drawing):
entity_section = cls()
entity_section._build(tags, drawing.dxfversion)
return entity_section
def get_entities(self):
return self._entities
# start of public interface
def __len__(self):
return len(self._entities)
def __iter__(self):
return iter(self._entities)
def __getitem__(self, index):
return self._entities[index]
# end of public interface
def _build(self, tags, dxfversion):
if len(tags) == 3: # empty entities section
return
groups = TagGroups(islice(tags, 2, len(tags)-1))
self._entities = build_entities(groups, dxfversion)
class ObjectsSection(EntitySection):
name = 'objects'
def build_entities(tag_groups, dxfversion):
def build_entity(group):
try:
entity = entity_factory(ClassifiedTags(group), dxfversion)
except KeyError:
entity = None # ignore unsupported entities
return entity
entities = list()
collector = None
for group in tag_groups:
entity = build_entity(group)
if entity is not None:
if collector:
if entity.dxftype == 'SEQEND':
collector.stop()
entities.append(collector.entity)
collector = None
else:
collector.append(entity)
elif entity.dxftype == 'POLYLINE':
collector = _Collector(entity)
elif entity.dxftype == 'INSERT' and entity.attribsfollow:
collector = _Collector(entity)
else:
entities.append(entity)
return entities
class _Collector:
def __init__(self, entity):
self.entity = entity
self._data = list()
def append(self, entity):
self._data.append(entity)
def stop(self):
self.entity.append_data(self._data)
if hasattr(self.entity, 'cast'):
self.entity = self.entity.cast()

View File

@ -0,0 +1,33 @@
# Purpose: handle header section
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .tags import TagGroups, DXFTag
class HeaderSection(dict):
name = "header"
def __init__(self):
super(HeaderSection, self).__init__()
self._create_default_vars()
@staticmethod
def from_tags(tags):
header = HeaderSection()
if tags[1] == DXFTag(2, 'HEADER'): # DXF12 without a HEADER section is valid!
header._build(tags)
return header
def _create_default_vars(self):
self['$ACADVER'] = 'AC1009'
self['$DWGCODEPAGE'] = 'ANSI_1252'
def _build(self, tags):
if len(tags) == 3: # empty header section!
return
groups = TagGroups(tags[2:-1], split_code=9)
for group in groups:
self[group[0].value] = group[1].value

View File

@ -0,0 +1,73 @@
# Purpose: julian date
# Created: 21.03.2011
# Copyright (C) 2011, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from math import floor
from datetime import datetime
def frac(number):
return number - floor(number)
class JulianDate:
def __init__(self, date):
self.date = date
self.result = self.julian_date() + self.fractional_day()
def fractional_day(self):
seconds = self.date.hour * 3600. + self.date.minute * 60. + self.date.second
return seconds / 86400.
def julian_date(self):
y = self.date.year + (float(self.date.month) - 2.85) / 12.
A = floor(367. * y) - 1.75 * floor(y) + self.date.day
B = floor(A) - 0.75 * floor(y / 100.)
return floor(B) + 1721115.
class CalendarDate:
def __init__(self, juliandate):
self.jdate = juliandate
year, month, day = self.get_date()
hour, minute, second = frac2time(self.jdate)
self.result = datetime(year, month, day, hour, minute, second)
def get_date(self):
Z = floor(self.jdate)
if Z < 2299161:
A = Z # julian calender
else:
g = floor((Z - 1867216.25) / 36524.25) # gregorian calendar
A = Z + 1. + g - floor(g / 4.)
B = A + 1524.
C = floor((B - 122.1) / 365.25)
D = floor(365.25 * C)
E = floor((B - D) / 30.6001)
day = B - D - floor(30.6001 * E)
month = E - 1 if E < 14 else E - 13
year = C - 4716 if month > 2 else C - 4715
return int(year), int(month), int(day)
def frac2time(jdate):
seconds = int(frac(jdate) * 86400.)
hour = int(seconds / 3600)
seconds = seconds % 3600
minute = int(seconds / 60)
second = seconds % 60
return hour, minute, second
def julian_date(date):
return JulianDate(date).result
def calendar_date(juliandate):
return CalendarDate(juliandate).result

View File

@ -0,0 +1,120 @@
# Purpose: handle layers
# Created: 21.07.12
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
__author__ = "mozman <mozman@gmx.at>"
from .tags import TagGroups
from .tags import ClassifiedTags
from .dxfentity import DXFEntity
from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
class Layer(object):
def __init__(self, wrapper):
self.name = wrapper.get_dxf_attrib('name')
self.color = wrapper.get_color()
self.linetype = wrapper.get_dxf_attrib('linetype')
self.locked = wrapper.is_locked()
self.frozen = wrapper.is_frozen()
self.on = wrapper.is_on()
class Table(object):
def __init__(self):
self._table_entries = dict()
# start public interface
def get(self, name, default=KeyError):
try:
return self._table_entries[name]
except KeyError:
if default is KeyError:
raise
else:
return default
def __getitem__(self, item):
return self.get(item)
def __contains__(self, name):
return name in self._table_entries
def __iter__(self):
return iter(self._table_entries.values())
def __len__(self):
return len(self._table_entries)
def names(self):
return sorted(self._table_entries.keys())
# end public interface
def _classified_tags(self, tags):
groups = TagGroups(tags)
assert groups.get_name(0) == 'TABLE'
assert groups.get_name(-1) == 'ENDTAB'
for entrytags in groups[1:-1]:
yield ClassifiedTags(entrytags)
class LayerTable(Table):
name = 'layers'
@staticmethod
def from_tags(tags, drawing):
dxfversion = drawing.dxfversion
layers = LayerTable()
for entrytags in layers._classified_tags(tags):
dxflayer = layers.wrap(entrytags, dxfversion)
layers._table_entries[dxflayer.get_dxf_attrib('name')] = Layer(dxflayer)
return layers
@staticmethod
def wrap(tags, dxfversion):
return DXF12Layer(tags) if dxfversion == "AC1009" else DXF13Layer(tags)
class DXF12Layer(DXFEntity):
DXFATTRIBS = DXFAttributes(DefSubclass(None, {
'handle': DXFAttr(5),
'name': DXFAttr(2),
'flags': DXFAttr(70),
'color': DXFAttr(62), # dxf color index, if < 0 layer is off
'linetype': DXFAttr(6),
}))
LOCK = 0b00000100
FROZEN = 0b00000001
def is_frozen(self):
return self.get_dxf_attrib('flags') & DXF12Layer.FROZEN > 0
def is_locked(self):
return self.get_dxf_attrib('flags') & DXF12Layer.LOCK > 0
def is_off(self):
return self.get_dxf_attrib('color') < 0
def is_on(self):
return not self.is_off()
def get_color(self):
return abs(self.get_dxf_attrib('color'))
none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
layer_subclass = DefSubclass('AcDbLayerTableRecord', {
'name': DXFAttr(2), # layer name
'flags': DXFAttr(70),
'color': DXFAttr(62), # dxf color index
'linetype': DXFAttr(6), # linetype name
})
class DXF13Layer(DXF12Layer):
DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, layer_subclass)

View File

@ -0,0 +1,74 @@
# Purpose: handle linetypes table
# Created: 06.01.2014
# Copyright (C) 2014, Manfred Moitzi
# License: MIT License
__author__ = "mozman <mozman@gmx.at>"
from .dxfentity import DXFEntity
from .layers import Table
from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
class Linetype(object):
def __init__(self, wrapper):
self.name = wrapper.get_dxf_attrib('name')
self.description = wrapper.get_dxf_attrib('description')
self.length = wrapper.get_dxf_attrib('length') # overall length of the pattern
self.pattern = wrapper.get_pattern() # list of floats: value>0: line, value<0: gap, value=0: dot
class LinetypeTable(Table):
name = 'linetypes'
@staticmethod
def from_tags(tags, drawing):
dxfversion = drawing.dxfversion
styles = LinetypeTable()
for entrytags in styles._classified_tags(tags):
dxfstyle = styles.wrap(entrytags, dxfversion)
styles._table_entries[dxfstyle.get_dxf_attrib('name')] = Linetype(dxfstyle)
return styles
@staticmethod
def wrap(tags, dxfversion):
return DXF12Linetype(tags) if dxfversion == "AC1009" else DXF13Linetype(tags)
class DXF12Linetype(DXFEntity):
DXFATTRIBS = DXFAttributes(DefSubclass(None, {
'handle': DXFAttr(5),
'name': DXFAttr(2),
'description': DXFAttr(3),
'length': DXFAttr(40),
'items': DXFAttr(73),
}))
def get_pattern(self):
items = self.get_dxf_attrib('items')
if items == 0:
return []
else:
tags = self.tags.noclass
return [pattern_tag.value for pattern_tag in tags.find_all(49)]
none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
linetype_subclass = DefSubclass('AcDbLinetypeTableRecord', {
'name': DXFAttr(2),
'description': DXFAttr(3),
'length': DXFAttr(40),
'items': DXFAttr(73),
})
class DXF13Linetype(DXF12Linetype):
DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, linetype_subclass)
def get_pattern(self):
items = self.get_dxf_attrib('items')
if items == 0:
return []
else:
tags = self.tags.get_subclass('AcDbLinetypeTableRecord')
return [pattern_tag.value for pattern_tag in tags.find_all(49)]

View File

@ -0,0 +1,385 @@
# Purpose: tag reader
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from io import StringIO
from collections import namedtuple
from itertools import chain, islice
from . import tostr
DXFTag = namedtuple('DXFTag', 'code value')
NONE_TAG = DXFTag(999999, 'NONE')
APP_DATA_MARKER = 102
SUBCLASS_MARKER = 100
XDATA_MARKER = 1001
class DXFStructureError(Exception):
pass
def point_tuple(value):
return tuple(float(f) for f in value)
POINT_CODES = frozenset(chain(range(10, 20), (210, ), range(110, 113), range(1010, 1020)))
def is_point_tag(tag):
return tag[0] in POINT_CODES
class TagIterator(object):
def __init__(self, textfile, assure_3d_coords=False):
self.textfile = textfile
self.readline = textfile.readline
self.undo = False
self.last_tag = NONE_TAG
self.undo_coord = None
self.eof = False
self.assure_3d_coords = assure_3d_coords
def __iter__(self):
return self
def __next__(self):
def undo_tag():
self.undo = False
tag = self.last_tag
return tag
def read_next_tag():
try:
code = int(self.readline())
value = self.readline().rstrip('\n')
except UnicodeDecodeError:
raise # because UnicodeDecodeError() is a subclass of ValueError()
except (EOFError, ValueError):
raise StopIteration()
return code, value
def read_point(code_x, value_x):
try:
code_y, value_y = read_next_tag() # 2. coordinate is always necessary
except StopIteration:
code_y = 0 # -> DXF structure error in following if-statement
if code_y != code_x + 10:
raise DXFStructureError("invalid 2D/3D point found")
value_y = float(value_y)
try:
code_z, value_z = read_next_tag()
except StopIteration: # 2D point at end of file
self.eof = True # store reaching end of file
if self.assure_3d_coords:
value = (value_x, value_y, 0.)
else:
value = (value_x, value_y)
else:
if code_z != code_x + 20: # not a Z coordinate -> 2D point
self.undo_coord = (code_z, value_z)
if self.assure_3d_coords:
value = (value_x, value_y, 0.)
else:
value = (value_x, value_y)
else: # is a 3D point
value = (value_x, value_y, float(value_z))
return value
def next_tag():
code = 999
while code == 999: # skip comments
if self.undo_coord is not None:
code, value = self.undo_coord
self.undo_coord = None
else:
code, value = read_next_tag()
if code in POINT_CODES: # 2D or 3D point
value = read_point(code, float(value)) # returns a tuple of floats, no casting needed
else:
value = cast_tag_value(code, value)
self.last_tag = DXFTag(code, value)
return self.last_tag
if self.eof: # stored end of file
raise StopIteration()
if self.undo:
return undo_tag()
else:
return next_tag()
# for Python 2.7
next = __next__
def undo_tag(self):
if not self.undo:
self.undo = True
else:
raise ValueError('No tag to undo')
class StringIterator(TagIterator):
def __init__(self, dxfcontent):
super(StringIterator, self).__init__(StringIO(dxfcontent))
class TagCaster:
def __init__(self):
self._cast = self._build()
def _build(self):
table = {}
for caster, codes in TYPES:
for code in codes:
table[code] = caster
return table
def cast(self, tag):
code, value = tag
typecaster = self._cast.get(code, tostr)
try:
value = typecaster(value)
except ValueError:
if typecaster is int: # convert float to int
value = int(float(value))
else:
raise
return DXFTag(code, value)
def cast_value(self, code, value):
typecaster = self._cast.get(code, tostr)
try:
return typecaster(value)
except ValueError:
if typecaster is int: # convert float to int
return int(float(value))
else:
raise
TYPES = [
(tostr, range(0, 10)),
(point_tuple, range(10, 20)),
(float, range(20, 60)),
(int, range(60, 100)),
(tostr, range(100, 106)),
(point_tuple, range(110, 113)),
(float, range(113, 150)),
(int, range(170, 180)),
(point_tuple, [210]),
(float, range(211, 240)),
(int, range(270, 290)),
(int, range(290, 300)), # bool 1=True 0=False
(tostr, range(300, 370)),
(int, range(370, 390)),
(tostr, range(390, 400)),
(int, range(400, 410)),
(tostr, range(410, 420)),
(int, range(420, 430)),
(tostr, range(430, 440)),
(int, range(440, 460)),
(float, range(460, 470)),
(tostr, range(470, 480)),
(tostr, range(480, 482)),
(tostr, range(999, 1010)),
(point_tuple, range(1010, 1020)),
(float, range(1020, 1060)),
(int, range(1060, 1072)),
]
_TagCaster = TagCaster()
cast_tag = _TagCaster.cast
cast_tag_value = _TagCaster.cast_value
class Tags(list):
""" DXFTag() chunk as flat list. """
def find_all(self, code):
""" Returns a list of DXFTag(code, ...). """
return [tag for tag in self if tag.code == code]
def tag_index(self, code, start=0, end=None):
""" Return first index of DXFTag(code, ...). """
if end is None:
end = len(self)
for index, tag in enumerate(islice(self, start, end)):
if tag.code == code:
return start+index
raise ValueError(code)
def get_value(self, code):
for tag in self:
if tag.code == code:
return tag.value
raise ValueError(code)
@staticmethod
def from_text(text):
return Tags(StringIterator(text))
def get_type(self):
return self.__getitem__(0).value
class TagGroups(list):
"""
Group of tags starting with a SplitTag and ending before the next SplitTag.
A SplitTag is a tag with code == splitcode, like (0, 'SECTION') for splitcode=0.
"""
def __init__(self, tags, split_code=0):
super(TagGroups, self).__init__()
self._buildgroups(tags, split_code)
def _buildgroups(self, tags, split_code):
def push_group():
if len(group) > 0:
self.append(group)
def start_tag(itags):
tag = next(itags)
while tag.code != split_code:
tag = next(itags)
return tag
itags = iter(tags)
group = Tags([start_tag(itags)])
for tag in itags:
if tag.code == split_code:
push_group()
group = Tags([tag])
else:
group.append(tag)
push_group()
def get_name(self, index):
return self[index][0].value
@staticmethod
def from_text(text, split_code=0):
return TagGroups(Tags.from_text(text), split_code)
class ClassifiedTags:
""" Manage Subclasses, AppData and Extended Data """
def __init__(self, iterable=None):
self.appdata = list() # code == 102, keys are "{<arbitrary name>", values are Tags()
self.subclasses = list() # code == 100, keys are "subclassname", values are Tags()
self.xdata = list() # code >= 1000, keys are "APPNAME", values are Tags()
if iterable is not None:
self._setup(iterable)
@property
def noclass(self):
return self.subclasses[0]
def _setup(self, iterable):
tagstream = iter(iterable)
def collect_subclass(start_tag):
""" a subclass can contain appdata, but not xdata, ends with
SUBCLASSMARKER or XDATACODE.
"""
data = Tags() if start_tag is None else Tags([start_tag])
try:
while True:
tag = next(tagstream)
if tag.code == APP_DATA_MARKER and tag.value[0] == '{':
app_data_pos = len(self.appdata)
data.append(DXFTag(tag.code, app_data_pos))
collect_appdata(tag)
elif tag.code in (SUBCLASS_MARKER, XDATA_MARKER):
self.subclasses.append(data)
return tag
else:
data.append(tag)
except StopIteration:
pass
self.subclasses.append(data)
return NONE_TAG
def collect_appdata(starttag):
""" appdata, can not contain xdata or subclasses """
data = Tags([starttag])
while True:
try:
tag = next(tagstream)
except StopIteration:
raise DXFStructureError("Missing closing DXFTag(102, '}') for appdata structure.")
data.append(tag)
if tag.code == APP_DATA_MARKER:
break
self.appdata.append(data)
def collect_xdata(starttag):
""" xdata are always at the end of the entity and can not contain
appdata or subclasses
"""
data = Tags([starttag])
try:
while True:
tag = next(tagstream)
if tag.code == XDATA_MARKER:
self.xdata.append(data)
return tag
else:
data.append(tag)
except StopIteration:
pass
self.xdata.append(data)
return NONE_TAG
tag = collect_subclass(None) # preceding tags without a subclass
while tag.code == SUBCLASS_MARKER:
tag = collect_subclass(tag)
while tag.code == XDATA_MARKER:
tag = collect_xdata(tag)
if tag is not NONE_TAG:
raise DXFStructureError("Unexpected tag '%r' at end of entity." % tag)
def __iter__(self):
for subclass in self.subclasses:
for tag in subclass:
if tag.code == APP_DATA_MARKER and isinstance(tag.value, int):
for subtag in self.appdata[tag.value]:
yield subtag
else:
yield tag
for xdata in self.xdata:
for tag in xdata:
yield tag
def get_subclass(self, name):
for subclass in self.subclasses:
if len(subclass) and subclass[0].value == name:
return subclass
raise KeyError("Subclass '%s' does not exist." % name)
def get_xdata(self, appid):
for xdata in self.xdata:
if xdata[0].value == appid:
return xdata
raise ValueError("No extended data for APPID '%s'" % appid)
def get_appdata(self, name):
for appdata in self.appdata:
if appdata[0].value == name:
return appdata
raise ValueError("Application defined group '%s' does not exist." % name)
def get_type(self):
return self.noclass[0].value
@staticmethod
def from_text(text):
return ClassifiedTags(StringIterator(text))

View File

@ -0,0 +1,70 @@
# Purpose: handle dxf sections
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .codepage import toencoding
from .defaultchunk import DefaultChunk, iterchunks
from .headersection import HeaderSection
from .tablessection import TablesSection
from .entitysection import EntitySection, ObjectsSection
from .blockssection import BlocksSection
from .acdsdata import AcDsDataSection
class Sections(object):
def __init__(self, tagreader, drawing):
self._sections = {}
self._create_default_sections()
self._setup_sections(tagreader, drawing)
def __contains__(self, name):
return name in self._sections
def _create_default_sections(self):
self._sections['header'] = HeaderSection()
for cls in SECTIONMAP.values():
section = cls()
self._sections[section.name] = section
def _setup_sections(self, tagreader, drawing):
def name(section):
return section[1].value
bootstrap = True
for section in iterchunks(tagreader, stoptag='EOF', endofchunk='ENDSEC'):
if bootstrap:
new_section = HeaderSection.from_tags(section)
drawing.dxfversion = new_section.get('$ACADVER', 'AC1009')
codepage = new_section.get('$DWGCODEPAGE', 'ANSI_1252')
drawing.encoding = toencoding(codepage)
bootstrap = False
else:
section_name = name(section)
if section_name in SECTIONMAP:
section_class = get_section_class(section_name)
new_section = section_class.from_tags(section, drawing)
else:
new_section = None
if new_section is not None:
self._sections[new_section.name] = new_section
def __getattr__(self, key):
try:
return self._sections[key]
except KeyError:
raise AttributeError(key)
SECTIONMAP = {
'TABLES': TablesSection,
'ENTITIES': EntitySection,
'OBJECTS': ObjectsSection,
'BLOCKS': BlocksSection,
'ACDSDATA': AcDsDataSection,
}
def get_section_class(name):
return SECTIONMAP.get(name, DefaultChunk)

View File

@ -0,0 +1,100 @@
# Purpose: handle text styles
# Created: 06.01.2014
# Copyright (C) 2014, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .dxfentity import DXFEntity
from .layers import Table
from .dxfattr import DXFAttr, DXFAttributes, DefSubclass
from .tags import ClassifiedTags
class Style(object):
def __init__(self, wrapper):
self.name = wrapper.get_dxf_attrib('name')
self.height = wrapper.get_dxf_attrib('height')
self.width = wrapper.get_dxf_attrib('width')
self.oblique = wrapper.get_dxf_attrib('oblique')
# backward & mirror_y was first and stays for compatibility
self.backward = bool(wrapper.get_dxf_attrib('generation_flags') & 2)
self.mirror_y = bool(wrapper.get_dxf_attrib('generation_flags') & 4)
self.is_backwards = self.backward
self.is_upside_down = self.mirror_y
self.font = wrapper.get_dxf_attrib('font')
self.bigfont = wrapper.get_dxf_attrib('bigfont', "")
class StyleTable(Table):
name = 'styles'
@staticmethod
def from_tags(tags, drawing):
dxfversion = drawing.dxfversion
styles = StyleTable()
for entrytags in styles._classified_tags(tags):
dxfstyle = styles.wrap(entrytags, dxfversion)
styles._table_entries[dxfstyle.get_dxf_attrib('name')] = Style(dxfstyle)
return styles
@staticmethod
def wrap(tags, dxfversion):
return DXF12Style(tags) if dxfversion == "AC1009" else DXF13Style(tags)
class DXF12Style(DXFEntity):
DXFATTRIBS = DXFAttributes(DefSubclass(None, {
'handle': DXFAttr(5),
'name': DXFAttr(2),
'flags': DXFAttr(70),
'height': DXFAttr(40), # fixed height, 0 if not fixed
'width': DXFAttr(41), # width factor
'oblique': DXFAttr(50), # oblique angle in degree, 0 = vertical
'generation_flags': DXFAttr(71), # 2 = backward, 4 = mirrored in Y
'last_height': DXFAttr(42), # last height used
'font': DXFAttr(3), # primary font file name
'bigfont': DXFAttr(4), # big font name, blank if none
}))
none_subclass = DefSubclass(None, {'handle': DXFAttr(5)})
symbol_subclass = DefSubclass('AcDbSymbolTableRecord', {})
style_subclass = DefSubclass('AcDbTextStyleTableRecord', {
'name': DXFAttr(2),
'flags': DXFAttr(70),
'height': DXFAttr(40), # fixed height, 0 if not fixed
'width': DXFAttr(41), # width factor
'oblique': DXFAttr(50), # oblique angle in degree, 0 = vertical
'generation_flags': DXFAttr(71), # 2 = backward, 4 = mirrored in Y
'last_height': DXFAttr(42), # last height used
'font': DXFAttr(3), # primary font file name
'bigfont': DXFAttr(4), # big font name, blank if none
})
class DXF13Style(DXF12Style):
DXFATTRIBS = DXFAttributes(none_subclass, symbol_subclass, style_subclass)
DEFAULT_STYLE = """ 0
STYLE
2
STANDARD
70
0
40
0.0
41
1.0
50
0.0
71
0
42
1.0
3
Arial
4
"""
default_text_style = Style(DXF12Style(ClassifiedTags.from_text(DEFAULT_STYLE)))

View File

@ -0,0 +1,92 @@
# Purpose: handle tables section
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
from .defaultchunk import iterchunks, DefaultChunk
from .layers import LayerTable
from .styles import StyleTable
from .linetypes import LinetypeTable
TABLENAMES = {
'layer': 'layers',
'ltype': 'linetypes',
'appid': 'appids',
'dimstyle': 'dimstyles',
'style': 'styles',
'ucs': 'ucs',
'view': 'views',
'vport': 'viewports',
'block_record': 'block_records',
}
def tablename(dxfname):
""" Translate DXF-table-name to attribute-name. ('LAYER' -> 'layers') """
name = dxfname.lower()
return TABLENAMES.get(name, name+'s')
class GenericTable(DefaultChunk):
@property
def name(self):
return tablename(self.tags[1].value)
class DefaultDrawing(object):
dxfversion = 'AC1009'
encoding = 'cp1252'
class TablesSection(object):
name = 'tables'
def __init__(self, drawing=DefaultDrawing()):
self._tables = dict()
self._drawing = drawing
self._create_default_tables()
def _create_default_tables(self):
for cls in TABLESMAP.values():
table = cls()
self._tables[table.name] = table
@staticmethod
def from_tags(tags, drawing):
tables_section = TablesSection(drawing)
tables_section._setup_tables(tags)
return tables_section
def _setup_tables(self, tags):
def name(table):
return table[1].value
def skiptags(tags, count):
for i in range(count):
next(tags)
return tags
itertags = skiptags(iter(tags), 2) # (0, 'SECTION'), (2, 'TABLES')
for table in iterchunks(itertags, stoptag='ENDSEC', endofchunk='ENDTAB'):
table_class = table_factory(name(table))
new_table = table_class.from_tags(table, self._drawing)
self._tables[new_table.name] = new_table
def __getattr__(self, key):
try:
return self._tables[key]
except KeyError:
raise AttributeError(key)
# support for further tables types are possible
TABLESMAP = {
'LAYER': LayerTable,
'STYLE': StyleTable,
'LTYPE': LinetypeTable,
}
def table_factory(name):
return TABLESMAP.get(name, GenericTable)

View File

@ -0,0 +1,73 @@
# Purpose: tag reader
# Created: 21.07.2012, taken from my ezdxf project
# Copyright (C) 2012, Manfred Moitzi
# License: MIT License
from __future__ import unicode_literals
__author__ = "mozman <mozman@gmx.at>"
import os
from .const import ENV_CYTHON
OPTIMIZE = True
if ENV_CYTHON in os.environ:
if os.environ[ENV_CYTHON].upper() in ('1', 'ON', 'TRUE'):
OPTIMIZE = True
else:
OPTIMIZE = False
try:
if not OPTIMIZE:
raise ImportError
CYTHON_EXT = True
from.cytags import TagIterator, Tags, TagGroups, DXFTag, NONE_TAG
from.cytags import DXFStructureError, StringIterator, ClassifiedTags
except ImportError:
CYTHON_EXT = False
from.pytags import TagIterator, Tags, TagGroups, DXFTag, NONE_TAG
from.pytags import DXFStructureError, StringIterator, ClassifiedTags
import sys
from .codepage import toencoding
from .const import acadrelease
from array import array
class DXFInfo(object):
def __init__(self):
self.release = 'R12'
self.version = 'AC1009'
self.encoding = 'cp1252'
self.handseed = '0'
def DWGCODEPAGE(self, value):
self.encoding = toencoding(value)
def ACADVER(self, value):
self.version = value
self.release = acadrelease.get(value, 'R12')
def HANDSEED(self, value):
self.handseed = value
def dxfinfo(stream):
info = DXFInfo()
tag = DXFTag(999999, '')
tagreader = TagIterator(stream)
while tag != DXFTag(0, 'ENDSEC'):
tag = next(tagreader)
if tag.code != 9:
continue
name = tag.value[1:]
method = getattr(info, name, None)
if method is not None:
method(next(tagreader).value)
return info
def binary_encoded_data_to_bytes(data):
PY3 = sys.version_info[0] >= 3
byte_array = array('B' if PY3 else b'B')
for text in data:
byte_array.extend(int(text[index:index+2], 16) for index in range(0, len(text), 2))
return byte_array.tobytes() if PY3 else byte_array.tostring()

View File

View File

@ -0,0 +1,287 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import itertools
from . import is_
from .fake_entities import ArcEntity
from mathutils import Vector, Matrix, Euler, Color
from math import pi, radians, floor, ceil, degrees
from copy import deepcopy
class ShortVec(Vector):
def __str__(self):
return "Vec" + str((round(self.x, 2), round(self.y, 2), round(self.z, 2)))
def __repr__(self):
return self.__str__()
def bspline_to_cubic(do, en, curve, errors=None):
"""
do: an instance of Do()
en: a DXF entity
curve: Blender geometry data of type "CURVE"
inserts knots until every knot has multiplicity of 3; returns new spline controlpoints
if degree of the spline is > 3 "None" is returned
"""
def clean_knots():
start = knots[:degree + 1]
end = knots[-degree - 1:]
if start.count(start[0]) < degree + 1:
maxa = max(start)
for i in range(degree + 1):
knots[i] = maxa
if end.count(end[0]) < degree + 1:
mina = min(end)
lenk = len(knots)
for i in range(lenk - degree - 1, lenk):
knots[i] = mina
def insert_knot(t, k, p):
""" http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/NURBS-knot-insert.html """
def a(t, ui, uip):
if uip == ui:
print("zero!")
return 0
return (t - ui) / (uip - ui)
new_spline = spline.copy()
for pp in range(p, 1, -1):
i = k - pp + 1
ai = a(t, knots[i], knots[i + p])
new_spline[i] = (1 - ai) * spline[i - 1] + ai * spline[i]
ai = a(t, knots[k], knots[k + p])
new_spline.insert(k, (1 - ai) * spline[k - 1] + ai * spline[k % len(spline)])
knots.insert(k, t)
return new_spline
knots = list(en.knots)
spline = [ShortVec(cp) for cp in en.controlpoints]
degree = len(knots) - len(spline) - 1
if degree <= 3:
clean_knots()
k = 1
st = 1
while k < len(knots) - 1:
t = knots[k]
multilen = knots[st:-st].count(t)
if multilen < degree:
before = multilen
while multilen < degree:
spline = insert_knot(t, k, degree)
multilen += 1
k += 1
k += before
else:
k += degree
if degree <= 2:
return quad_to_cube(spline)
# the ugly truth
if len(spline) % 3 == 0:
spline.append([spline[-1]])
errors.add("Cubic spline: Something went wrong with knot insertion")
return spline
def quad_to_cube(spline):
"""
spline: list of (x,y,z)-tuples)
Converts quad bezier to cubic bezier curve.
"""
s = []
for i, p in enumerate(spline):
if i % 2 == 1:
before = Vector(spline[i - 1])
after = Vector(spline[(i + 1) % len(spline)])
s.append(before + 2 / 3 * (Vector(p) - before))
s.append(after + 2 / 3 * (Vector(p) - after))
else:
s.append(p)
# degree == 1
if len(spline) == 2:
s.append(spline[-1])
return s
def bulge_to_arc(point, next, bulge):
"""
point: start point of segment in lwpolyline
next: end point of segment in lwpolyline
bulge: number between 0 and 1
Converts a bulge of lwpolyline to an arc with a bulge describing the amount of how much a straight segment should
be bended to an arc. With the bulge one can find the center point of the arc that replaces the segment.
"""
rot = Matrix(((0, -1, 0), (1, 0, 0), (0, 0, 1)))
section = next - point
section_length = section.length / 2
direction = -bulge / abs(bulge)
correction = 1
sagitta_len = section_length * abs(bulge)
radius = (sagitta_len**2 + section_length**2) / (2 * sagitta_len)
if sagitta_len < radius:
cosagitta_len = radius - sagitta_len
else:
cosagitta_len = sagitta_len - radius
direction *= -1
correction *= -1
center = point + section / 2 + section.normalized() * cosagitta_len * rot * direction
cp = point - center
cn = next - center
cr = cp.to_3d().cross(cn.to_3d()) * correction
start = Vector((1, 0))
if cr[2] > 0:
angdir = 0
startangle = -start.angle_signed(cp.to_2d())
endangle = -start.angle_signed(cn.to_2d())
else:
angdir = 1
startangle = start.angle_signed(cp.to_2d())
endangle = start.angle_signed(cn.to_2d())
return ArcEntity(startangle, endangle, center.to_3d(), radius, angdir)
def bulgepoly_to_cubic(do, lwpolyline):
"""
do: instance of Do()
lwpolyline: DXF entity of type polyline
Bulges define how much a straight segment of a polyline should be transformed to an arc. Hence do.arc() is called
for segments with a bulge and all segments are being connected to a cubic bezier curve in the end.
Reference: http://www.afralisp.net/archive/lisp/Bulges1.htm
"""
def handle_segment(last, point, bulge):
if bulge != 0 and (point - last).length != 0:
arc = bulge_to_arc(last, point, bulge)
cubic_bezier = do.arc(arc, None, aunits=1, angdir=arc.angdir, angbase=0)
else:
la = last.to_3d()
po = point.to_3d()
section = point - last
cubic_bezier = [la, la + section * 1 / 3, la + section * 2 / 3, po]
return cubic_bezier
points = lwpolyline.points
bulges = lwpolyline.bulge
lenpo = len(points)
spline = []
for i in range(1, lenpo):
spline += handle_segment(Vector(points[i - 1]), Vector(points[i]), bulges[i - 1])[:-1]
if lwpolyline.is_closed:
spline += handle_segment(Vector(points[-1]), Vector(points[0]), bulges[-1])
else:
spline.append(points[-1])
return spline
def bulgepoly_to_lenlist(lwpolyline):
"""
returns a list with the segment lengths of a lwpolyline
"""
def handle_segment(last, point, bulge):
seglen = (point - last).length
if bulge != 0 and seglen != 0:
arc = bulge_to_arc(last, point, bulge)
if arc.startangle > arc.endangle:
arc.endangle += 2 * pi
angle = arc.endangle - arc.startangle
lenlist.append(abs(arc.radius * angle))
else:
lenlist.append(seglen)
points = lwpolyline.points
bulges = lwpolyline.bulge
lenpo = len(points)
lenlist = []
for i in range(1, lenpo):
handle_segment(Vector(points[i - 1][:2]), Vector(points[i][:2]), bulges[i - 1])
if lwpolyline.is_closed:
handle_segment(Vector(points[-1][:2]), Vector(points[0][:2]), bulges[-1])
return lenlist
def extrusion_to_matrix(entity):
"""
Converts an extrusion vector to a rotation matrix that denotes the transformation between world coordinate system
and the entity's own coordinate system (described by the extrusion vector).
"""
def arbitrary_x_axis(extrusion_normal):
world_y = Vector((0, 1, 0))
world_z = Vector((0, 0, 1))
if abs(extrusion_normal[0]) < 1 / 64 and abs(extrusion_normal[1]) < 1 / 64:
a_x = world_y.cross(extrusion_normal)
else:
a_x = world_z.cross(extrusion_normal)
a_x.normalize()
return a_x, extrusion_normal.cross(a_x)
az = Vector(entity.extrusion)
ax, ay = arbitrary_x_axis(az)
return Matrix((ax, ay, az)).inverted()
def split_by_width(entity):
"""
Used to split a curve (polyline, lwpolyline) into smaller segments if their width is varying in the overall curve.
"""
class WidthTuple:
def __init__(self, w):
self.w1 = w[0]
self.w2 = w[1]
def __eq__(self, other):
return self.w1 == other.w1 and self.w2 == other.w2 and self.w1 == self.w2
if is_.varying_width(entity):
entities = []
en_template = deepcopy(entity)
en_template.points = []
en_template.bulge = []
en_template.width = []
en_template.tangents = []
en_template.is_closed = False
i = 0
for pair, same_width in itertools.groupby(entity.width, key=lambda w: WidthTuple(w)):
en = deepcopy(en_template)
for segment in same_width:
en.points.append(entity.points[i])
en.points.append(entity.points[(i + 1) % len(entity.points)])
en.bulge.append(entity.bulge[i])
en.width.append(entity.width[i])
i += 1
entities.append(en)
if not entity.is_closed:
entities.pop(-1)
return entities
else:
return [entity]

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
class ArcEntity:
"""
Used in convert.bulge_to_cubic() since bulges define how much a straight polyline segment should be transformed to
an arc. ArcEntity is just used to call do.arc() without having a real Arc-Entity from dxfgrabber.
"""
def __init__(self, start, end, center, radius, angdir):
self.startangle = start
self.endangle = end
self.center = center
self.radius = radius
self.angdir = angdir
def __str__(self):
return "startangle: %s, endangle: %s, center: %s, radius: %s, angdir: %s" % \
(str(self.startangle), str(self.endangle), str(self.center), str(self.radius), str(self.angdir))
class LineEntity:
"""
Used in do._gen_meshface()
"""
def __init__(self, start, end):
self.start = start
self.end = end

View File

@ -0,0 +1,83 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import itertools
from . import is_
from mathutils import Vector
def map_dxf_to_blender_type(TYPE):
"""
TYPE: DXF entity type (String)
"""
if is_.mesh(TYPE):
return "object_mesh"
if is_.curve(TYPE):
return "object_curve"
if is_.nurbs(TYPE):
return "object_surface"
else:
print("groupsort: not mergeable type ", TYPE)
return "not_mergeable"
def by_blender_type(entities):
"""
entities: list of DXF entities
"""
keyf = lambda e: map_dxf_to_blender_type(e.dxftype)
return itertools.groupby(sorted(entities, key=keyf), key=keyf)
def by_layer(entities):
"""
entities: list of DXF entities
"""
keyf = lambda e: e.layer
return itertools.groupby(sorted(entities, key=keyf), key=keyf)
def by_dxftype(entities):
"""
entities: list of DXF entities
"""
keyf = lambda e: e.dxftype
return itertools.groupby(sorted(entities, key=keyf), key=keyf)
def by_attributes(entities):
"""
entities: list of DXF entities
attributes: thickness and width occuring in curve types; subdivision_levels occuring in MESH dxf types
"""
def attributes(entity):
width = [(0, 0)]
subd = -1
extrusion = entity.extrusion
if hasattr(entity, "width"):
if any((w != 0 for ww in entity.width for w in ww)):
width = entity.width
if hasattr(entity, "subdivision_levels"):
subd = entity.subdivision_levels
if entity.dxftype in {"LINE", "POINT"}:
extrusion = (0.0, 0.0, 1.0)
return entity.thickness, subd, width, extrusion
return itertools.groupby(sorted(entities, key=attributes), key=attributes)

View File

@ -0,0 +1,129 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
_MESH_ENTITIES = frozenset(["POLYFACE", "POLYMESH", "MESH", "POINT", "3DFACE", "SOLID", "TRACE"])
def mesh_entity(entity):
return entity.dxftype in _MESH_ENTITIES
def mesh(typestr):
return typestr in _MESH_ENTITIES
_CURVE_ENTITIES = frozenset(("POLYLINE", "POLYGON", "LWPOLYLINE", "SPLINE",
"CIRCLE", "ARC", "ELLIPSE", "LINE", "HELIX"))
def curve_entity(entity):
return entity.dxftype in _CURVE_ENTITIES
def curve(typestr):
return typestr in _CURVE_ENTITIES
_NURBS_ENTITIES = frozenset(("BODY", "REGION", "PLANESURFACE", "SURFACE", "3DSOLID"))
def nurbs_entity(entity):
return entity.dxftype in _NURBS_ENTITIES
def nurbs(typestr):
return typestr in _NURBS_ENTITIES
_TEXT_ENTITIES = frozenset(("MTEXT", "TEXT"))
def text_entity(entity):
return entity.dxftype in _TEXT_ENTITIES
def text(typestr):
return typestr in _TEXT_ENTITIES
def insert_entity(entity):
return entity.dxftype == "INSERT"
def insert(typestr):
return typestr == "INSERT"
def light_entity(entity):
return entity.dxftype == "LIGHT"
def light(typestr):
return typestr == "LIGHT"
def attrib(entity):
return entity.dxftype == "ATTDEF"
def attrib(typestr):
return typestr == "ATTDEF"
_2D_ENTITIES = frozenset(("CIRCLE", "ARC", "SOLID", "TRACE", "TEXT", "ATTRIB", "ATTDEF", "SHAPE",
"INSERT", "LWPOLYLINE", "HATCH", "IMAGE", "ELLIPSE"))
def _2D_entity(entity):
return entity.dxftype in _2D_ENTITIES or (entity.dxftype == "POLYGON" and entity.mode == "spline2d")
def varying_width(entity):
if hasattr(entity, "width"):
ew = entity.width
if hasattr(ew, "__iter__"):
return ew.count(ew[0]) != len(ew) or ew[0][0] != ew[0][1]
return False
_SEPERATED_ENTITIES = frozenset(("POLYFACE", "POLYMESH", "LIGHT", "MTEXT", "TEXT", "INSERT", "BLOCK"))
def separated_entity(entity):
"""
Indicates if the entity should be imported to one single Blender object or if it can be merged with other entities.
This depends not only on the type of a dxf-entity but also whether the width values are varying or all the same.
"""
return entity.dxftype in _SEPERATED_ENTITIES or varying_width(entity)
def separated(typestr):
return typestr in _SEPERATED_ENTITIES
_NOT_COMBINED_ENTITIES = frozenset(tuple(_SEPERATED_ENTITIES) + ("ATTDEF",))
def combined_entity(entity):
return not separated_entity(entity) and not entity.dxftype == "ATTDEF"
def combined(typestr):
return typestr not in _NOT_COMBINED_ENTITIES

View File

@ -0,0 +1,113 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
def line_merger(lines, precision=6):
merger = _LineMerger(lines, precision)
return merger.polylines
def _round_point(point, precision):
return tuple(round(c, precision) for c in point)
class _LineMerger:
def __init__(self, lines, precision):
self.segments = set() # single lines as tuples: ((sx, sy[, sz]), (ex, ey[, ez]))
self.used_segments = set()
self.points = dict() # key: point -> value: list of segments with this point as start or end point
self.precision = precision
self.setup(lines)
self.polylines = self.merge_lines() # result of merging process
def setup(self, lines):
for line in lines:
s = _round_point(line.start, self.precision)
e = _round_point(line.end, self.precision)
self.add_segment(s, e)
def add_segment(self, start, end):
if start == end:
return # this is not a segment
if end < start: # order start and end points to detect all doubles
segment = (end, start)
else:
segment = (start, end)
if segment in self.segments:
return # this segment already exist
self.segments.add(segment)
self.add_point(start, segment)
self.add_point(end, segment)
def add_point(self, point, segment):
segments = self.points.get(point)
if segments is None:
segments = list()
self.points[point] = segments
segments.append(segment)
def get_segment_with_point(self, point):
segments = self.points.get(point)
if segments is None:
return None
# Very important: do not return already used segments
for segment in segments:
if segment not in self.used_segments:
return segment
return None
def mark_as_used_segment(self, segment):
self.used_segments.add(segment)
self.segments.discard(segment)
def merge_lines(self):
def get_extension_point(point):
extension = self.get_segment_with_point(point)
if extension is not None:
self.mark_as_used_segment(extension)
if extension[0] == point:
return extension[1]
else:
return extension[0]
return None
polylines = []
while len(self.segments):
segment = self.segments.pop() # take an arbitrary segment
self.mark_as_used_segment(segment)
polyline = list(segment) # start a new polyline
extend_start = True
extend_end = True
while extend_start or extend_end:
if extend_start:
extension_point = get_extension_point(polyline[0]) # extend start of polyline
if extension_point is not None:
polyline.insert(0, extension_point)
else:
extend_start = False
if extend_end:
extension_point = get_extension_point(polyline[-1]) # extend end of polyline
if extension_point is not None:
polyline.append(extension_point)
else:
extend_end = False
polylines.append(polyline)
return polylines

View File

@ -0,0 +1,54 @@
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
from math import sin, cos, atan, atanh, radians, tan, sinh, asin, cosh, degrees
# see conversion formulas at
# http://en.wikipedia.org/wiki/Transverse_Mercator_projection
# http://mathworld.wolfram.com/MercatorProjection.html
class TransverseMercator:
radius = 6378137
def __init__(self, lat=0, lon=0):
self.lat = lat # in degrees
self.lon = lon # in degrees
self.lat_rad = radians(self.lat)
self.lon_rad = radians(self.lon)
def fromGeographic(self, lat, lon):
lat_rad = radians(lat)
lon_rad = radians(lon)
B = cos(lat_rad) * sin(lon_rad - self.lon_rad)
x = self.radius * atanh(B)
y = self.radius * (atan(tan(lat_rad) / cos(lon_rad - self.lon_rad)) - self.lat_rad)
return x, y
def toGeographic(self, x, y):
x /= self.radius
y /= self.radius
D = y + self.lat_rad
lon = atan(sinh(x) / cos(D))
lat = asin(sin(D) / cosh(x))
lon = self.lon + degrees(lon)
lat = degrees(lat)
return lat, lon

File diff suppressed because it is too large Load Diff