Skip to content

Commit a1036f9

Browse files
seto77claude
andcommitted
Add v4.928: render symmetry elements in Structure Viewer
- New SymmetryDiagram.cs builds 3D OpenGL objects for axes, mirror/glide planes, and inversion centers from SymmetryElementsTable. - FormStructureViewer gains a toolbar toggle (toolStripButtonSymmetryElements) and a status strip; renames toolStripLabel* to toolStripStatusLabel*. - Rename RotationAxis/MirrorPlane to SymmetryAxis/SymmetryPlane (and the matching collector methods/fields) so the table covers all element kinds. - SymmetryElementsTable: keep pure mirrors as mirrors when combined with centering translations (skip glide synthesis for op.IntrinsicTranslation = 0). - CheckResxDuplicates.targets: detect duplicates by (alias, name) pair so legitimate writer output is not flagged as RESX001. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 67669a9 commit a1036f9

14 files changed

Lines changed: 5911 additions & 4566 deletions

Crystallography.Controls/Crystal/SymmetryDiagramElements.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ private sealed class ElementsContext
139139
public Pen Pen, MirrorPen, InPlanePen, DepthPen, DiagPen, EPen;
140140
public Brush Fill, White;
141141
public List<PerpendicularMirrorDraft> PerpendicularMirrors;
142-
public HashSet<(long Nx, long Ny, long D, int Style)> DrawnMirrorPlanes;
142+
public HashSet<(long Nx, long Ny, long D, int Style)> DrawnSymmetryPlanes;
143143
public HashSet<(long X, long Y)> StereonetAnchorKeys; // (260505Cl) inset 半径は CubicStereonetInsetRadius 定数を直接使う。
144144
public double DisplayMaxS; // (260505Ch) F 格子は [0, 1/2]²、それ以外は [0, 1]² を描画対象にする。
145145
}
@@ -211,7 +211,7 @@ public static void DrawSymmetryElements(Graphics g, Size clientSize, int seriesN
211211
DepthPen = depthPen, DiagPen = diagPen, EPen = ePen,
212212
Fill = fill, White = white,
213213
PerpendicularMirrors = [],
214-
DrawnMirrorPlanes = [],
214+
DrawnSymmetryPlanes = [],
215215
StereonetAnchorKeys = isCubicHigh ? CollectStereonetAnchorKeys(layout, stereonetPositions) : null,
216216
DisplayMaxS = displayMaxS,
217217
};
@@ -221,7 +221,7 @@ public static void DrawSymmetryElements(Graphics g, Size clientSize, int seriesN
221221
var parallelMirrors = new HashSet<(double Height, bool Glide, double GlideSx, double GlideSy)>();
222222
// (260503Cl) key に order を追加して 2 / 4 / -4 を別々に集める。draw 時に高次優先で重複を抑制。
223223
var inPlaneAxisDrafts = new Dictionary<(long, long, long, long, int, bool), InPlaneAxisArrowDraft>();
224-
foreach (var mp in table.MirrorPlanes)
224+
foreach (var mp in table.SymmetryPlanes)
225225
{
226226
bool perp = IsAxisPerpendicularToProjection(mp.Normal, actualAxis);
227227
bool inPlane = IsAxisInPlane(mp.Normal, actualAxis);
@@ -238,7 +238,7 @@ public static void DrawSymmetryElements(Graphics g, Size clientSize, int seriesN
238238
ctx.PerpendicularMirrors.Add(new(sx, sy, mp.Normal, mp.Glide));
239239
}
240240
}
241-
foreach (var ax in table.RotationAxes)
241+
foreach (var ax in table.SymmetryAxes)
242242
{
243243
int absO = Math.Abs(ax.Order);
244244
// 紙面内軸として扱うのは |order| = 2 (proper のみ; -2 は mirror) と |order| = 4 (proper / screw / -4)。
@@ -250,7 +250,7 @@ public static void DrawSymmetryElements(Graphics g, Size clientSize, int seriesN
250250
ax.Order, ax.Screw, ax.FinCount, ax.EdgeStep, inPlaneAxisDrafts, displayMaxS);
251251
}
252252

253-
DrawCollectedPerpendicularMirrorPlanes(ctx);
253+
DrawCollectedPerpendicularSymmetryPlanes(ctx);
254254
DrawParallelMirrorStack(g, layout, parallelMirrors, fill, isCubic: isCubic); // (260505Cl) cubic は紙面平行軸との干渉回避で bracket を離す。
255255
// (260503Cl) 紙面平行軸の anchor が斜め回転軸の foot / 紙面垂直回転軸 (4_2 など) の position と重なる場合、
256256
// 矢印を axis 方向にずらして点記号と重ならないようにする。
@@ -281,7 +281,7 @@ public static void DrawSymmetryElements(Graphics g, Size clientSize, int seriesN
281281
private static void DrawPerpendicularRotationMarks(ElementsContext ctx, Crystallography.SymmetryElementsTable table,
282282
ProjectionAxis projAxis, bool isCubic = false)
283283
{
284-
var axes = table.RotationAxes;
284+
var axes = table.SymmetryAxes;
285285
// 同位置の高次 proper rotation 集合 (低次抑制 / -N 抑制用)。
286286
var covered2 = new HashSet<(int, int)>();
287287
var covered3 = new HashSet<(int, int)>();
@@ -525,7 +525,7 @@ private static void DrawRotationPerp(Graphics g, Brush fill, Brush white, PointF
525525
private static void DrawDiagonalRotationMarks(ElementsContext ctx, Crystallography.SymmetryElementsTable table,
526526
ProjectionAxis projAxis, HashSet<(long, long)> skipPositionKeys = null)
527527
{
528-
var axes = table.RotationAxes;
528+
var axes = table.SymmetryAxes;
529529
var drawnAxes = new HashSet<(long Sx, long Sy, int Order, int U, int V, int W, bool Screw, int Fin, int Edge)>();
530530

531531
foreach (var ax in axes)
@@ -559,7 +559,7 @@ private static void DrawDiagonalRotationMarks(ElementsContext ctx, Crystallograp
559559
}
560560
}
561561

562-
private static bool TryGetDiagonalAxisFootprint(RotationAxis ax, ProjectionAxis projAxis, out double sx, out double sy)
562+
private static bool TryGetDiagonalAxisFootprint(SymmetryAxis ax, ProjectionAxis projAxis, out double sx, out double sy)
563563
{
564564
sx = sy = 0;
565565
double depthPos = ProjectedDepth(ax.X, ax.Y, ax.Z, projAxis);
@@ -715,7 +715,7 @@ private static double NormalizeBoundary(double x, double maxS = 1.0) =>
715715
double displayMaxS = 1.0)
716716
{
717717
var result = new HashSet<(long, long)>();
718-
foreach (var ax in table.RotationAxes)
718+
foreach (var ax in table.SymmetryAxes)
719719
{
720720
if (ax.Order is not (2 or 3)) continue;
721721
if (!IsAxisDiagonalToProjection(ax.Direction, projAxis)) continue;
@@ -744,7 +744,7 @@ private static double NormalizeBoundary(double x, double maxS = 1.0) =>
744744
{
745745
var result = new HashSet<(long, long)>();
746746
var proj = GetProjection(projAxis);
747-
foreach (var ax in table.RotationAxes)
747+
foreach (var ax in table.SymmetryAxes)
748748
{
749749
int absO = Math.Abs(ax.Order);
750750
if (absO is not (2 or 3 or 4 or 6)) continue;
@@ -1116,7 +1116,7 @@ static double HeightKey(double height)
11161116
#region 紙面垂直 mirror/glide
11171117
/// <summary>紙面垂直 mirror/glide を幾何線ごとに集約し、d-glide 優先 → e-glide ペア → glide score 最小、の順に 1 つだけ描画。
11181118
/// (260503Ch) [ITA-D1], [ITA-D4] 同じ幾何面に属する複数操作は、defining graphical symbol 1 個へ畳み込む。</summary>
1119-
private static void DrawCollectedPerpendicularMirrorPlanes(ElementsContext ctx)
1119+
private static void DrawCollectedPerpendicularSymmetryPlanes(ElementsContext ctx)
11201120
{
11211121
if (ctx.PerpendicularMirrors.Count == 0) return;
11221122
var groups = ctx.PerpendicularMirrors
@@ -1217,7 +1217,7 @@ void Draw(double lineSx, double lineSy)
12171217
if (ux < -1e-9 || (Math.Abs(ux) < 1e-9 && uy < 0)) { ux = -ux; uy = -uy; }
12181218
var pt = c.ToScreen(lineSx, lineSy);
12191219
var key = (R6(ux), R6(uy), (long)Math.Round((ux * pt.X + uy * pt.Y) * 1000), style);
1220-
if (!ctx.DrawnMirrorPlanes.Add(key)) return;
1220+
if (!ctx.DrawnSymmetryPlanes.Add(key)) return;
12211221
if (dGlide)
12221222
{
12231223
var (arrowX, arrowY) = GetDGlideArrowDirection(c, gSx, gSy, gSz);
@@ -1382,7 +1382,7 @@ void Arrow(float t1, float t2)
13821382
}
13831383
}
13841384

1385-
/// <summary>s≈1 (右辺/下辺) は 0 に折り畳まず 1 のまま残す: drawnMirrorPlanes の dedup キーが左/右辺で別になり両辺それぞれに描画できる。</summary>
1385+
/// <summary>s≈1 (右辺/下辺) は 0 に折り畳まず 1 のまま残す: drawnSymmetryPlanes の dedup キーが左/右辺で別になり両辺それぞれに描画できる。</summary>
13861386
private static double NormalizeCellBoundary(double s)
13871387
{
13881388
if (Math.Abs(s - 1) < 1e-8) return 1;
@@ -1431,21 +1431,21 @@ private static long R6Periodic(double s)
14311431
private static void DrawCubicStereonetInsets(ElementsContext ctx, Crystallography.SymmetryElementsTable table,
14321432
ProjectionAxis projAxis, (double Sx, double Sy)[] positions)
14331433
{
1434-
var diagonalMirrors = table.MirrorPlanes
1434+
var diagonalMirrors = table.SymmetryPlanes
14351435
.Where(mp => IsAxisDiagonalToProjection(mp.Normal, projAxis))
14361436
.ToList(); // (260505Ch) perpendicular/in-plane の否定形ではなく diagonal 判定へ統一。
14371437
// 260505Cl 旧: 斜め 3 回軸のみを収集していたため Pm-3m などで face-diagonal 2 回軸 (<110>系) が
14381438
// cell-side 描画でスキップされる一方 inset にも乗らず欠落していた。
1439-
// var diagonalThreefolds = new List<RotationAxis>();
1440-
// foreach (var ax in table.RotationAxes)
1439+
// var diagonalThreefolds = new List<SymmetryAxis>();
1440+
// foreach (var ax in table.SymmetryAxes)
14411441
// {
14421442
// if (Math.Abs(ax.Order) != 3) continue;
14431443
// if (!IsAxisDiagonalToProjection(ax.Direction, projAxis)) continue;
14441444
// diagonalThreefolds.Add(ax);
14451445
// }
14461446
// 260505Cl: ITA Vol.A Pm-3m 等で inset に出ている斜め 2 回軸 (face-diagonal) も拾う。
14471447
// proper rotation 軸 (Order 2/3) のみ。-3 等の rotoinversion は cell-side draw と同様に除外。
1448-
var diagonalAxes = table.RotationAxes
1448+
var diagonalAxes = table.SymmetryAxes
14491449
.Where(ax => ax.Order is 2 or 3 && IsAxisDiagonalToProjection(ax.Direction, projAxis))
14501450
.ToList(); // (260505Ch) cell-side の斜め軸判定と同じ条件に揃える。
14511451

@@ -1580,7 +1580,7 @@ private enum StereonetGlideStyle
15801580
}
15811581

15821582
/// <summary>(260505Cl) 鏡映/glide 平面 mp が site (xc, yc, zc) を通るか厳密判定 (lattice 周期を含む)。</summary>
1583-
private static bool PlanePassesThroughSite(MirrorPlane mp, double xc, double yc, double zc)
1583+
private static bool PlanePassesThroughSite(SymmetryPlane mp, double xc, double yc, double zc)
15841584
{
15851585
double h = mp.Normal.U, k = mp.Normal.V, l = mp.Normal.W;
15861586
double c = h * mp.X + k * mp.Y + l * mp.Z;
@@ -1590,7 +1590,7 @@ private static bool PlanePassesThroughSite(MirrorPlane mp, double xc, double yc,
15901590
}
15911591

15921592
/// <summary>(260505Cl) 軸 ax の line {(X+tU, Y+tV, Z+tW)} が site (xc, yc, zc) を通るか厳密判定 (lattice 周期を含む)。</summary>
1593-
private static bool AxisPassesThroughSite(RotationAxis ax, double xc, double yc, double zc)
1593+
private static bool AxisPassesThroughSite(SymmetryAxis ax, double xc, double yc, double zc)
15941594
{
15951595
int U = ax.Direction.U, V = ax.Direction.V, W = ax.Direction.W;
15961596
// 各座標方向の格子並進を試し、その方向で t を求めて他軸が整数オフセットに収まるか確認。

Crystallography.Controls/Crystallography.Controls.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<TargetFramework>net10.0-windows</TargetFramework>
66
<UseWindowsForms>true</UseWindowsForms>
77
<UseWPF>true</UseWPF><!-- (260427Ch) LabelLaTeX で WpfMath の WPF 描画を使うため有効化 -->
8-
<AssemblyVersion>2026.5.5.1351</AssemblyVersion>
9-
<FileVersion>2026.5.5.1351</FileVersion>
8+
<AssemblyVersion>2026.5.6.0606</AssemblyVersion>
9+
<FileVersion>2026.5.6.0606</FileVersion>
1010
<ApplicationHighDpiMode>PerMonitorV2</ApplicationHighDpiMode>
1111
<ApplicationUseCompatibleTextRendering>true</ApplicationUseCompatibleTextRendering>
1212
<ApplicationVisualStyles>true</ApplicationVisualStyles>

Crystallography.Controls/build/CheckResxDuplicates.targets

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<!-- 260426Cl 追加: VS デザイナが古い <assembly alias="..."> エントリを残すケースの再発防止 -->
3+
<!-- 260506Cl 改: 重複判定を (alias, name) ペア単位に変更。
4+
resx writer は同一 alias でも型ごとに `Version=` 有無の違う `name` を吐くため、
5+
alias だけでグルーピングすると正常な書き戻しまで誤検知してしまう。
6+
真の staleness (alias 同じ・name 同じ) だけを警告するように絞り込む。 -->
37
<Project>
48

59
<UsingTask
@@ -18,14 +22,15 @@
1822
var path = item.ItemSpec;
1923
var doc = XDocument.Load(path);
2024
var dups = doc.Root.Elements("assembly")
21-
.Select(a => (string)a.Attribute("alias"))
22-
.Where(s => s != null)
23-
.GroupBy(s => s)
25+
.Select(a => new { Alias = (string)a.Attribute("alias"), Name = (string)a.Attribute("name") })
26+
.Where(x => x.Alias != null)
27+
.GroupBy(x => new { x.Alias, x.Name })
2428
.Where(g => g.Count() > 1);
2529
foreach (var g in dups)
2630
{
2731
Log.LogWarning(null, "RESX001", null, path, 0, 0, 0, 0,
28-
"Duplicate <assembly alias=\"{0}\"> entries (count={1})", g.Key, g.Count());
32+
"Duplicate <assembly alias=\"{0}\" name=\"{1}\"> entries (count={2})",
33+
g.Key.Alias, g.Key.Name, g.Count());
2934
}
3035
}
3136
]]></Code>

Crystallography.OpenGL/Crystallography.OpenGL.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
<TargetFramework>net10.0-windows</TargetFramework>
66
<UseWindowsForms>true</UseWindowsForms>
77
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
8-
<AssemblyVersion>2026.5.5.1351</AssemblyVersion>
9-
<FileVersion>2026.5.5.1351</FileVersion>
8+
<AssemblyVersion>2026.5.6.0606</AssemblyVersion>
9+
<FileVersion>2026.5.6.0606</FileVersion>
1010
<!-- <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion> --><!-- 260405Cl .NET 10はWindows7非対応のため削除 -->
1111
<Platforms>x64</Platforms>
1212
<!--

Crystallography/Crystallography.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
<OutputType>Library</OutputType>
55
<TargetFramework>net10.0-windows</TargetFramework>
66
<UseWindowsForms>true</UseWindowsForms>
7-
<AssemblyVersion>2026.5.5.1351</AssemblyVersion>
8-
<FileVersion>2026.5.5.1351</FileVersion>
7+
<AssemblyVersion>2026.5.6.0606</AssemblyVersion>
8+
<FileVersion>2026.5.6.0606</FileVersion>
99
<!-- <SupportedOSPlatformVersion>7.0</SupportedOSPlatformVersion> --><!-- 260405Cl .NET 10はWindows7非対応のため削除 -->
1010
<Platforms>x64</Platforms>
1111
<PublishReadyToRun>true</PublishReadyToRun>

0 commit comments

Comments
 (0)