@@ -141,14 +141,20 @@ protected static CellLayout ComputeCellLayout(Size canvas, Symmetry sym, Project
141141 double cosA = Math . Cos ( rad ) , sinA = Math . Sin ( rad ) ;
142142 var ( hLen , vLen ) = GetCellLengths ( sym , projAxis ) ;
143143 // (260502Cl) 上下左右で余白を独立に取る。
144- float availW = Math . Max ( 8f , canvas . Width - CellMarginLeft - CellMarginRight ) ;
145- float availH = Math . Max ( 8f , canvas . Height - CellMarginTop - CellMarginBottom ) ;
144+ // (260503Cl) 立方晶系では in-plane 4 回軸の平行四辺形などがセル外側に張り出すため、上下左右の余白を +20 加算。
145+ float extra = sym . CrystalSystemNumber == 7 ? 20f : 0f ;
146+ float ml = CellMarginLeft + extra ;
147+ float mr = CellMarginRight + extra ;
148+ float mt = CellMarginTop + extra ;
149+ float mb = CellMarginBottom + extra ;
150+ float availW = Math . Max ( 8f , canvas . Width - ml - mr ) ;
151+ float availH = Math . Max ( 8f , canvas . Height - mt - mb ) ;
146152 double scale = Math . Min ( availW / ( hLen + Math . Abs ( cosA ) * vLen ) , availH / ( sinA * vLen ) ) ;
147153 float horzLen = ( float ) ( hLen * scale ) , vertLen = ( float ) ( vLen * scale ) ;
148154 float bboxW = ( float ) ( ( hLen + Math . Abs ( cosA ) * vLen ) * scale ) ;
149155 float bboxH = ( float ) ( sinA * vLen * scale ) ;
150- float ox = CellMarginLeft + ( availW - bboxW ) / 2f + ( cosA < 0 ? - ( float ) cosA * vertLen : 0 ) ;
151- float oy = CellMarginTop + ( availH - bboxH ) / 2f ;
156+ float ox = ml + ( availW - bboxW ) / 2f + ( cosA < 0 ? - ( float ) cosA * vertLen : 0 ) ;
157+ float oy = mt + ( availH - bboxH ) / 2f ;
152158 return new ( new PointF ( ox , oy ) , new PointF ( horzLen , 0f ) , new PointF ( ( float ) cosA * vertLen , ( float ) sinA * vertLen ) ) ;
153159 }
154160
@@ -206,25 +212,39 @@ protected static string TZToFraction(double t)
206212 return $ "{ t : 0.00} ";
207213 }
208214
209- /// <summary>(sx,sy) を通り (dSx,dSy) 方向の直線をセル [0,1]² でクリップ。</summary>
210- protected static ( PointF ? Start , PointF ? End ) SpanLineThroughCell ( CellLayout c , double sx , double sy , double dSx , double dSy )
215+ /// <summary>(260504Ch) (sx,sy) を通り (dSx,dSy) 方向の直線をセル [0,1]² の媒介変数区間でクリップ。
216+ /// 旧実装はセル外で辺と平行な直線を tMin=1 にするだけで、tMax が無限大のままなら有効扱いになることがあった。</summary>
217+ protected static bool TryClipLineThroughUnitCell ( double sx , double sy , double dSx , double dSy ,
218+ out double tMin , out double tMax )
211219 {
212- double tMin = double . NegativeInfinity , tMax = double . PositiveInfinity ;
213- UpdateInterval ( sx , dSx , ref tMin , ref tMax ) ;
214- UpdateInterval ( sy , dSy , ref tMin , ref tMax ) ;
215- if ( tMin > tMax ) return ( null , null ) ;
216- return ( c . ToScreen ( sx + tMin * dSx , sy + tMin * dSy ) , c . ToScreen ( sx + tMax * dSx , sy + tMax * dSy ) ) ;
220+ tMin = double . NegativeInfinity ;
221+ tMax = double . PositiveInfinity ;
222+ if ( Math . Abs ( dSx ) < 1e-12 && Math . Abs ( dSy ) < 1e-12 ) return false ; // (260504Ch) 退化方向では ±Infinity*0 に落とさない。
223+ return ClipCoordinate ( sx , dSx , ref tMin , ref tMax )
224+ && ClipCoordinate ( sy , dSy , ref tMin , ref tMax )
225+ && tMin <= tMax + 1e-12 ;
217226
218- static void UpdateInterval ( double s , double d , ref double tMin , ref double tMax )
227+ static bool ClipCoordinate ( double s , double d , ref double tMin , ref double tMax )
219228 {
220- if ( Math . Abs ( d ) < 1e-9 ) { if ( s < 0 || s > 1 ) tMin = 1 ; return ; }
229+ const double eps = 1e-9 ;
230+ if ( Math . Abs ( d ) < eps )
231+ return s >= - eps && s <= 1 + eps ;
221232 double t1 = - s / d , t2 = ( 1 - s ) / d ;
222233 if ( t1 > t2 ) ( t1 , t2 ) = ( t2 , t1 ) ;
223234 if ( t1 > tMin ) tMin = t1 ;
224235 if ( t2 < tMax ) tMax = t2 ;
236+ return true ;
225237 }
226238 }
227239
240+ /// <summary>(sx,sy) を通り (dSx,dSy) 方向の直線をセル [0,1]² でクリップ。</summary>
241+ protected static ( PointF ? Start , PointF ? End ) SpanLineThroughCell ( CellLayout c , double sx , double sy , double dSx , double dSy )
242+ {
243+ if ( ! TryClipLineThroughUnitCell ( sx , sy , dSx , dSy , out double tMin , out double tMax ) )
244+ return ( null , null ) ;
245+ return ( c . ToScreen ( sx + tMin * dSx , sy + tMin * dSy ) , c . ToScreen ( sx + tMax * dSx , sy + tMax * dSy ) ) ;
246+ }
247+
228248 protected static Bitmap NewBitmap ( Size size , out Graphics g )
229249 {
230250 var bmp = new Bitmap ( Math . Max ( size . Width , 16 ) , Math . Max ( size . Height , 16 ) ) ;
@@ -242,16 +262,8 @@ protected static bool TryGetSym(int seriesNumber, out Symmetry sym, out int reso
242262 {
243263 sym = default ; msg = null ; resolvedSeriesNumber = seriesNumber ;
244264 if ( seriesNumber <= 0 || seriesNumber >= SymmetryStatic . TotalSpaceGroupNumber ) return false ;
245- sym = SymmetryStatic . Symmetries [ seriesNumber ] ;
246- if ( sym . SpaceGroupHMStr != null && sym . SpaceGroupHMStr . EndsWith ( "Rho" ) )
247- for ( int i = 1 ; i < SymmetryStatic . TotalSpaceGroupNumber ; i ++ )
248- {
249- var alt = SymmetryStatic . Symmetries [ i ] ;
250- if ( alt . SpaceGroupNumber == sym . SpaceGroupNumber && alt . SpaceGroupHMStr is { } s && s . EndsWith ( "Hex" ) )
251- {
252- sym = alt ; resolvedSeriesNumber = i ; break ;
253- }
254- }
265+ resolvedSeriesNumber = SymmetryElementsTable . ResolveRhoToHex ( seriesNumber ) ; // (260504Ch) Rho→Hex 解決を対称要素テーブルと共有。
266+ sym = SymmetryStatic . Symmetries [ resolvedSeriesNumber ] ;
255267 if ( sym . CrystalSystemNumber is not ( 1 or 2 or 3 or 4 or 5 or 6 or 7 ) )
256268 {
257269 msg = $ "({ sym . CrystalSystemStr } not yet supported)";
0 commit comments