/
docNemo
/
clothes-graph
Обзор
Документация
Войти
/
docNemo
/
clothes-graph
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/ClothesGraph.Model/Underlay/UnderlayImage.cs
149 строк
7 KB
docNemo
Запрос на слияние 'interface-to-spec' (
#1
) из interface-to-spec в main
09 авг 2026, 20:28
Верифицирован
09 авг 2026, 20:28
1bbc436
Код
Авторство
О чём код?
using System; using ClothesGraph.Geometry; namespace ClothesGraph.Model.Underlay; /// <summary>Устойчивый идентификатор подложки.</summary> public readonly struct UnderlayId : IEquatable<UnderlayId> { private readonly Guid _value; private UnderlayId(Guid value) => _value = value; public static UnderlayId New() => new(Guid.NewGuid()); public static UnderlayId FromGuid(Guid value) => new(value); public Guid Value => _value; public bool IsEmpty => _value == Guid.Empty; public bool Equals(UnderlayId other) => _value == other._value; public override bool Equals(object? obj) => obj is UnderlayId other && Equals(other); public override int GetHashCode() => _value.GetHashCode(); public override string ToString() => _value.ToString("N")[..8]; public static bool operator ==(UnderlayId left, UnderlayId right) => left.Equals(right); public static bool operator !=(UnderlayId left, UnderlayId right) => !left.Equals(right); } /// <summary> /// Растровое изображение, подложенное под чертёж для работы от образца. /// </summary> /// <remarks> /// Хранит содержимое файла целиком, а не путь к нему: проект переносят между /// компьютерами, и ссылка на исходный файл превратилась бы в битую при первой /// же передаче. /// </remarks> public sealed class UnderlayImage { public UnderlayImage(UnderlayId id, string name, byte[] content, int pixelWidth, int pixelHeight) { if (id.IsEmpty) throw new ArgumentException("Идентификатор подложки не задан", nameof(id)); if (pixelWidth <= 0 || pixelHeight <= 0) throw new ArgumentException("Размер изображения в точках должен быть положительным"); Id = id; Name = string.IsNullOrWhiteSpace(name) ? "Подложка" : name.Trim(); Content = content; PixelWidth = pixelWidth; PixelHeight = pixelHeight; } public UnderlayId Id { get; } public string Name { get; set; } /// <summary>Содержимое файла изображения.</summary> public byte[] Content { get; } public int PixelWidth { get; } public int PixelHeight { get; } /// <summary>Положение левого верхнего угла изображения на чертеже.</summary> public Point2 Origin { get; set; } = Point2.Origin; /// <summary>Поворот изображения в градусах против часовой стрелки.</summary> public double RotationDegrees { get; set; } public double Opacity { get; set; } = 0.6; /// <summary>Порядок вывода: подложки с меньшим номером лежат ниже.</summary> public int Order { get; set; } public bool IsVisible { get; set; } = true; /// <summary> /// Сколько единиц чертежа приходится на одну точку изображения. /// </summary> /// <remarks> /// Это масштаб показа: он задан всегда, иначе изображение нечем было бы /// вывести на чертёж. О соответствии натуре он сам по себе не говорит — /// об этом говорит <see cref="IsCalibrated"/>. /// </remarks> public double UnitsPerPixel { get; private set; } /// <summary> /// Масштаб подложки соотнесён с натурой. /// </summary> /// <remarks> /// Признак отделён от самого масштаба намеренно. Раньше калиброванной /// считалась любая подложка с ненулевым масштабом, а программа выставляла /// его сразу при добавлении, чтобы изображение было видно. Из-за этого /// состояние «масштаб не задан» не наступало никогда, и предупреждение об /// обводке по неоткалиброванной подложке не могло сработать: снятые с /// фотографии размеры оказывались произвольными молча. /// </remarks> public bool IsCalibrated { get; private set; } /// <summary>Ширина изображения на чертеже. Ноль, пока масштаб не задан.</summary> public Length Width => Length.FromUnits((long)Math.Round(PixelWidth * UnitsPerPixel)); public Length Height => Length.FromUnits((long)Math.Round(PixelHeight * UnitsPerPixel)); /// <summary> /// Задаёт масштаб по паре точек изображения и реальному расстоянию между ними. /// </summary> /// <param name="firstPixel">Первая точка в координатах изображения.</param> /// <param name="secondPixel">Вторая точка в координатах изображения.</param> /// <param name="realDistance">Расстояние между ними в натуре.</param> public void Calibrate((double X, double Y) firstPixel, (double X, double Y) secondPixel, Length realDistance) { var dx = secondPixel.X - firstPixel.X; var dy = secondPixel.Y - firstPixel.Y; var pixelDistance = Math.Sqrt(dx * dx + dy * dy); if (pixelDistance < 1e-6) throw new ArgumentException( "Точки калибровки совпадают: расстояние между ними неизвестно"); if (realDistance <= Length.Zero) throw new ArgumentException( "Реальное расстояние должно быть положительным", nameof(realDistance)); UnitsPerPixel = realDistance.Units / pixelDistance; IsCalibrated = true; } /// <summary> /// Задаёт масштаб показа, не объявляя его соответствующим натуре. /// </summary> /// <remarks> /// Служит предварительному размещению при добавлении: без масштаба /// изображение вышло бы на чертёж размером с ноготь. Калибровкой не /// является — за неё отвечает <see cref="Calibrate"/>. /// </remarks> public void SetPreviewScale(double unitsPerPixel) => UnitsPerPixel = Math.Max(0, unitsPerPixel); /// <summary>Восстанавливает масштаб и признак калибровки при чтении файла.</summary> public void Restore(double unitsPerPixel, bool calibrated) { UnitsPerPixel = Math.Max(0, unitsPerPixel); IsCalibrated = calibrated && UnitsPerPixel > 0; } public override string ToString() => IsCalibrated ? $"«{Name}»" : $"«{Name}» (масштаб не задан)"; }