Entity: rozvinout bloky, doplnit elipsy a hlásit, co program neumí
Switch v Replay neměl default větev: INSERT, ELLIPSE, POINT, SOLID, 3DFACE a POLYLINE3D propadly bez akce a bez záznamu. V dodávaných souborech žádná z nich není, ale uživatel si přidává vlastní loga – to nejhorší, co se mu může stát, je logo, které se nevykreslí a nikdo mu to neřekne. - INSERT se rozvine rekurzivně (vztažný bod, měřítko os, natočení), s ochranou proti bloku vkládajícímu sám sebe. Uvnitř bloku se hladina „0" a barva ByBlock berou od vkládající entity – proto jde stejný blok vložit dvakrát a pokaždé v jiné barvě. - TransformRenderer umí celou afinní transformaci, ne jen měřítko a posun. Oblouk zůstane obloukem jen u podobnosti bez zrcadlení; jinak by z kružnice byla elipsa, kterou kreslicí rozhraní nezná, tak se rozloží na lomenou čáru. - ELLIPSE, POINT, SOLID, 3DFACE, POLYLINE3D se kreslí. U SOLID pozor na prohozený 3. a 4. roh, jinak vyjdou přesýpací hodiny. - Zbytek se počítá do DxfTemplate.NezpracovaneEntity a souhrn exportu (PDF i DXF) ho vypíše. Export se kvůli tomu nezastaví. - Spline se dělí podle délky (dřív pevných 64 dílků – málo pro dlouhé křivky, zbytečně moc pro krátké). - Verze výstupního DXF se zafixovala na AC1027 (jako zdrojová šablona). Dřív ji určoval default ACadSharp, který může upgrade balíčku tiše změnit. Šablona i všech 9 log projdou s prázdným počítadlem (test). Render log do PNG je pixel po pixelu shodný s předchozím stavem. 114 testů zelených. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -87,6 +87,15 @@ public class DxfBarvyRoundTripTesty : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VerzeVystupuJeZafixovana()
|
||||||
|
{
|
||||||
|
// Bez explicitního nastavení určí verzi výchozí hodnota ACadSharp – a tu může upgrade
|
||||||
|
// balíčku tiše změnit. Verze rozhoduje, čím půjde výkres u kolegy otevřít.
|
||||||
|
var doc = Kolecko();
|
||||||
|
Assert.Equal(DxfProfileRenderer.VerzeVystupu, doc.Header.Version);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void RamecekNoPrintZustaneNetisknutelny()
|
public void RamecekNoPrintZustaneNetisknutelny()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,194 @@
|
|||||||
|
using ACadSharp;
|
||||||
|
using ACadSharp.Entities;
|
||||||
|
using ACadSharp.Tables;
|
||||||
|
using CSMath;
|
||||||
|
using Rozpisky.Dxf;
|
||||||
|
using Rozpisky.Rendering;
|
||||||
|
using Xunit;
|
||||||
|
using AcColor = ACadSharp.Color;
|
||||||
|
|
||||||
|
namespace Rozpisky.Tests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity, které dřív propadly <c>switch</c>em bez <c>default</c> větve, a tedy zmizely tiše:
|
||||||
|
/// vložené bloky (INSERT), elipsy, body, vyplněné čtyřúhelníky. Uživatel si přidává vlastní
|
||||||
|
/// loga, takže „nevykreslí se a nikdo se to nedozví" je nejhorší možné chování.
|
||||||
|
/// </summary>
|
||||||
|
public class EntityTesty
|
||||||
|
{
|
||||||
|
/// <summary>Přehraje dokument sestavený v paměti přes dočasný soubor (DxfTemplate čte ze souboru).</summary>
|
||||||
|
private static (RecordingRenderer G, IReadOnlyDictionary<string, int> Nezpracovane) Prehraj(CadDocument doc)
|
||||||
|
{
|
||||||
|
string cesta = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
|
||||||
|
"rozpisky-entity-" + Guid.NewGuid().ToString("N") + ".dxf");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ACadSharp.IO.DxfWriter.Write(cesta, doc, binary: true);
|
||||||
|
var tpl = new DxfTemplate(cesta);
|
||||||
|
var g = new RecordingRenderer();
|
||||||
|
tpl.Replay(g);
|
||||||
|
return (g, tpl.NezpracovaneEntity);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { System.IO.File.Delete(cesta); } catch (System.IO.IOException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Dokument s jedním blokem (čtvercová čára 0,0–10,10) a jeho vložením.</summary>
|
||||||
|
private static CadDocument SBlokem(Action<Insert> uprav, AcColor? barvaCary = null)
|
||||||
|
{
|
||||||
|
var doc = new CadDocument();
|
||||||
|
var blok = new BlockRecord("ctverec");
|
||||||
|
blok.Entities.Add(new Line(new XYZ(0, 0, 0), new XYZ(10, 0, 0))
|
||||||
|
{
|
||||||
|
Color = barvaCary ?? AcColor.ByLayer,
|
||||||
|
});
|
||||||
|
doc.BlockRecords.Add(blok);
|
||||||
|
|
||||||
|
var ins = new Insert(blok);
|
||||||
|
uprav(ins);
|
||||||
|
doc.Entities.Add(ins);
|
||||||
|
return doc;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VsechnaDodavanaDataSeVykresliBezeZbytku()
|
||||||
|
{
|
||||||
|
// Šablona i všech 9 log musí projít beze zbytku – jinak se v nich něco nekreslí.
|
||||||
|
var sablona = new DxfTemplate(TestData.Sablona);
|
||||||
|
sablona.Replay(new RecordingRenderer(), values: null, includeNoPrint: true);
|
||||||
|
Assert.Empty(sablona.NezpracovaneEntity);
|
||||||
|
|
||||||
|
foreach (var logo in TestData.VsechnaLoga)
|
||||||
|
{
|
||||||
|
var tpl = new DxfTemplate(TestData.Logo(logo));
|
||||||
|
tpl.Replay(new RecordingRenderer());
|
||||||
|
Assert.True(tpl.NezpracovaneEntity.Count == 0,
|
||||||
|
$"{logo}: nezpracované entity {string.Join(", ", tpl.NezpracovaneEntity.Select(p => $"{p.Key}×{p.Value}"))}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void VlozenyBlokSeRozvineNaSpravnemMiste()
|
||||||
|
{
|
||||||
|
var (g, nezpracovane) = Prehraj(SBlokem(ins => ins.InsertPoint = new XYZ(100, 50, 0)));
|
||||||
|
|
||||||
|
Assert.Empty(nezpracovane);
|
||||||
|
Assert.Equal(1, g.LineCount);
|
||||||
|
Assert.Equal((100, 50), g.Lines[0].Od);
|
||||||
|
Assert.Equal((110, 50), g.Lines[0].Do);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MeritkoANatoceniBlokuSeProjevi()
|
||||||
|
{
|
||||||
|
var (g, _) = Prehraj(SBlokem(ins =>
|
||||||
|
{
|
||||||
|
ins.InsertPoint = new XYZ(0, 0, 0);
|
||||||
|
ins.XScale = 2;
|
||||||
|
ins.YScale = 2;
|
||||||
|
ins.Rotation = Math.PI / 2; // 90° → úsečka z osy X na osu Y
|
||||||
|
}));
|
||||||
|
|
||||||
|
Assert.Equal(1, g.LineCount);
|
||||||
|
var (od, doB) = g.Lines[0];
|
||||||
|
Assert.Equal(0, od.X, 6);
|
||||||
|
Assert.Equal(0, od.Y, 6);
|
||||||
|
Assert.Equal(0, doB.X, 6);
|
||||||
|
Assert.Equal(20, doB.Y, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void BarvaByBlockSeVezmeZVkladajiciEntity()
|
||||||
|
{
|
||||||
|
// Tohle je smysl ByBlock: stejný blok vložený dvakrát může mít pokaždé jinou barvu.
|
||||||
|
var (g, _) = Prehraj(SBlokem(
|
||||||
|
ins => { ins.InsertPoint = new XYZ(0, 0, 0); ins.Color = new AcColor(5); },
|
||||||
|
barvaCary: AcColor.ByBlock));
|
||||||
|
|
||||||
|
// Stavů jsou dva: jeden za samotný INSERT, druhý za úsečku uvnitř bloku – zajímá nás ten druhý.
|
||||||
|
var stav = g.PosledniStav;
|
||||||
|
Assert.Equal(BarvaDruh.Index, stav.Barva.Druh);
|
||||||
|
Assert.Equal(5, stav.Barva.Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void ElipsaSeVykresliJakoLomenaCara()
|
||||||
|
{
|
||||||
|
var doc = new CadDocument();
|
||||||
|
doc.Entities.Add(new Ellipse
|
||||||
|
{
|
||||||
|
Center = new XYZ(0, 0, 0),
|
||||||
|
MajorAxisEndPoint = new XYZ(10, 0, 0),
|
||||||
|
RadiusRatio = 0.5,
|
||||||
|
StartParameter = 0,
|
||||||
|
EndParameter = 2 * Math.PI,
|
||||||
|
});
|
||||||
|
|
||||||
|
var (g, nezpracovane) = Prehraj(doc);
|
||||||
|
|
||||||
|
Assert.Empty(nezpracovane);
|
||||||
|
var body = Assert.Single(g.Polylines);
|
||||||
|
Assert.True(body.Count >= 8);
|
||||||
|
// Body musí ležet na elipse s poloosami 10 a 5.
|
||||||
|
foreach (var (x, y) in body)
|
||||||
|
Assert.Equal(1.0, x * x / 100 + y * y / 25, 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void SolidSeVyplniVeSpravnemPoradiRohu()
|
||||||
|
{
|
||||||
|
// V DXF jsou 3. a 4. roh proti obvodu prohozené – při špatném pořadí vyjde přesýpací hodiny.
|
||||||
|
var doc = new CadDocument();
|
||||||
|
doc.Entities.Add(new Solid
|
||||||
|
{
|
||||||
|
FirstCorner = new XYZ(0, 0, 0),
|
||||||
|
SecondCorner = new XYZ(10, 0, 0),
|
||||||
|
ThirdCorner = new XYZ(0, 10, 0),
|
||||||
|
FourthCorner = new XYZ(10, 10, 0),
|
||||||
|
});
|
||||||
|
|
||||||
|
var (g, nezpracovane) = Prehraj(doc);
|
||||||
|
|
||||||
|
Assert.Empty(nezpracovane);
|
||||||
|
var smycky = Assert.Single(g.Fills);
|
||||||
|
var body = Assert.Single(smycky);
|
||||||
|
Assert.Equal(new[] { (0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0) },
|
||||||
|
body.Take(4).Select(p => (Math.Round(p.X, 6), Math.Round(p.Y, 6))));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void NeznamaEntitaSeSpocitaMistoTichehoZahozeni()
|
||||||
|
{
|
||||||
|
var doc = new CadDocument();
|
||||||
|
doc.Entities.Add(new Ray { StartPoint = new XYZ(0, 0, 0), Direction = new XYZ(1, 0, 0) });
|
||||||
|
|
||||||
|
var (g, nezpracovane) = Prehraj(doc);
|
||||||
|
|
||||||
|
Assert.Empty(g.Polylines);
|
||||||
|
Assert.Equal(0, g.LineCount);
|
||||||
|
Assert.Equal(1, nezpracovane.Values.Sum());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PocitadloSeMeziPruchodyNehromadi()
|
||||||
|
{
|
||||||
|
var doc = new CadDocument();
|
||||||
|
doc.Entities.Add(new Ray { StartPoint = new XYZ(0, 0, 0), Direction = new XYZ(1, 0, 0) });
|
||||||
|
string cesta = System.IO.Path.Combine(System.IO.Path.GetTempPath(),
|
||||||
|
"rozpisky-entity-" + Guid.NewGuid().ToString("N") + ".dxf");
|
||||||
|
try
|
||||||
|
{
|
||||||
|
ACadSharp.IO.DxfWriter.Write(cesta, doc, binary: true);
|
||||||
|
var tpl = new DxfTemplate(cesta);
|
||||||
|
tpl.Replay(new RecordingRenderer());
|
||||||
|
tpl.Replay(new RecordingRenderer());
|
||||||
|
Assert.Equal(1, tpl.NezpracovaneEntity.Values.Sum());
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
try { System.IO.File.Delete(cesta); } catch (System.IO.IOException) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -33,7 +33,14 @@ public sealed class RecordingRenderer : IProfileRenderer
|
|||||||
|
|
||||||
public void Stav(in KresliciStav s) => Stavy.Add(s);
|
public void Stav(in KresliciStav s) => Stavy.Add(s);
|
||||||
|
|
||||||
public void Line(double x1, double y1, double x2, double y2) => LineCount++;
|
/// <summary>Koncové body zaznamenaných úseček – testy tvrdí i o poloze, nejen o počtu.</summary>
|
||||||
|
public List<((double X, double Y) Od, (double X, double Y) Do)> Lines { get; } = new();
|
||||||
|
|
||||||
|
public void Line(double x1, double y1, double x2, double y2)
|
||||||
|
{
|
||||||
|
LineCount++;
|
||||||
|
Lines.Add(((x1, y1), (x2, y2)));
|
||||||
|
}
|
||||||
|
|
||||||
public void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false) => Polylines.Add(pts);
|
public void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false) => Polylines.Add(pts);
|
||||||
|
|
||||||
|
|||||||
+167
-13
@@ -86,12 +86,34 @@ public sealed partial class DxfTemplate
|
|||||||
/// (např. A4 rámeček) – pro DXF export jako referenci hranice papíru.</summary>
|
/// (např. A4 rámeček) – pro DXF export jako referenci hranice papíru.</summary>
|
||||||
public void Replay(IProfileRenderer g, IReadOnlyDictionary<string, string>? values = null, bool includeNoPrint = false)
|
public void Replay(IProfileRenderer g, IReadOnlyDictionary<string, string>? values = null, bool includeNoPrint = false)
|
||||||
{
|
{
|
||||||
// Jeden průchod v pořadí kresby (viz EntitiesInDrawOrder) – zachovává překrývání entit ze
|
_nezpracovane.Clear();
|
||||||
// zdroje (výplně vzadu, popisky vepředu apod.). Dispatch podle typu; odvozené typy (Arc:Circle,
|
Prehraj(g, EntitiesInDrawOrder(), values, includeNoPrint, vlozeny: null, hloubka: 0);
|
||||||
// AttributeDefinition:AttributeBase:TextEntity) musí být ve switchi PŘED svými předky.
|
}
|
||||||
foreach (var e in EntitiesInDrawOrder())
|
|
||||||
|
/// <summary>Počty entit, které kreslítko neumí, podle typu (např. <c>DIMENSION</c> → 3).
|
||||||
|
/// Naplní se při každém <see cref="Replay"/>. Prázdné = vykreslilo se všechno. Slouží
|
||||||
|
/// k upozornění uživatele, když si přidá logo s prvky, které program nezná – dřív takové
|
||||||
|
/// prvky mizely tiše.</summary>
|
||||||
|
public IReadOnlyDictionary<string, int> NezpracovaneEntity => _nezpracovane;
|
||||||
|
|
||||||
|
private readonly Dictionary<string, int> _nezpracovane = new(StringComparer.Ordinal);
|
||||||
|
|
||||||
|
/// <summary>Nejhlubší zanoření bloků, do kterého se ještě jde. Chrání před blokem, který
|
||||||
|
/// (chybou ve zdroji) vkládá sám sebe.</summary>
|
||||||
|
private const int MaxHloubkaBloku = 8;
|
||||||
|
|
||||||
|
/// <summary>Jeden průchod v pořadí kresby (viz <see cref="EntitiesInDrawOrder"/>) – zachovává
|
||||||
|
/// překrývání entit ze zdroje (výplně vzadu, popisky vepředu). Dispatch podle typu; odvozené
|
||||||
|
/// typy (Arc:Circle, AttributeDefinition:AttributeBase:TextEntity) musí být ve switchi PŘED
|
||||||
|
/// svými předky. <paramref name="vlozeny"/> = stav vkládajícího <c>INSERT</c>u při rozvíjení
|
||||||
|
/// bloku (řeší barvy ByBlock a hladinu „0"), null v model space.</summary>
|
||||||
|
private void Prehraj(IProfileRenderer g, IEnumerable<Entity> entity,
|
||||||
|
IReadOnlyDictionary<string, string>? values, bool includeNoPrint,
|
||||||
|
KresliciStav? vlozeny, int hloubka)
|
||||||
|
{
|
||||||
|
foreach (var e in entity)
|
||||||
{
|
{
|
||||||
if (!SetLayer(g, e, includeNoPrint)) continue;
|
if (!SetLayer(g, e, includeNoPrint, vlozeny)) continue;
|
||||||
switch (e)
|
switch (e)
|
||||||
{
|
{
|
||||||
case Line l:
|
case Line l:
|
||||||
@@ -110,7 +132,7 @@ public sealed partial class DxfTemplate
|
|||||||
// Spliny (NURBS) – tesselace na lomenou čáru přes ACadSharp.
|
// Spliny (NURBS) – tesselace na lomenou čáru přes ACadSharp.
|
||||||
case Spline sp:
|
case Spline sp:
|
||||||
{
|
{
|
||||||
var pts = sp.PolygonalVertexes(64).Select(p => (p.X, p.Y)).ToList();
|
var pts = sp.PolygonalVertexes(DilkuSplinu(sp)).Select(p => (p.X, p.Y)).ToList();
|
||||||
if (pts.Count >= 2) g.Polyline(pts, sp.IsClosed);
|
if (pts.Count >= 2) g.Polyline(pts, sp.IsClosed);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -174,10 +196,107 @@ public sealed partial class DxfTemplate
|
|||||||
StyleOf(mt.Style), wf, mt.RectangleWidth);
|
StyleOf(mt.Style), wf, mt.RectangleWidth);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Vložený blok – rozvine se rekurzivně. Bez toho by se uživatelovo logo s blokem
|
||||||
|
// nevykreslilo vůbec a bez hlášky.
|
||||||
|
case Insert ins:
|
||||||
|
RozvinBlok(g, ins, values, includeNoPrint, hloubka);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Elipsa: rozhraní zná jen kruhové oblouky, takže se navzorkuje na lomenou čáru.
|
||||||
|
case Ellipse el:
|
||||||
|
{
|
||||||
|
var body = VzorkujEllipse(el);
|
||||||
|
if (body.Count >= 2) g.Polyline(body, el.IsFullEllipse);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bod – kreslí se jako tečka (úsečka nulové délky, kulaté zakončení čáry).
|
||||||
|
case Point p:
|
||||||
|
g.Line(p.Location.X, p.Location.Y, p.Location.X, p.Location.Y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Vyplněný čtyřúhelník. Pozor: v DXF jsou 3. a 4. roh prohozené proti obvodu.
|
||||||
|
case Solid s:
|
||||||
|
KresliCtyruhelnik(g, s.FirstCorner.X, s.FirstCorner.Y, s.SecondCorner.X, s.SecondCorner.Y,
|
||||||
|
s.ThirdCorner.X, s.ThirdCorner.Y, s.FourthCorner.X, s.FourthCorner.Y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case Face3D f:
|
||||||
|
KresliCtyruhelnik(g, f.FirstCorner.X, f.FirstCorner.Y, f.SecondCorner.X, f.SecondCorner.Y,
|
||||||
|
f.ThirdCorner.X, f.ThirdCorner.Y, f.FourthCorner.X, f.FourthCorner.Y);
|
||||||
|
break;
|
||||||
|
|
||||||
|
// Prostorová polylinie – promítne se do roviny XY (kresba je 2D).
|
||||||
|
case Polyline3D pl3:
|
||||||
|
{
|
||||||
|
var body = pl3.Vertices.Select(v => (v.Location.X, v.Location.Y)).ToList();
|
||||||
|
if (body.Count >= 2) g.Polyline(body, pl3.IsClosed);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
Nezpracovano(e.ObjectName);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Rozvine <c>INSERT</c>: entity bloku se přehrají přes transformaci (posun na
|
||||||
|
/// vkládací bod, měřítko os, natočení). Barvy <c>ByBlock</c> a hladina „0" uvnitř bloku
|
||||||
|
/// přebírají stav vkládající entity – tak to dělá i AutoCAD.</summary>
|
||||||
|
private void RozvinBlok(IProfileRenderer g, Insert ins,
|
||||||
|
IReadOnlyDictionary<string, string>? values, bool includeNoPrint, int hloubka)
|
||||||
|
{
|
||||||
|
if (hloubka >= MaxHloubkaBloku) { Nezpracovano("INSERT (příliš zanořený)"); return; }
|
||||||
|
|
||||||
|
var blok = ins.Block;
|
||||||
|
if (blok is null) { Nezpracovano("INSERT (chybí definice bloku)"); return; }
|
||||||
|
|
||||||
|
var zaklad = blok.BlockEntity?.BasePoint ?? default;
|
||||||
|
var transformace = TransformRenderer.ProBlok(
|
||||||
|
g, ins.InsertPoint.X, ins.InsertPoint.Y, zaklad.X, zaklad.Y,
|
||||||
|
ins.XScale, ins.YScale, ins.Rotation);
|
||||||
|
|
||||||
|
var stavVkladajici = new KresliciStav(HladinaZ(ins.Layer), BarvaZ(ins.Color),
|
||||||
|
ins.LineType?.Name, TloustkaZ(ins.LineWeight));
|
||||||
|
Prehraj(transformace, blok.Entities, values, includeNoPrint, stavVkladajici, hloubka + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Vyplněný čtyřúhelník ze čtyř rohů v pořadí DXF (3. a 4. roh prohozený).
|
||||||
|
/// Trojúhelník se pozná podle shodného 3. a 4. rohu.</summary>
|
||||||
|
private static void KresliCtyruhelnik(IProfileRenderer g, double x1, double y1, double x2, double y2,
|
||||||
|
double x3, double y3, double x4, double y4)
|
||||||
|
{
|
||||||
|
var hrany = new List<Edge> { Edge.Line(x1, y1, x2, y2), Edge.Line(x2, y2, x4, y4) };
|
||||||
|
if (Math.Abs(x3 - x4) > 1e-9 || Math.Abs(y3 - y4) > 1e-9)
|
||||||
|
hrany.Add(Edge.Line(x4, y4, x3, y3));
|
||||||
|
hrany.Add(Edge.Line(hrany[^1].EX, hrany[^1].EY, x1, y1));
|
||||||
|
g.Fill(new[] { new Contour(hrany, Closed: true) });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Na kolik dílků rozložit spline. Pevných 64 bylo málo pro dlouhé křivky (viditelné
|
||||||
|
/// hrany) a zbytečně moc pro krátké; počítá se z délky řídicího polygonu tak, aby jeden dílek
|
||||||
|
/// vyšel zhruba na 0,3 mm výkresu.</summary>
|
||||||
|
private static int DilkuSplinu(Spline sp)
|
||||||
|
{
|
||||||
|
const double DilekMm = 0.3;
|
||||||
|
double delka = 0;
|
||||||
|
var body = sp.ControlPoints;
|
||||||
|
for (int i = 1; i < body.Count; i++)
|
||||||
|
{
|
||||||
|
double dx = body[i].X - body[i - 1].X, dy = body[i].Y - body[i - 1].Y;
|
||||||
|
delka += Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
return Math.Clamp((int)Math.Ceiling(delka / DilekMm), 16, 512);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void Nezpracovano(string typ)
|
||||||
|
=> _nezpracovane[typ] = _nezpracovane.GetValueOrDefault(typ) + 1;
|
||||||
|
|
||||||
|
/// <summary>Tloušťka čáry v mm z DXF hodnoty (setiny mm); záporná = podle hladiny → null.</summary>
|
||||||
|
private static double? TloustkaZ(LineWeightType lw) => (int)lw > 0 ? (int)lw / 100.0 : null;
|
||||||
|
|
||||||
/// <summary>Entity model-space v pořadí kresby: pokud má blok tabulku pořadí (<c>SortEntitiesTable</c>,
|
/// <summary>Entity model-space v pořadí kresby: pokud má blok tabulku pořadí (<c>SortEntitiesTable</c>,
|
||||||
/// tj. „draw order" nastavené v CADu), seřadí podle ní; jinak zachová pořadí ze souboru. Nižší sort
|
/// tj. „draw order" nastavené v CADu), seřadí podle ní; jinak zachová pořadí ze souboru. Nižší sort
|
||||||
/// handle = kreslí se dřív (vzadu). LINQ <c>OrderBy</c> je stabilní → entity bez záznamu drží pořadí.</summary>
|
/// handle = kreslí se dřív (vzadu). LINQ <c>OrderBy</c> je stabilní → entity bez záznamu drží pořadí.</summary>
|
||||||
@@ -208,18 +327,30 @@ public sealed partial class DxfTemplate
|
|||||||
/// <summary>Nastaví hladinu do kreslítka. Vrací false, pokud se hladina nemá tisknout (přeskočit kresbu).
|
/// <summary>Nastaví hladinu do kreslítka. Vrací false, pokud se hladina nemá tisknout (přeskočit kresbu).
|
||||||
/// <paramref name="includeNoPrint"/> = true → geometrie na <see cref="HladinaNoPrint"/> se nepřeskočí
|
/// <paramref name="includeNoPrint"/> = true → geometrie na <see cref="HladinaNoPrint"/> se nepřeskočí
|
||||||
/// (kreslí se i přes vypnutý plot flag – DXF reference A4 rámečku).</summary>
|
/// (kreslí se i přes vypnutý plot flag – DXF reference A4 rámečku).</summary>
|
||||||
private static bool SetLayer(IProfileRenderer g, Entity e, bool includeNoPrint = false)
|
private static bool SetLayer(IProfileRenderer g, Entity e, bool includeNoPrint = false,
|
||||||
|
KresliciStav? vlozeny = null)
|
||||||
{
|
{
|
||||||
var layer = e.Layer;
|
var layer = e.Layer;
|
||||||
bool noPrint = JeNoPrint(e);
|
bool noPrint = JeNoPrint(e);
|
||||||
if (noPrint && !includeNoPrint) return false; // vodicí rámeček vkládaného DXF se nekreslí
|
if (noPrint && !includeNoPrint) return false; // vodicí rámeček vkládaného DXF se nekreslí
|
||||||
if (!noPrint && layer is not null && (!layer.PlotFlag || !layer.IsOn)) return false;
|
if (!noPrint && layer is not null && (!layer.PlotFlag || !layer.IsOn)) return false;
|
||||||
int lw = (int)e.LineWeight; // LineWeightType: Wnn = nn/100 mm, záporné = ByLayer/Default/ByBlock
|
|
||||||
g.Stav(new KresliciStav(
|
var hladina = HladinaZ(layer);
|
||||||
HladinaZ(layer),
|
var barva = BarvaZ(e.Color);
|
||||||
BarvaZ(e.Color),
|
var tloustka = TloustkaZ(e.LineWeight);
|
||||||
e.LineType?.Name,
|
var typCary = e.LineType?.Name;
|
||||||
lw > 0 ? lw / 100.0 : null)); // null = tloušťka podle hladiny
|
|
||||||
|
// Uvnitř bloku: hladina „0" a barva/typ čáry ByBlock se berou od vkládající entity –
|
||||||
|
// právě proto se stejný blok dá vložit dvakrát a pokaždé v jiné barvě.
|
||||||
|
if (vlozeny is { } v)
|
||||||
|
{
|
||||||
|
if (hladina.Nazev is null or "0") hladina = v.Hladina;
|
||||||
|
if (barva.Druh == BarvaDruh.ByBlock) barva = v.Barva;
|
||||||
|
tloustka ??= v.TloustkaMm;
|
||||||
|
if (typCary is null or "ByBlock") typCary = v.TypCary;
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Stav(new KresliciStav(hladina, barva, typCary, tloustka));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,6 +752,29 @@ public sealed partial class DxfTemplate
|
|||||||
return pts;
|
return pts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Navzorkuje entitu ELLIPSE na body. Kreslicí rozhraní zná jen kruhové oblouky,
|
||||||
|
/// takže elipsa jde do lomené čáry – dělení podle hlavní poloosy, aby byla dost hladká.
|
||||||
|
/// Parametry (nikoli úhly) jdou vždy proti směru hodin od <c>StartParameter</c>.</summary>
|
||||||
|
private static List<(double X, double Y)> VzorkujEllipse(Ellipse el)
|
||||||
|
{
|
||||||
|
double sa = el.StartParameter, ea = el.EndParameter;
|
||||||
|
if (ea <= sa) ea += 2 * Math.PI;
|
||||||
|
double sweep = ea - sa;
|
||||||
|
|
||||||
|
double mx = el.MajorAxisEndPoint.X, my = el.MajorAxisEndPoint.Y;
|
||||||
|
double vx = -my * el.RadiusRatio, vy = mx * el.RadiusRatio; // vedlejší poloosa = kolmice·ratio
|
||||||
|
double major = Math.Sqrt(mx * mx + my * my);
|
||||||
|
|
||||||
|
int seg = Math.Max(8, ContourGeometry.ArcSegments(sweep, Math.Max(major, 1e-9), 0.02));
|
||||||
|
var pts = new List<(double X, double Y)>(seg + 1);
|
||||||
|
for (int i = 0; i <= seg; i++)
|
||||||
|
{
|
||||||
|
double t = sa + sweep * i / seg, c = Math.Cos(t), s = Math.Sin(t);
|
||||||
|
pts.Add((el.Center.X + c * mx + s * vx, el.Center.Y + c * my + s * vy));
|
||||||
|
}
|
||||||
|
return pts;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Navzorkuje eliptický oblouk hatch hranice na body v OCS (kolmice·ratio, CW přes osu X).</summary>
|
/// <summary>Navzorkuje eliptický oblouk hatch hranice na body v OCS (kolmice·ratio, CW přes osu X).</summary>
|
||||||
private static List<(double X, double Y)> VzorkujEllipse(Hatch.BoundaryPath.Ellipse el)
|
private static List<(double X, double Y)> VzorkujEllipse(Hatch.BoundaryPath.Ellipse el)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -81,6 +81,28 @@ public sealed class RozpiskaRenderer
|
|||||||
catch { return null; } // poškozený/nečitelný DXF fragment → bez loga
|
catch { return null; } // poškozený/nečitelný DXF fragment → bez loga
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// <summary>Prvky vložených DXF (log, schémat), které kreslítko neumí – po jednom řádku na
|
||||||
|
/// soubor, např. <c>„nase_logo.dxf: DIMENSION ×3, LEADER ×1"</c>. Prázdné = vykreslí se všechno.
|
||||||
|
/// <para>Uživatel si přidává vlastní loga; bez tohohle by v nich chybějící prvky prostě zmizely
|
||||||
|
/// a nikdo by se to nedozvěděl. Nekreslí se nic – kresba jde do <see cref="BoundsRenderer"/>,
|
||||||
|
/// jen aby se sesbírala statistika.</para></summary>
|
||||||
|
public static IReadOnlyList<string> NepodporovanePrvky(IEnumerable<string> cestyKFragmentum)
|
||||||
|
{
|
||||||
|
var hlaseni = new List<string>();
|
||||||
|
foreach (var cesta in cestyKFragmentum.Where(c => !string.IsNullOrWhiteSpace(c)).Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
var frag = NactiFragment(cesta);
|
||||||
|
if (frag is null) continue;
|
||||||
|
|
||||||
|
frag.Replay(new BoundsRenderer());
|
||||||
|
if (frag.NezpracovaneEntity.Count == 0) continue;
|
||||||
|
|
||||||
|
var vypis = string.Join(", ", frag.NezpracovaneEntity.OrderBy(p => p.Key).Select(p => $"{p.Key} ×{p.Value}"));
|
||||||
|
hlaseni.Add($"{Path.GetFileName(cesta)}: {vypis}");
|
||||||
|
}
|
||||||
|
return hlaseni;
|
||||||
|
}
|
||||||
|
|
||||||
public void ExportPdf(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c, string path,
|
public void ExportPdf(Projekt p, Priloha r, IEnumerable<Revize> revize, Ciselniky c, string path,
|
||||||
IReadOnlyDictionary<string, string>? loga = null)
|
IReadOnlyDictionary<string, string>? loga = null)
|
||||||
=> Exporters.PdfA4(BuildDraw(p, r, revize, c, vyplnene: true, loga), path, frame: _tpl.SheetFrame);
|
=> Exporters.PdfA4(BuildDraw(p, r, revize, c, vyplnene: true, loga), path, frame: _tpl.SheetFrame);
|
||||||
|
|||||||
@@ -37,11 +37,17 @@ public sealed class DxfProfileRenderer : IProfileRenderer
|
|||||||
}
|
}
|
||||||
_vrstvy[_aktualniVrstva.Name] = _aktualniVrstva;
|
_vrstvy[_aktualniVrstva.Name] = _aktualniVrstva;
|
||||||
_aktualniBarva = Color.ByLayer;
|
_aktualniBarva = Color.ByLayer;
|
||||||
|
_doc.Header.Version = VerzeVystupu;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Hotový výkres se všemi rozpiskami (pro <c>DxfWriter.Write</c>).</summary>
|
/// <summary>Hotový výkres se všemi rozpiskami (pro <c>DxfWriter.Write</c>).</summary>
|
||||||
public CadDocument Document => _doc;
|
public CadDocument Document => _doc;
|
||||||
|
|
||||||
|
/// <summary>Verze DXF, ve které se výkres zapisuje – shodná se zdrojovou šablonou
|
||||||
|
/// (AutoCAD 2013). Nastavuje se natvrdo: kdyby se nechala na výchozí hodnotě ACadSharp,
|
||||||
|
/// změnil by ji tiše upgrade balíčku a s ní i to, čím půjde výkres otevřít.</summary>
|
||||||
|
public const ACadVersion VerzeVystupu = ACadVersion.AC1027;
|
||||||
|
|
||||||
public void Stav(in KresliciStav s)
|
public void Stav(in KresliciStav s)
|
||||||
{
|
{
|
||||||
_aktualniVrstva = NajdiVrstvu(s.Hladina);
|
_aktualniVrstva = NajdiVrstvu(s.Hladina);
|
||||||
|
|||||||
@@ -1,63 +1,153 @@
|
|||||||
namespace Rozpisky.Rendering;
|
namespace Rozpisky.Rendering;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Dekorátor <see cref="IProfileRenderer"/>, který na všechny souřadnice aplikuje
|
/// Dekorátor <see cref="IProfileRenderer"/>, který na souřadnice aplikuje afinní transformaci
|
||||||
/// uniformní měřítko + posun: <c>X' = Ox + S·X</c>, <c>Y' = Oy + S·Y</c>. Slouží k umístění
|
/// <c>X' = A·x + C·y + Tx</c>, <c>Y' = B·x + D·y + Ty</c>.
|
||||||
/// externího DXF fragmentu (logo / orientační schéma) do vymezeného rámečku rozpisky –
|
/// <para>Dvě použití: umístění externího DXF fragmentu (logo / orientační schéma) do rámečku
|
||||||
/// fragment se nakreslí ve svých souřadnicích a tato vrstva ho přemapuje do cílového obdélníku.
|
/// rozpisky – tam stačí měřítko a posun (<see cref="TransformRenderer(IProfileRenderer,double,double,double)"/>)
|
||||||
/// Výška textu, poloměr a tloušťka čar se škálují <c>·S</c>; hladina/barva propadá beze změny.
|
/// – a rozvinutí vloženého bloku (<c>INSERT</c>), kde přibude natočení a nestejné měřítko os
|
||||||
|
/// (<see cref="ProBlok"/>).</para>
|
||||||
|
/// <para>Kruhový oblouk zůstane obloukem jen tehdy, je-li transformace <b>podobnost</b> (stejné
|
||||||
|
/// měřítko obou os a kolmé osy). Při nestejném měřítku by z něj byla elipsa, kterou kreslicí
|
||||||
|
/// rozhraní nezná – tam se oblouk rozloží na lomenou čáru.</para>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class TransformRenderer : IProfileRenderer
|
public sealed class TransformRenderer : IProfileRenderer
|
||||||
{
|
{
|
||||||
private readonly IProfileRenderer _inner;
|
private readonly IProfileRenderer _inner;
|
||||||
private readonly double _s, _ox, _oy;
|
private readonly double _a, _b, _c, _d, _tx, _ty;
|
||||||
|
|
||||||
|
/// <summary>Měřítko délek (u podobnosti přesné, jinak odmocnina z determinantu).</summary>
|
||||||
|
private readonly double _meritko;
|
||||||
|
|
||||||
|
/// <summary>Natočení transformace ve stupních – přičítá se k natočení textů.</summary>
|
||||||
|
private readonly double _uhelDeg;
|
||||||
|
|
||||||
|
/// <summary>true = oblouky a kružnice zůstanou oblouky a kružnicemi. Platí pro podobnost bez
|
||||||
|
/// zrcadlení; při nestejném měřítku os by z kružnice byla elipsa a při zrcadlení by se obrátil
|
||||||
|
/// smysl oblouku – v obou případech je bezpečnější rozložit tvar na lomenou čáru.</summary>
|
||||||
|
private readonly bool _zachovavaOblouky;
|
||||||
|
|
||||||
|
/// <summary>Uniformní měřítko + posun, bez natočení (umístění fragmentu do rámečku).</summary>
|
||||||
public TransformRenderer(IProfileRenderer inner, double scale, double offsetX, double offsetY)
|
public TransformRenderer(IProfileRenderer inner, double scale, double offsetX, double offsetY)
|
||||||
|
: this(inner, scale, 0, 0, scale, offsetX, offsetY) { }
|
||||||
|
|
||||||
|
private TransformRenderer(IProfileRenderer inner, double a, double b, double c, double d, double tx, double ty)
|
||||||
{
|
{
|
||||||
_inner = inner;
|
_inner = inner;
|
||||||
_s = scale;
|
_a = a; _b = b; _c = c; _d = d; _tx = tx; _ty = ty;
|
||||||
_ox = offsetX;
|
|
||||||
_oy = offsetY;
|
double delkaX = Math.Sqrt(a * a + b * b), delkaY = Math.Sqrt(c * c + d * d);
|
||||||
|
double skalar = a * c + b * d; // 0 = osy jsou kolmé
|
||||||
|
bool podobnost = Math.Abs(delkaX - delkaY) <= 1e-9 * Math.Max(delkaX, 1)
|
||||||
|
&& Math.Abs(skalar) <= 1e-9 * Math.Max(delkaX * delkaY, 1);
|
||||||
|
|
||||||
|
double det = a * d - b * c;
|
||||||
|
_zachovavaOblouky = podobnost && det > 0;
|
||||||
|
_meritko = podobnost ? delkaX : Math.Sqrt(Math.Abs(det));
|
||||||
|
_uhelDeg = Math.Atan2(b, a) * 180 / Math.PI;
|
||||||
}
|
}
|
||||||
|
|
||||||
private double X(double x) => _ox + _s * x;
|
/// <summary>Transformace vloženého bloku: entity bloku jsou zapsané vůči jeho vztažnému bodu,
|
||||||
private double Y(double y) => _oy + _s * y;
|
/// ten se klade na vkládací bod. Tedy <c>p' = vkládací + R·S·(p − vztažný)</c>. Nulové měřítko
|
||||||
|
/// (setkáno v poškozených souborech) se bere jako 1, jinak by blok zkolaboval do bodu.</summary>
|
||||||
|
public static TransformRenderer ProBlok(
|
||||||
|
IProfileRenderer inner, double vlozX, double vlozY, double zakladX, double zakladY,
|
||||||
|
double meritkoX, double meritkoY, double natoceniRad)
|
||||||
|
{
|
||||||
|
if (meritkoX == 0) meritkoX = 1;
|
||||||
|
if (meritkoY == 0) meritkoY = 1;
|
||||||
|
double cos = Math.Cos(natoceniRad), sin = Math.Sin(natoceniRad);
|
||||||
|
double a = meritkoX * cos, b = meritkoX * sin, c = -meritkoY * sin, d = meritkoY * cos;
|
||||||
|
return new TransformRenderer(
|
||||||
|
inner, a, b, c, d,
|
||||||
|
vlozX - (a * zakladX + c * zakladY),
|
||||||
|
vlozY - (b * zakladX + d * zakladY));
|
||||||
|
}
|
||||||
|
|
||||||
|
private double X(double x, double y) => _tx + _a * x + _c * y;
|
||||||
|
private double Y(double x, double y) => _ty + _b * x + _d * y;
|
||||||
|
|
||||||
/// <summary>Stav propadá beze změny až na tloušťku čáry – ta je v mm, takže se škáluje
|
|
||||||
/// stejně jako geometrie (na entitě i na hladině).</summary>
|
|
||||||
public void Stav(in KresliciStav s)
|
public void Stav(in KresliciStav s)
|
||||||
=> _inner.Stav(s with
|
=> _inner.Stav(s with
|
||||||
{
|
{
|
||||||
TloustkaMm = s.TloustkaMm * _s,
|
TloustkaMm = s.TloustkaMm * _meritko,
|
||||||
Hladina = s.Hladina with { TloustkaMm = s.Hladina.TloustkaMm * _s },
|
Hladina = s.Hladina with { TloustkaMm = s.Hladina.TloustkaMm * _meritko },
|
||||||
});
|
});
|
||||||
|
|
||||||
public void Line(double x1, double y1, double x2, double y2)
|
public void Line(double x1, double y1, double x2, double y2)
|
||||||
=> _inner.Line(X(x1), Y(y1), X(x2), Y(y2));
|
=> _inner.Line(X(x1, y1), Y(x1, y1), X(x2, y2), Y(x2, y2));
|
||||||
|
|
||||||
public void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false)
|
public void Polyline(IReadOnlyList<(double X, double Y)> pts, bool closed = false)
|
||||||
=> _inner.Polyline(pts.Select(p => (X(p.X), Y(p.Y))).ToList(), closed);
|
=> _inner.Polyline(pts.Select(p => (X(p.X, p.Y), Y(p.X, p.Y))).ToList(), closed);
|
||||||
|
|
||||||
public void Circle(double cx, double cy, double r)
|
public void Circle(double cx, double cy, double r)
|
||||||
=> _inner.Circle(X(cx), Y(cy), r * _s);
|
{
|
||||||
|
if (_zachovavaOblouky) { _inner.Circle(X(cx, cy), Y(cx, cy), r * _meritko); return; }
|
||||||
|
|
||||||
public void Stroke(Contour c) => _inner.Stroke(new Contour(c.Edges.Select(T).ToList(), c.Closed));
|
// Nestejné měřítko os → z kružnice je elipsa; kreslicí rozhraní ji nezná, tedy lomená čára.
|
||||||
|
int n = Math.Max(16, ContourGeometry.ArcSegments(2 * Math.PI, Math.Max(r * _meritko, 1e-9), 0.02));
|
||||||
|
var body = new List<(double X, double Y)>(n);
|
||||||
|
for (int i = 0; i < n; i++)
|
||||||
|
{
|
||||||
|
double t = 2 * Math.PI * i / n;
|
||||||
|
double x = cx + r * Math.Cos(t), y = cy + r * Math.Sin(t);
|
||||||
|
body.Add((X(x, y), Y(x, y)));
|
||||||
|
}
|
||||||
|
_inner.Polyline(body, closed: true);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Přemapuje hranu: body scale+offset, poloměr ·S, úhly beze změny (transformace je
|
public void Stroke(Contour c)
|
||||||
/// uniformní scale + posun, bez rotace/zrcadlení).</summary>
|
{
|
||||||
private Edge T(Edge e) => e.Kind == EdgeKind.Line
|
if (_zachovavaOblouky) { _inner.Stroke(new Contour(c.Edges.Select(T).ToList(), c.Closed)); return; }
|
||||||
? Edge.Line(X(e.SX), Y(e.SY), X(e.EX), Y(e.EY))
|
_inner.Polyline(Zplosti(c), c.Closed);
|
||||||
: Edge.Arc(X(e.SX), Y(e.SY), X(e.EX), Y(e.EY), X(e.Cx), Y(e.Cy), e.R * _s, e.A0, e.Sweep);
|
}
|
||||||
|
|
||||||
|
/// <summary>Přemapuje hranu (jen pro transformace zachovávající oblouky): body afinně,
|
||||||
|
/// poloměr ·měřítko, počáteční úhel o natočení, rozsah beze změny.</summary>
|
||||||
|
private Edge T(Edge e)
|
||||||
|
{
|
||||||
|
if (e.Kind == EdgeKind.Line)
|
||||||
|
return Edge.Line(X(e.SX, e.SY), Y(e.SX, e.SY), X(e.EX, e.EY), Y(e.EX, e.EY));
|
||||||
|
|
||||||
|
return Edge.Arc(
|
||||||
|
X(e.SX, e.SY), Y(e.SX, e.SY), X(e.EX, e.EY), Y(e.EX, e.EY),
|
||||||
|
X(e.Cx, e.Cy), Y(e.Cx, e.Cy), e.R * _meritko, e.A0 + _uhelDeg * Math.PI / 180, e.Sweep);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<(double X, double Y)> Zplosti(Contour c)
|
||||||
|
{
|
||||||
|
var body = new List<(double X, double Y)>();
|
||||||
|
foreach (var e in c.Edges) e.AppendPoints(body, 0.02);
|
||||||
|
if (c.Edges.Count > 0) body.Add((c.Edges[^1].EX, c.Edges[^1].EY));
|
||||||
|
return body.Select(p => (X(p.X, p.Y), Y(p.X, p.Y))).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
public void Text(double x, double y, string text, double heightMm,
|
public void Text(double x, double y, string text, double heightMm,
|
||||||
HAlign h = HAlign.Left, VAlign v = VAlign.Baseline, double rotationDeg = 0,
|
HAlign h = HAlign.Left, VAlign v = VAlign.Baseline, double rotationDeg = 0,
|
||||||
TextStyle style = default, double widthFactor = 1, double maxWidthMm = 0)
|
TextStyle style = default, double widthFactor = 1, double maxWidthMm = 0)
|
||||||
=> _inner.Text(X(x), Y(y), text, heightMm * _s, h, v, rotationDeg, style, widthFactor, maxWidthMm * _s);
|
=> _inner.Text(X(x, y), Y(x, y), text, heightMm * _meritko, h, v, rotationDeg + _uhelDeg,
|
||||||
|
style, widthFactor, maxWidthMm * _meritko);
|
||||||
|
|
||||||
public void Fill(IReadOnlyList<Contour> loops, Vypln vypln = default)
|
public void Fill(IReadOnlyList<Contour> loops, Vypln vypln = default)
|
||||||
=> _inner.Fill(
|
=> _inner.Fill(
|
||||||
loops.Select(c => new Contour(c.Edges.Select(T).ToList(), c.Closed)).ToList(),
|
loops.Select(c => new Contour(
|
||||||
|
_zachovavaOblouky ? c.Edges.Select(T).ToList() : Usecky(c), c.Closed)).ToList(),
|
||||||
vypln.Cary is null
|
vypln.Cary is null
|
||||||
? vypln
|
? vypln
|
||||||
: vypln with { Cary = vypln.Cary.Select(u => new Usecka(X(u.X1), Y(u.Y1), X(u.X2), Y(u.Y2))).ToList() });
|
: vypln with
|
||||||
|
{
|
||||||
|
Cary = vypln.Cary
|
||||||
|
.Select(u => new Usecka(X(u.X1, u.Y1), Y(u.X1, u.Y1), X(u.X2, u.Y2), Y(u.X2, u.Y2)))
|
||||||
|
.ToList(),
|
||||||
|
});
|
||||||
|
|
||||||
|
/// <summary>Obrys rozložený na úsečky (pro transformace, které oblouk nezachovají).</summary>
|
||||||
|
private List<Edge> Usecky(Contour c)
|
||||||
|
{
|
||||||
|
var body = Zplosti(c);
|
||||||
|
var hrany = new List<Edge>(Math.Max(body.Count - 1, 0));
|
||||||
|
for (int i = 0; i + 1 < body.Count; i++)
|
||||||
|
hrany.Add(Edge.Line(body[i].X, body[i].Y, body[i + 1].X, body[i + 1].Y));
|
||||||
|
return hrany;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -834,6 +834,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
_exportZrusen = false;
|
_exportZrusen = false;
|
||||||
int hotovo = 0, bezJmena = 0, n = prilohy.Count, index = 0;
|
int hotovo = 0, bezJmena = 0, n = prilohy.Count, index = 0;
|
||||||
var chyby = new List<string>();
|
var chyby = new List<string>();
|
||||||
|
var pouziteLoga = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
foreach (var p in prilohy)
|
foreach (var p in prilohy)
|
||||||
@@ -853,7 +854,9 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
renderer.ExportPdf(Projekt, p, Revize, Ciselniky, cesta, ResolveLoga(p));
|
var loga = ResolveLoga(p);
|
||||||
|
pouziteLoga.UnionWith(loga.Values);
|
||||||
|
renderer.ExportPdf(Projekt, p, Revize, Ciselniky, cesta, loga);
|
||||||
hotovo++;
|
hotovo++;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -868,7 +871,8 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
StavovaZprava = "Připraveno";
|
StavovaZprava = "Připraveno";
|
||||||
}
|
}
|
||||||
|
|
||||||
ZobrazSouhrnExportu(cilRozpisky, hotovo, bezJmena, chyby);
|
ZobrazSouhrnExportu(cilRozpisky, hotovo, bezJmena, chyby,
|
||||||
|
RozpiskaRenderer.NepodporovanePrvky(pouziteLoga));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Mezera mezi rozpiskami v dlaždicovém DXF (výkresové jednotky).</summary>
|
/// <summary>Mezera mezi rozpiskami v dlaždicovém DXF (výkresové jednotky).</summary>
|
||||||
@@ -912,6 +916,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
int cols = Math.Min(DxfSloupcu, Math.Max(1, n));
|
int cols = Math.Min(DxfSloupcu, Math.Max(1, n));
|
||||||
var dxf = new DxfProfileRenderer();
|
var dxf = new DxfProfileRenderer();
|
||||||
var chyby = new List<string>();
|
var chyby = new List<string>();
|
||||||
|
var pouziteLoga = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
for (int i = 0; i < n; i++)
|
for (int i = 0; i < n; i++)
|
||||||
@@ -925,8 +930,10 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
double oy = -row * (cell.H + DxfMezera); // další řádky pod sebe (osa Y nahoru)
|
double oy = -row * (cell.H + DxfMezera); // další řádky pod sebe (osa Y nahoru)
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
var loga = ResolveLoga(prilohy[i]);
|
||||||
|
pouziteLoga.UnionWith(loga.Values);
|
||||||
var draw = renderer.BuildDraw(Projekt, prilohy[i], Revize, Ciselniky, vyplnene: true,
|
var draw = renderer.BuildDraw(Projekt, prilohy[i], Revize, Ciselniky, vyplnene: true,
|
||||||
ResolveLoga(prilohy[i]), ramecekA4: true);
|
loga, ramecekA4: true);
|
||||||
// Generálka naprázdno: výjimka uprostřed kreslení by ve sdíleném dokumentu
|
// Generálka naprázdno: výjimka uprostřed kreslení by ve sdíleném dokumentu
|
||||||
// nechala torzo rozpisky (entity přidané před pádem nejde vzít zpět). Do
|
// nechala torzo rozpisky (entity přidané před pádem nejde vzít zpět). Do
|
||||||
// skutečného DXF se tak kreslí až kresba, která celá prošla.
|
// skutečného DXF se tak kreslí až kresba, která celá prošla.
|
||||||
@@ -950,6 +957,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
StavovaZprava = "Připraveno";
|
StavovaZprava = "Připraveno";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
// Verzi výstupu drží DxfProfileRenderer (natvrdo, ať ji neurčí default balíčku).
|
||||||
DxfWriter.Write(cesta, dxf.Document, binary: true); // binární DXF = výrazně menší soubor
|
DxfWriter.Write(cesta, dxf.Document, binary: true); // binární DXF = výrazně menší soubor
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -971,6 +979,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
sb.AppendLine($"Chyby: {chyby.Count}");
|
sb.AppendLine($"Chyby: {chyby.Count}");
|
||||||
foreach (var ch in chyby) sb.AppendLine($" • {ch}");
|
foreach (var ch in chyby) sb.AppendLine($" • {ch}");
|
||||||
}
|
}
|
||||||
|
PripojNepodporovane(sb, RozpiskaRenderer.NepodporovanePrvky(pouziteLoga));
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.Append($"Soubor: {cesta}");
|
sb.Append($"Soubor: {cesta}");
|
||||||
MessageBox.Show(sb.ToString(), "Export do DXF dokončen", MessageBoxButton.OK,
|
MessageBox.Show(sb.ToString(), "Export do DXF dokončen", MessageBoxButton.OK,
|
||||||
@@ -1200,7 +1209,8 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Souhrn dávkového exportu (počet hotových, přeskočených bez jména, chyby) + cílová složka.</summary>
|
/// <summary>Souhrn dávkového exportu (počet hotových, přeskočených bez jména, chyby) + cílová složka.</summary>
|
||||||
private static void ZobrazSouhrnExportu(string cilRozpisky, int hotovo, int bezJmena, IReadOnlyList<string> chyby)
|
private static void ZobrazSouhrnExportu(string cilRozpisky, int hotovo, int bezJmena,
|
||||||
|
IReadOnlyList<string> chyby, IReadOnlyList<string> nepodporovane)
|
||||||
{
|
{
|
||||||
var sb = new System.Text.StringBuilder();
|
var sb = new System.Text.StringBuilder();
|
||||||
sb.AppendLine($"Vyexportováno do PDF: {hotovo}");
|
sb.AppendLine($"Vyexportováno do PDF: {hotovo}");
|
||||||
@@ -1210,6 +1220,7 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
sb.AppendLine($"Chyby: {chyby.Count}");
|
sb.AppendLine($"Chyby: {chyby.Count}");
|
||||||
foreach (var ch in chyby) sb.AppendLine($" • {ch}");
|
foreach (var ch in chyby) sb.AppendLine($" • {ch}");
|
||||||
}
|
}
|
||||||
|
PripojNepodporovane(sb, nepodporovane);
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.Append($"Složka: {cilRozpisky}");
|
sb.Append($"Složka: {cilRozpisky}");
|
||||||
|
|
||||||
@@ -1217,6 +1228,16 @@ public sealed class MainViewModel : ObservableObject
|
|||||||
chyby.Count > 0 ? MessageBoxImage.Warning : MessageBoxImage.Information);
|
chyby.Count > 0 ? MessageBoxImage.Warning : MessageBoxImage.Information);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Přidá do souhrnu upozornění na prvky vložených DXF, které kreslítko neumí.
|
||||||
|
/// Export se kvůli nim nezastaví – jen se nevykreslí, a o tom se uživatel musí dozvědět.</summary>
|
||||||
|
private static void PripojNepodporovane(System.Text.StringBuilder sb, IReadOnlyList<string> nepodporovane)
|
||||||
|
{
|
||||||
|
if (nepodporovane.Count == 0) return;
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("Ve vložených DXF jsou prvky, které program neumí vykreslit:");
|
||||||
|
foreach (var radek in nepodporovane) sb.AppendLine($" • {radek}");
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Vybere DXF orientačního schématu pro vybranou přílohu. Leží-li soubor ve složce
|
/// <summary>Vybere DXF orientačního schématu pro vybranou přílohu. Leží-li soubor ve složce
|
||||||
/// aktuálního .rzp, uloží se relativně (jen jméno), jinak absolutní cestou.</summary>
|
/// aktuálního .rzp, uloží se relativně (jen jméno), jinak absolutní cestou.</summary>
|
||||||
private void VyberSchema()
|
private void VyberSchema()
|
||||||
|
|||||||
Reference in New Issue
Block a user