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>
92 lines
4.9 KiB
C#
92 lines
4.9 KiB
C#
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);
|
||
}
|