SolidWorks re-exports produce files with identical geometry but different entity ordering, handle assignments, style names, and floating-point epsilon values. This caused hash mismatches and unnecessary API updates. Uses ACadSharp to parse DXF entities and build canonical, sorted signatures (LINE, ARC, CIRCLE, MTEXT) with coordinates rounded to 4 decimal places. Falls back to raw file hash if parsing fails. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
138 lines
4.4 KiB
C#
138 lines
4.4 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.IO;
|
|
using System.Linq;
|
|
using System.Security.Cryptography;
|
|
using System.Text;
|
|
using ACadSharp.Entities;
|
|
using ACadSharp.IO;
|
|
|
|
namespace ExportDXF.Utilities
|
|
{
|
|
public static class ContentHasher
|
|
{
|
|
/// <summary>
|
|
/// Computes a SHA256 hash of DXF geometry, ignoring entity ordering,
|
|
/// handle assignments, style names, and floating-point epsilon differences
|
|
/// that SolidWorks changes between re-exports of identical geometry.
|
|
/// Falls back to a raw file hash if ACadSharp parsing fails.
|
|
/// </summary>
|
|
public static string ComputeDxfContentHash(string filePath)
|
|
{
|
|
try
|
|
{
|
|
return ComputeGeometricHash(filePath);
|
|
}
|
|
catch
|
|
{
|
|
return ComputeFileHash(filePath);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Computes a SHA256 hash of the entire file contents (for PDFs and other binary files).
|
|
/// </summary>
|
|
public static string ComputeFileHash(string filePath)
|
|
{
|
|
using (var sha = SHA256.Create())
|
|
using (var stream = File.OpenRead(filePath))
|
|
{
|
|
var bytes = sha.ComputeHash(stream);
|
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
|
}
|
|
}
|
|
|
|
private static string ComputeGeometricHash(string filePath)
|
|
{
|
|
using (var reader = new DxfReader(filePath))
|
|
{
|
|
var doc = reader.Read();
|
|
var signatures = new List<string>();
|
|
|
|
foreach (var entity in doc.Entities)
|
|
{
|
|
signatures.Add(GetEntitySignature(entity));
|
|
}
|
|
|
|
signatures.Sort(StringComparer.Ordinal);
|
|
var combined = string.Join("\n", signatures);
|
|
|
|
using (var sha = SHA256.Create())
|
|
{
|
|
var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(combined));
|
|
return BitConverter.ToString(bytes).Replace("-", "").ToLowerInvariant();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static string GetEntitySignature(Entity entity)
|
|
{
|
|
var layer = entity.Layer?.Name ?? "";
|
|
|
|
switch (entity)
|
|
{
|
|
case Line line:
|
|
return GetLineSignature(line, layer);
|
|
case Arc arc:
|
|
return GetArcSignature(arc, layer);
|
|
case Circle circle:
|
|
return GetCircleSignature(circle, layer);
|
|
case MText mtext:
|
|
return GetMTextSignature(mtext, layer);
|
|
default:
|
|
return $"{entity.GetType().Name}|{layer}";
|
|
}
|
|
}
|
|
|
|
private static string GetLineSignature(Line line, string layer)
|
|
{
|
|
var p1 = FormatPoint(line.StartPoint.X, line.StartPoint.Y);
|
|
var p2 = FormatPoint(line.EndPoint.X, line.EndPoint.Y);
|
|
|
|
// Normalize endpoint order so direction doesn't affect the hash
|
|
if (string.Compare(p1, p2, StringComparison.Ordinal) > 0)
|
|
{
|
|
var tmp = p1;
|
|
p1 = p2;
|
|
p2 = tmp;
|
|
}
|
|
|
|
return $"LINE|{layer}|{p1}|{p2}";
|
|
}
|
|
|
|
private static string GetArcSignature(Arc arc, string layer)
|
|
{
|
|
var center = FormatPoint(arc.Center.X, arc.Center.Y);
|
|
var r = R(arc.Radius);
|
|
var sa = R(arc.StartAngle);
|
|
var ea = R(arc.EndAngle);
|
|
return $"ARC|{layer}|{center}|{r}|{sa}|{ea}";
|
|
}
|
|
|
|
private static string GetCircleSignature(Circle circle, string layer)
|
|
{
|
|
var center = FormatPoint(circle.Center.X, circle.Center.Y);
|
|
var r = R(circle.Radius);
|
|
return $"CIRCLE|{layer}|{center}|{r}";
|
|
}
|
|
|
|
private static string GetMTextSignature(MText mtext, string layer)
|
|
{
|
|
var point = FormatPoint(mtext.InsertPoint.X, mtext.InsertPoint.Y);
|
|
var text = mtext.Value ?? "";
|
|
return $"MTEXT|{layer}|{point}|{text}";
|
|
}
|
|
|
|
private static string R(double value)
|
|
{
|
|
return Math.Round(value, 4).ToString(CultureInfo.InvariantCulture);
|
|
}
|
|
|
|
private static string FormatPoint(double x, double y)
|
|
{
|
|
return $"{R(x)},{R(y)}";
|
|
}
|
|
}
|
|
}
|