compas-dev / compas-dev/compas

`DXF` reader enhancement

Open
#1,210 3 comments 0 reactions 0 assignees View on GitHub
Dominant language
Python
Stars
386
Forks
122
Avg merge
11d 46m
Merged PRs (30d)
1

Description

# 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

```python

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 | |

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.