Files
Rozpisky/Rozpisky/Dxf/RozpiskaRenderer.cs
T
marekandClaude Opus 5 a6377ab583 Baseline: současný stav projektu Rozpisky
První commit — WPF aplikace pro generování výkresových rohových razítek
(.NET 10, SkiaSharp, ACadSharp, ClosedXML) včetně xUnit testů, DXF šablony,
log a dokumentace. Build výstupy (bin/, obj/, .vs/) jsou ignorovány.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-07 09:45:49 +02:00

92 lines
4.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Concurrent;
using System.IO;
using Rozpisky.Models;
using Rozpisky.Rendering;
namespace Rozpisky.Dxf;
/// <summary>
/// Spojuje DXF šablonu, mapování hodnot a exportéry do jednoho místa pro UI
/// (náhled, tisk). Šablona se načte jednou a cachuje.
/// </summary>
public sealed class RozpiskaRenderer
{
private readonly DxfTemplate _tpl;
public RozpiskaRenderer(string sablonaPath) => _tpl = new DxfTemplate(ResolvePath(sablonaPath));
/// <summary>Najde šablonu: absolutní/relativní k pracovní složce, jinak vedle .exe.</summary>
public static string ResolvePath(string path)
=> File.Exists(path) ? path : Path.Combine(AppContext.BaseDirectory, Path.GetFileName(path));
/// <summary>A4 rámeček šablony (mm) referenční obdélník pro vystředění náhledu i exportů.</summary>
public (double X, double Y, double W, double H)? SheetFrame => _tpl.SheetFrame;
/// <summary>Rozměr jedné buňky pro dlaždicový DXF export přednostně celkový <c>!!NOPRINT</c>
/// rámeček, jinak A4 rámeček, jinak rozsah kresby.</summary>
public (double X, double Y, double W, double H)? CellFrame => _tpl.FrameBounds ?? _tpl.SheetFrame ?? _tpl.Bounds;
// Načtené fragmenty (loga / schémata) podle cesty šablony jsou immutable, lze sdílet.
private static readonly ConcurrentDictionary<string, DxfTemplate?> FragmentCache = new(StringComparer.OrdinalIgnoreCase);
/// <summary>Vyprázdní cache fragmentů volá se při načtení/založení projektu, aby cache nerostla
/// donekonečna a aby se mezitím změněné DXF soubory (loga, schémata) načetly znovu.</summary>
public static void VycistiCache() => FragmentCache.Clear();
/// <summary>Sestaví kresbu rozpisky (vektorové příkazy) pro libovolný cíl náhled/PNG/PDF/DXF.
/// <paramref name="loga"/> = mapa „hladina placeholderu → absolutní cesta k DXF" (logo/schéma);
/// každý fragment se fitne (uniformní scale + vystředění) do svého rámečku.
/// <paramref name="ramecekA4"/> = true → přidá i <c>!!NOPRINT</c> rámeček (hranice papíru A4)
/// pro DXF export, aby šlo z CADu rozpisku případně vytisknout. Fragmenty (loga) svůj NOPRINT
/// vodicí rámeček nikdy nekreslí.</summary>
public Action<IProfileRenderer> BuildDraw(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c,
bool vyplnene, IReadOnlyDictionary<string, string>? loga = null,
bool ramecekA4 = false)
{
var values = vyplnene ? RozpiskaValues.Build(p, r, revize, c) : null;
return g =>
{
_tpl.Replay(g, values, ramecekA4);
if (loga is { Count: > 0 }) KresliFragmenty(g, loga);
};
}
/// <summary>Vykreslí každý DXF fragment do jeho rámečku: uniformní zmenšení tak, aby se vešel,
/// a vystředění. Chybějící rámeček/soubor/prázdná kresba se tiše přeskočí.</summary>
private void KresliFragmenty(IProfileRenderer g, IReadOnlyDictionary<string, string> loga)
{
foreach (var (hladina, cesta) in loga)
{
if (string.IsNullOrWhiteSpace(cesta)) continue;
if (!_tpl.Placeholders.TryGetValue(hladina, out var f)) continue;
if (f.W <= 0 || f.H <= 0) continue;
var frag = NactiFragment(cesta);
if (frag is null) continue;
// Reference pro fit: přednostně vodicí rámeček !!NOPRINT (návrhové umístění uvnitř loga,
// poměr i okraje řeší autor → fit 1:1), jinak bbox kresby (s 5% rezervou).
bool maRamecek = frag.FrameBounds is { };
if ((maRamecek ? frag.FrameBounds : frag.Bounds) is not { } cb || cb.W <= 0 || cb.H <= 0) continue;
double s = Math.Min(f.W / cb.W, f.H / cb.H) * (maRamecek ? 1.0 : 0.95);
double ox = f.X + f.W / 2 - s * (cb.X + cb.W / 2);
double oy = f.Y + f.H / 2 - s * (cb.Y + cb.H / 2);
frag.Replay(new TransformRenderer(g, s, ox, oy));
}
}
private static DxfTemplate? NactiFragment(string cesta) => FragmentCache.GetOrAdd(cesta, c =>
{
try { return File.Exists(c) ? new DxfTemplate(c) : null; }
catch { return null; } // poškozený/nečitelný DXF fragment → bez loga
});
public void ExportPdf(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c, string path,
IReadOnlyDictionary<string, string>? loga = null)
=> Exporters.PdfA4(BuildDraw(p, r, revize, c, vyplnene: true, loga), path, frame: _tpl.SheetFrame);
public void ExportPng(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c, string path,
IReadOnlyDictionary<string, string>? loga = null)
=> Exporters.PngA4(BuildDraw(p, r, revize, c, vyplnene: true, loga), path, frame: _tpl.SheetFrame);
}