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>
This commit is contained in:
2026-09-07 09:45:49 +02:00
co-authored by Claude Opus 5
commit a6377ab583
107 changed files with 349485 additions and 0 deletions
+91
View File
@@ -0,0 +1,91 @@
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);
}