mozman / ezdxf

Python interface to DXF
https://ezdxf.mozman.at
MIT License
926 stars 190 forks source link

HATCH rendering bug in the drawing add-on #454

Closed chibai closed 3 years ago

chibai commented 3 years ago

test.zip

import matplotlib.pyplot as plt
from ezdxf import recover
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
dxf_doc, dxf_auditor= recover.readfile('test.dxf')
fig = plt.figure(dpi=200)
ax = fig.add_axes([0, 0, 1, 1])
ax.margins(0)
ctx = RenderContext(dxf_doc)
ctx.current_layout.set_colors(bg='#FFFFFF')
out = MatplotlibBackend(ax)
Frontend(ctx, out).draw_layout(dxf_doc.modelspace(), finalize=True)
fig.savefig('test.png')

This is the code result: test

This is the AutoCad result: 1622713172(1)

chibai commented 3 years ago

I'm Mr Bug-Producer

I think I'm Mr Bug-Presentor, not Mr Bug-Producer

mozman commented 3 years ago

What is your workflow to create your CAD drawings?

chibai commented 3 years ago

What is your workflow to create your CAD drawings?

test_simplified.zip I'm sorry. I'm not the auther of dxf file and the workflow is not recordable. I smiplified the dxf to the bug HATCH entity, hope it can be helpful.

mozman commented 3 years ago

I just wanted to know how this HATCH was created because this is not a result that a human would create on purpose.

chibai commented 3 years ago

I just wanted to know how this HATCH was created because this is not a result that a human would create on purpose.

Well, I guess it's a work of my unskilled colleague. But I really don't know how the HATCH is created......

mozman commented 3 years ago

This is the degenerated path boundary paths which causes the problem. I have no idea how to filter out such (erroneous) boundary paths like AutoCAD and BricsCAD do.

image

This is the edge path (path of single lines) without connection lines added by ezdxf:

image

Ezdxf is designed to create always connected paths, if the following line element has no common point with the previous line element, ezdxf adds automatically a line segment from the previous end to the following start.

I don't think I will add a logic to detect these degenerated paths, there are infinite possibilities for such paths and frankly I also have no idea how to do that.

chibai commented 3 years ago

Got it, thank your for telling me this!

mozman commented 3 years ago

Not so fast young padawan - maybe I have an idea later.

chibai commented 3 years ago

Not so fast young padawan - maybe I have an idea later.

I think you could foucus more on some general problems. And I don't want this specific rare bug to waste your time.

mozman commented 3 years ago

But this is real programming task, most of the other problems are: how to write the damn DXF tags to make AutoCAD happy, or how to interpret or render a special or undocumented DXF tag.

mozman commented 3 years ago

Some new insights:

from pathlib import Path
import ezdxf

DIR = Path("~/Desktop/Outbox").expanduser()

doc = ezdxf.new()
msp = doc.modelspace()
msp.add_lwpolyline([(0, 0), (1, 0), (1, 1), (0, 1)], close = True)
hatch = msp.add_hatch(1)
p = hatch.paths.add_edge_path()
# 2 open segments
p.add_line((1, 0), (1, 1))
p.add_line((1, 1), (0, 1))  # break in continuity
p.add_line((1, 0), (0, 0))
p.add_line((0, 0), (0, 1))

doc.set_modelspace_vport(2, center=(0.5, 0.5))
doc.saveas(DIR / "hatch_edge_with_open_segments.dxf")

hatch = msp.add_hatch(1)
p = hatch.paths.add_edge_path()
# 1st loop: closed segments
p.add_line((0, 0), (1, 0))  # edit2
p.add_line((1, 0), (1, 1))  # edit2
p.add_line((1, 1), (0, 1))
p.add_line((0, 1), (0, 0))  # edit: fixed continuity break
doc.saveas(DIR / "hatch_edge_with_one_closed_loop.dxf")

# 2nd loop: closed segments
p.add_line((2, 0), (3, 0))
p.add_line((3, 0), (3, 1))
p.add_line((3, 1), (2, 1))
p.add_line((2, 1), (2, 0))
doc.saveas(DIR / "hatch_edge_with_two_closed_loops.dxf")

hatch_with_open_segments.dxf does not render a HATCH: image

hatch_edge_with_one_closed_loop.dxf renders the expected HATCH: image

hatch_edge_with_two_closed_loops.dxf renders the weird and unexpected HATCH, same result in AutoCAD and BricsCAD: image

I can can easily remove open segments from the edge path, and render only closed loops, but I think I can not reproduce the result for the two closed loops at the same edge path. My solution will render two separated squares/loops.

Edit: error in two closed loops, but result isn't really better: image

Edit2: found another error and now the result is at least understandable: image

mozman commented 3 years ago

The current solution renders this for the two loops: image

and this for the example DXF: image

chibai commented 3 years ago

Thanks very much!!

mozman commented 3 years ago

I leave this issue open for a while. I am afraid this is not the end of the road!

Where do you live/work?

chibai commented 3 years ago

I leave this issue open for a while. I am afraid this is not the end of the road!

Where do you live/work?

I was born and grew up in small citi of Dongyang, Zhejiang province of China And now I'm living and working in Shanghai, China How about U?

mozman commented 3 years ago

I am from Austria, Graz.

I only asked because I was surprised by your quick answers during my work day that didn't quite match my assumption that you were from China.

chibai commented 3 years ago

test_hatch.zip I think I found another hatch bug? ezdxf drawing test AutoCad showing 1623034668(1)

I only answer in my work time, which is 10:am to 7:pm(GMT+8). I hope one day I can have time to dig into this code and make contribution on this dxf community!

mozman commented 3 years ago

BricsCAD shows also another result: image

I'll look at the DXF file later.

mozman commented 3 years ago
  1. AutoCAD renders a line where no line should be. BricsCAD perfectly subtracts the inner rectangle from the outer shape, as I would expect. Maybe that is a battleground for a war about "boundary" conditions :) image

  2. Matplotlib and PyQt handle overlapping paths differently than CAD applications. This are the same boundary paths and even the simple island detection of ezdxf works as expected for this example, but the rendering of the Qt-backend is different to BricsCAD: image

I'm afraid there is nothing I can do about it.

mozman commented 3 years ago

This code snippet shows how I isolated such entities form the source document:

from pathlib import Path
import ezdxf

DIR = Path("~/Desktop/Now/ezdxf").expanduser()

doc = ezdxf.readfile(DIR / "chibai04.dxf")
hatch = doc.entitydb.get("1D19")  # get entity by handle

doc2 = ezdxf.new()
msp2 = doc2.modelspace()
msp2.add_foreign_entity(hatch)  # unlinks and unbinds hatch from the source doc!
doc2.saveas(DIR / "isolated_hatch.dxf")

This is not a general recommendation to do this, because this is only sometimes useful if the context is not important for the issue like for the last example and zipped DXF files are really small. And it only works if the CAD application shows the entity handle like BricsCAD:

image

chibai commented 3 years ago

got it!

chibai commented 3 years ago

test_missing_hatch.zip Dear mozman @mozman found another one Missing Circle Hatch EZDXF drawing result test autocad showing 1623207912(1)

mozman commented 3 years ago

This should be fixed.

chibai commented 3 years ago

This should be fixed. @mozman


from ezdxf import recover
from ezdxf.entities import Insert, Hatch, EdgePath, LineEdge

def process_hatch(entity: Hatch): available_boudPaths = list(entity.paths.rendering_paths(hatch_style=entity.dxf.hatch_style)) for path in available_boudPaths: if isinstance(path, EdgePath): for edge in path.edges: if isinstance(edge, LineEdge): print(entity.ocs().to_wcs(edge.start))

def process_insert(entity: Insert): for e in entity.virtual_entities(): entity_type = e.dxftype() if entity_type == 'INSERT': process_insert(e) elif entity_type == 'HATCH': process_hatch(e)

dxf_doc, dxf_auditor= recover.readfile('test_missing_hatch.dxf')

for e in dxf_doc.modelspace(): if e.dxf.invisible == 0: entity_type = e.dxftype() if entity_type == 'HATCH': process_hatch(e) elif entity_type == 'INSERT': process_insert(e)



Please try this code , must be something wrong by #454
![1623230617(1)](https://user-images.githubusercontent.com/5702248/121329131-81a70600-c947-11eb-8b2d-cd6dfaf7a0a9.png)
mozman commented 3 years ago

ocs.to_wcs() requires a vector with x-, y- and and z-axis, wrap it by ezdxf.matzh.Vec3().

chibai commented 3 years ago

ocs.to_wcs() requires a vector with x-, y- and and z-axis, wrap it by ezdxf.math.Vec3().

ok!

mozman commented 3 years ago

Added safety castings to Vec3() to the transformation methods of Matrix44() for the pure Python implementation to get the same behavior as for the C-extension.