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