loko/streetup/documents/utils.py
2026-07-22 14:48:40 +02:00

145 lines
5.7 KiB
Python

import os
import subprocess
def render_cad_file_to_image(file_path, output_png_path):
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import ezdxf
from ezdxf import bbox
from ezdxf.addons.drawing import RenderContext, Frontend
from ezdxf.addons.drawing.matplotlib import MatplotlibBackend
from ezdxf.addons.drawing.properties import LayoutProperties
from ezdxf.addons.drawing.config import Configuration, LineweightPolicy
ext = os.path.splitext(file_path)[1].lower()
temp_dxf_path = None
if ext == '.dwg':
temp_dxf_path = file_path + ".temp.dxf"
try:
# Run dwg2dxf subprocess to convert DWG to temporary DXF
# dwg2dxf syntax: dwg2dxf -o output.dxf input.dwg
subprocess.run(
["dwg2dxf", "-o", temp_dxf_path, file_path],
check=True,
capture_output=True,
text=True
)
except (subprocess.CalledProcessError, FileNotFoundError) as e:
# Clean up if created
if temp_dxf_path and os.path.exists(temp_dxf_path):
try:
os.remove(temp_dxf_path)
except Exception:
pass
raise RuntimeError(
"L'utilitaire dwg2dxf (package libredwg-utils) est requis pour la conversion des fichiers DWG, "
f"mais il n'a pas pu être exécuté : {str(e)}"
)
dxf_to_render = temp_dxf_path
elif ext == '.dxf':
dxf_to_render = file_path
else:
raise ValueError(f"Format de fichier CAO non supporté : {ext}")
try:
# Load and render DXF
doc = ezdxf.readfile(dxf_to_render)
msp = doc.modelspace()
# Calculate modelspace bounds
bounds = bbox.extents(msp)
min_x, min_y = bounds.extmin.x, bounds.extmin.y
max_x, max_y = bounds.extmax.x, bounds.extmax.y
# Calculate dimensions to maintain exact aspect ratio
dx = max_x - min_x
dy = max_y - min_y
if dx <= 0: dx = 100
if dy <= 0: dy = 100
aspect_ratio = dy / dx
# Scale figure size so the maximum dimension is exactly 20.0 inches
# At 300 DPI, this results in exactly 6,000 pixels for the maximum dimension.
if aspect_ratio > 1:
fig_height = 20.0
fig_width = fig_height / aspect_ratio
else:
fig_width = 20.0
fig_height = fig_width * aspect_ratio
fig = plt.figure(frameon=True, figsize=(fig_width, fig_height), facecolor='#FFFFFF')
ax = fig.add_axes([0, 0, 1, 1])
ax.set_axis_off()
ctx = RenderContext(doc)
layout_properties = LayoutProperties.from_layout(msp)
layout_properties.set_colors(bg='#FFFFFF')
# Configure drawing settings to scale lineweights down for crispness
config = Configuration(
lineweight_scaling=0.8,
lineweight_policy=LineweightPolicy.ABSOLUTE,
min_lineweight=0.06
)
class DarkeningMatplotlibBackend(MatplotlibBackend):
def _adjust_properties(self, properties):
color = properties.color
if color.startswith('#'):
h = color.lstrip('#')
if len(h) == 6:
r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)
elif len(h) == 3:
r, g, b = int(h[0]*2, 16), int(h[1]*2, 16), int(h[2]*2, 16)
else:
return properties
lum = (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255.0
if lum > 0.55:
scale = 0.25 / lum
scale = min(scale, 0.8)
r_new = max(0, min(255, int(r * scale)))
g_new = max(0, min(255, int(g * scale)))
b_new = max(0, min(255, int(b * scale)))
new_color = f"#{r_new:02x}{g_new:02x}{b_new:02x}"
return properties._replace(color=new_color)
return properties
def draw_path(self, path, properties):
super().draw_path(path, self._adjust_properties(properties))
def draw_filled_paths(self, paths, properties):
super().draw_filled_paths(paths, self._adjust_properties(properties))
def draw_filled_polygon(self, points, properties):
super().draw_filled_polygon(points, self._adjust_properties(properties))
def draw_line(self, start, end, properties):
super().draw_line(start, end, self._adjust_properties(properties))
def draw_point(self, pos, properties):
super().draw_point(pos, self._adjust_properties(properties))
def draw_solid_lines(self, lines, properties):
super().draw_solid_lines(lines, self._adjust_properties(properties))
backend = DarkeningMatplotlibBackend(ax, adjust_figure=False)
Frontend(ctx, backend, config=config).draw_layout(msp, layout_properties=layout_properties, finalize=True)
ax.set_xlim(min_x, max_x)
ax.set_ylim(min_y, max_y)
os.makedirs(os.path.dirname(output_png_path), exist_ok=True)
fig.savefig(output_png_path, dpi=300, bbox_inches='tight', pad_inches=0, facecolor='#FFFFFF')
plt.close(fig)
finally:
# Clean up temporary DXF file if created
if temp_dxf_path and os.path.exists(temp_dxf_path):
try:
os.remove(temp_dxf_path)
except Exception:
pass