compas
compas copied to clipboard
`DXF` reader enhancement
Feature Request
Currently, the DXF
reader is not development. I had a bit of time looking at it and felt we could develop it. @tomvanmele
Details
.dxf
is a fairly complex format containing many many datatypes, but general datatype like Line
and Mesh
support might be helpful. I found that ezdxf
package is pretty lightweight and can be very helpful. shall we introduce this package?
Example code
import ezdxf
class DXF(object):
"""Class for working with DXF files.
Parameters
----------
filepath : path string | file-like object
A path, a file-like object.
precision : str, optional
A precision specification.
Attributes
----------
reader : :class:`DXFReader`, read-only
A DXF file reader.
parser : :class:`DXFParser`, read-only
A DXF data parser.
References
----------
* https://en.wikipedia.org/wiki/AutoCAD_DXF
* http://paulbourke.net/dataformats/dxf/
* http://paulbourke.net/dataformats/dxf/min3d.html
* https://ezdxf.readthedocs.io/
"""
def __init__(self, filepath, precision=None):
self.filepath = filepath
self.precision = precision
self._is_parsed = False
self._reader = None
self._parser = None
def read(self):
"""Read and parse the contents of the file."""
self._reader = DXFReader(self.filepath)
self._parser = DXFParser(self._reader, precision=self.precision)
self._reader.open()
self._reader.read()
self._parser.parse()
self._is_parsed = True
@property
def reader(self):
if not self._is_parsed:
self.read()
return self._reader
@property
def parser(self):
if not self._is_parsed:
self.read()
return self._parser
@property
def vertices(self):
return self.parser.vertices
@property
def lines(self):
return self.parser.lines
@property
def points(self):
return self.parser.points
@property
def face3ds(self):
return self.parser.face3ds
@property
def meshes(self):
return self.parser.meshes
@property
def polyline2ds (self):
return self.parser.polyline2ds
@property
def polyline3ds (self):
return self.parser.polyline3ds
class DXFReader(object):
"""Class for reading data from DXF files.
Parameters
----------
filepath : path string | file-like object
A path, a file-like object.
"""
def __init__(self, filepath):
self.filepath = filepath
self.doc = None
self.content = None
self.lines = None
self.points = None
self.face3ds = None
self.meshes = None
self.polyline2ds = []
self.polyline3ds = []
def open(self):
"""Open the file and read its contents.
Returns
-------
None
"""
try:
self.content = ezdxf.readfile(self.filepath)
except IOError:
print(f"Not a DXF file or a generic I/O error.")
except ezdxf.DXFStructureError:
print(f"Invalid or corrupted DXF file.")
def read(self):
"""Read the contents of the file."""
self.lines = self.content.modelspace().query("LINE")
self.points = self.content.modelspace().query("POINT")
# The 3DFACE entity is real 3D solid filled triangle or quadrilateral.
self.face3ds = self.content.modelspace().query("3DFACE")
# Not yet implemented, example file needed
# self.meshes = self.content.modelspace().query("MESH")
polyline_entities = self.content.modelspace().query("POLYLINE")
for entity in polyline_entities:
if entity.is_2d_polyline:
self.polyline2ds.append(entity)
elif entity.is_3d_polyline:
self.polyline3ds.append(entity)
class DXFParser(object):
"""Class for parsing data from DXF files.
The parser converts the raw geometric data of the file
into corresponding geometry objects and data structures.
Parameters
----------
reader : :class:`DXFReader`
A DXF file reader.
precision : str
Precision specification for parsing geometric data.
"""
def __init__(self, reader, precision):
self.precision = precision
self.reader = reader
self.vertices = None
self.points = None
self.face3ds = None
self.lines = None
self.polylines = None
self.faces = None
self.groups = None
self.objects = None
self.meshes = None
self.polyline2ds = None
self.polyline3ds = None
def parse(self):
"""Parse the the data found by the reader."""
self.lines = [
[list(line.dxf.start), list(line.dxf.end)] for line in self.reader.lines
]
self.points = [list(point.dxf.location) for point in self.reader.points]
# self.meshes = [
# [mesh.MeshData.vertices, mesh.MeshData.faces] for mesh in self.reader.meshes
# ]
# self.parse_face3ds()
self.face3ds = [[v.xyz for v in face.wcs_vertices()] for face in self.reader.face3ds]
# The POLYLINE entity is very complex, it’s used to build 2D/3D polylines, 3D meshes and 3D polyfaces.
# TODO Convert OCS to WCS
self.polyline2ds = [[v.dxf.location for v in polyline.vertices] for polyline in self.reader.polyline2ds]
self.polyline3ds = [[v.dxf.location for v in polyline.vertices] for polyline in self.reader.polyline3ds]
def parse_face3ds_deprecated(self):
# Not used for now
# At the moment, the 3d faces are parsed as points
if self.face3ds is None:
self.face3ds = []
for face3d in self.reader.face3ds:
[f0, f1, f2, f3] = face3d.get_edges_visibility()
if f0:
self.face3ds.append([list(face3d.dxf.vtx0), list(face3d.dxf.vtx1)])
if f1:
self.face3ds.append([list(face3d.dxf.vtx1), list(face3d.dxf.vtx2)])
if f2:
self.face3ds.append([list(face3d.dxf.vtx2), list(face3d.dxf.vtx3)])
if f3:
self.face3ds.append([list(face3d.dxf.vtx3), list(face3d.dxf.vtx0)])
This code support:
.dxf | |
---|---|
unit | |
coordinate system | |
vertices | list[list[x,y,z]] |
faces | list[int] |
normals | |
lines | list[[x0,y0,z0],[x1,y1,z1]] |
polyline | list[list[x,y,z]] |
points | list[x,y,z] |
groups | |
textures |
@tomvanmele || @gonzalocasas ezdxf is no longer a dependency, pls close
@jf--- actually i would like to keep this open because i see some value in supporting DXF files in the context of being able to read geometric data from older sources and formats. ezdxf
was only removed because we never got around to writing an actual implementation...
Gotcha, I assumed that with the dependency gone that this maybe have become irrelevant. Thanks for clarifying.