From 50a557137938cd204d5c49edb0e3c15992c52849 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Tue, 30 Nov 2021 22:52:14 +0800 Subject: [PATCH 01/18] =?UTF-8?q?=E6=83=8A=E6=83=8A=E4=BF=AE=E6=94=B9?= =?UTF-8?q?=E7=9A=84=E4=B8=9C=E8=A5=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 2 +- .gitignore | 118 +++++++++--------- src/IFoxCAD.Basal/IFoxCAD.Basal.csproj | 3 +- src/IFoxCAD.Basal/LinqEx.cs | 2 +- src/IFoxCAD.Basal/LoopList.cs | 2 - .../ExtensionMethod/CollectionEx.cs | 37 +++++- src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs | 4 +- src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs | 4 +- src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs | 86 ++++++------- .../ExtensionMethod/DBDictionaryEx.cs | 14 +-- src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs | 4 +- src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 24 ++-- src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs | 23 ++-- src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs | 80 ++++++------ src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs | 24 ++-- .../ExtensionMethod/SelectionSetEx.cs | 12 +- .../ExtensionMethod/SymbolTableEx.cs | 12 +- .../ExtensionMethod/SymbolTableRecordEx.cs | 37 +++--- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 13 +- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data | 1 + src/IFoxCAD.Cad/ResultData/LispDottedPair.cs | 2 +- src/IFoxCAD.Cad/ResultData/LispList.cs | 2 +- src/IFoxCAD.Cad/ResultData/TypedValueList.cs | 2 +- src/IFoxCAD.Cad/ResultData/XRecordDataList.cs | 2 +- src/IFoxCAD.Cad/ResultData/XdataList.cs | 4 +- src/IFoxCAD.Cad/Runtime/AcadVersion.cs | 2 +- src/IFoxCAD.Cad/Runtime/AssemInfo.cs | 2 +- src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs | 8 +- src/IFoxCAD.Cad/Runtime/DBTrans.cs | 14 +-- src/IFoxCAD.Cad/Runtime/Env.cs | 4 +- src/IFoxCAD.Cad/Runtime/SymbolTable.cs | 16 +-- src/IFoxCAD.Cad/SelectionFilter/OpComp.cs | 2 +- src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs | 2 +- src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs | 2 +- src/IFoxCAD.WPF/IFoxCAD.WPF.csproj | 15 +-- tests/DBTrans.test/DBTrans.test.csproj | 13 +- tests/DBTrans.test/Test.cs | 48 +++---- tests/DBTrans.test/testenv.cs | 12 +- tests/DBTrans.test/testselectfilter.cs | 11 +- 39 files changed, 326 insertions(+), 339 deletions(-) diff --git a/.gitattributes b/.gitattributes index 63a09c5..8bb03aa 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -############################################################################### +############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto diff --git a/.gitignore b/.gitignore index faf8762..25cc348 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## Visual StudioʱļɽVisualStudioòɵļ +## 忽略Visual Studio临时文件、生成结果和由VisualStudio常用插件生成的文件。 ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files -# ûضļ +# 用户特定文件 *.rsuser *.suo *.user @@ -13,12 +13,12 @@ *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) -# ûضļ (MonoDevelop/Xamarin Studio) +# 用户特定文件 (MonoDevelop/Xamarin Studio) *.userprefs # Build results -# Build -# ﷨[abc]ƥκһڷеַҪôƥһ aҪôƥһ bҪôƥһ c *.[oa]Git .o .a βļ +# Build 结果 +# 语法:[abc]匹配任何一个列在方括号中的字符(要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)——如 *.[oa]表明Git忽略所有以 .o 或 .a 结尾的文件 [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ @@ -33,34 +33,34 @@ bld/ [Ll]og/ # Visual Studio 2015/2017 cache/options directory -# Visual Studio 2015/2017 /ѡ Ŀ¼ +# Visual Studio 2015/2017 缓存/选项 目录 .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot -# wwwrootдĿ̬ļȡע +# 如果您有在wwwroot中创建项目静态文件的任务,请取消注释 #wwwroot/ # Visual Studio 2017 auto generated files -# Visual Studio 2017Զɵļ +# Visual Studio 2017自动生成的文件 Generated\ Files/ # MSTest test Results -# MSTestԽ +# MSTest测试结果 [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT -# NUnit JUnit .NET 棬֧ .NET ԣȫʹ C# дȫúܶ߼ .NET ԣ綨Լصķ书ܡ +# NUnit 是 JUnit 的 .NET 版,支持所有 .NET 语言,完全使用 C# 编写,并进行完全重新设计以利用很多高级的 .NET 语言特性,例如定制属性以及其他相关的反射功能。 *.VisualState.xml TestResult.xml # Build Results of an ATL Project -# ATLĿɽ +# ATL项目的生成结果 [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results -# Benchmark +# Benchmark结果 BenchmarkDotNet.Artifacts/ # .NET Core @@ -69,11 +69,11 @@ project.fragment.lock.json artifacts/ # StyleCop -# ⹤ +# 代码检测工具 StyleCopReport.xml # Files built by Visual Studio -# Visual Studio builtļ +# Visual Studio built的文件 *_i.c *_p.c *_h.h @@ -103,11 +103,11 @@ StyleCopReport.xml *.scc # Chutzpah Test files -# Chutzpahļ +# Chutzpah测试文件 _Chutzpah* # Visual C++ cache files -# Visual C++ ļ +# Visual C++ 缓存文件 ipch/ *.aps *.ncb @@ -119,23 +119,23 @@ ipch/ *.VC.VC.opendb # Visual Studio profiler -# Ӧóܷ +# 应用程序性能分析工具 *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files -# Visual Studio ļ +# Visual Studio 跟踪文件 *.e2e # TFS 2012 Local Workspace -# TFS 2012 ع +# TFS 2012 本地工作区 $tf/ # Guidance Automation Toolkit -# ׹ּڼ򻯽õĴ뼯ɵӦóḶ́ʹܹʦִֶܽͨеһϵпԶ -# ʹô˹ߣȷظԵġ׳ĿԺһµķʽɣʱ䡣 +# 这套工具旨在简化将可重用的代码集成到应用程序的过程,使架构师能将通常需手动执行的一系列开发工作自动化起来。 +# 使用此工具,还能确保重复性的、易出错的开发工作以合理、一致的方式完成,并能缩短软件开发时间。 *.gpState # ReSharper is a .NET coding add-in @@ -173,11 +173,11 @@ AutoTest.Net/ .sass-cache/ # Installshield output folder -# Installshield ļ +# Installshield 输出文件夹 [Ee]xpress/ # DocProject is a documentation generator add-in -# DocProject һĵӳ +# DocProject 是一个文档生成器外接程序 DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC @@ -195,32 +195,32 @@ publish/ *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted -# ע⣺Ҫǩwebãһעͣ -# ݿַпܵ룩δܵ +# 注意:如果要签入web部署设置,请在下一行添加注释, +# 但是数据库连接字符串(带有可能的密码)将是未加密的 *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted -# Microsoft Azure Web Appá -# ǩAzure Web AppãһעͣЩűаϢ +# Microsoft Azure Web App发布设置。 +# 如果您想签入Azure Web App发布设置,请在下一行添加注释,但这些脚本中包含的敏感信息将不加密 PublishScripts/ # NuGet Packages -# NuGet +# NuGet包 *.nupkg # The packages folder can be ignored because of Package Restore -# Package RestoreļпԺ +# 由于Package Restore,包文件夹可以忽略 **/[Pp]ackages/* # except build/, which is used as an MSBuild target. -# build/MSBuildĿꡣ +# 除了build/,它用作MSBuild目标。 !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -# ҪʱȡעͣͨҪʱע +# 必要时取消注释,但通常需要时会重新生成注释 #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files -# NuGet v3project.jsonļɸɺԵļ +# NuGet v3的project.json文件生成更多可忽略的文件 *.nuget.props *.nuget.targets @@ -233,7 +233,7 @@ ecf/ rcf/ # Windows Store app package directories and files -# WindowsӦ̵ӦóĿ¼ļ +# Windows应用商店应用程序包目录和文件 AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml @@ -241,16 +241,16 @@ _pkginfo.txt *.appx # Visual Studio cache files -# Visual Studioļ +# Visual Studio缓存文件 # files ending in .cache can be ignored -# Ժ.cacheβļ +# 可以忽略以.cache结尾的文件 *.[Cc]ache # but keep track of directories ending in .cache -# Ҫ.cacheβĿ¼ +# 但要跟踪以.cache结尾的目录 !?*.[Cc]ache/ # Others -# +# 其他 ClientBin/ ~$* *~ @@ -262,16 +262,16 @@ ClientBin/ orleans.codegen.cs # Including strong name files can present a security risk -# ǿļܻȫ +# 包含强名称文件可能会带来安全风险 # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components -#жȡעһԺbower_components +#由于有多个工作流,取消注释下一行以忽略bower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true -#ASP.NETĬãbowerĿ¼Ϊwwwroot/lib/bower restoreΪtrue +#ASP.NET核心默认设置:bower目录配置为wwwroot/lib/并且bower restore为true **/wwwroot/lib/ # RIA/Silverlight projects @@ -280,7 +280,7 @@ Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) -#ĿļתΪµVisual Studio汾ıݺͱļҪļΪgit +#将旧项目文件转换为新的Visual Studio版本的备份和报告文件。不需要备份文件,因为我们有git; _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML @@ -294,7 +294,7 @@ ServiceFabricBackup/ *.ndf # Business Intelligence projects -# ҵĿ +# 商业智能项目 *.rdl.data *.bim.layout *.bim_*.settings @@ -304,28 +304,28 @@ ServiceFabricBackup/ FakesAssemblies/ # GhostDoc plugin setting file -# GhostDocļ +# GhostDoc插件设置文件 *.GhostDoc.xml # Node.js Tools for Visual Studio -# Visual StudioNode.js +# 用于Visual Studio的Node.js工具 .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log -# Visual Studio 6־ +# Visual Studio 6生成日志 *.plg # Visual Studio 6 workspace options file -# Visual Studio 6ѡļ +# Visual Studio 6工作区选项文件 *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -# Visual Studio 6ԶɵĹļ򿪵ļȣ +# Visual Studio 6自动生成的工作区文件(包含打开的文件等) *.vbw # Visual Studio LightSwitch build output -# Visual Studio LightSwitch +# Visual Studio LightSwitch生成输出 **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml @@ -334,7 +334,7 @@ node_modules/ _Pvt_Extensions # Paket dependency manager -# Paketϵ +# Paket依赖关系管理器 .paket/paket.exe paket-files/ @@ -353,7 +353,7 @@ __pycache__/ *.pyc # Cake - Uncomment if you are using it -# Cake-ʹȡע +# Cake-如果你正在使用它,请取消注释 # tools/** # !tools/packages.config @@ -361,7 +361,7 @@ __pycache__/ *.tss # Telerik's JustMock configuration file -# TelerikJustMockļ +# Telerik的JustMock配置文件 *.jmconfig # BizTalk build output @@ -372,29 +372,29 @@ __pycache__/ *.xsd.cs # OpenCover UI analysis results -# OpenCover UI +# OpenCover UI分析结果 OpenCover/ # Azure Stream Analytics local run output -# Azure +# Azure流分析本地运行输出 ASALocalRun/ # MSBuild Binary and Structured Log -# MSBuildƺͽṹ־ +# MSBuild二进制和结构化日志 *.binlog # NVidia Nsight GPU debugger configuration file -# NVidia Nsight GPUļ +# NVidia Nsight GPU调试器配置文件 *.nvuser # MFractors (Xamarin productivity tool) working folder -# MFractorsXamarinߣļ +# MFractors(Xamarin生产力工具)工作文件夹 .mfractor/ # Local History for Visual Studio -# Visual Studio ıʷ¼ +# Visual Studio 的本地历史记录 .localhistory/ # BeatPulse healthcheck temp database -# BeatPulse healthcheck ʱݿ +# BeatPulse healthcheck 临时数据库 healthchecksdb diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj index 9330969..8ae4570 100644 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj @@ -1,5 +1,4 @@ - - + net35;net40 true diff --git a/src/IFoxCAD.Basal/LinqEx.cs b/src/IFoxCAD.Basal/LinqEx.cs index b0454a4..7b20401 100644 --- a/src/IFoxCAD.Basal/LinqEx.cs +++ b/src/IFoxCAD.Basal/LinqEx.cs @@ -339,4 +339,4 @@ public static IOrderedEnumerable ThenBy(this IOrderedEnumerable s #endregion Order } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 3d132c8..79319f0 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -496,9 +496,7 @@ public override string ToString() { string s = "( "; foreach (T value in this) - { s += value.ToString() + " "; - } return s + ")"; } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs index 8fb683a..9b0c984 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs @@ -2,6 +2,7 @@ using Autodesk.AutoCAD.Geometry; using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.Linq; namespace IFoxCAD.Cad @@ -75,6 +76,27 @@ public static List ToList(this ObjectIdCollection ids) return ids.Cast().ToList(); } + public static List ToList(this StringCollection strs) + { + return strs.Cast().ToList(); + } + + /* Cast不进行过滤,而是直接强转 + var s = new System.Collections.ArrayList(); + s.Add("1"); + s.Add("a"); + s.Add(5666); + + var aaa = s.Cast().ToList(); + System.InvalidCastException: 指定的转换无效。 + + List..ctor(IEnumerable) + + System.Linq.Enumerable.ToList(IEnumerable) + + var aaa = s.Cast().ToList(); + System.InvalidCastException: 无法将类型为“System.Int32”的对象强制转换为类型“System.String”。 + + List..ctor(IEnumerable) + + System.Linq.Enumerable.ToList(IEnumerable) + */ /// /// 遍历集合的迭代器,执行action委托 @@ -84,11 +106,13 @@ public static List ToList(this ObjectIdCollection ids) /// 要运行的委托 public static void ForEach(this IEnumerable source, Action action) { + if (action == null) + throw new ArgumentNullException(nameof(action)); foreach (var element in source) - { - action?.Invoke(element); - } + action.Invoke(element); } + + /// /// 同时遍历集合索引和值的迭代器,执行action委托 /// @@ -97,14 +121,15 @@ public static void ForEach(this IEnumerable source, Action action) /// 要运行的委托 public static void ForEach(this IEnumerable source, Action action) { + if (action == null) + throw new ArgumentNullException(nameof(action)); int i = 0; foreach (var item in source) { - action?.Invoke(i, item); + action.Invoke(i, item); i++; } - } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs index e5d7943..fb3b773 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs @@ -183,7 +183,7 @@ public static Ellipse ToCurve(this EllipticalArc2d ea2d) /// 实体类构造线 public static Xline ToCurve(this Line2d line2d) { - Plane plane = new Plane(); + var plane = new Plane(); return new Xline { @@ -315,4 +315,4 @@ public static Spline ToCurve(this NurbCurve2d nc2d) #endregion NurbCurve2d } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs index d5ad9ae..9d477ba 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs @@ -403,7 +403,7 @@ public static Curve ToCurve(this CircularArc3d ca3d) /// /// 三维解析类圆/弧 /// 实体圆 - public static Circle ToCircle(this CircularArc3d ca3d) => + public static Circle ToCircle(this CircularArc3d ca3d) => new Circle(ca3d.Center, ca3d.Normal, ca3d.Radius); /// @@ -564,4 +564,4 @@ public static Polyline3d ToCurve(this PolylineCurve3d pl3d) #endregion PolylineCurve3d } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs index fd3bf83..af13df1 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs @@ -644,7 +644,7 @@ public static EllipticalArc3d ToEllipticalArc3d(this Arc arc) public static NurbCurve3d ToNurbCurve3d(this Arc arc) { return new NurbCurve3d(ToEllipticalArc3d(arc)); - } + } #endregion Arc @@ -780,14 +780,11 @@ public static NurbCurve3d ToNurbCurve3d(this Polyline2d pl2d) /// 三维ge多段线 public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) { - Point3dCollection pnts = new Point3dCollection(); + var pnts = new Point3dCollection(); foreach (Vertex2d ver in pl) - { pnts.Add(ver.Position); - } return new PolylineCurve3d(pnts); } - #endregion Polyline2d #region Polyline3d @@ -912,16 +909,16 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b if (index < 1 || index > polyline.NumberOfVertices - 2) throw new System.Exception("错误的索引号"); - if (polyline.GetSegmentType(index - 1) != SegmentType.Line || polyline.GetSegmentType(index) != SegmentType.Line) + if (polyline.GetSegmentType(index - 1) != SegmentType.Line || + polyline.GetSegmentType(index) != SegmentType.Line) throw new System.Exception("非直线段不能倒角"); //获取当前索引号的前后两段直线,并组合为Ge复合曲线 - Curve3d[] c3ds = - new Curve3d[] - { - polyline.GetLineSegmentAt(index - 1), - polyline.GetLineSegmentAt(index) - }; + var c3ds = new Curve3d[] + { + polyline.GetLineSegmentAt(index - 1), + polyline.GetLineSegmentAt(index) + }; var cc3d = new CompositeCurve3d(c3ds); //试倒直角 @@ -929,31 +926,26 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b //1、=3时倒角方向正确 //2、=2时倒角方向相反 //3、=0或为直线时失败 - c3ds = - cc3d.GetTrimmedOffset - ( - radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Chamfer - ); + c3ds = cc3d.GetTrimmedOffset + ( + radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Chamfer + ); - if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d) + if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d newcc3d) { - var newcc3d = c3ds[0] as CompositeCurve3d; c3ds = newcc3d.GetCurves(); if (c3ds.Length == 3) { - c3ds = - cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Chamfer - ); + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Chamfer + ); if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) - { throw new System.Exception("倒角半径过大"); - } } else if (c3ds.Length == 2) { @@ -966,26 +958,22 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b } //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 - c3ds = - cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Extend - ); - OffsetCurveExtensionType type = - isFillet ? - OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; - c3ds = - c3ds[0].GetTrimmedOffset - ( - radius, - Vector3d.ZAxis, - type - ); + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Extend + ); + var type = isFillet ? OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; + c3ds = c3ds[0].GetTrimmedOffset + ( + radius, + Vector3d.ZAxis, + type + ); //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 - Polyline plTemp = c3ds[0].ToCurve() as Polyline; + var plTemp = c3ds[0].ToCurve() as Polyline; polyline.RemoveVertexAt(index); polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); @@ -995,4 +983,4 @@ public static void ChamferAt(this Polyline polyline, int index, double radius, b #endregion Curve } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs index dff4ff3..6d3921e 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs @@ -55,16 +55,16 @@ public static T GetAt(this DBDictionary dict, string key, Transaction trans = /// /// 对象类型 /// 字典 - /// 事务 + /// 事务 /// 键 /// 值 - public static void SetAt(this DBDictionary dict, string key, T obj, Transaction trans = null) where T : DBObject + public static void SetAt(this DBDictionary dict, string key, T obj, Transaction tr = null) where T : DBObject { - trans ??= DBTrans.Top.Transaction; + tr ??= DBTrans.Top.Transaction; using (dict.ForWrite()) { dict.SetAt(key, obj); - trans.AddNewlyCreatedDBObject(obj, true); + tr.AddNewlyCreatedDBObject(obj, true); } } @@ -116,7 +116,7 @@ public static DBDictionary GetXDictionary(this DBObject obj, Transaction trans = id = obj.ExtensionDictionary; } - return id.GetObject(tr:trans); + return id.GetObject(tr: trans); } #region 数据表 @@ -158,8 +158,8 @@ public static DataTable CreateDataTable(Dictionary colTypes, o /// 数据 public static void SetValue(this DataCell cell, CellType type, object value) { - - + + switch (type) { case CellType.Bool: diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs index eac264c..3052258 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs @@ -75,7 +75,7 @@ public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCod break; if (data[i].TypeCode == (int)dxfCode) { - data[i] = new TypedValue((int)dxfCode,newvalue); + data[i] = new TypedValue((int)dxfCode, newvalue); } } } @@ -83,7 +83,7 @@ public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCod using (obj.ForWrite()) { obj.XData = data; - } + } } #endregion diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index a396bbd..f7234c8 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -516,7 +516,7 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, (CoordinateSystemCode.MDcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(), (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), - (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) + (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput,"To be used only with DCS"), (_, _) => Matrix3d.Identity }; @@ -624,7 +624,7 @@ public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double o /// 偏移距离 public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) { - Database db = ed.Document.Database; + var db = ed.Document.Database; db.UpdateExt(true); ed.ZoomWindow(db.Extmax, db.Extmin, offsetDist); } @@ -737,22 +737,16 @@ public static PromptResult GetString(this Editor ed, string Message, string Defa /// 缓冲结果,返回值 public static ResultBuffer RunLisp(this Editor ed, string arg) { - _ = AcedEvaluateLisp(arg, out IntPtr rb); - if (rb != IntPtr.Zero) + AcedEvaluateLisp(arg, out IntPtr rb); + try { - try - { - var rbb = DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; - return rbb; - } - catch - { - return null; - } + if (rb != IntPtr.Zero) + return DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; } + catch + { } return null; } - #endregion 执行lisp } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs index 795e9df..0635006 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs @@ -225,8 +225,7 @@ public static string GetUnFormatString(this MText mt) List strs = new(); mt.ExplodeFragments( strs, - (f, o) => - { + (f, o) => { o.Add(f.Text); return MTextFragmentCallbackStatus.Continue; }); @@ -317,13 +316,13 @@ public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) /// 三点法创建圆(失败则返回Null) /// /// 第一点 - /// 第二点 + /// 第二点 /// 第三点 /// - public static Circle CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) + public static Circle CreateCircle(Point3d pt1, Point3d PointV, Point3d pt3) { - //先判断三点是否共线,得到pt1点指向pt2、pt2点的矢量 - Vector3d va = pt1.GetVectorTo(pt2); + //先判断三点是否共线,得到pt1点指向PointV、PointV点的矢量 + Vector3d va = pt1.GetVectorTo(PointV); Vector3d vb = pt1.GetVectorTo(pt3); //如两矢量夹角为0或180度(π弧度),则三点共线. if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) @@ -333,7 +332,7 @@ public static Circle CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) else { //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(pt1, pt2, pt3); + CircularArc3d geArc = new(pt1, PointV, pt3); geArc.ToCircle(); return geArc.ToCircle(); } @@ -382,8 +381,8 @@ public static void ClipBlockRef(this BlockReference bref, IEnumerable p /// /// 块参照 /// 第一角点 - /// 第二角点 - public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d pt2) + /// 第二角点 + public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d PointV) { if (bref == null) { @@ -391,11 +390,11 @@ public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d p } Matrix3d mat = bref.BlockTransform.Inverse(); pt1 = pt1.TransformBy(mat); - pt2 = pt2.TransformBy(mat); + PointV = PointV.TransformBy(mat); Point2dCollection pts = new() { - new Point2d(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), - new Point2d(Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y)) + new Point2d(Math.Min(pt1.X, PointV.X), Math.Min(pt1.Y, PointV.Y)), + new Point2d(Math.Max(pt1.X, PointV.X), Math.Max(pt1.Y, PointV.Y)) }; SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs index befb4a6..21c5956 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs @@ -13,6 +13,10 @@ namespace IFoxCAD.Cad /// public static class GeometryEx { + public static double DistanceTo(this Point2d pt1, Point2d PointV) + { + return pt1.GetDistanceTo(PointV); + } #region Point&Circle @@ -50,12 +54,12 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi foreach (var node in ptlst.GetNodes()) { var pt1 = node.Value; - var pt2 = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < pt2.Y) + var PointV = node.Next.Value; + if (pt.Y < pt1.Y && pt.Y < PointV.Y) continue; - if (pt1.X < pt.X && pt2.X < pt.X) + if (pt1.X < pt.X && PointV.X < pt.X) continue; - Vector2d vec = pt2 - pt1; + Vector2d vec = PointV - pt1; double t = (pt.X - pt1.X) / vec.X; double y = t * vec.Y + pt1.Y; if (y < pt.Y && t >= 0 && t <= 1) @@ -102,12 +106,12 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi foreach (var node in ptlst.GetNodes()) { var pt1 = node.Value; - var pt2 = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < pt2.Y) + var PointV = node.Next.Value; + if (pt.Y < pt1.Y && pt.Y < PointV.Y) continue; - if (pt1.X < pt.X && pt2.X < pt.X) + if (pt1.X < pt.X && PointV.X < pt.X) continue; - Vector3d vec = pt2 - pt1; + Vector3d vec = PointV - pt1; double t = (pt.X - pt1.X) / vec.X; double y = t * vec.Y + pt1.Y; if (y < pt.Y && t >= 0 && t <= 1) @@ -122,17 +126,17 @@ public static PointOnRegionType PointOnRegion(this IEnumerable pts, Poi /// 按两点返回最小包围圆 /// /// 基准点 - /// 基准点 + /// 基准点 /// 输出圆上的点 /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, out LoopList ptlst) + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, out LoopList ptlst) { - ptlst = new LoopList { pt1, pt2 }; + ptlst = new LoopList { pt1, PointV }; return new CircularArc2d ( - (pt1 + pt2.GetAsVector()) / 2, - pt1.GetDistanceTo(pt2) / 2 + (pt1 + PointV.GetAsVector()) / 2, + pt1.DistanceTo(PointV) / 2 ); } @@ -140,14 +144,14 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, out LoopList< /// 按三点返回最小包围圆 /// /// 基准点 - /// 基准点 + /// 基准点 /// 基准点 /// 输出圆上的点 /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, out LoopList ptlst) + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, out LoopList ptlst) { ptlst = - new LoopList { pt1, pt2, pt3 }; + new LoopList { pt1, PointV, pt3 }; //遍历各点与下一点的向量长度,找到距离最大的两个点 double maxLength; @@ -155,7 +159,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, ptlst.GetNodes().FindByMax ( out maxLength, - node => node.Value.GetDistanceTo(node.Next.Value) + node => node.Value.DistanceTo(node.Next.Value) ); //以两点做最小包围圆 @@ -173,7 +177,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, //否则按三点做圆 ptlst.SetFirst(maxNode); - ca2d = new CircularArc2d(pt1, pt2, pt3); + ca2d = new CircularArc2d(pt1, PointV, pt3); ca2d.SetAngles(0, Math.PI * 2); return ca2d; } @@ -182,15 +186,15 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, /// 按四点返回最小包围圆 /// /// 基准点 - /// 基准点 + /// 基准点 /// 基准点 /// 基准点 /// 输出圆上的点 /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, Point2d pt4, out LoopList ptlst) + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, Point2d pt4, out LoopList ptlst) { LoopList iniptlst = - new LoopList { pt1, pt2, pt3, pt4 }; + new LoopList { pt1, PointV, pt3, pt4 }; ptlst = null; CircularArc2d ca2d = null; @@ -224,22 +228,22 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, /// /// 基准点 /// 第一点 - /// 第二点 + /// 第二点 /// 三点围成的三角形的有向面积 - private static double CalArea(Point2d ptBase, Point2d pt1, Point2d pt2) + private static double CalArea(Point2d ptBase, Point2d pt1, Point2d PointV) { - return (pt2 - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; + return (PointV - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; } /// /// 计算三点围成的三角形的真实面积 /// /// 基准点 /// 第一点 - /// 第二点 + /// 第二点 /// 三点围成的三角形的真实面积 - public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d pt2) + public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d PointV) { - return Math.Abs(CalArea(ptBase, pt1, pt2)); + return Math.Abs(CalArea(ptBase, pt1, PointV)); } /// @@ -247,12 +251,12 @@ public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d pt2) /// /// 基点 /// 第一点 - /// 第二点 + /// 第二点 /// OrientationType 类型值 - public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d pt2) + public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d PointV) { - return CalArea(ptBase, pt1, pt2) switch + return CalArea(ptBase, pt1, PointV) switch { > 0 => OrientationType.CounterClockWise, @@ -385,7 +389,7 @@ public static CircularArc2d GetMinCircle(this List pnts, out LoopList

ca2d.Center.GetDistanceTo(pnt)); + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); //如果最远点属于圆结束 while (!ca2d.IsIn(tpnts[3])) @@ -404,7 +408,7 @@ public static CircularArc2d GetMinCircle(this List pnts, out LoopList

ca2d.Center.GetDistanceTo(pnt)); + .FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); } tpnts[0] = ptlst.First.Value; tpnts[1] = ptlst.First.Next.Value; @@ -413,7 +417,7 @@ public static CircularArc2d GetMinCircle(this List pnts, out LoopList

ca2d.Center.GetDistanceTo(pnt)); + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); } return ca2d; @@ -691,16 +695,16 @@ public static Point3d Point3d(this Point2d pt) { return new Point3d(pt.X, pt.Y, 0); } - + ///

/// 获取两个点之间的中点 /// /// 第一点 - /// 第二点 + /// 第二点 /// 返回两个点之间的中点 - public static Point3d GetMidPointTo(this Point3d pt1, Point3d pt2) + public static Point3d GetMidPointTo(this Point3d pt1, Point3d PointV) { - return new Point3d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5, (pt1.Z + pt2.Z) * 0.5); + return new Point3d((pt1.X + PointV.X) * 0.5, (pt1.Y + PointV.Y) * 0.5, (pt1.Z + PointV.Z) * 0.5); } /// @@ -730,4 +734,4 @@ public static Point3d Polar(this Point3d pt, double ang, double len) return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs index fef501c..2a89bd5 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs @@ -21,7 +21,8 @@ public static class ObjectIdEx /// 打开模式 /// 打开删除对象 /// 指定类型对象 - public static T GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject + public static T GetObject(this ObjectId id, + OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject { tr ??= DBTrans.Top.Transaction; return tr.GetObject(id, mode, openErased) as T; @@ -36,11 +37,12 @@ public static T GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, /// 打开模式 /// 打开删除对象 /// 指定类型对象集合 - public static IEnumerable GetObject(this IEnumerable ids, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject + public static IEnumerable GetObject(this IEnumerable ids, + OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject { return ids.Select(id => id.GetObject(mode, openErased, tr)); } - + /// /// 返回符合类型的对象id /// @@ -50,11 +52,19 @@ public static IEnumerable GetObject(this IEnumerable ids, OpenMo public static IEnumerable OfType(this IEnumerable ids) where T : DBObject { string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return - ids - .Where(id => id.ObjectClass.DxfName == dxfName); + return ids.Where(id => id.ObjectClass().DxfName == dxfName); + } + + //Acad08缺少 id.ObjectClass 如何补偿? + public static RXClass ObjectClass(this ObjectId id) + { +#if NET35 + return RXClass.GetClass(id.GetType()); +#else + return id.ObjectClass; +#endif } #endregion GetObject } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs index e8e91f5..9ede888 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs @@ -7,7 +7,7 @@ namespace IFoxCAD.Cad { - + /// /// 选择集扩展类 /// @@ -47,7 +47,7 @@ public static IEnumerable GetObjectIds(this SelectionSet ss) where return ss .GetObjectIds() - .Where(id => id.ObjectClass.DxfName == dxfName); + .Where(id => id.ObjectClass().DxfName == dxfName); } /// @@ -60,7 +60,7 @@ public static IEnumerable> GetObjectIdGroup(this Sel return ss .GetObjectIds() - .GroupBy(id => id.ObjectClass.DxfName); + .GroupBy(id => id.ObjectClass().DxfName); } #endregion @@ -74,14 +74,14 @@ public static IEnumerable> GetObjectIdGroup(this Sel /// 事务 /// 打开模式 /// 图元集合 - public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode=OpenMode.ForRead, Transaction tr = default) where T : Entity + public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity { return ss .GetObjectIds() .Select(id => tr.GetObject(id, openMode) as T); } - + #endregion #region ForEach @@ -103,4 +103,4 @@ public static void ForEach(this SelectionSet ss, Action action, OpenMode o } #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs index bbeba00..a3f0687 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs @@ -51,8 +51,7 @@ public static ObjectId Rename(this SymbolTable tab { if (table.Has(Oldname)) { - table.Change(Oldname, ly => - { + table.Change(Oldname, ly => { ly.Name = NewName; } ); @@ -102,8 +101,7 @@ public static bool Delete(this SymbolTable table, /// TODO: 需要测试匿名块等特殊的块是否能定义 public static ObjectId Add(this SymbolTable table, string name, Action action = null, Func> ents = null, Func> attdef = null) { - return table.Add(name, btr => - { + return table.Add(name, btr => { action?.Invoke(btr); if (ents is not null) { @@ -206,8 +204,7 @@ public static ObjectId Add(this SymbolTable { return table.Add( name, - ltt => - { + ltt => { ltt.AsciiDescription = description; ltt.PatternLength = length; //线型的总长度 ltt.NumDashes = dash.Length; //组成线型的笔画数目 @@ -238,8 +235,7 @@ public static ObjectId Add(this SymbolTable - { + tstr => { tstr.Name = textStyleName; tstr.FileName = font; tstr.XScale = xscale; diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs index 4f73f65..c66b2e2 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs @@ -27,19 +27,19 @@ public static class SymbolTableRecordEx /// /// 块表记录 /// 实体 - /// 事务管理器 + /// 事务管理器 /// 对象 id - public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Transaction trans = null) + public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Transaction tr = null) { if (entity is null) - throw new ArgumentNullException(nameof(entity),"对象为 null"); + throw new ArgumentNullException(nameof(entity), "对象为 null"); ObjectId id; - trans ??= DBTrans.Top.Transaction; + tr ??= DBTrans.Top.Transaction; using (btr.ForWrite()) { id = btr.AppendEntity(entity); - trans.AddNewlyCreatedDBObject(entity, true); + tr.AddNewlyCreatedDBObject(entity, true); } return id; } @@ -52,20 +52,19 @@ public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Trans /// 事务 /// 实体集合 /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, Transaction trans = null) where T : Entity + public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, Transaction tr = null) where T : Entity { - if (ents.Any(ent => ent is null)) + if (ents.Any(ent => ent is null)) throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); - trans ??= DBTrans.Top.Transaction; + tr ??= DBTrans.Top.Transaction; using (btr.ForWrite()) { return ents .Select( - ent => - { + ent => { ObjectId id = btr.AppendEntity(ent); - trans.AddNewlyCreatedDBObject(ent, true); + tr.AddNewlyCreatedDBObject(ent, true); return id; }) .ToList(); @@ -180,13 +179,13 @@ public static ObjectId AddPline(this BlockTableRecord btr, List pts, Li /// 轻多段线id public static ObjectId AddPline(this BlockTableRecord btr, List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, Action action = default, Transaction trans = default) { - + Polyline pl = new(); pts.ForEach((i, vertex) => { pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); }); - + return btr.AddEnt(pl, action, trans); } #endif @@ -238,7 +237,7 @@ public static IEnumerable GetObjectIds(this BlockTableRecord btr) w { string dxfName = RXClass.GetClass(typeof(T)).DxfName; return btr.Cast() - .Where(id => id.ObjectClass.DxfName == dxfName); + .Where(id => id.ObjectClass().DxfName == dxfName); } /// @@ -251,7 +250,7 @@ public static IEnumerable> GetObjectIds(this BlockTa return btr .Cast() - .GroupBy(id => id.ObjectClass.DxfName); + .GroupBy(id => id.ObjectClass().DxfName); } /// @@ -306,9 +305,9 @@ public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point ObjectId blockId, Scale3d scale = default, double rotation = default, - Dictionary atts = default, Transaction trans = null) + Dictionary atts = default, Transaction tr = null) { - trans ??= DBTrans.Top.Transaction; + tr ??= DBTrans.Top.Transaction; if (!DBTrans.Top.BlockTable.Has(blockId)) { DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{DBTrans.Top.GetObject(blockId).Name}的块定义。"); @@ -335,12 +334,10 @@ public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); attref.AdjustAlignment(DBTrans.Top.Database); if (atts.ContainsKey(attdef.Tag)) - { attref.TextString = atts[attdef.Tag]; - } blockref.AttributeCollection.AppendAttribute(attref); - trans.AddNewlyCreatedDBObject(attref, true); + tr.AddNewlyCreatedDBObject(attref, true); } } } diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index b28ee58..9227301 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -1,5 +1,4 @@ - - + net35;net40 true @@ -22,12 +21,10 @@ - - runtime - + - + runtime @@ -42,8 +39,8 @@ $(Configuration);ac2013 - - + + True diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data index e69de29..5f28270 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs index 9acad02..0aa2f69 100644 --- a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs +++ b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs @@ -69,4 +69,4 @@ public override List Value #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ResultData/LispList.cs b/src/IFoxCAD.Cad/ResultData/LispList.cs index 8f87c2f..4742d38 100644 --- a/src/IFoxCAD.Cad/ResultData/LispList.cs +++ b/src/IFoxCAD.Cad/ResultData/LispList.cs @@ -199,4 +199,4 @@ public void Add(LispList value) public static implicit operator LispList(TypedValue[] values) => new(values); #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs index c82d464..2fccce5 100644 --- a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs +++ b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs @@ -66,4 +66,4 @@ public override string ToString() } #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs index 1aa2304..0364f9a 100644 --- a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs @@ -62,4 +62,4 @@ public void Add(DxfCode code, object obj) public static implicit operator XRecordDataList(TypedValue[] values) => new(values); #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/ResultData/XdataList.cs b/src/IFoxCAD.Cad/ResultData/XdataList.cs index c31f53c..e22ffe2 100644 --- a/src/IFoxCAD.Cad/ResultData/XdataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XdataList.cs @@ -16,7 +16,7 @@ public XDataList() } public XDataList(IEnumerable values) : base(values) { } - + #region 添加数据 /// /// 添加数据 @@ -68,4 +68,4 @@ public void Add(DxfCode code, object obj) public static implicit operator XDataList(TypedValue[] values) => new(values); #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs index 4e33b58..a0f977c 100644 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs @@ -127,4 +127,4 @@ public override string ToString() $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs index 5974800..62b70f8 100644 --- a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs +++ b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs @@ -33,4 +33,4 @@ public struct AssemInfo /// public string Description { get; set; } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs index 0042dd2..1852003 100644 --- a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs +++ b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs @@ -112,9 +112,9 @@ public void RegApp() appkey.Close(); } -#endregion RegApp + #endregion RegApp -#region IExtensionApplication 成员 + #region IExtensionApplication 成员 /// /// 初始化函数 @@ -126,6 +126,6 @@ public void RegApp() /// public abstract void Terminate(); -#endregion IExtensionApplication 成员 + #endregion IExtensionApplication 成员 } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/Runtime/DBTrans.cs b/src/IFoxCAD.Cad/Runtime/DBTrans.cs index 9175048..555153b 100644 --- a/src/IFoxCAD.Cad/Runtime/DBTrans.cs +++ b/src/IFoxCAD.Cad/Runtime/DBTrans.cs @@ -1,14 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; - - -using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.Geometry; using System.IO; namespace IFoxCAD.Cad @@ -81,7 +75,7 @@ public DBTrans(Document doc = null, bool commit = true, bool doclock = false) public DBTrans(Database database, bool commit = true) { Database = database; - Init(commit,false); + Init(commit, false); } /// /// 构造函数,打开文件,默认提交事务 @@ -93,7 +87,7 @@ public DBTrans(string fileName, bool commit = true) Database = new Database(false, true); Database.ReadDwgFile(fileName, FileShare.Read, true, null); Database.CloseInput(true); - Init(commit,false); + Init(commit, false); } /// /// 初始化事务及事务队列、提交模式 @@ -251,7 +245,7 @@ public T GetObject(ObjectId id, } -#endregion + #endregion @@ -272,7 +266,7 @@ public void Commit() { Abort(); } - + } protected virtual void Dispose(bool disposing) diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/IFoxCAD.Cad/Runtime/Env.cs index e6cf84e..d2919ca 100644 --- a/src/IFoxCAD.Cad/Runtime/Env.cs +++ b/src/IFoxCAD.Cad/Runtime/Env.cs @@ -1,4 +1,4 @@ -using Autodesk.AutoCAD.ApplicationServices; +using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.GraphicsSystem; @@ -443,4 +443,4 @@ public static void SetEnv(string var, string? value) #nullable disable #endregion } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs index 374ab58..b141835 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs @@ -1,14 +1,14 @@ -using System; +using System; using System.Collections; using System.Collections.Generic; -using System.Linq; +using System.Linq; using Autodesk.AutoCAD.DatabaseServices; namespace IFoxCAD.Cad { - public class SymbolTable : IEnumerable - where TTable : SymbolTable + public class SymbolTable : IEnumerable + where TTable : SymbolTable where TRecord : SymbolTableRecord, new() - { + { #region 程序集内部属性 /// /// 事务管理器 @@ -50,7 +50,7 @@ internal SymbolTable(DBTrans tr, ObjectId tableId) /// 对象的id public ObjectId this[string key] { - get + get { if (Has(key)) { @@ -146,7 +146,7 @@ public void Remove(string name) { Remove(record); } - + } /// /// 删除符号表记录 @@ -273,7 +273,7 @@ public ObjectId GetRecordFrom(SymbolTable table, string name, b { ObjectId id = table[name]; using IdMapping idm = new(); - using (ObjectIdCollection ids = new(){ id }) + using (ObjectIdCollection ids = new() { id }) { table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs index 4e1fb16..567be46 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs @@ -80,4 +80,4 @@ public override IEnumerable GetValues() yield return Value; } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs index dac84d5..76dc738 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs @@ -87,4 +87,4 @@ public void SetValue(int code, object value) Value = new TypedValue(code, value); } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs index 23ef25b..cd73129 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs @@ -339,4 +339,4 @@ private static Op GetCompOp(string content, Op left, object right) #endregion Operator } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj index c23b004..8690b9e 100644 --- a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj +++ b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj @@ -1,7 +1,8 @@  - net45 + + NET45;NET46;NET47;NET48 true true true @@ -21,18 +22,18 @@ - DEBUG;TRACE + DEBUG;TRACE - + - - True - - + + True + + diff --git a/tests/DBTrans.test/DBTrans.test.csproj b/tests/DBTrans.test/DBTrans.test.csproj index a6f4773..d856f39 100644 --- a/tests/DBTrans.test/DBTrans.test.csproj +++ b/tests/DBTrans.test/DBTrans.test.csproj @@ -1,10 +1,18 @@  - net45 + + preview + + 1.0.0.* + 1.0.0.0 + False + git + + + NET45;NET46;NET47;NET48 true true - preview @@ -12,4 +20,5 @@ + diff --git a/tests/DBTrans.test/Test.cs b/tests/DBTrans.test/Test.cs index e942ff4..4f7265c 100644 --- a/tests/DBTrans.test/Test.cs +++ b/tests/DBTrans.test/Test.cs @@ -1,24 +1,17 @@ using System; - using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Diagnostics; -using System.Collections; +using System.IO; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using Autodesk.AutoCAD.Geometry; using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Internal; +using Autodesk.AutoCAD.Colors; using IFoxCAD.Cad; -using Autodesk.AutoCAD.Colors; -using IFoxCAD.WPF; using test.wpf; -using System.IO; namespace test { @@ -45,7 +38,7 @@ public void Dbtest() Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); //var lienid = tr.AddEntity(line); //var cirid = tr.AddEntity(circle); - //var linent = tr.GetObject(lienid); + //var linent = tr.GetObject(lienid); //var lineent = tr.GetObject(cirid); //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null //var dd = tr.GetObject(lienid); @@ -70,8 +63,8 @@ public void drawarc() { using var tr = new DBTrans(); Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 - Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2);//起点,圆心,弧度 - Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0));//起点,圆上一点,终点 + Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 + Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 tr.CurrentSpace.AddEntity(arc1, arc2, arc3); tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 } @@ -80,7 +73,7 @@ public void drawarc() public void draCircle() { using var tr = new DBTrans(); - Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0));//起点,终点 + Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 tr.CurrentSpace.AddEntity(circle1, circle2); @@ -102,16 +95,14 @@ public void Layertest() { using var tr = new DBTrans(); tr.LayerTable.Add("1"); - tr.LayerTable.Add("2", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + tr.LayerTable.Add("2", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); lt.LineWeight = LineWeight.LineWeight030; }); tr.LayerTable.Remove("3"); tr.LayerTable.Delete("0"); - tr.LayerTable.Change("4", lt => - { + tr.LayerTable.Change("4", lt => { lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); }); } @@ -138,11 +129,11 @@ public void Layertest2() public void LayerDel() { using var tr = new DBTrans(); - Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 + Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints - Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString());//删除不存在的图层 1 - Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString());//删除有图元的图层 2 - Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString());//删除图层 3 + Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 + Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 + Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 tr.LayerTable.Remove("2"); //测试是否能强制删除 } @@ -213,8 +204,7 @@ public void BlockDef() using var tr = new DBTrans(); //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); tr.BlockTable.Add("test", - btr => - { + btr => { btr.Origin = new Point3d(0, 0, 0); }, () => //图元 @@ -237,8 +227,7 @@ public void BlockDefChange() { using var tr = new DBTrans(); //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Change("test", btr => - { + tr.BlockTable.Change("test", btr => { btr.Origin = new Point3d(5, 5, 0); btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); btr.GetEntities() @@ -280,7 +269,7 @@ public void InsertBlockDef() { "tagTest3", "1" }, { "tagTest4", "" } }; - tr.CurrentSpace.InsertBlock(new Point3d(10,10, 0), "test2", atts: def2); + tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); } @@ -289,8 +278,7 @@ public void TestClipBlock() { using var tr = new DBTrans(); tr.BlockTable.Add("test1", - btr => - { + btr => { btr.Origin = new Point3d(0, 0, 0); btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) @@ -305,7 +293,7 @@ public void TestClipBlock() var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); var bref1 = tr.GetObject(id); - + bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); } diff --git a/tests/DBTrans.test/testenv.cs b/tests/DBTrans.test/testenv.cs index e2513b5..c665641 100644 --- a/tests/DBTrans.test/testenv.cs +++ b/tests/DBTrans.test/testenv.cs @@ -1,11 +1,5 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using IFoxCAD.Cad; +using IFoxCAD.Cad; using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.ApplicationServices; namespace test { @@ -14,16 +8,12 @@ public class testenv [CommandMethod("testenum")] public void testenum() { - Env.CmdEcho = true; - } [CommandMethod("testenum1")] public void testenum1() { - Env.CmdEcho = false; - } [CommandMethod("testdimblk")] diff --git a/tests/DBTrans.test/testselectfilter.cs b/tests/DBTrans.test/testselectfilter.cs index 1224a2f..9b658ee 100644 --- a/tests/DBTrans.test/testselectfilter.cs +++ b/tests/DBTrans.test/testselectfilter.cs @@ -17,13 +17,13 @@ public class testselectfilter [CommandMethod("testfilter")] public void testfilter() { - + var p = new Point3d(10, 10, 0); var f = OpFilter.Bulid( - e =>!(e.Dxf(0) == "line" & e.Dxf(8) == "0") + e => !(e.Dxf(0) == "line" & e.Dxf(8) == "0") | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); - - + + var f2 = OpFilter.Bulid( e => e.Or( !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), @@ -39,10 +39,7 @@ public void testfilter() [CommandMethod("testselectanpoint")] public void testselectanpoint() { - - var sel2 = Env.Editor.SelectAtPoint(new Point3d(0, 0, 0)); - Env.Editor.WriteMessage(""); } } -- Gitee From f41578b79805257e3e33a20d55fcb5ec3683a48e Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Thu, 2 Dec 2021 02:45:17 +0800 Subject: [PATCH 02/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9csproj=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.en.md | 36 - src/IFoxCAD.Basal/IFoxCAD.Basal.csproj | 66 +- src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 2 +- tests/DBTrans.test/Test.cs | 974 ++++++++++---------- 4 files changed, 526 insertions(+), 552 deletions(-) delete mode 100644 README.en.md diff --git a/README.en.md b/README.en.md deleted file mode 100644 index 7c3d2c4..0000000 --- a/README.en.md +++ /dev/null @@ -1,36 +0,0 @@ -# IFoxCAD - -#### Description -基于.NET的Cad二次开发类库 - -#### Software Architecture -Software architecture description - -#### Installation - -1. xxxx -2. xxxx -3. xxxx - -#### Instructions - -1. xxxx -2. xxxx -3. xxxx - -#### Contribution - -1. Fork the repository -2. Create Feat_xxx branch -3. Commit your code -4. Create Pull Request - - -#### Gitee Feature - -1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md -2. Gitee blog [blog.gitee.com](https://blog.gitee.com) -3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore) -4. The most valuable open source project [GVP](https://gitee.com/gvp) -5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help) -6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/) diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj index 8ae4570..b3cec15 100644 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj @@ -1,35 +1,43 @@  - - net35;net40 - true - 0.1.1 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的二次开发基本类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;C#;NET;Common;Basal - 增加在net35支持 - true - true - preview - true - LICENSE - true - + + + preview + + 1.0.0.* + 1.0.0.0 + False + net35;net40 + 0.1.1 - - - + true + true + InspireFunction + xsfhlzh;vicwjb + 基于.NET的二次开发基本类库 + InspireFunction + git + https://gitee.com/inspirefunction/ifoxcad.git - - - True - - - + https://gitee.com/inspirefunction/ifoxcad + IFoxCAD;C#;NET;Common;Basal + 增加在net35支持 + true + true + + LICENSE + true + + + + + + + + + True + + + diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index f7234c8..2175194 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -638,7 +638,7 @@ public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) public static void ZoomObject(this Editor ed, Entity ent, double offsetDist = 0.00) { Extents3d ext = ent.GeometricExtents; - ed.ZoomWindow(ext.MinPoint, ext.MinPoint, offsetDist); + ed.ZoomWindow(ext.MinPoint, ext.MaxPoint, offsetDist); } #endregion diff --git a/tests/DBTrans.test/Test.cs b/tests/DBTrans.test/Test.cs index c05b5c5..c261a33 100644 --- a/tests/DBTrans.test/Test.cs +++ b/tests/DBTrans.test/Test.cs @@ -14,181 +14,181 @@ using test.wpf; -namespace test; - -public class Test +namespace test { - [CommandMethod("dbtest")] - public void Dbtest() + public class Test { - using var tr = new DBTrans(); - tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); - tr.Editor.WriteMessage("\n----------开始测试--------------"); - tr.Editor.WriteMessage("\n测试document属性是否工作"); - if (tr.Document == Getdoc()) + [CommandMethod("dbtest")] + public void Dbtest() { - tr.Editor.WriteMessage("\ndocument 正常"); + using var tr = new DBTrans(); + tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); + tr.Editor.WriteMessage("\n----------开始测试--------------"); + tr.Editor.WriteMessage("\n测试document属性是否工作"); + if (tr.Document == Getdoc()) + { + tr.Editor.WriteMessage("\ndocument 正常"); + } + tr.Editor.WriteMessage("\n测试database属性是否工作"); + if (tr.Database == Getdb()) + { + tr.Editor.WriteMessage("\ndatabase 正常"); + } + + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); + //var lienid = tr.AddEntity(line); + //var cirid = tr.AddEntity(circle); + //var linent = tr.GetObject(lienid); + //var lineent = tr.GetObject(cirid); + //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null + //var dd = tr.GetObject(lienid); + //List ds = new() { linee, dd }; } - tr.Editor.WriteMessage("\n测试database属性是否工作"); - if (tr.Database == Getdb()) + + //add entity test + [CommandMethod("addent")] + public void Addent() { - tr.Editor.WriteMessage("\ndatabase 正常"); + using var tr = new DBTrans(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); + tr.ModelSpace.AddEntity(line1); + Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); + tr.PaperSpace.AddEntity(line2); } - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); - //var lienid = tr.AddEntity(line); - //var cirid = tr.AddEntity(circle); - //var linent = tr.GetObject(lienid); - //var lineent = tr.GetObject(cirid); - //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null - //var dd = tr.GetObject(lienid); - //List ds = new() { linee, dd }; - } - - //add entity test - [CommandMethod("addent")] - public void Addent() - { - using var tr = new DBTrans(); - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line); - Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); - tr.ModelSpace.AddEntity(line1); - Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); - tr.PaperSpace.AddEntity(line2); - } - - [CommandMethod("drawarc")] - public void drawarc() - { - using var tr = new DBTrans(); - Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 - Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2);//起点,圆心,弧度 - Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0));//起点,圆上一点,终点 - tr.CurrentSpace.AddEntity(arc1, arc2, arc3); - tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 - } + [CommandMethod("drawarc")] + public void drawarc() + { + using var tr = new DBTrans(); + Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 + Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 + Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 + tr.CurrentSpace.AddEntity(arc1, arc2, arc3); + tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 + } - [CommandMethod("drawcircle")] - public void draCircle() - { - using var tr = new DBTrans(); - Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0));//起点,终点 - Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 - Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 - tr.CurrentSpace.AddEntity(circle1, circle2); - if (circle3 is not null) + [CommandMethod("drawcircle")] + public void draCircle() { + using var tr = new DBTrans(); + Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 + Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 + Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 + tr.CurrentSpace.AddEntity(circle1, circle2); + if (circle3 is not null) + { + tr.CurrentSpace.AddEntity(circle3); + } + else + { + tr.Editor.WriteMessage("三点画圆失败"); + } tr.CurrentSpace.AddEntity(circle3); + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) } - else + + [CommandMethod("layertest")] + public void Layertest() { - tr.Editor.WriteMessage("三点画圆失败"); + using var tr = new DBTrans(); + tr.LayerTable.Add("1"); + tr.LayerTable.Add("2", lt => + { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + lt.LineWeight = LineWeight.LineWeight030; + + }); + tr.LayerTable.Remove("3"); + tr.LayerTable.Delete("0"); + tr.LayerTable.Change("4", lt => + { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); + }); } - tr.CurrentSpace.AddEntity(circle3); - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) - } - [CommandMethod("layertest")] - public void Layertest() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("1"); - tr.LayerTable.Add("2", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); - lt.LineWeight = LineWeight.LineWeight030; - }); - tr.LayerTable.Remove("3"); - tr.LayerTable.Delete("0"); - tr.LayerTable.Change("4", lt => + //添加图层 + [CommandMethod("layerAdd1")] + public void Layertest1() { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); - }); - } - + using var tr = new DBTrans(); + tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); + } - //添加图层 - [CommandMethod("layerAdd1")] - public void Layertest1() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); - } + //添加图层 + [CommandMethod("layerAdd2")] + public void Layertest2() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("test2", 2); + //tr.LayerTable["3"] = new LayerTableRecord(); + } + //删除图层 + [CommandMethod("layerdel")] + public void LayerDel() + { + using var tr = new DBTrans(); + Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 + Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints + Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 + Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 + Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 - //添加图层 - [CommandMethod("layerAdd2")] - public void Layertest2() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test2", 2); - //tr.LayerTable["3"] = new LayerTableRecord(); - } - //删除图层 - [CommandMethod("layerdel")] - public void LayerDel() - { - using var tr = new DBTrans(); - Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 - Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints - Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString());//删除不存在的图层 1 - Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString());//删除有图元的图层 2 - Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString());//删除图层 3 - - tr.LayerTable.Remove("2"); //测试是否能强制删除 - } + tr.LayerTable.Remove("2"); //测试是否能强制删除 + } - //添加直线 - [CommandMethod("linedemo1")] - public void AddLine1() - { - using var tr = new DBTrans(); - // tr.ModelSpace.AddEnt(line); - // tr.ModelSpace.AddEnts(line,circle); - - // tr.PaperSpace.AddEnt(line); - // tr.PaperSpace.AddEnts(line,circle); - - // tr.addent(btr,line); - // tr.addents(btr,line,circle); - - - // tr.BlockTable.Add(new BlockTableRecord(), line => - // { - // line. - // }); - Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); - Circle circle = new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); - tr.CurrentSpace.AddEntity(line1); - tr.CurrentSpace.AddEntity(line2, line3, circle); - } + //添加直线 + [CommandMethod("linedemo1")] + public void AddLine1() + { + using var tr = new DBTrans(); + // tr.ModelSpace.AddEnt(line); + // tr.ModelSpace.AddEnts(line,circle); + + // tr.PaperSpace.AddEnt(line); + // tr.PaperSpace.AddEnts(line,circle); + + // tr.addent(btr,line); + // tr.addents(btr,line,circle); + + + // tr.BlockTable.Add(new BlockTableRecord(), line => + // { + // line. + // }); + Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); + Circle circle = new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); + tr.CurrentSpace.AddEntity(line1); + tr.CurrentSpace.AddEntity(line2, line3, circle); + } - //增加多段线1 - [CommandMethod("Pldemo1")] - public void AddPolyline1() - { - using var tr = new DBTrans(); - Polyline pl = new Polyline(); - pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); - pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); - pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); - pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); - pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); - pl.Closed = true; - pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); - tr.CurrentSpace.AddEntity(pl); - } + //增加多段线1 + [CommandMethod("Pldemo1")] + public void AddPolyline1() + { + using var tr = new DBTrans(); + Polyline pl = new Polyline(); + pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); + pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); + pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); + pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); + pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); + pl.Closed = true; + pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); + tr.CurrentSpace.AddEntity(pl); + } - //增加多段线2 - [CommandMethod("pldemo2")] - public void Addpl2() - { - var pts = new List<(Point3d, double, double, double)> + //增加多段线2 + [CommandMethod("pldemo2")] + public void Addpl2() + { + var pts = new List<(Point3d, double, double, double)> { (new Point3d(0,0,0),0,0,0), (new Point3d(10,0,0),0,0,0), @@ -196,154 +196,154 @@ public void Addpl2() (new Point3d(0,10,0),0,0,0), (new Point3d(5,5,0),0,0,0) }; - using var tr = new DBTrans(); - tr.CurrentSpace.AddPline(pts); - } + using var tr = new DBTrans(); + tr.CurrentSpace.AddPline(pts); + } - //块定义 - [CommandMethod("blockdef")] - public void BlockDef() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Add("test", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - }, - () => //图元 - { - return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; - }, - () => //属性定义 - { - var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; - var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; - return new List { id1, id2 }; - } - ); - //ObjectId objectId = tr.BlockTable.Add("a");//新建块 - //objectId.GetObject().AddEntity();//测试添加空实体 - } - //修改块定义 - [CommandMethod("blockdefchange")] - public void BlockDefChange() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Change("test", btr => + //块定义 + [CommandMethod("blockdef")] + public void BlockDef() { - btr.Origin = new Point3d(5, 5, 0); - btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); - btr.GetEntities() - .ToList() - .ForEach(e => e.Flush()); //刷新块显示 - - }); - tr.Editor.Regen(); - } + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Add("test", + btr => + { + btr.Origin = new Point3d(0, 0, 0); + }, + () => //图元 + { + return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; + }, + () => //属性定义 + { + var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; + var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; + return new List { id1, id2 }; + } + ); + //ObjectId objectId = tr.BlockTable.Add("a");//新建块 + //objectId.GetObject().AddEntity();//测试添加空实体 + } + //修改块定义 + [CommandMethod("blockdefchange")] + public void BlockDefChange() + { + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Change("test", btr => + { + btr.Origin = new Point3d(5, 5, 0); + btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); + btr.GetEntities() + .ToList() + .ForEach(e => e.Flush()); //刷新块显示 + + }); + tr.Editor.Regen(); + } - [CommandMethod("insertblockdef")] - public void InsertBlockDef() - { - using var tr = new DBTrans(); - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; - var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; - tr.BlockTable.Add("test1", line1, line2, att1, att2); - - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - - - var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); - var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); - var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; - var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; - tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 - //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 - - var def1 = new Dictionary + [CommandMethod("insertblockdef")] + public void InsertBlockDef() + { + using var tr = new DBTrans(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + tr.BlockTable.Add("test1", line1, line2, att1, att2); + + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + + + var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); + var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); + var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; + var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; + tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 + //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 + + var def1 = new Dictionary { { "tagTest1", "1" }, { "tagTest2", "2" } }; - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); - var def2 = new Dictionary + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); + var def2 = new Dictionary { { "tagTest3", "1" }, { "tagTest4", "" } }; - tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); - tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); - } - - [CommandMethod("testblocknullbug")] - public void TestBlockNullBug() - { - using var tr = new DBTrans(); - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); - } + tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); + tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); + } + [CommandMethod("testblocknullbug")] + public void TestBlockNullBug() + { + using var tr = new DBTrans(); - [CommandMethod("testclip")] - public void TestClipBlock() - { - using var tr = new DBTrans(); - tr.BlockTable.Add("test1", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), - new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) - ); - } - ); - //tr.BlockTable.Add("hah"); - var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); - var bref = tr.GetObject(id); - var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; - bref.ClipBlockRef(pts); + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); + } - var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); - var bref1 = tr.GetObject(id); - bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); - } + [CommandMethod("testclip")] + public void TestClipBlock() + { + using var tr = new DBTrans(); + tr.BlockTable.Add("test1", + btr => + { + btr.Origin = new Point3d(0, 0, 0); + btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), + new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) + ); + } + ); + //tr.BlockTable.Add("hah"); + var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); + var bref = tr.GetObject(id); + var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; + bref.ClipBlockRef(pts); + var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); + var bref1 = tr.GetObject(id); + bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); + } - // 测试扩展数据 - [CommandMethod("addxdata")] - public void AddXdata() - { - using var tr = new DBTrans(); - var appname = "myapp"; - tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 - tr.RegAppTable.Add("myapp2"); - var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) + // 测试扩展数据 + [CommandMethod("addxdata")] + public void AddXdata() { - XData = new XDataList() + using var tr = new DBTrans(); + var appname = "myapp"; + + tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 + tr.RegAppTable.Add("myapp2"); + + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) + { + XData = new XDataList() { { DxfCode.ExtendedDataRegAppName, appname }, //可以用dxfcode和int表示组码 { DxfCode.ExtendedDataAsciiString, "hahhahah" }, @@ -352,276 +352,278 @@ public void AddXdata() { DxfCode.ExtendedDataAsciiString, "hahhahah" }, {1070, 12 } } - }; - - tr.CurrentSpace.AddEntity(line); - } + }; - [CommandMethod("getxdata")] - public void GetXdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; + tr.CurrentSpace.AddEntity(line); + } - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) + [CommandMethod("getxdata")] + public void GetXdata() { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId).XData; - ed.WriteMessage(data.ToString()); + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) + { + using var tr = new DBTrans(); + var data = tr.GetObject(res.ObjectId).XData; + ed.WriteMessage(data.ToString()); + } } - } - [CommandMethod("changexdata")] - public void Changexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) + [CommandMethod("changexdata")] + public void Changexdata() { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) + { + using var tr = new DBTrans(); + var data = tr.GetObject(res.ObjectId); + data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); - ed.WriteMessage(data.XData.ToString()); + ed.WriteMessage(data.XData.ToString()); + } } - } - [CommandMethod("removexdata")] - public void Removexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) + [CommandMethod("removexdata")] + public void Removexdata() { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) + { + using var tr = new DBTrans(); + var data = tr.GetObject(res.ObjectId); + data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); - ed.WriteMessage(data.XData.ToString()); + ed.WriteMessage(data.XData.ToString()); + } } - } - [CommandMethod("PrintLayerName")] - public void PrintLayerName() - { - using var tr = new DBTrans(); - foreach (var layerRecord in tr.LayerTable.GetRecords()) + [CommandMethod("PrintLayerName")] + public void PrintLayerName() { - tr.Editor.WriteMessage(layerRecord.Name); - } - - } + using var tr = new DBTrans(); + foreach (var layerRecord in tr.LayerTable.GetRecords()) + { + tr.Editor.WriteMessage(layerRecord.Name); + } + } - [CommandMethod("testwpf")] - public void TestWPf() - { - var test = new TestView(); - Application.ShowModalWindow(test); - } + [CommandMethod("testwpf")] + public void TestWPf() + { - [CommandMethod("testpt")] - public void TestPt() - { - //var pt = Env.Editor.GetPoint("pick pt:").Value; - //var pl = Env.Editor.GetEntity("pick pl").ObjectId; - var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - using var tr2 = new DBTrans(); - var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr2.Transaction == tr3); - Env.Print(tr3 == tr6); - using var tr4 = new DBTrans(); - var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr4.Transaction == tr5); - Env.Print(tr5 == tr7); - var trm = HostApplicationServices.WorkingDatabase.TransactionManager; - //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); - //var pt1 = new Point3d(0, 0.00000000000001, 0); - //var pt2 = new Point3d(0, 0.00001, 0); - //Env.Print(Tolerance.Global.EqualPoint); - //Env.Print(pt1.IsEqualTo(pt2).ToString()); - //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); - //Env.Print((pt1 == pt2).ToString()); - //Env.Print((pt1 != pt2).ToString()); + var test = new TestView(); + Application.ShowModalWindow(test); + } + [CommandMethod("testpt")] + public void TestPt() + { + //var pt = Env.Editor.GetPoint("pick pt:").Value; + //var pl = Env.Editor.GetEntity("pick pl").ObjectId; + var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + using var tr2 = new DBTrans(); + var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr2.Transaction == tr3); + Env.Print(tr3 == tr6); + using var tr4 = new DBTrans(); + var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr4.Transaction == tr5); + Env.Print(tr5 == tr7); + var trm = HostApplicationServices.WorkingDatabase.TransactionManager; + //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); + //var pt1 = new Point3d(0, 0.00000000000001, 0); + //var pt2 = new Point3d(0, 0.00001, 0); + //Env.Print(Tolerance.Global.EqualPoint); + //Env.Print(pt1.IsEqualTo(pt2).ToString()); + //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); + //Env.Print((pt1 == pt2).ToString()); + //Env.Print((pt1 != pt2).ToString()); - } + } - public Database Getdb() - { - var db = Application.DocumentManager.MdiActiveDocument.Database; - return db; - } + public Database Getdb() + { + var db = Application.DocumentManager.MdiActiveDocument.Database; + return db; + } - public Document Getdoc() - { - var doc = Application.DocumentManager.MdiActiveDocument; - return doc; - } - //public override void Initialize() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); - //} + public Document Getdoc() + { + var doc = Application.DocumentManager.MdiActiveDocument; + return doc; + } - //public override void Terminate() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nunload...."); - //} -} + //public override void Initialize() + //{ + // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); + //} + //public override void Terminate() + //{ + // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nunload...."); + //} + } -public class BlockImportClass -{ - [CommandMethod("CBLL")] - public void cbll() + public class BlockImportClass { - string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; - using var tr = new DBTrans(); - using var tr1 = new DBTrans(filename); - //tr.BlockTable.GetBlockFrom(filename, true); - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); - tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 - } + [CommandMethod("CBLL")] + public void cbll() + { + string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; + using var tr = new DBTrans(); + using var tr1 = new DBTrans(filename); + //tr.BlockTable.GetBlockFrom(filename, true); + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); + tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 + } - [CommandMethod("CBL")] - public void CombineBlocksIntoLibrary() - { - Document doc = - Application.DocumentManager.MdiActiveDocument; - Editor ed = doc.Editor; - Database destDb = doc.Database; - // Get name of folder from which to load and import blocks + [CommandMethod("CBL")] + public void CombineBlocksIntoLibrary() + { + Document doc = + Application.DocumentManager.MdiActiveDocument; + Editor ed = doc.Editor; + Database destDb = doc.Database; - PromptResult pr = - ed.GetString("\nEnter the folder of source drawings: "); + // Get name of folder from which to load and import blocks - if (pr.Status != PromptStatus.OK) - return; - string pathName = pr.StringResult; + PromptResult pr = + ed.GetString("\nEnter the folder of source drawings: "); - // Check the folder exists + if (pr.Status != PromptStatus.OK) + return; + string pathName = pr.StringResult; - if (!Directory.Exists(pathName)) - { - ed.WriteMessage( - "\nDirectory does not exist: {0}", pathName - ); - return; - } + // Check the folder exists - // Get the names of our DWG files in that folder + if (!Directory.Exists(pathName)) + { + ed.WriteMessage( + "\nDirectory does not exist: {0}", pathName + ); + return; + } - string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); + // Get the names of our DWG files in that folder - // A counter for the files we've imported + string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); - int imported = 0, failed = 0; + // A counter for the files we've imported - // For each file in our list + int imported = 0, failed = 0; - foreach (string fileName in fileNames) - { - // Double-check we have a DWG file (probably unnecessary) + // For each file in our list - if (fileName.EndsWith( - ".dwg", - StringComparison.InvariantCultureIgnoreCase - ) - ) + foreach (string fileName in fileNames) { - // Catch exceptions at the file level to allow skipping + // Double-check we have a DWG file (probably unnecessary) - try + if (fileName.EndsWith( + ".dwg", + StringComparison.InvariantCultureIgnoreCase + ) + ) { - // Suggestion from Thorsten Meinecke... + // Catch exceptions at the file level to allow skipping - string destName = - SymbolUtilityServices.GetSymbolNameFromPathName( - fileName, "dwg" - ); + try + { + // Suggestion from Thorsten Meinecke... - // And from Dan Glassman... + string destName = + SymbolUtilityServices.GetSymbolNameFromPathName( + fileName, "dwg" + ); - destName = - SymbolUtilityServices.RepairSymbolName( - destName, false - ); + // And from Dan Glassman... - // Create a source database to load the DWG into + destName = + SymbolUtilityServices.RepairSymbolName( + destName, false + ); - using (Database db = new Database(false, true)) - { - // Read the DWG into our side database + // Create a source database to load the DWG into - db.ReadDwgFile(fileName, FileShare.Read, true, ""); - bool isAnno = db.AnnotativeDwg; + using (Database db = new Database(false, true)) + { + // Read the DWG into our side database - // Insert it into the destination database as - // a named block definition + db.ReadDwgFile(fileName, FileShare.Read, true, ""); + bool isAnno = db.AnnotativeDwg; - ObjectId btrId = destDb.Insert( - destName, - db, - false - ); + // Insert it into the destination database as + // a named block definition - if (isAnno) - { - // If an annotative block, open the resultant BTR - // and set its annotative definition status + ObjectId btrId = destDb.Insert( + destName, + db, + false + ); - Transaction tr = - destDb.TransactionManager.StartTransaction(); - using (tr) + if (isAnno) { - BlockTableRecord btr = - (BlockTableRecord)tr.GetObject( - btrId, - OpenMode.ForWrite - ); - btr.Annotative = AnnotativeStates.True; - tr.Commit(); + // If an annotative block, open the resultant BTR + // and set its annotative definition status + + Transaction tr = + destDb.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + btrId, + OpenMode.ForWrite + ); + btr.Annotative = AnnotativeStates.True; + tr.Commit(); + } } - } - // Print message and increment imported block counter + // Print message and increment imported block counter - ed.WriteMessage("\nImported from \"{0}\".", fileName); - imported++; + ed.WriteMessage("\nImported from \"{0}\".", fileName); + imported++; + } + } + catch (System.Exception ex) + { + ed.WriteMessage( + "\nProblem importing \"{0}\": {1} - file skipped.", + fileName, ex.Message + ); + failed++; } - } - catch (System.Exception ex) - { - ed.WriteMessage( - "\nProblem importing \"{0}\": {1} - file skipped.", - fileName, ex.Message - ); - failed++; } } - } - ed.WriteMessage( - "\nImported block definitions from {0} files{1} in " + - "\"{2}\" into the current drawing.", - imported, - failed > 0 ? " (" + failed + " failed)" : "", - pathName - ); + ed.WriteMessage( + "\nImported block definitions from {0} files{1} in " + + "\"{2}\" into the current drawing.", + imported, + failed > 0 ? " (" + failed + " failed)" : "", + pathName + ); + } } -} \ No newline at end of file + +} -- Gitee From cbccce1166ef092d4b0ceda7168a5203ecd12729 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sun, 20 Mar 2022 18:12:24 +0800 Subject: [PATCH 03/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E9=93=BE=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 183 +++++++++++------- src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs | 7 +- 2 files changed, 120 insertions(+), 70 deletions(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 79319f0..58c6412 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; namespace IFoxCAD.Collections { /// @@ -12,25 +11,23 @@ public class LoopListNode /// /// 取值 /// - public T Value { get; set; } + public T Value { internal set; get; } /// /// 上一个节点 /// - public LoopListNode Previous - { internal set; get; } + public LoopListNode Previous { internal set; get; } /// /// 下一个节点 /// - public LoopListNode Next - { internal set; get; } + public LoopListNode Next { internal set; get; } /// ///环链表序列 /// - public LoopList List - { internal set; get; } + public LoopList List { internal set; get; } + /// /// 环链表节点构造函数 /// @@ -54,43 +51,49 @@ public LoopListNode GetNext(bool forward) /// 环链表 /// /// - public class LoopList : - IEnumerable, IFormattable + public class LoopList : IEnumerable, IFormattable { + #region 成员 + /// - /// 默认构造函数 + /// 节点数 /// - public LoopList() - { } + public int Count { get; private set; } + /// - /// 环链表构造函数 + /// 首节点 /// - /// 节点迭代器 - public LoopList(IEnumerable values) - { - foreach (T value in values) - Add(value); - } + public LoopListNode First { get; private set; } /// - /// 节点数 + /// 尾节点 /// - public int Count - { get; private set; } + public LoopListNode Last => First?.Previous; + + #endregion + + #region 构造 /// - /// 首节点 + /// 默认构造函数 /// - public LoopListNode First - { get; private set; } + public LoopList() { } /// - /// 尾节点 + /// 环链表构造函数 /// - public LoopListNode Last + /// 节点迭代器 + public LoopList(IEnumerable values) { - get { return First?.Previous; } + var ge = values.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); } + + #endregion + + #region 方法 + /// /// 设置首节点 /// @@ -113,12 +116,60 @@ public bool SetFirst(LoopListNode node) /// 第二个节点 public void Swap(LoopListNode node1, LoopListNode node2) { - T value = node1.Value; + T value = node1.Value; node1.Value = node2.Value; node2.Value = value; } - #region Contains + /// + /// 链内翻转 + /// + public void Reverse() + { + LoopListNode first = First; + if (first == null) + return; + var last = Last; + for (int i = 0; i < Count / 2; i++) + { + Swap(first, last); + first = first.Next; + last = last.Previous; + } + } + + public void Clear() + { + //清理的时候不释放数组长度 + ForEach(a => + { + a.Value = default; + return false; + }); + Count = 0; + } + + /// + /// 从头遍历 + /// + /// + public void ForEach(Func, bool> action) + { + LoopListNode node = First; + if (node == null) + return; + + for (int i = 0; i < Count; i++) + { + if (action(node)) + break; + node = node.Next; + } + } + #endregion + + #region + /// /// 是否包含节点 /// @@ -128,6 +179,7 @@ public bool Contains(LoopListNode node) { return node != null && node.List == this; } + /// /// 是否包含值 /// @@ -135,19 +187,19 @@ public bool Contains(LoopListNode node) /// public bool Contains(T value) { - LoopListNode node = First; - if (node == null) - return false; - - for (int i = 0; i < Count; i++) + bool result = false; + ForEach(node => { - if (node.Value.Equals(value)) + if (node.Equals(value)) + { + result = true; return true; - node = node.Next; - } - - return false; + } + return false; + }); + return result; } + /// /// 获取节点 /// @@ -155,22 +207,20 @@ public bool Contains(T value) /// public LoopListNode GetNode(Func func) { - LoopListNode node = First; - if (node == null) - return null; - - for (int i = 0; i < Count; i++) + LoopListNode result = null; + ForEach(a => { - if (func(node.Value)) + if (func(a.Value)) { - return node; + result = a; + return true; } - node = node.Next; - } - return null; + return false; + }); + return result; } - #endregion Contains + #endregion #region Add @@ -187,16 +237,16 @@ public LoopListNode AddFirst(T value) }; if (Count == 0) { - First = node; + First = node; First.Previous = First.Next = node; } else { LoopListNode last = Last; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; - First = node; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; + First = node; } Count++; return First; @@ -213,15 +263,15 @@ public LoopListNode Add(T value) node.List = this; if (Count == 0) { - First = node; + First = node; First.Previous = First.Next = node; } else { LoopListNode last = First.Previous; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; } Count++; return Last; @@ -266,7 +316,7 @@ public LoopListNode AddAfter(LoopListNode node, T value) return tnode; } - #endregion Add + #endregion #region Remove @@ -352,7 +402,7 @@ public bool Remove(LoopListNode node) return false; } - #endregion Remove + #endregion #region LinkTo @@ -424,7 +474,7 @@ public void LinkTo(LoopListNode from, LoopListNode to, int number, bool is } } - #endregion LinkTo + #endregion #region IEnumerable 成员 @@ -485,9 +535,10 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() #endregion IEnumerable 成员 - #endregion IEnumerable 成员 + #endregion #region IFormattable 成员 + /// /// 转换为字符串 /// @@ -505,6 +556,6 @@ string IFormattable.ToString(string format, IFormatProvider formatProvider) return ToString(); } - #endregion IFormattable 成员 + #endregion } } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs index 1eaa06a..dacdc65 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs @@ -150,8 +150,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, out LoopLi /// 解析类圆对象 public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, out LoopList ptlst) { - ptlst = - new LoopList { pt1, PointV, pt3 }; + ptlst = new LoopList { pt1, PointV, pt3 }; //遍历各点与下一点的向量长度,找到距离最大的两个点 double maxLength; @@ -213,7 +212,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt { if (ca2d == null || tca2d.Radius < ca2d.Radius) { - ca2d = tca2d; + ca2d = tca2d; ptlst = tptlst; } } @@ -563,7 +562,7 @@ public static Vector3d Wcs2Dcs(this Vector3d vec, bool atPaperSpace) #endregion Ucs - + /// /// 返回不等比例变换矩阵 /// -- Gitee From 646b3f476f2106f9bb164d2f8ed13def2e54bf5e Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sun, 20 Mar 2022 19:16:35 +0800 Subject: [PATCH 04/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8F=8C=E5=90=91?= =?UTF-8?q?=E9=93=BE=E8=A1=A8=E7=9A=84=E4=B8=80=E4=BA=9Bbug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 79 ++++++++++++++-------- src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs | 26 +++---- 2 files changed, 63 insertions(+), 42 deletions(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 58c6412..c8ca91b 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -32,10 +32,12 @@ public class LoopListNode /// 环链表节点构造函数 /// /// 节点值 - public LoopListNode(T value) + public LoopListNode(T value, LoopList ts) { Value = value; + List = ts; } + /// /// 获取当前节点的临近节点 /// @@ -149,6 +151,7 @@ public void Clear() Count = 0; } + /// /// 从头遍历 /// @@ -190,7 +193,7 @@ public bool Contains(T value) bool result = false; ForEach(node => { - if (node.Equals(value)) + if (node.Value.Equals(value)) { result = true; return true; @@ -200,6 +203,26 @@ public bool Contains(T value) return result; } + /// + /// 查找节点 + /// + /// + /// + public LoopListNode Find(T t2) + { + LoopListNode result = null; + ForEach(node => + { + if (node.Value.Equals(t2)) + { + result = node; + return true; + } + return false; + }); + return result; + } + /// /// 获取节点 /// @@ -208,11 +231,11 @@ public bool Contains(T value) public LoopListNode GetNode(Func func) { LoopListNode result = null; - ForEach(a => + ForEach(node => { - if (func(a.Value)) + if (func(node.Value)) { - result = a; + result = node; return true; } return false; @@ -231,10 +254,8 @@ public LoopListNode GetNode(Func func) /// public LoopListNode AddFirst(T value) { - LoopListNode node = new(value) - { - List = this - }; + LoopListNode node = new(value, this); + if (Count == 0) { First = node; @@ -259,8 +280,8 @@ public LoopListNode AddFirst(T value) /// public LoopListNode Add(T value) { - LoopListNode node = new(value); - node.List = this; + LoopListNode node = new(value, this); + if (Count == 0) { First = node; @@ -290,7 +311,8 @@ public LoopListNode AddBefore(LoopListNode node, T value) } else { - LoopListNode tnode = new(value); + LoopListNode tnode = new(value, this); + node.Previous.Next = tnode; tnode.Previous = node.Previous; node.Previous = tnode; @@ -307,7 +329,8 @@ public LoopListNode AddBefore(LoopListNode node, T value) /// public LoopListNode AddAfter(LoopListNode node, T value) { - LoopListNode tnode = new(value); + LoopListNode tnode = new(value, this); + node.Next.Previous = tnode; tnode.Next = node.Next; node.Next = tnode; @@ -342,7 +365,6 @@ public bool RemoveFirst() last.Next = First; break; } - Count--; return true; } @@ -378,28 +400,27 @@ public bool RemoveLast() /// public bool Remove(LoopListNode node) { - if (Contains(node)) + if (!Contains(node)) + return false; + + if (Count == 1) { - if (Count == 1) + First = null; + } + else + { + if (node == First) { - First = null; + RemoveFirst(); } else { - if (node == First) - { - RemoveFirst(); - } - else - { - node.Next.Previous = node.Previous; - node.Previous.Next = node.Next; - } + node.Next.Previous = node.Previous; + node.Previous.Next = node.Next; } - Count--; - return true; } - return false; + Count--; + return true; } #endregion diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs index af13df1..524a6ea 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs @@ -58,7 +58,7 @@ private struct EdgeItem : IEquatable public EdgeItem(Edge edge, bool forward) { - Edge = edge; + Edge = edge; Forward = forward; } @@ -125,12 +125,12 @@ public EdgeItem GetNext(List edges) int next; if (Forward) { - vec = Edge.GetEndVector(); + vec = Edge.GetEndVector(); next = Edge.EndIndex; } else { - vec = Edge.GetStartVector(); + vec = Edge.GetStartVector(); next = Edge.StartIndex; } @@ -148,19 +148,19 @@ public EdgeItem GetNext(List edges) var angle2 = vec.GetAngleTo(vec3, Vector3d.ZAxis); if (angle2 < angle) { - vec2 = vec3; - angle = angle2; - item.Edge = edge; + vec2 = vec3; + angle = angle2; + item.Edge = edge; item.Forward = forward; } } else { - vec2 = vec3; - angle = vec.GetAngleTo(vec2, Vector3d.ZAxis); - item.Edge = edge; + vec2 = vec3; + angle = vec.GetAngleTo(vec2, Vector3d.ZAxis); + item.Edge = edge; item.Forward = forward; - hasNext = true; + hasNext = true; } } } @@ -335,7 +335,7 @@ public static List Topo(List curves) if (nums[edge.StartIndex] == 1 && nums[edge.EndIndex] == 1) { nums[edge.StartIndex] = 0; - nums[edge.EndIndex] = 0; + nums[edge.EndIndex] = 0; } else { @@ -343,12 +343,12 @@ public static List Topo(List curves) if (nums[edge.StartIndex] == 1) { nums[edge.StartIndex] = 0; - nums[next = edge.EndIndex]--; + nums[next = edge.EndIndex]--; } else { nums[edge.EndIndex] = 0; - nums[next = edge.StartIndex]--; + nums[next = edge.StartIndex]--; } } } -- Gitee From e100a24886b857377912b144027f551d42f05f26 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Mon, 21 Mar 2022 01:14:36 +0800 Subject: [PATCH 05/18] =?UTF-8?q?=E4=BC=98=E5=8C=96LoopList?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 228 ++++++++++++++++++++-------------- 1 file changed, 134 insertions(+), 94 deletions(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index c8ca91b..bac9911 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -1,5 +1,8 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Text; + namespace IFoxCAD.Collections { /// @@ -11,22 +14,22 @@ public class LoopListNode /// /// 取值 /// - public T Value { internal set; get; } + public T Value; /// /// 上一个节点 /// - public LoopListNode Previous { internal set; get; } + public LoopListNode? Previous { internal set; get; } /// /// 下一个节点 /// - public LoopListNode Next { internal set; get; } + public LoopListNode? Next { internal set; get; } /// ///环链表序列 /// - public LoopList List { internal set; get; } + public LoopList? List { internal set; get; } /// /// 环链表节点构造函数 @@ -43,10 +46,21 @@ public LoopListNode(T value, LoopList ts) /// /// 搜索方向标志,为向前搜索,为向后搜索 /// - public LoopListNode GetNext(bool forward) + public LoopListNode? GetNext(bool forward) { return forward ? Next : Previous; } + + /// + /// 无效化成员 + /// + internal void Invalidate() + { + Value = default!; + List = null; + Next = null; + Previous = null; + } } /// @@ -65,12 +79,12 @@ public class LoopList : IEnumerable, IFormattable /// /// 首节点 /// - public LoopListNode First { get; private set; } + public LoopListNode? First { get; private set; } /// /// 尾节点 /// - public LoopListNode Last => First?.Previous; + public LoopListNode? Last => First?.Previous; #endregion @@ -103,12 +117,11 @@ public LoopList(IEnumerable values) /// public bool SetFirst(LoopListNode node) { - if (Contains(node)) - { - First = node; - return true; - } - return false; + if (!Contains(node)) + return false; + + First = node; + return true; } /// @@ -118,9 +131,13 @@ public bool SetFirst(LoopListNode node) /// 第二个节点 public void Swap(LoopListNode node1, LoopListNode node2) { - T value = node1.Value; - node1.Value = node2.Value; - node2.Value = value; +#if NET35 + var value = node1.Value; + node1.Value = node2.Value; + node2.Value = value; +#else + (node2.Value, node1.Value) = (node1.Value, node2.Value); +#endif } /// @@ -128,24 +145,29 @@ public void Swap(LoopListNode node1, LoopListNode node2) /// public void Reverse() { - LoopListNode first = First; + var first = First; if (first == null) return; + var last = Last; for (int i = 0; i < Count / 2; i++) { - Swap(first, last); - first = first.Next; - last = last.Previous; + Swap(first!, last!); + first = first!.Next; + last = last!.Previous; } } + /// + /// 清理 + /// public void Clear() { //清理的时候不释放数组长度 ForEach(a => { - a.Value = default; + //a.Value = default; + a.Invalidate(); return false; }); Count = 0; @@ -158,20 +180,19 @@ public void Clear() /// public void ForEach(Func, bool> action) { - LoopListNode node = First; + var node = First; if (node == null) return; - for (int i = 0; i < Count; i++) { - if (action(node)) + if (action(node!)) break; - node = node.Next; + node = node!.Next; } } #endregion - #region + #region Contains /// /// 是否包含节点 @@ -193,7 +214,7 @@ public bool Contains(T value) bool result = false; ForEach(node => { - if (node.Value.Equals(value)) + if (node.Value!.Equals(value)) { result = true; return true; @@ -208,19 +229,44 @@ public bool Contains(T value) /// /// /// - public LoopListNode Find(T t2) + public LoopListNode? Find(T value) { - LoopListNode result = null; - ForEach(node => + //LoopListNode result = null; + //ForEach(node => + //{ + // if (node.Value.Equals(t2)) + // { + // result = node; + // return true; + // } + // return false; + //}); + //return result; + + LoopListNode? node = First; + EqualityComparer c = EqualityComparer.Default; + if (node != null) { - if (node.Value.Equals(t2)) + if (value != null) { - result = node; - return true; + do + { + if (c.Equals(node!.Value, value)) + return node; + node = node.Next; + } while (node != First); } - return false; - }); - return result; + else + { + do + { + if (node!.Value == null) + return node; + node = node.Next; + } while (node != First); + } + } + return null; } /// @@ -228,9 +274,9 @@ public LoopListNode Find(T t2) /// /// /// - public LoopListNode GetNode(Func func) + public LoopListNode? GetNode(Func func) { - LoopListNode result = null; + LoopListNode? result = null; ForEach(node => { if (func(node.Value)) @@ -254,7 +300,7 @@ public LoopListNode GetNode(Func func) /// public LoopListNode AddFirst(T value) { - LoopListNode node = new(value, this); + var node = new LoopListNode(value, this); if (Count == 0) { @@ -263,8 +309,8 @@ public LoopListNode AddFirst(T value) } else { - LoopListNode last = Last; - First.Previous = last.Next = node; + LoopListNode last = Last!; + First!.Previous = last.Next = node; node.Next = First; node.Previous = last; First = node; @@ -280,7 +326,7 @@ public LoopListNode AddFirst(T value) /// public LoopListNode Add(T value) { - LoopListNode node = new(value, this); + var node = new LoopListNode(value, this); if (Count == 0) { @@ -289,14 +335,15 @@ public LoopListNode Add(T value) } else { - LoopListNode last = First.Previous; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; + var last = First!.Previous!; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; } Count++; - return Last; + return Last!; } + /// /// 前面增加节点 /// @@ -306,21 +353,17 @@ public LoopListNode Add(T value) public LoopListNode AddBefore(LoopListNode node, T value) { if (node == First) - { return AddFirst(value); - } - else - { - LoopListNode tnode = new(value, this); - - node.Previous.Next = tnode; - tnode.Previous = node.Previous; - node.Previous = tnode; - tnode.Next = node; - Count++; - return tnode; - } + + var tnode = new LoopListNode(value, this); + node.Previous!.Next = tnode; + tnode.Previous = node.Previous; + node.Previous = tnode; + tnode.Next = node; + Count++; + return tnode; } + /// /// 后面增加节点 /// @@ -329,9 +372,8 @@ public LoopListNode AddBefore(LoopListNode node, T value) /// public LoopListNode AddAfter(LoopListNode node, T value) { - LoopListNode tnode = new(value, this); - - node.Next.Previous = tnode; + var tnode = new LoopListNode(value, this); + node.Next!.Previous = tnode; tnode.Next = node.Next; node.Next = tnode; tnode.Previous = node; @@ -359,9 +401,9 @@ public bool RemoveFirst() break; default: - LoopListNode last = Last; - First = First.Next; - First.Previous = last; + LoopListNode last = Last!; + First = First!.Next; + First!.Previous = last; last.Next = First; break; } @@ -384,9 +426,9 @@ public bool RemoveLast() break; default: - LoopListNode last = Last.Previous; + LoopListNode last = Last!.Previous!; last.Next = First; - First.Previous = last; + First!.Previous = last; break; } Count--; @@ -415,10 +457,12 @@ public bool Remove(LoopListNode node) } else { - node.Next.Previous = node.Previous; - node.Previous.Next = node.Next; + node.Next!.Previous = node.Previous; + node.Previous!.Next = node.Next; } } + node.Invalidate(); + Count--; return true; } @@ -436,7 +480,7 @@ public void LinkTo(LoopListNode from, LoopListNode to) { if (from != to && Contains(from) && Contains(to)) { - LoopListNode node = from.Next; + LoopListNode node = from.Next!; bool isFirstChanged = false; int number = 0; @@ -445,7 +489,7 @@ public void LinkTo(LoopListNode from, LoopListNode to) if (node == First) isFirstChanged = true; - node = node.Next; + node = node.Next!; number++; } @@ -506,11 +550,11 @@ public void LinkTo(LoopListNode from, LoopListNode to, int number, bool is /// public IEnumerable> GetNodes(LoopListNode from) { - LoopListNode node = from; + var node = from; for (int i = 0; i < Count; i++) { - yield return node; - node = node.Next; + yield return node!; + node = node!.Next; } } @@ -520,11 +564,11 @@ public IEnumerable> GetNodes(LoopListNode from) /// public IEnumerable> GetNodes() { - LoopListNode node = First; + LoopListNode node = First!; for (int i = 0; i < Count; i++) { - yield return node; - node = node.Next; + yield return node!; + node = node.Next!; } } @@ -534,42 +578,38 @@ public IEnumerable> GetNodes() /// public IEnumerator GetEnumerator() { - LoopListNode node = First; + LoopListNode node = First!; for (int i = 0; i < Count; i++) { - yield return node.Value; - node = node.Next; + yield return node!.Value; + node = node.Next!; } } - IEnumerator IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #region IEnumerable 成员 - System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() - { - return GetEnumerator(); - } + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion IEnumerable 成员 #endregion #region IFormattable 成员 - /// /// 转换为字符串 /// /// public override string ToString() { - string s = "( "; + var s = new StringBuilder(); + s.Append("( "); foreach (T value in this) - s += value.ToString() + " "; - return s + ")"; + s.Append($"{value} "); + + s.Append(")"); + return s.ToString(); } string IFormattable.ToString(string format, IFormatProvider formatProvider) @@ -579,4 +619,4 @@ string IFormattable.ToString(string format, IFormatProvider formatProvider) #endregion } -} +} \ No newline at end of file -- Gitee From 88e45b8de8579abb0bc073ff0e052ad7dc4507a1 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Mon, 21 Mar 2022 02:38:25 +0800 Subject: [PATCH 06/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9LoopList=E7=9A=84?= =?UTF-8?q?=E6=B8=85=E7=90=86=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 62 +++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 25 deletions(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index bac9911..0a6cbd1 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -27,7 +27,7 @@ public class LoopListNode public LoopListNode? Next { internal set; get; } /// - ///环链表序列 + /// 环链表序列 /// public LoopList? List { internal set; get; } @@ -56,7 +56,6 @@ public LoopListNode(T value, LoopList ts) /// internal void Invalidate() { - Value = default!; List = null; Next = null; Previous = null; @@ -163,17 +162,12 @@ public void Reverse() /// public void Clear() { - //清理的时候不释放数组长度 - ForEach(a => - { - //a.Value = default; - a.Invalidate(); - return false; - }); + //移除头部,表示链表再也无法遍历得到 + First = null; Count = 0; + GC.Collect(); } - /// /// 从头遍历 /// @@ -225,7 +219,7 @@ public bool Contains(T value) } /// - /// 查找节点 + /// 查找第一个出现的节点 /// /// /// @@ -438,33 +432,51 @@ public bool RemoveLast() /// /// 删除节点 /// - /// + /// 指定节点 /// public bool Remove(LoopListNode node) { if (!Contains(node)) return false; + InternalRemove(node); + return true; + } - if (Count == 1) + /// + /// 删除节点 + /// + /// 将移除所有含有此值 + /// + public bool Remove(T value) + { + ForEach(node => { - First = null; + if (node!.Value.Equals(value)) + InternalRemove(node); + return false; + }); + return true; + } + + /// + /// 删除节点_内部调用 + /// + /// 此值肯定存在当前链表 + /// + void InternalRemove(LoopListNode node) + { + if (Count == 1 || node == First) + { + RemoveFirst(); } else { - if (node == First) - { - RemoveFirst(); - } - else - { - node.Next!.Previous = node.Previous; - node.Previous!.Next = node.Next; - } + node.Next!.Previous = node.Previous; + node.Previous!.Next = node.Next; } - node.Invalidate(); + node.Invalidate(); Count--; - return true; } #endregion -- Gitee From 5521d7cb5ba2a872c115e0dc7232fea1b1a9e44d Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Mon, 21 Mar 2022 02:40:56 +0800 Subject: [PATCH 07/18] =?UTF-8?q?=E5=B0=91=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E6=84=9F=E5=8F=B9=E5=8F=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 0a6cbd1..6504033 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -451,7 +451,7 @@ public bool Remove(T value) { ForEach(node => { - if (node!.Value.Equals(value)) + if (node!.Value!.Equals(value)) InternalRemove(node); return false; }); -- Gitee From 74c2eee904e2c87afdb3b028aa73490620bdd5f1 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Mon, 21 Mar 2022 11:43:05 +0800 Subject: [PATCH 08/18] =?UTF-8?q?=E5=A2=9E=E5=8A=A0LoopList=E7=9A=84Finds?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 95 ++++++++++++++++++++++++++++------- 1 file changed, 77 insertions(+), 18 deletions(-) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 6504033..e75eccd 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -11,6 +11,7 @@ namespace IFoxCAD.Collections /// public class LoopListNode { + #region 成员 /// /// 取值 /// @@ -30,7 +31,9 @@ public class LoopListNode /// 环链表序列 /// public LoopList? List { internal set; get; } + #endregion + #region 构造 /// /// 环链表节点构造函数 /// @@ -50,7 +53,9 @@ public LoopListNode(T value, LoopList ts) { return forward ? Next : Previous; } + #endregion + #region 方法 /// /// 无效化成员 /// @@ -60,6 +65,7 @@ internal void Invalidate() Next = null; Previous = null; } + #endregion } /// @@ -184,7 +190,6 @@ public void ForEach(Func, bool> action) node = node!.Next; } } - #endregion #region Contains @@ -238,7 +243,7 @@ public bool Contains(T value) //return result; LoopListNode? node = First; - EqualityComparer c = EqualityComparer.Default; + var c = EqualityComparer.Default; if (node != null) { if (value != null) @@ -263,6 +268,40 @@ public bool Contains(T value) return null; } + /// + /// 查找所有出现的节点 + /// + /// + /// + public IEnumerable>? Finds(T value) + { + LoopListNode? node = First; + if (node == null) + return null; + + List> result = new(); + var c = EqualityComparer.Default; + if (value != null) + { + do + { + if (c.Equals(node!.Value, value)) + result.Add(node); + node = node.Next; + } while (node != First); + } + else + { + do + { + if (node!.Value == null) + result.Add(node); + node = node.Next; + } while (node != First); + } + return result; + } + /// /// 获取节点 /// @@ -449,12 +488,13 @@ public bool Remove(LoopListNode node) /// public bool Remove(T value) { - ForEach(node => - { - if (node!.Value!.Equals(value)) - InternalRemove(node); + var lst = Finds(value); + if (lst == null) return false; - }); + + var ge = lst!.GetEnumerator(); + while (ge.MoveNext()) + InternalRemove(ge.Current); return true; } @@ -553,6 +593,8 @@ public void LinkTo(LoopListNode from, LoopListNode to, int number, bool is #endregion + #endregion + #region IEnumerable 成员 /// @@ -610,25 +652,42 @@ public IEnumerator GetEnumerator() #region IFormattable 成员 /// - /// 转换为字符串 + /// 转换为字符串_格式化实现 /// + /// + /// /// - public override string ToString() + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) { - var s = new StringBuilder(); - s.Append("( "); - foreach (T value in this) - s.Append($"{value} "); - - s.Append(")"); - return s.ToString(); + return ToString(format, formatProvider); } - string IFormattable.ToString(string format, IFormatProvider formatProvider) + /// + /// 转换为字符串_无参调用 + /// + /// + public override string ToString() { - return ToString(); + return ToString(null, null); } + /// + /// 转换为字符串_有参调用 + /// + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) + { + var s = new StringBuilder(); + s.Append($"Count = {Count};"); + if (format == null) + { + s.Append("{ "); + foreach (T value in this) + s.Append($"{value} "); + s.Append(" }"); + } + return s.ToString(); + } #endregion } } \ No newline at end of file -- Gitee From 22bf3b3acc24ec811fc1a37763ef4a3f91fc8dd8 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Tue, 29 Mar 2022 21:00:51 +0800 Subject: [PATCH 09/18] =?UTF-8?q?=E5=A2=9E=E5=8A=A0AddRange?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/IFoxCAD.Basal.csproj | 2 + src/IFoxCAD.Basal/LoopList.cs | 93 +++++++------ .../ExtensionMethod/CollectionEx.cs | 4 +- src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs | 2 +- src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs | 30 ++--- .../ExtensionMethod/DBDictionaryEx.cs | 2 +- src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 10 +- src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs | 6 +- src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs | 4 +- .../ExtensionMethod/SymbolTableEx.cs | 12 +- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 123 +++++++++--------- src/IFoxCAD.Cad/Runtime/AcadVersion.cs | 4 +- src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs | 4 +- src/IFoxCAD.Cad/Runtime/SymbolTable.cs | 10 +- src/IFoxCAD.WPF/DependencyObjectExtensions.cs | 16 ++- src/IFoxCAD.WPF/EventBindingExtension.cs | 46 ++++--- src/IFoxCAD.WPF/IFoxCAD.WPF.csproj | 68 +++++----- src/IFoxCAD.WPF/RelayCommand.cs | 24 ++-- .../Properties/launchSettings.json | 0 tests/{DBTrans.test => Test}/Test.cs | 5 +- .../DBTrans.test.csproj => Test/Test.csproj} | 17 ++- .../{DBTrans.test => Test}/testConvexHull.cs | 0 tests/{DBTrans.test => Test}/testeditor.cs | 0 tests/{DBTrans.test => Test}/testenv.cs | 0 .../testselectfilter.cs | 0 .../{DBTrans.test => Test}/wpf/TestView.xaml | 0 .../wpf/TestView.xaml.cs | 0 .../wpf/TestViewModel.cs | 0 28 files changed, 254 insertions(+), 228 deletions(-) rename tests/{DBTrans.test => Test}/Properties/launchSettings.json (100%) rename tests/{DBTrans.test => Test}/Test.cs (99%) rename tests/{DBTrans.test/DBTrans.test.csproj => Test/Test.csproj} (58%) rename tests/{DBTrans.test => Test}/testConvexHull.cs (100%) rename tests/{DBTrans.test => Test}/testeditor.cs (100%) rename tests/{DBTrans.test => Test}/testenv.cs (100%) rename tests/{DBTrans.test => Test}/testselectfilter.cs (100%) rename tests/{DBTrans.test => Test}/wpf/TestView.xaml (100%) rename tests/{DBTrans.test => Test}/wpf/TestView.xaml.cs (100%) rename tests/{DBTrans.test => Test}/wpf/TestViewModel.cs (100%) diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj index b3cec15..a15060d 100644 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj @@ -2,6 +2,8 @@ preview + enable + 1.0.0.* 1.0.0.0 diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index e75eccd..fca9931 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -32,7 +32,6 @@ public class LoopListNode /// public LoopList? List { internal set; get; } #endregion - #region 构造 /// /// 环链表节点构造函数 @@ -54,7 +53,6 @@ public LoopListNode(T value, LoopList ts) return forward ? Next : Previous; } #endregion - #region 方法 /// /// 无效化成员 @@ -75,7 +73,6 @@ internal void Invalidate() public class LoopList : IEnumerable, IFormattable { #region 成员 - /// /// 节点数 /// @@ -92,9 +89,7 @@ public class LoopList : IEnumerable, IFormattable public LoopListNode? Last => First?.Previous; #endregion - #region 构造 - /// /// 默认构造函数 /// @@ -112,9 +107,7 @@ public LoopList(IEnumerable values) } #endregion - #region 方法 - /// /// 设置首节点 /// @@ -137,9 +130,9 @@ public bool SetFirst(LoopListNode node) public void Swap(LoopListNode node1, LoopListNode node2) { #if NET35 - var value = node1.Value; - node1.Value = node2.Value; - node2.Value = value; + var value = node1.Value; + node1.Value = node2.Value; + node2.Value = value; #else (node2.Value, node1.Value) = (node1.Value, node2.Value); #endif @@ -151,9 +144,8 @@ public void Swap(LoopListNode node1, LoopListNode node2) public void Reverse() { var first = First; - if (first == null) + if (first is null) return; - var last = Last; for (int i = 0; i < Count / 2; i++) { @@ -171,17 +163,16 @@ public void Clear() //移除头部,表示链表再也无法遍历得到 First = null; Count = 0; - GC.Collect(); } /// - /// 从头遍历 + /// 从头遍历_非迭代器 /// /// public void ForEach(Func, bool> action) { var node = First; - if (node == null) + if (node is null) return; for (int i = 0; i < Count; i++) { @@ -192,7 +183,6 @@ public void ForEach(Func, bool> action) } #region Contains - /// /// 是否包含节点 /// @@ -200,7 +190,7 @@ public void ForEach(Func, bool> action) /// public bool Contains(LoopListNode node) { - return node != null && node.List == this; + return node is not null && node.List == this; } /// @@ -211,8 +201,7 @@ public bool Contains(LoopListNode node) public bool Contains(T value) { bool result = false; - ForEach(node => - { + ForEach(node => { if (node.Value!.Equals(value)) { result = true; @@ -244,9 +233,9 @@ public bool Contains(T value) LoopListNode? node = First; var c = EqualityComparer.Default; - if (node != null) + if (node is not null) { - if (value != null) + if (value is not null) { do { @@ -259,7 +248,7 @@ public bool Contains(T value) { do { - if (node!.Value == null) + if (node!.Value is null) return node; node = node.Next; } while (node != First); @@ -276,12 +265,12 @@ public bool Contains(T value) public IEnumerable>? Finds(T value) { LoopListNode? node = First; - if (node == null) + if (node is null) return null; List> result = new(); var c = EqualityComparer.Default; - if (value != null) + if (value is not null) { do { @@ -294,7 +283,7 @@ public bool Contains(T value) { do { - if (node!.Value == null) + if (node!.Value is null) result.Add(node); node = node.Next; } while (node != First); @@ -310,8 +299,7 @@ public bool Contains(T value) public LoopListNode? GetNode(Func func) { LoopListNode? result = null; - ForEach(node => - { + ForEach(node => { if (func(node.Value)) { result = node; @@ -323,9 +311,7 @@ public bool Contains(T value) } #endregion - #region Add - /// /// 在首节点之前插入节点,并设置新节点为首节点 /// @@ -377,6 +363,23 @@ public LoopListNode Add(T value) return Last!; } + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 + /// + /// + /// + public LoopListNode AddLast(T value) + { + return Add(value); + } + + public void AddRange(IEnumerable list) + { + var ge = list.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } + /// /// 前面增加节点 /// @@ -415,9 +418,7 @@ public LoopListNode AddAfter(LoopListNode node, T value) } #endregion - #region Remove - /// /// 删除首节点 /// @@ -489,7 +490,7 @@ public bool Remove(LoopListNode node) public bool Remove(T value) { var lst = Finds(value); - if (lst == null) + if (lst is null) return false; var ge = lst!.GetEnumerator(); @@ -520,9 +521,7 @@ void InternalRemove(LoopListNode node) } #endregion - #region LinkTo - /// /// 链接两节点,并去除这两个节点间的所有节点 /// @@ -592,11 +591,8 @@ public void LinkTo(LoopListNode from, LoopListNode to, int number, bool is } #endregion - #endregion - - #region IEnumerable 成员 - + #region IEnumerable /// /// 获取节点的查询器 /// @@ -643,14 +639,11 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #region IEnumerable 成员 - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); #endregion IEnumerable 成员 - #endregion - - #region IFormattable 成员 + #region IFormattable /// /// 转换为字符串_格式化实现 /// @@ -679,7 +672,7 @@ public string ToString(string? format, IFormatProvider? formatProvider = null) { var s = new StringBuilder(); s.Append($"Count = {Count};"); - if (format == null) + if (format is null) { s.Append("{ "); foreach (T value in this) @@ -689,5 +682,19 @@ public string ToString(string? format, IFormatProvider? formatProvider = null) return s.ToString(); } #endregion + #region ICloneable + /* 山人说无法分辨ICloneable接口是深浅克隆,因此不要在泛型模板实现克隆函数,让用户自己来 + * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; + * public object Clone() + * { + * var lst = new LoopList>(); + * ForEach(node => { + * lst.Add(node); + * return false; + * }); + * return lst; + * } + */ + #endregion } } \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs index 9b0c984..406a33d 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs @@ -106,7 +106,7 @@ public static List ToList(this StringCollection strs) /// 要运行的委托 public static void ForEach(this IEnumerable source, Action action) { - if (action == null) + if (action is null) throw new ArgumentNullException(nameof(action)); foreach (var element in source) action.Invoke(element); @@ -121,7 +121,7 @@ public static void ForEach(this IEnumerable source, Action action) /// 要运行的委托 public static void ForEach(this IEnumerable source, Action action) { - if (action == null) + if (action is null) throw new ArgumentNullException(nameof(action)); int i = 0; foreach (var item in source) diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs index fb3b773..7acb61e 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs @@ -18,7 +18,7 @@ public static class Curve2dEx /// Ge2d曲线 /// 曲线转换矩阵 /// Db曲线 - public static Curve ToCurve(this Curve2d curve, Matrix3d mat) + public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) { return curve switch { diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs index 524a6ea..26fbd18 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs @@ -64,14 +64,12 @@ public EdgeItem(Edge edge, bool forward) public CompositeCurve3d GetCurve() { - CompositeCurve3d cc3d = Edge.Curve; + var cc3d = Edge.Curve; if (Forward) - { return cc3d; - } else { - cc3d = cc3d.Clone() as CompositeCurve3d; + cc3d = (CompositeCurve3d)cc3d.Clone(); return cc3d.GetReverseParameterCurve() as CompositeCurve3d; } } @@ -89,13 +87,13 @@ public void FindRegion(List edges, List> regions) var edgeItem = this; region.Add(edgeItem); var edgeItem2 = this.GetNext(edges); - if (edgeItem2.Edge != null) + if (edgeItem2.Edge is not null) { bool hasList = false; foreach (var edgeList2 in regions) { var node = edgeList2.GetNode(e => e.Equals(edgeItem)); - if (node != null) + if (node is not null) { if (node.Next.Value.Equals(edgeItem2)) { @@ -106,7 +104,7 @@ public void FindRegion(List edges, List> regions) } if (!hasList) { - while (edgeItem2.Edge != null) + while (edgeItem2.Edge is not null) { if (edgeItem2.Edge == edgeItem.Edge) break; @@ -226,7 +224,7 @@ public static List Topo(List curves) foreach (var curve in curves) { var cc3d = curve.ToCompositeCurve3d(); - if (cc3d != null) + if (cc3d is not null) { geCurves.Add(cc3d); paramss.Add(new List()); @@ -373,7 +371,7 @@ public static List Topo(List curves) var node = regions[i].First; var curve = node.Value.Edge.Curve; var node2 = regions[j].GetNode(e => e.Edge.Curve == curve); - if (eq = node2 != null) + if (eq = node2 is not null) { var b = node.Value.Forward; var b2 = node2.Value.Forward; @@ -421,7 +419,7 @@ public static List BreakCurve(List curves) foreach (var curve in curves) { var cc3d = curve.ToCompositeCurve3d(); - if (cc3d != null) + if (cc3d is not null) { geCurves.Add(cc3d); paramss.Add(new List()); @@ -459,7 +457,7 @@ public static List BreakCurve(List curves) foreach (CompositeCurve3d c3d in c3ds) { Curve c = c3d.ToCurve(); - if (c != null) + if (c is not null) { c.SetPropertiesFrom(curves[i]); newCurves.Add(c); @@ -483,7 +481,7 @@ public static List BreakCurve(List curves) /// 曲线 /// ge曲线 [Obsolete("请使用Cad自带的 GetGeCurve 函数!")] - public static Curve3d ToCurve3d(this Curve curve) + public static Curve3d? ToCurve3d(this Curve curve) { return curve switch { @@ -504,7 +502,7 @@ public static Curve3d ToCurve3d(this Curve curve) /// /// 曲线 /// 复合曲线 - public static CompositeCurve3d ToCompositeCurve3d(this Curve curve) + public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) { return curve switch { @@ -526,7 +524,7 @@ public static CompositeCurve3d ToCompositeCurve3d(this Curve curve) /// /// 曲线 /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Curve curve) + public static NurbCurve3d? ToNurbCurve3d(this Curve curve) { return curve switch { @@ -885,11 +883,11 @@ public static NurbCurve3d ToNurbCurve3d(this Polyline pl) default: break; } - if (nc3d == null) + if (nc3d is null) { nc3d = nc3dtemp; } - else if (nc3dtemp != null) + else if (nc3dtemp is not null) { nc3d.JoinWith(nc3dtemp); } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs index 6d3921e..d4fba82 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs @@ -79,7 +79,7 @@ public static void SetAt(this DBDictionary dict, string key, T obj, Transacti public static XRecordDataList GetXRecord(this DBDictionary dict, string key) { Xrecord rec = dict.GetAt(key); - if (rec != null) + if (rec is not null) return rec.Data; return null; } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index 2175194..9745eb9 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -200,9 +200,9 @@ public static void WriteMessage(string format, params object[] args) /// 有,没有 public static bool HasEditor() { - return Application.DocumentManager.MdiActiveDocument != null + return Application.DocumentManager.MdiActiveDocument is not null && Application.DocumentManager.Count != 0 - && Application.DocumentManager.MdiActiveDocument.Editor != null; + && Application.DocumentManager.MdiActiveDocument.Editor is not null; }// /// @@ -548,8 +548,8 @@ public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint { for (int k = 0; k < 2; k++) { - int n = i * 4 + j * 2 + k; - pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); + int n = i * 4 + j * 2 + k; + pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); } } @@ -656,7 +656,7 @@ public static PromptPointResult GetPoint(this Editor ed, string Message, Point3d { PromptPointOptions ptOp = new(Message) { - BasePoint = BasePoint, + BasePoint = BasePoint, UseBasePoint = true }; return ed.GetPoint(ptOp); diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs index 0635006..c14b566 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs @@ -354,11 +354,11 @@ public static Circle CreateCircle(Point3d pt1, Point3d PointV, Point3d pt3) /// 裁剪多边形点表 public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) { - if (bref == null) + if (bref is null) { throw new ArgumentNullException(nameof(bref)); } - if (pt3ds == null) + if (pt3ds is null) { throw new ArgumentNullException(nameof(pt3ds)); } @@ -384,7 +384,7 @@ public static void ClipBlockRef(this BlockReference bref, IEnumerable p /// 第二角点 public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d PointV) { - if (bref == null) + if (bref is null) { throw new ArgumentNullException(nameof(bref)); } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs index dacdc65..8f2a7a1 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs @@ -210,7 +210,7 @@ public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 if (tca2d.IsIn(firstNode.Previous.Value)) { - if (ca2d == null || tca2d.Radius < ca2d.Radius) + if (ca2d is null || tca2d.Radius < ca2d.Radius) { ca2d = tca2d; ptlst = tptlst; @@ -429,7 +429,7 @@ public static CircularArc2d GetMinCircle(this List pnts, out LoopList

凸包 public static List ConvexHull(this List points) { - if (points == null) + if (points is null) return null; if (points.Count <= 1) diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs index c3c9e6b..327f8ba 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs @@ -37,7 +37,7 @@ public static ObjectId Add(this SymbolTable table, /// 图层id public static ObjectId Add(this SymbolTable table, string name, int colorIndex) { - colorIndex %= 256;//防止输入的颜色超出256 + colorIndex %= 256; //防止输入的颜色超出256 colorIndex = Math.Abs(colorIndex);//防止负数 return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); } @@ -104,12 +104,12 @@ public static ObjectId Add(this SymbolTable table, return table.Add(name, btr => { action?.Invoke(btr); var entsres = ents?.Invoke(); - if (entsres != null) + if (entsres is not null) { btr.AddEntity(entsres); } var adddefres = attdef?.Invoke(); - if (adddefres != null) + if (adddefres is not null) { btr.AddEntity(adddefres); } @@ -216,7 +216,7 @@ public static ObjectId Add(this SymbolTable name, ltt => { ltt.AsciiDescription = description; - ltt.PatternLength = length; //线型的总长度 + ltt.PatternLength = length; //线型的总长度 ltt.NumDashes = dash.Length; //组成线型的笔画数目 for (int i = 0; i < dash.Length; i++) { @@ -246,9 +246,9 @@ public static ObjectId Add(this SymbolTable { - tstr.Name = textStyleName; + tstr.Name = textStyleName; tstr.FileName = font; - tstr.XScale = xscale; + tstr.XScale = xscale; }); } #endregion diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index 9227301..d742bef 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -1,63 +1,66 @@  - - net35;net40 - true - 0.1.3 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的Cad二次开发类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;CAD;AutoCad;C#;NET - Optimize and add multiple functions. - true - true - preview - true - LICENSE - true - - - - - - - - runtime - - - - - - DEBUG - - - $(Configuration);ac2009 - - - $(Configuration);ac2013 - - - - - - True - - - - - - - - - - - - - - - + + + preview + enable + + net35;net40 + true + 0.1.3 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + Optimize and add multiple functions. + true + true + true + LICENSE + true + + + + + + + + runtime + + + + + + DEBUG + + + $(Configuration);ac2009 + + + $(Configuration);ac2013 + + + + + + True + + + + + + + + + + + + + + + diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs index a0f977c..7be301f 100644 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs @@ -51,7 +51,7 @@ public static List Versions { get { - if (_versions == null) + if (_versions is null) { string[] copys = Registry.LocalMachine @@ -91,7 +91,7 @@ public static List Versions /// cad版本号对象 public static AcadVersion FromApp(object app) { - if (app == null) + if (app is null) { throw new ArgumentNullException(nameof(app)); } diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs index 1852003..111be63 100644 --- a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs +++ b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs @@ -26,7 +26,7 @@ public enum AssemLoadType ///

/// 无效 /// - Disabled = 20 + Disabled = 20 } /// @@ -53,7 +53,7 @@ public abstract class AutoRegAssem : CadRuntime.IExtensionApplication /// 路径对象 public static DirectoryInfo GetDirectory(Assembly assem) { - if (assem == null) + if (assem is null) { throw new(nameof(assem)); } diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs index b141835..c319b5e 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs @@ -36,7 +36,7 @@ public class SymbolTable : IEnumerable /// 符号表id internal SymbolTable(DBTrans tr, ObjectId tableId) { - DTrans = tr; + DTrans = tr; CurrentSymbolTable = DTrans.GetObject(tableId); } @@ -142,7 +142,7 @@ private static void Remove(TRecord record) public void Remove(string name) { TRecord record = GetRecord(name); - if (record != null) + if (record is not null) { Remove(record); } @@ -155,7 +155,7 @@ public void Remove(string name) public void Remove(ObjectId id) { TRecord record = GetRecord(id); - if (record != null) + if (record is not null) { Remove(record); } @@ -185,7 +185,7 @@ private static void Change(TRecord record, Action action) public void Change(string name, Action action) { var record = GetRecord(name); - if (record != null) + if (record is not null) { Change(record, action); } @@ -198,7 +198,7 @@ public void Change(string name, Action action) public void Change(ObjectId id, Action action) { var record = GetRecord(id); - if (record != null) + if (record is not null) { Change(record, action); } diff --git a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs index f45e259..39cd184 100644 --- a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs +++ b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs @@ -13,23 +13,25 @@ public static class DependencyObjectExtensions /// /// 子对象 /// 依赖属性 - public static DependencyObject GetParentObject(this DependencyObject child) + public static DependencyObject? GetParentObject(this DependencyObject child) { - if (child == null) return null; + if (child is null) + return null; if (child is ContentElement contentElement) { - DependencyObject parent = ContentOperations.GetParent(contentElement); - if (parent != null) return parent; + var parent = ContentOperations.GetParent(contentElement); + if (parent is not null) return parent; - FrameworkContentElement fce = contentElement as FrameworkContentElement; + var fce = contentElement as FrameworkContentElement; return fce?.Parent; } if (child is FrameworkElement frameworkElement) { - DependencyObject parent = frameworkElement.Parent; - if (parent != null) return parent; + var parent = frameworkElement.Parent; + if (parent is not null) + return parent; } return VisualTreeHelper.GetParent(child); diff --git a/src/IFoxCAD.WPF/EventBindingExtension.cs b/src/IFoxCAD.WPF/EventBindingExtension.cs index 6701677..9298c07 100644 --- a/src/IFoxCAD.WPF/EventBindingExtension.cs +++ b/src/IFoxCAD.WPF/EventBindingExtension.cs @@ -34,7 +34,7 @@ public class EventBindingExtension : MarkupExtension /// public override object ProvideValue(IServiceProvider serviceProvider) { - if (serviceProvider == null) + if (serviceProvider is null) { throw new ArgumentNullException(nameof(serviceProvider)); } @@ -49,7 +49,7 @@ public override object ProvideValue(IServiceProvider serviceProvider) } var memberInfo = targetProvider.TargetProperty as MemberInfo; - if (memberInfo == null) + if (memberInfo is null) { throw new InvalidOperationException(); } @@ -69,22 +69,22 @@ public override object ProvideValue(IServiceProvider serviceProvider) return CreateHandler(memberInfo, Command, targetObject.GetType()); } - + private Type GetEventHandlerType(MemberInfo memberInfo) { Type eventHandlerType = null; if (memberInfo is EventInfo) { - var info = memberInfo as EventInfo; - var eventInfo = info; + var info = memberInfo as EventInfo; + var eventInfo = info; eventHandlerType = eventInfo.EventHandlerType; } else if (memberInfo is MethodInfo) { - var info = memberInfo as MethodInfo; - var methodInfo = info; + var info = memberInfo as MethodInfo; + var methodInfo = info; ParameterInfo[] pars = methodInfo.GetParameters(); - eventHandlerType = pars[1].ParameterType; + eventHandlerType = pars[1].ParameterType; } return eventHandlerType; @@ -94,7 +94,7 @@ private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetT { Type eventHandlerType = GetEventHandlerType(memberInfo); - if (eventHandlerType == null) return null; + if (eventHandlerType is null) return null; var handlerInfo = eventHandlerType.GetMethod("Invoke"); var method = new DynamicMethod("", handlerInfo.ReturnType, @@ -108,7 +108,7 @@ private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetT gen.Emit(OpCodes.Ldarg, 0); gen.Emit(OpCodes.Ldarg, 1); gen.Emit(OpCodes.Ldstr, cmdName); - if (CommandParameter == null) + if (CommandParameter is null) { gen.Emit(OpCodes.Ldnull); } @@ -138,7 +138,7 @@ static void Handler(object sender, object args) public static void HandlerIntern(object sender, object args, string cmdName, string commandParameter) { var fe = sender as FrameworkElement; - if (fe != null) + if (fe is not null) { ICommand cmd = GetCommand(fe, cmdName); object commandParam = null; @@ -146,7 +146,7 @@ public static void HandlerIntern(object sender, object args, string cmdName, str { commandParam = GetCommandParameter(fe, args, commandParameter); } - if ((cmd != null) && cmd.CanExecute(commandParam)) + if ((cmd is not null) && cmd.CanExecute(commandParam)) { cmd.Execute(commandParam); } @@ -156,18 +156,15 @@ public static void HandlerIntern(object sender, object args, string cmdName, str internal static ICommand GetCommand(FrameworkElement target, string cmdName) { var vm = FindViewModel(target); - if (vm == null) return null; + if (vm is null) return null; var vmType = vm.GetType(); var cmdProp = vmType.GetProperty(cmdName); - if (cmdProp != null) - { + if (cmdProp is not null) return cmdProp.GetValue(vm) as ICommand; - } #if DEBUG throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); #endif - return null; } @@ -191,28 +188,29 @@ internal static object GetCommandParameter(FrameworkElement target, object args, return ret; } - internal static ViewModelBase FindViewModel(FrameworkElement target) + internal static ViewModelBase? FindViewModel(FrameworkElement? target) { - if (target == null) return null; + if (target is null) + return null; - if (target.DataContext is ViewModelBase vm) return vm; + if (target.DataContext is ViewModelBase vm) + return vm; var parent = target.GetParentObject() as FrameworkElement; - return FindViewModel(parent); } internal static object FollowPropertyPath(object target, string path, Type valueType = null) { - if (target == null) throw new ArgumentNullException(nameof(target)); - if (path == null) throw new ArgumentNullException(nameof(path)); + if (target is null) throw new ArgumentNullException(nameof(target)); + if (path is null) throw new ArgumentNullException(nameof(path)); Type currentType = valueType ?? target.GetType(); foreach (string propertyName in path.Split('.')) { PropertyInfo property = currentType.GetProperty(propertyName); - if (property == null) throw new NullReferenceException("property"); + if (property is null) throw new NullReferenceException("property"); target = property.GetValue(target); currentType = property.PropertyType; diff --git a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj index 8690b9e..297d6ce 100644 --- a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj +++ b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj @@ -1,39 +1,43 @@  - - - NET45;NET46;NET47;NET48 - true - true - true - true - 0.1.0 - xsfhlzh;vicwjb - InspireFunction - WPF的简单MVVM模式开发类库 - InspireFunction - LICENSE - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - IFoxCAD;C#;NET;WPF;MVVM - git - 基于NFOX类库的重构版本 - preview - + + + preview + enable - - DEBUG;TRACE - + + NET45;NET46;NET47;NET48; - - - + true + true + true + true + 0.1.0 + xsfhlzh;vicwjb + InspireFunction + WPF的简单MVVM模式开发类库 + InspireFunction + LICENSE + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + IFoxCAD;C#;NET;WPF;MVVM + git + 基于NFOX类库的重构版本 + - - - True - - - + + DEBUG;TRACE + + + + + + + + + True + + + diff --git a/src/IFoxCAD.WPF/RelayCommand.cs b/src/IFoxCAD.WPF/RelayCommand.cs index 338c515..cb4f118 100644 --- a/src/IFoxCAD.WPF/RelayCommand.cs +++ b/src/IFoxCAD.WPF/RelayCommand.cs @@ -30,7 +30,7 @@ public RelayCommand(Action execute):this(execute,null) /// execute public RelayCommand(Action execute,Func canExecute) { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } @@ -41,14 +41,14 @@ public event EventHandler CanExecuteChanged { add { - if (_canExecute != null) + if (_canExecute is not null) { CommandManager.RequerySuggested += value; } } remove { - if (_canExecute != null) + if (_canExecute is not null) { CommandManager.RequerySuggested -= value; } @@ -64,7 +64,7 @@ public event EventHandler CanExecuteChanged [DebuggerStepThrough] public bool CanExecute(object parameter) { - return _canExecute == null ? true : _canExecute(parameter); + return _canExecute is null ? true : _canExecute(parameter); } /// /// 定义在调用此命令时要调用的方法。 @@ -93,7 +93,7 @@ public RelayCommand(Action execute) : this(execute, (o)=>true) { } - + /// /// 初始化 类。 /// @@ -102,7 +102,7 @@ public RelayCommand(Action execute) : this(execute, (o)=>true) /// execute public RelayCommand(Action execute, Func canExecute) { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); _canExecute = canExecute; } /// @@ -112,14 +112,14 @@ public event EventHandler CanExecuteChanged { add { - if (_canExecute != null) + if (_canExecute is not null) { CommandManager.RequerySuggested += value; } } remove { - if (_canExecute != null) + if (_canExecute is not null) { CommandManager.RequerySuggested -= value; } @@ -134,7 +134,7 @@ public event EventHandler CanExecuteChanged /// public bool CanExecute(object parameter) { - if (_canExecute == null) + if (_canExecute is null) { return true; } @@ -146,7 +146,7 @@ public bool CanExecute(object parameter) /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 public void Execute(object parameter) { - if (_execute != null && CanExecute(parameter)) + if (_execute is not null && CanExecute(parameter)) { _execute((T)parameter); } @@ -164,11 +164,11 @@ public class EventCommand : TriggerAction /// 要执行的动作参数, 如果动作为提供参数,就设置为null protected override void Invoke(object parameter) { - if (CommandParameter != null) + if (CommandParameter is not null) { parameter = CommandParameter; } - if (Command != null) + if (Command is not null) { Command.Execute(parameter); } diff --git a/tests/DBTrans.test/Properties/launchSettings.json b/tests/Test/Properties/launchSettings.json similarity index 100% rename from tests/DBTrans.test/Properties/launchSettings.json rename to tests/Test/Properties/launchSettings.json diff --git a/tests/DBTrans.test/Test.cs b/tests/Test/Test.cs similarity index 99% rename from tests/DBTrans.test/Test.cs rename to tests/Test/Test.cs index c261a33..78746f4 100644 --- a/tests/DBTrans.test/Test.cs +++ b/tests/Test/Test.cs @@ -79,12 +79,11 @@ public void draCircle() Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 tr.CurrentSpace.AddEntity(circle1, circle2); if (circle3 is not null) - { tr.CurrentSpace.AddEntity(circle3); - } else { tr.Editor.WriteMessage("三点画圆失败"); + return; } tr.CurrentSpace.AddEntity(circle3); tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 @@ -98,7 +97,7 @@ public void Layertest() tr.LayerTable.Add("1"); tr.LayerTable.Add("2", lt => { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); lt.LineWeight = LineWeight.LineWeight030; }); diff --git a/tests/DBTrans.test/DBTrans.test.csproj b/tests/Test/Test.csproj similarity index 58% rename from tests/DBTrans.test/DBTrans.test.csproj rename to tests/Test/Test.csproj index d856f39..fa8ed34 100644 --- a/tests/DBTrans.test/DBTrans.test.csproj +++ b/tests/Test/Test.csproj @@ -3,18 +3,31 @@ preview + enable + 1.0.0.* 1.0.0.0 False git - - NET45;NET46;NET47;NET48 + + NET45; true true + + + + + + + + + + + diff --git a/tests/DBTrans.test/testConvexHull.cs b/tests/Test/testConvexHull.cs similarity index 100% rename from tests/DBTrans.test/testConvexHull.cs rename to tests/Test/testConvexHull.cs diff --git a/tests/DBTrans.test/testeditor.cs b/tests/Test/testeditor.cs similarity index 100% rename from tests/DBTrans.test/testeditor.cs rename to tests/Test/testeditor.cs diff --git a/tests/DBTrans.test/testenv.cs b/tests/Test/testenv.cs similarity index 100% rename from tests/DBTrans.test/testenv.cs rename to tests/Test/testenv.cs diff --git a/tests/DBTrans.test/testselectfilter.cs b/tests/Test/testselectfilter.cs similarity index 100% rename from tests/DBTrans.test/testselectfilter.cs rename to tests/Test/testselectfilter.cs diff --git a/tests/DBTrans.test/wpf/TestView.xaml b/tests/Test/wpf/TestView.xaml similarity index 100% rename from tests/DBTrans.test/wpf/TestView.xaml rename to tests/Test/wpf/TestView.xaml diff --git a/tests/DBTrans.test/wpf/TestView.xaml.cs b/tests/Test/wpf/TestView.xaml.cs similarity index 100% rename from tests/DBTrans.test/wpf/TestView.xaml.cs rename to tests/Test/wpf/TestView.xaml.cs diff --git a/tests/DBTrans.test/wpf/TestViewModel.cs b/tests/Test/wpf/TestViewModel.cs similarity index 100% rename from tests/DBTrans.test/wpf/TestViewModel.cs rename to tests/Test/wpf/TestViewModel.cs -- Gitee From 46f10bb2b99e4d1f7b06af2b38caa361faf81c2f Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Tue, 29 Mar 2022 21:03:51 +0800 Subject: [PATCH 10/18] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/LoopList.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index fca9931..2c0b088 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -373,6 +373,11 @@ public LoopListNode AddLast(T value) return Add(value); } + + /// + /// 容器内容全部加入到末尾 + /// + /// public void AddRange(IEnumerable list) { var ge = list.GetEnumerator(); -- Gitee From 231a1df376aeac16820eab32ebd99ebf204e21da Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Thu, 11 Aug 2022 22:56:29 +0800 Subject: [PATCH 11/18] =?UTF-8?q?jing=E5=88=86=E6=94=AF=E6=94=AF=E6=8C=81a?= =?UTF-8?q?cad08?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 217 ++ .gitattributes | 2 +- .gitignore | 128 +- IFoxCAD.sln | 14 +- README.md | 163 +- docs/DBTrans.md | 4 +- docs/WPF.md | 2 +- ...4\344\272\214\347\273\264\347\240\201.png" | Bin 0 -> 20425 bytes docs/png/nuget.png | Bin 0 -> 159167 bytes docs/png/nuget1.png | Bin 0 -> 27373 bytes docs/png/standard.png | Bin 0 -> 148673 bytes ...66\346\236\204\350\257\264\346\230\216.md" | 8 +- src/IFoxCAD.Basal/ArrayEx.cs | 50 + src/IFoxCAD.Basal/CLS/Index.cs | 148 + src/IFoxCAD.Basal/CLS/Range.cs | 103 + src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs | 41 + .../CLS/TupleElementNamesAttribute.cs | 48 + src/IFoxCAD.Basal/CLS/ValueTuple.cs | 2144 ++++++++++++ src/IFoxCAD.Basal/DictEx.cs | 21 + src/IFoxCAD.Basal/GlobalUsings.cs | 9 + src/IFoxCAD.Basal/IFoxCAD.Basal.csproj | 75 +- src/IFoxCAD.Basal/LinkedHashMap.cs | 171 + src/IFoxCAD.Basal/LinkedHashSet.cs | 221 ++ src/IFoxCAD.Basal/LinqEx.cs | 543 ++- src/IFoxCAD.Basal/ListEx.cs | 25 + src/IFoxCAD.Basal/LoopList.cs | 1223 +++---- src/IFoxCAD.Basal/Sortedset/ISet.cs | 71 + src/IFoxCAD.Basal/Sortedset/SR.cs | 907 +++++ src/IFoxCAD.Basal/Sortedset/Sortedset.cs | 2935 +++++++++++++++++ src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs | 382 +++ src/IFoxCAD.Basal/Sortedset/bithelper.cs | 173 + src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs | 647 ++++ src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs | 98 + .../Algorithms/QuadTree/QuadEntity.cs | 25 + .../Algorithms/QuadTree/QuadTree.cs | 259 ++ .../Algorithms/QuadTree/QuadTreeEvn.cs | 26 + .../Algorithms/QuadTree/QuadTreeNode.cs | 818 +++++ .../Algorithms/QuadTree/QuadTreeSelectMode.cs | 21 + src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs | 605 ++++ .../Algorithms/QuadTree/Utility.cs | 60 + .../ExtensionMethod/BulgeVertexWidth.cs | 85 + .../ExtensionMethod/CollectionEx.cs | 291 +- src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs | 530 ++- src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs | 920 +++--- src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs | 1400 +++----- .../ExtensionMethod/DBDictionaryEx.cs | 622 ++-- src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs | 262 +- src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs | 23 + src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 1600 +++++---- src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs | 730 ++-- src/IFoxCAD.Cad/ExtensionMethod/Enums.cs | 161 +- src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs | 1189 +++---- src/IFoxCAD.Cad/ExtensionMethod/Jig.cs | 343 ++ src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs | 21 + src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs | 133 +- src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs | 128 + .../ExtensionMethod/SelectionSetEx.cs | 173 +- .../ExtensionMethod/SymbolTableEx.cs | 540 +-- .../ExtensionMethod/SymbolTableRecordEx.cs | 721 ++-- src/IFoxCAD.Cad/ExtensionMethod/Tools.cs | 188 ++ .../HatchConverter.cs" | 358 ++ .../HatchEx.cs" | 15 + .../HatchInfo.cs" | 351 ++ .../AttachmentPointHelper.cs" | 95 + .../TextEntityAdd.cs" | 62 + .../TextInfo.cs" | 178 + src/IFoxCAD.Cad/GlobalUsings.cs | 31 + src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 51 +- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data | 1 - src/IFoxCAD.Cad/ResultData/LispDottedPair.cs | 106 +- src/IFoxCAD.Cad/ResultData/LispList.cs | 353 +- src/IFoxCAD.Cad/ResultData/TypedValueList.cs | 114 +- src/IFoxCAD.Cad/ResultData/XRecordDataList.cs | 111 +- src/IFoxCAD.Cad/ResultData/XdataList.cs | 115 +- src/IFoxCAD.Cad/Runtime/AOP.cs | 99 + src/IFoxCAD.Cad/Runtime/AcadVersion.cs | 164 +- src/IFoxCAD.Cad/Runtime/AssemInfo.cs | 105 +- src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs | 242 +- src/IFoxCAD.Cad/Runtime/CadVersion.cs | 92 + src/IFoxCAD.Cad/Runtime/DBTrans.cs | 699 ++-- src/IFoxCAD.Cad/Runtime/Env.cs | 792 ++--- src/IFoxCAD.Cad/Runtime/FileOpenMode.cs | 13 + src/IFoxCAD.Cad/Runtime/IAutoGo.cs | 307 ++ src/IFoxCAD.Cad/Runtime/Log.cs | 412 +++ src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs | 53 + src/IFoxCAD.Cad/Runtime/SymbolTable.cs | 544 +-- src/IFoxCAD.Cad/Runtime/Utils.cs | 98 + src/IFoxCAD.Cad/SelectionFilter/OpComp.cs | 138 +- src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs | 148 +- src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs | 582 ++-- src/IFoxCAD.Cad/SelectionFilter/OpList.cs | 272 +- src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs | 205 +- src/IFoxCAD.WPF/Converter.cs | 176 +- src/IFoxCAD.WPF/DependencyObjectExtensions.cs | 52 +- src/IFoxCAD.WPF/EnumSelection.cs | 72 + src/IFoxCAD.WPF/EventBindingExtension.cs | 337 +- src/IFoxCAD.WPF/GlobalUsings.cs | 18 + src/IFoxCAD.WPF/IFoxCAD.WPF.csproj | 67 +- src/IFoxCAD.WPF/RelayCommand.cs | 316 +- src/IFoxCAD.WPF/ViewModelBase.cs | 103 +- tests/Test/CmdINI.cs | 111 + tests/Test/GlobalUsings.cs | 30 + tests/Test/Properties/launchSettings.json | 2 +- tests/Test/Test.cs | 818 ++--- tests/Test/Test.csproj | 29 +- tests/Test/TestAOP.cs | 66 + tests/Test/TestCurve.cs | 158 + tests/Test/TestDBTrans.cs | 78 + tests/Test/TestEnt.cs | 40 + tests/Test/TestFileDatabase.cs | 29 + tests/Test/TestJig.cs | 335 ++ tests/Test/TestLisp.cs | 122 + tests/Test/TestLoop.cs | 36 + tests/Test/TestMirrorFile.cs | 73 + tests/Test/TestPoint.cs | 160 + tests/Test/TestQuadTree.cs | 461 +++ tests/Test/Testid.cs | 74 + tests/Test/testConvexHull.cs | 6 +- tests/Test/testblock.cs | 631 ++++ tests/Test/testeditor.cs | 95 +- tests/Test/testenv.cs | 136 +- tests/Test/testselectfilter.cs | 16 +- tests/Test/wpf/Class1.cs | 13 + tests/Test/wpf/TestView.xaml | 4 +- tests/Test/wpf/TestView.xaml.cs | 2 +- tests/Test/wpf/TestViewModel.cs | 156 +- tests/TestConsole/Program.cs | 55 + tests/TestConsole/RuntimeHelpers.cs | 50 + tests/TestConsole/TestConsole.csproj | 22 + ...50\350\276\276\345\274\217\346\240\221.cs" | 143 + 130 files changed, 25364 insertions(+), 9081 deletions(-) create mode 100644 .editorconfig create mode 100644 "docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" create mode 100644 docs/png/nuget.png create mode 100644 docs/png/nuget1.png create mode 100644 docs/png/standard.png create mode 100644 src/IFoxCAD.Basal/ArrayEx.cs create mode 100644 src/IFoxCAD.Basal/CLS/Index.cs create mode 100644 src/IFoxCAD.Basal/CLS/Range.cs create mode 100644 src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs create mode 100644 src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs create mode 100644 src/IFoxCAD.Basal/CLS/ValueTuple.cs create mode 100644 src/IFoxCAD.Basal/DictEx.cs create mode 100644 src/IFoxCAD.Basal/GlobalUsings.cs create mode 100644 src/IFoxCAD.Basal/LinkedHashMap.cs create mode 100644 src/IFoxCAD.Basal/LinkedHashSet.cs create mode 100644 src/IFoxCAD.Basal/ListEx.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/ISet.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/SR.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/Sortedset.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/bithelper.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/Jig.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/Tools.cs create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" create mode 100644 src/IFoxCAD.Cad/GlobalUsings.cs delete mode 100644 src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data create mode 100644 src/IFoxCAD.Cad/Runtime/AOP.cs create mode 100644 src/IFoxCAD.Cad/Runtime/CadVersion.cs create mode 100644 src/IFoxCAD.Cad/Runtime/FileOpenMode.cs create mode 100644 src/IFoxCAD.Cad/Runtime/IAutoGo.cs create mode 100644 src/IFoxCAD.Cad/Runtime/Log.cs create mode 100644 src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs create mode 100644 src/IFoxCAD.Cad/Runtime/Utils.cs create mode 100644 src/IFoxCAD.WPF/EnumSelection.cs create mode 100644 src/IFoxCAD.WPF/GlobalUsings.cs create mode 100644 tests/Test/CmdINI.cs create mode 100644 tests/Test/GlobalUsings.cs create mode 100644 tests/Test/TestAOP.cs create mode 100644 tests/Test/TestCurve.cs create mode 100644 tests/Test/TestDBTrans.cs create mode 100644 tests/Test/TestEnt.cs create mode 100644 tests/Test/TestFileDatabase.cs create mode 100644 tests/Test/TestJig.cs create mode 100644 tests/Test/TestLisp.cs create mode 100644 tests/Test/TestLoop.cs create mode 100644 tests/Test/TestMirrorFile.cs create mode 100644 tests/Test/TestPoint.cs create mode 100644 tests/Test/TestQuadTree.cs create mode 100644 tests/Test/Testid.cs create mode 100644 tests/Test/testblock.cs create mode 100644 tests/Test/wpf/Class1.cs create mode 100644 tests/TestConsole/Program.cs create mode 100644 tests/TestConsole/RuntimeHelpers.cs create mode 100644 tests/TestConsole/TestConsole.csproj create mode 100644 "tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f0012cd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,217 @@ +# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行 +root = true + +# c# 文件 +[*.cs] + +#### Core EditorConfig 选项 #### + +# 缩进和间距 +indent_size = 4 +indent_style = space +tab_width = 4 + +# 新行首选项 +end_of_line = crlf +insert_final_newline = false + +#### .NET 编码约定 #### + +# 组织 Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. 和 Me. 首选项 +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# 语言关键字与 bcl 类型首选项 +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# 括号首选项 +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# 修饰符首选项 +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# 表达式级首选项 +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# 字段首选项 +dotnet_style_readonly_field = true + +# 参数首选项 +dotnet_code_quality_unused_parameters = all + +# 禁止显示首选项 +dotnet_remove_unnecessary_suppression_exclusions = none + +# 新行首选项 +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### c# 编码约定 #### + +# var 首选项 +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied 成员 +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# 模式匹配首选项 +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null 检查首选项 +csharp_style_conditional_delegate_call = true + +# 修饰符首选项 +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# 代码块首选项 +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true + +# 表达式级首选项 +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_pattern_local_over_anonymous_function = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_range_operator = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# "using" 指令首选项 +csharp_using_directive_placement = outside_namespace + +# 新行首选项 +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# 格式规则 #### + +# 新行首选项 +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = accessors,anonymous_methods,anonymous_types,control_blocks,methods,object_collection_array_initializers,properties,types +csharp_new_line_between_query_expression_clauses = true + +# 缩进首选项 +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# 空格键首选项 +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# 包装首选项 +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/.gitattributes b/.gitattributes index 8bb03aa..63a09c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -############################################################################### +############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto diff --git a/.gitignore b/.gitignore index 25cc348..e910608 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## 忽略Visual Studio临时文件、生成结果和由VisualStudio常用插件生成的文件。 +## Visual StudioʱļɽVisualStudioòɵļ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files -# 用户特定文件 +# ûضļ *.rsuser *.suo *.user @@ -13,12 +13,12 @@ *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) -# 用户特定文件 (MonoDevelop/Xamarin Studio) +# ûضļ (MonoDevelop/Xamarin Studio) *.userprefs # Build results -# Build 结果 -# 语法:[abc]匹配任何一个列在方括号中的字符(要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)——如 *.[oa]表明Git忽略所有以 .o 或 .a 结尾的文件 +# Build +# ﷨[abc]ƥκһڷеַҪôƥһ aҪôƥһ bҪôƥһ c *.[oa]Git .o .a βļ [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ @@ -33,34 +33,34 @@ bld/ [Ll]og/ # Visual Studio 2015/2017 cache/options directory -# Visual Studio 2015/2017 缓存/选项 目录 +# Visual Studio 2015/2017 /ѡ Ŀ¼ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot -# 如果您有在wwwroot中创建项目静态文件的任务,请取消注释 +# wwwrootдĿ̬ļȡע #wwwroot/ # Visual Studio 2017 auto generated files -# Visual Studio 2017自动生成的文件 +# Visual Studio 2017Զɵļ Generated\ Files/ # MSTest test Results -# MSTest测试结果 +# MSTestԽ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT -# NUnit 是 JUnit 的 .NET 版,支持所有 .NET 语言,完全使用 C# 编写,并进行完全重新设计以利用很多高级的 .NET 语言特性,例如定制属性以及其他相关的反射功能。 +# NUnit JUnit .NET 棬֧ .NET ԣȫʹ C# дȫúܶ߼ .NET ԣ綨Լصķ书ܡ *.VisualState.xml TestResult.xml # Build Results of an ATL Project -# ATL项目的生成结果 +# ATLĿɽ [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results -# Benchmark结果 +# Benchmark BenchmarkDotNet.Artifacts/ # .NET Core @@ -69,11 +69,11 @@ project.fragment.lock.json artifacts/ # StyleCop -# 代码检测工具 +# ⹤ StyleCopReport.xml # Files built by Visual Studio -# Visual Studio built的文件 +# Visual Studio builtļ *_i.c *_p.c *_h.h @@ -103,11 +103,11 @@ StyleCopReport.xml *.scc # Chutzpah Test files -# Chutzpah测试文件 +# Chutzpahļ _Chutzpah* # Visual C++ cache files -# Visual C++ 缓存文件 +# Visual C++ ļ ipch/ *.aps *.ncb @@ -119,23 +119,23 @@ ipch/ *.VC.VC.opendb # Visual Studio profiler -# 应用程序性能分析工具 +# Ӧóܷ *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files -# Visual Studio 跟踪文件 +# Visual Studio ļ *.e2e # TFS 2012 Local Workspace -# TFS 2012 本地工作区 +# TFS 2012 ع $tf/ # Guidance Automation Toolkit -# 这套工具旨在简化将可重用的代码集成到应用程序的过程,使架构师能将通常需手动执行的一系列开发工作自动化起来。 -# 使用此工具,还能确保重复性的、易出错的开发工作以合理、一致的方式完成,并能缩短软件开发时间。 +# ׹ּڼ򻯽õĴ뼯ɵӦóḶ́ʹܹʦִֶܽͨеһϵпԶ +# ʹô˹ߣȷظԵġ׳ĿԺһµķʽɣʱ䡣 *.gpState # ReSharper is a .NET coding add-in @@ -173,11 +173,11 @@ AutoTest.Net/ .sass-cache/ # Installshield output folder -# Installshield 输出文件夹 +# Installshield ļ [Ee]xpress/ # DocProject is a documentation generator add-in -# DocProject 是一个文档生成器外接程序 +# DocProject һĵӳ DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC @@ -195,32 +195,32 @@ publish/ *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted -# 注意:如果要签入web部署设置,请在下一行添加注释, -# 但是数据库连接字符串(带有可能的密码)将是未加密的 +# ע⣺Ҫǩwebãһעͣ +# ݿַпܵ룩δܵ *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted -# Microsoft Azure Web App发布设置。 -# 如果您想签入Azure Web App发布设置,请在下一行添加注释,但这些脚本中包含的敏感信息将不加密 +# Microsoft Azure Web Appá +# ǩAzure Web AppãһעͣЩűаϢ PublishScripts/ # NuGet Packages -# NuGet包 +# NuGet *.nupkg # The packages folder can be ignored because of Package Restore -# 由于Package Restore,包文件夹可以忽略 +# Package RestoreļпԺ **/[Pp]ackages/* # except build/, which is used as an MSBuild target. -# 除了build/,它用作MSBuild目标。 +# build/MSBuildĿꡣ !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -# 必要时取消注释,但通常需要时会重新生成注释 +# ҪʱȡעͣͨҪʱע #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files -# NuGet v3的project.json文件生成更多可忽略的文件 +# NuGet v3project.jsonļɸɺԵļ *.nuget.props *.nuget.targets @@ -233,7 +233,7 @@ ecf/ rcf/ # Windows Store app package directories and files -# Windows应用商店应用程序包目录和文件 +# WindowsӦ̵ӦóĿ¼ļ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml @@ -241,16 +241,16 @@ _pkginfo.txt *.appx # Visual Studio cache files -# Visual Studio缓存文件 +# Visual Studioļ # files ending in .cache can be ignored -# 可以忽略以.cache结尾的文件 +# Ժ.cacheβļ *.[Cc]ache # but keep track of directories ending in .cache -# 但要跟踪以.cache结尾的目录 +# Ҫ.cacheβĿ¼ !?*.[Cc]ache/ # Others -# 其他 +# ClientBin/ ~$* *~ @@ -262,16 +262,16 @@ ClientBin/ orleans.codegen.cs # Including strong name files can present a security risk -# 包含强名称文件可能会带来安全风险 +# ǿļܻȫ # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components -#由于有多个工作流,取消注释下一行以忽略bower_components +#жȡעһԺbower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true -#ASP.NET核心默认设置:bower目录配置为wwwroot/lib/并且bower restore为true +#ASP.NETĬãbowerĿ¼Ϊwwwroot/lib/bower restoreΪtrue **/wwwroot/lib/ # RIA/Silverlight projects @@ -280,7 +280,7 @@ Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) -#将旧项目文件转换为新的Visual Studio版本的备份和报告文件。不需要备份文件,因为我们有git; +#ĿļתΪµVisual Studio汾ıݺͱļҪļΪgit _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML @@ -294,7 +294,7 @@ ServiceFabricBackup/ *.ndf # Business Intelligence projects -# 商业智能项目 +# ҵĿ *.rdl.data *.bim.layout *.bim_*.settings @@ -304,28 +304,28 @@ ServiceFabricBackup/ FakesAssemblies/ # GhostDoc plugin setting file -# GhostDoc插件设置文件 +# GhostDocļ *.GhostDoc.xml # Node.js Tools for Visual Studio -# 用于Visual Studio的Node.js工具 +# Visual StudioNode.js .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log -# Visual Studio 6生成日志 +# Visual Studio 6־ *.plg # Visual Studio 6 workspace options file -# Visual Studio 6工作区选项文件 +# Visual Studio 6ѡļ *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -# Visual Studio 6自动生成的工作区文件(包含打开的文件等) +# Visual Studio 6ԶɵĹļ򿪵ļȣ *.vbw # Visual Studio LightSwitch build output -# Visual Studio LightSwitch生成输出 +# Visual Studio LightSwitch **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml @@ -334,7 +334,7 @@ node_modules/ _Pvt_Extensions # Paket dependency manager -# Paket依赖关系管理器 +# Paketϵ .paket/paket.exe paket-files/ @@ -353,7 +353,7 @@ __pycache__/ *.pyc # Cake - Uncomment if you are using it -# Cake-如果你正在使用它,请取消注释 +# Cake-ʹȡע # tools/** # !tools/packages.config @@ -361,7 +361,7 @@ __pycache__/ *.tss # Telerik's JustMock configuration file -# Telerik的JustMock配置文件 +# TelerikJustMockļ *.jmconfig # BizTalk build output @@ -372,29 +372,39 @@ __pycache__/ *.xsd.cs # OpenCover UI analysis results -# OpenCover UI分析结果 +# OpenCover UI OpenCover/ # Azure Stream Analytics local run output -# Azure流分析本地运行输出 +# Azure ASALocalRun/ # MSBuild Binary and Structured Log -# MSBuild二进制和结构化日志 +# MSBuildƺͽṹ־ *.binlog # NVidia Nsight GPU debugger configuration file -# NVidia Nsight GPU调试器配置文件 +# NVidia Nsight GPUļ *.nvuser # MFractors (Xamarin productivity tool) working folder -# MFractors(Xamarin生产力工具)工作文件夹 +# MFractorsXamarinߣļ .mfractor/ # Local History for Visual Studio -# Visual Studio 的本地历史记录 +# Visual Studio ıʷ¼ .localhistory/ # BeatPulse healthcheck temp database -# BeatPulse healthcheck 临时数据库 +# BeatPulse healthcheck ʱݿ healthchecksdb + +#macos +*.DS_Store + +#vscode +.ionide +.vscode + +#ifoxcad +#tests/TestConsole/ \ No newline at end of file diff --git a/IFoxCAD.sln b/IFoxCAD.sln index 309172c..a9e0664 100644 --- a/IFoxCAD.sln +++ b/IFoxCAD.sln @@ -1,15 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Cad", "src\IFoxCAD.Cad\IFoxCAD.Cad.csproj", "{D5FAED7C-A99B-4BEE-A745-45442DC44971}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBTrans.test", "tests\DBTrans.test\DBTrans.test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "tests\Test\Test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.WPF", "src\IFoxCAD.WPF\IFoxCAD.WPF.csproj", "{D820D629-1AB3-4BE3-A772-CB753D98CCB8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFoxCAD.Basal", "src\IFoxCAD.Basal\IFoxCAD.Basal.csproj", "{40BF07C4-100A-4810-A27B-4587CFB38E6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Basal", "src\IFoxCAD.Basal\IFoxCAD.Basal.csproj", "{40BF07C4-100A-4810-A27B-4587CFB38E6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsole", "tests\TestConsole\TestConsole.csproj", "{E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.Build.0 = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index f312887..6a4b012 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,90 @@ #### 介绍 -基于.NET的Cad二次开发类库 +基于.NET的Cad二次开发类库。 + +可以加群交流: + +![ifoxcad用户交流群群二维码](./docs/png/ifoxcad用户交流群群二维码.png) #### 软件架构及相关说明 - [软件架构说明](/docs/关于IFoxCAD的架构说明.md) - - [扩展函数说明](/docs/关于扩展函数的说明.md) -#### 安装教程 +#### 编译 IFox 源码工程 -1. vs新建net standord 类库 -2. 修改项目TargetFramework为net45,保存重加载项目 -3. 右键项目,管理nuget程序包,搜索ifoxcad,安装最新版就可以了 +由于vs2022抛弃了某几个net版本,所以我们同时安装vs2019和vs2022,然后使用vs2022; +其中的原因是vs2019拥有全部net版本,而vs2022拥有最新的分析器和语法. -#### 使用说明 +#### IFoxCad 项目模版 -1. 快速入门 +可以在vs扩展菜单-管理扩展中搜索ifoxcad,即可安装项目模板。使用项目模版可以方便的创建支持多目标多版本的使用ifoxcad类库的项目和类。如果无法在vs的市场里下载,就去上面的QQ群里下载。 - - 打开vs,新建一个standard类型的类库项目,修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET35以上的标准TFM(如:net35、net40、net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译]( https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 - - 右键项目文件,选择管理nuget程序包。 - - 在nuget程序里搜索**ifoxcad**,直接选择最新的版本,然后点击安装**IFoxCAD.Cad**,nuget会自动安装ifoxcad依赖的库。 - - 添加引用 +#### 安装教程 - ```c# - using Autodesk.AutoCAD.ApplicationServices; - using Autodesk.AutoCAD.EditorInput; - using Autodesk.AutoCAD.Runtime; - using Autodesk.AutoCAD.Geometry; - using Autodesk.AutoCAD.DatabaseServices; - using IFoxCAD.Cad; - ``` +1. 新建net standard 类库 +2. 修改项目`.csproj`的`TargetFrameworks`为net45,保存重加载项目,这里需要注意和cad版本对照. +3. 右键项目,管理nuget程序包,搜索ifoxcad,安装最新版就可以了. - - 添加代码 +#### 使用说明 - ```c# - [CommandMethod("hello")] - public void Hello() - { - using var tr = new DBTrans() - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line1); - } +1. 快速入门 + + - 打开vs,新建一个standard类型的类库项目,**注意,需要选择类型的时候一定要选standard2.0** ![](./docs/png/standard.png) + + - 双击项目,打开项目文件: + + - 修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET45以上的标准TFM(如:net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译]( https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 + + - 在 ` xxx ` 中增加 `preview`,主要是为了支持最新的语法,本项目采用了最新的语法编写。项目文件现在的内容类似如下: + + ```xml + + + net45 + preview + + ``` - - 这段代码就是在cad的当前空间内添加了一条直线。 - + + - 右键项目文件,选择管理nuget程序包。![](./docs/png/nuget1.png) + + - 在nuget程序里搜索**ifoxcad**,直接选择最新的版本(如果你是 **net40** 或者 **net35** 的用户,可以安装 **0.1.6** 版本),然后点击安装**IFoxCAD.Cad**,nuget会自动安装ifoxcad依赖的库。(按下图绿色框框里选择浏览,程序包来源选择nuget.org,安装IFoxCAD.Cad包。IFoxCAD.Basal是IFoxCAD.Cad的依赖项会自动安装,如果要开发wpf界面的话,可以安装IFoxCAD.WPF,提供了简单的mvvm支持)![](./docs/png/nuget.png) + + - 添加引用 + + ```c# + using Autodesk.AutoCAD.ApplicationServices; + using Autodesk.AutoCAD.EditorInput; + using Autodesk.AutoCAD.Runtime; + using Autodesk.AutoCAD.Geometry; + using Autodesk.AutoCAD.DatabaseServices; + using IFoxCAD.Cad; + ``` + + - 添加代码 + + ```c# + [CommandMethod("hello")] + public void Hello() + { + using var tr = new DBTrans(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line1); + // 如果你没有添加preview到项目文件里的话:按如下旧语法: + // using(var tr = new DBTrans()) + // { + // var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + // tr.CurrentSpace.AddEntity(line1); + // } + } + ``` + + 这段代码就是在cad的当前空间内添加了一条直线。 + - F6生成,然后打开cad,netload命令将刚刚生成的dll加载。 + - 运行hello命令,然后缩放一下视图,现在一条直线和一个圆已经显示在屏幕上了。 2. [事务管理器用法](/docs/DBTrans.md) @@ -60,23 +97,62 @@ 5. [WPF支持](/docs/WPF.md) 6. 天秀的自动加载与初始化 - - 为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,内裤提供了`AutoRegAssem`抽象类来完成此功能,只要在需要初始化的类继承`AutoRegAssem`类,然后实现`Initialize()` 和`Terminate()`两个函数就可以了。特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 - + + 为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,类裤提供了`AutoRegAssem`抽象类来完成此功能,只要在需要初始化的类继承`AutoRegAssem`类,然后实现`Initialize()` 和`Terminate()`两个函数就可以了。 + 特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 + + 但是为了满足开闭原则,使用特性进行分段初始化是目前最佳选择 + ```c# - public class Test : AutoRegAssem //继承 + using Autodesk.AutoCAD.Runtime; + using IFoxCAD.Cad; + using System; + using System.Reflection; + + /* + * 自动执行接口 + * 这里必须要实现一次这个接口,才能使用 IFoxInitialize 特性进行自动执行 + */ + public class CmdINI : AutoRegAssem { - public override void Initialize() //实现接口函数 - { - throw new NotImplementedException(); + // 这里可以写任何普通的函数,也可以写下面 AutoTest 类里的实现了 IFoxInitialize 特性的初始化函数 + // 继承AutoRegAssem的主要作用是写注册表用来自动加载dll,同时执行实现了 IFoxInitialize 特性的函数 + // 注意这里的自动执行是在cad启动后,加载了dll之后执行,而不是运行命令后执行。 + + [IFoxInitialize] + public void InitOne() + { + //TODO 你想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 } - public override void Terminate() //实现接口函数 + + } + + //其他的类中的函数: + //实现自动接口之后,在任意一个函数上面使用此特性,减少每次改动 CmdINI 类 + public class AutoTest + { + [IFoxInitialize] + public void Initialize() + { + //TODO 你想在加载dll之后自动执行的函数 + } + [IFoxInitialize] + public void InitTwo() + { + //TODO 你想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 + } + [IFoxInitialize(isInitialize: false)] // 特性的参数为false的时候就表示卸载时执行的函数 + public void Terminate() { - throw new NotImplementedException(); + //TODO 你想在关闭cad时自动执行的函数 } } ``` - + + + 7. 天秀的打开模式提权 由于cad的对象是有打开模式,是否可写等等,为了安全起见,在处理对象时,一般是用读模式打开,然后需要写数据的时候在提权为写模式,然后在降级到读模式,但是这个过程中,很容易漏掉某些步骤,然后cad崩溃。为了处理这些情况,内裤提供了提权类来保证读写模式的有序转换。 @@ -88,4 +164,5 @@ } //关闭事务自动处理读写模式 ``` -8. 未完待续。。。。 \ No newline at end of file +8. 未完待续。。。。 + diff --git a/docs/DBTrans.md b/docs/DBTrans.md index 29f88dc..5e75860 100644 --- a/docs/DBTrans.md +++ b/docs/DBTrans.md @@ -59,7 +59,7 @@ using (DBTrans tr = new DBTrans()) // 第一步,开启事务 - **向符号表里添加元素** ```c# - using (DBTransaction tr = new DBTransaction()) + using (DBTrans tr = new DBTrans()) { // 第一步,开启事务 var layerTable = tr.LayerTable; // 第二步,获取图层表 @@ -75,7 +75,7 @@ using (DBTrans tr = new DBTrans()) // 第一步,开启事务 想要添加和获取符号表内的某个元素非常的简单: ```c# - using (DBTransaction tr = new DBTransaction()) // 第一步,开启事务 + using (DBTrans tr = new DBTrans()) // 第一步,开启事务 { var layerTable = tr.LayerTable; // 第二步,获取图层表 layerTable.Add("1"); // 第三步,添加名为“1”的图层,即新建图层 diff --git a/docs/WPF.md b/docs/WPF.md index 08d7c28..73f4f11 100644 --- a/docs/WPF.md +++ b/docs/WPF.md @@ -225,7 +225,7 @@ public class TestViewModel : ViewModelBase 首先是在xaml里引入命名空间。 -`xmlns:eb="clr-namespace:IFoxCad.WPF;assembly=IFoxCad"` +`xmlns:eb="clr-namespace:IFoxCAD.WPF;assembly=IFoxCAD.WPF"` 然后 diff --git "a/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" "b/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..a9d08c483b80f21d4b0ac6a0afe80f9b6b4233e6 GIT binary patch literal 20425 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Ð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 literal 0 HcmV?d00001 diff --git a/docs/png/nuget1.png b/docs/png/nuget1.png new file mode 100644 index 0000000000000000000000000000000000000000..91983ffef3b246ea796296b14fe09963663de184 GIT binary patch literal 27373 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 literal 0 HcmV?d00001 diff --git a/docs/png/standard.png b/docs/png/standard.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc34fecb32120f54436115208a4abaea2dac516 GIT binary patch literal 148673 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%0DO&7ix%Xw#WJ4GhT6iE|`sg)>zmh51hyQTaFv=}2 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 literal 0 HcmV?d00001 diff --git "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" index d828220..69a9b79 100644 --- "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" +++ "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" @@ -7,10 +7,10 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ## 一、组织结构图 - IFoxCAD - - IFoxCAD.Cad - cad相关的类库 + - IFoxCAD.Basal - cad以外常用的类库 - LinqEx - linq扩展类 - LoopList - 环链表 - - IFoxCAD.Basal - cad以外常用的类库 + - IFoxCAD.Cad - cad相关的类库 - Runtime - 包含系统级别的功能 - AcadVersion - cad版本号类 - AssemInfo - 程序集信息 @@ -45,7 +45,7 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ### 2.2 关于DBTrans类的具体构成元素的意义 -DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而抓这些其实都是cad二次开发关于图元操作经常打交道的概念。 +DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而这些其实都是cad二次开发关于图元操作经常打交道的概念。 DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的相关类库,通过这些属性就可以对数据进行相应的操作。特别是符号表中最常用的就是块表,通过对块表的操作来实现添加图元等。 @@ -109,7 +109,7 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - Has --- 判断符号表是否有符号表记录的函数 - 。。。 -特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法。 +特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法,原因在于cad的实体都是存在符号表记录里的,通常为模型这个块表记录。 # 慢慢完善,想到哪写到哪。。。 diff --git a/src/IFoxCAD.Basal/ArrayEx.cs b/src/IFoxCAD.Basal/ArrayEx.cs new file mode 100644 index 0000000..34fb4b7 --- /dev/null +++ b/src/IFoxCAD.Basal/ArrayEx.cs @@ -0,0 +1,50 @@ +namespace IFoxCAD.Basal +{ + /* + * 由于linq的函数大部分带有状态机,而cad是一个单机程序, + * 使用状态机会变得缓慢,因此我们设计的时候着重于时间优化, + * 本工具类在着重于数组遍历时候替代linq + */ + public static class ArrayEx + { + ///

+ /// 合并数组 + /// + /// + /// + public static T[] Combine2(this T[] a, T[] b) + { + var c = new T[a.Length + b.Length]; + Array.Copy(a, 0, c, 0, a.Length); + Array.Copy(b, 0, c, a.Length, b.Length); + return c; + } + + /// + /// 一维数组消重,此函数建议更改为: + /// set = new(); + /// foreach (var item in listInOut) + /// set.Add(item); + /// ]]> + /// + /// + /// 传入有重复成员的数组,传出没有重复的 + /// 传出参数1:数组开头;传出参数2:数组结尾;返回值比较结尾为就移除 + [Obsolete] + public static void Deduplication(List listInOut, Func func) + { + for (int i = 0; i < listInOut.Count; i++) + { + var first = listInOut[i]; + for (int j = listInOut.Count - 1; j > i; j--) + { + var last = listInOut[j]; + if (func(first, last)) + listInOut.RemoveAt(j); + } + } + } + + } +} diff --git a/src/IFoxCAD.Basal/CLS/Index.cs b/src/IFoxCAD.Basal/CLS/Index.cs new file mode 100644 index 0000000..97b51e5 --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/Index.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System; + +using System.Runtime.CompilerServices; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +public readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index index && _value == index._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return "^" + ((uint)Value).ToString(); + + return ((uint)Value).ToString(); + } +} + diff --git a/src/IFoxCAD.Basal/CLS/Range.cs b/src/IFoxCAD.Basal/CLS/Range.cs new file mode 100644 index 0000000..221167b --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/Range.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System; + +using System.Runtime.CompilerServices; + + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +public readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return Start.GetHashCode() * 31 + End.GetHashCode(); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start + ".." + End; + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + //[CLSCompliant(false)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start; + Index startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + Index endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } +} + diff --git a/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs new file mode 100644 index 0000000..b5698b4 --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs @@ -0,0 +1,41 @@ +//#if NET35 +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.CompilerServices; + +public static class RuntimeHelpers +{ + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array!!, Range range) + { + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + if (length == 0) + { + //return Array.Empty(); + return new T[0]; + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + + +} +//#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs new file mode 100644 index 0000000..bb0716d --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs @@ -0,0 +1,48 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.CompilerServices; + +/// +/// Indicates that the use of on a member is meant to be treated as a tuple with element names. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event)] +public sealed class TupleElementNamesAttribute : Attribute +{ + private readonly string[] _transformNames; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which occurrences are + /// meant to carry element names. + /// + /// + /// This constructor is meant to be used on types that contain an + /// instantiation of that contains + /// element names. For instance, if C is a generic type with + /// two type parameters, then a use of the constructed type C{, might be intended to + /// treat the first type argument as a tuple with element names and the + /// second as a tuple without element names. In which case, the + /// appropriate attribute specification should use a + /// transformNames value of { "name1", "name2", null, null, + /// null }. + /// + public TupleElementNamesAttribute(string[] transformNames!!) + { + _transformNames = transformNames; + } + + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which elements are + /// meant to carry element names. + /// + public IList TransformNames => _transformNames; +} \ No newline at end of file diff --git a/src/IFoxCAD.Basal/CLS/ValueTuple.cs b/src/IFoxCAD.Basal/CLS/ValueTuple.cs new file mode 100644 index 0000000..bece1ed --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/ValueTuple.cs @@ -0,0 +1,2144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//#pragma warning disable SA1141 // explicitly not using tuple syntax in tuple implementation + + +using System.Diagnostics; +using System.Numerics.Hashing; +/* + * 惊惊: + * 首先是因为有人想要编译的时候只形成一个dll,然后把元组塞入IFox,同时也补充了net35没有元组的遗憾. + * 而利用nuget元组包必然会形成依赖地狱. + * + * 如果你的工程使用了nuget元组包,就造成了必须要剔除IFox. + * + * 改IFox的元组命名空间倒是可以分离两者,但是 vs编译器 无法识别带其他命名空间的元组. + * 所以元组本身就是冲突的,需要把其他元组卸载掉,由IFox提供. + */ + +#if NET35 +namespace System.Collections +{ + public interface IStructuralComparable + { + int CompareTo(object? other, IComparer comparer); + } + public interface IStructuralEquatable + { + bool Equals(object? other, IEqualityComparer comparer); + int GetHashCode(IEqualityComparer comparer); + } +} +#endif + + + +namespace System.Numerics.Hashing +{ + internal static class HashHelpers + { + public static readonly int RandomSeed = Guid.NewGuid().GetHashCode(); + + public static int Combine(int h1, int h2) + { + unchecked + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: dotnet/coreclr#1830 + + // RyuJIT 对此进行了优化以使用 ROL 指令 + // 相关 GitHub 拉取请求:dotnet/coreclr#1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } + } +} + + + + +namespace System +{ + //internal static class SR + internal sealed partial class SR + { + // public const string ArgumentException_ValueTupleIncorrectType = "The parameter should be a ValueTuple type of appropriate arity."; + // public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "The TRest type argument of ValueTuple`8 must be a ValueTuple."; + public const string ArgumentException_ValueTupleIncorrectType = "该参数应该是适当数量的 ValueTuple 类型."; + public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "ValueTuple`8 的 TREST 类型参数必须是 ValueTuple."; + } + + // Helper so we can call some tuple methods recursively without knowing the underlying types. + /// + /// 帮助器,因此我们可以在不知道底层类型的情况下递归调用一些元组方法. + /// + internal interface ITupleInternal + { + int GetHashCode(IEqualityComparer comparer); + int Size { get; } + string ToStringEnd(); + } + + + // The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#. + // Aside from created via language syntax, they are most easily created via the ValueTuple.Create factory methods. + // The System.ValueTuple types differ from the System.Tuple types in that: + // - they are structs rather than classes, + // - they are mutable rather than readonly, and + // - their members (such as Item1, Item2, etc) are fields rather than properties. + /// + /// ValueTuple 类型(从 arity 0 到 8)包含运行时实现,它是 C# 中的元组和 F# 中的结构元组的基础. + /// 除了通过语言语法创建之外,它们最容易通过 ValueTuple.Create 工厂方法创建. + /// System.ValueTuple 类型与 System.Tuple 类型的不同之处在于: + /// - 它们是结构而不是类, + /// - 它们是可变的而不是只读的,并且 + /// - 它们的成员(例如 Item1、Item2 等)是字段而不是属性. + /// + public struct ValueTuple + : IEquatable, IStructuralEquatable, IStructuralComparable, IComparable, IComparable, ITupleInternal + { + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if is a . + public override bool Equals(object obj) + { + return obj is ValueTuple; + } + + /// Returns a value indicating whether this instance is equal to a specified value. + /// An instance to compare to this instance. + /// true if has the same value as this instance; otherwise, false. + public bool Equals(ValueTuple other) + { + return true; + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + return other is ValueTuple; + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return 0; + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Returns the hash code for this instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return 0; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (). + /// + public override string ToString() + { + return "()"; + } + + string ITupleInternal.ToStringEnd() + { + return ")"; + } + + int ITupleInternal.Size => 0; + + /// Creates a new struct 0-tuple. + /// A 0-tuple. + public static ValueTuple Create() => new(); + + /// Creates a new struct 1-tuple, or singleton. + /// The type of the first component of the tuple. + /// The value of the first component of the tuple. + /// A 1-tuple (singleton) whose value is (item1). + public static ValueTuple Create(T1 item1) => new(item1); + + /// Creates a new struct 2-tuple, or pair. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// A 2-tuple (pair) whose value is (item1, item2). + public static ValueTuple Create(T1 item1, T2 item2) => new(item1, item2); + + /// Creates a new struct 3-tuple, or triple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// A 3-tuple (triple) whose value is (item1, item2, item3). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3) => + new(item1, item2, item3); + + /// Creates a new struct 4-tuple, or quadruple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// A 4-tuple (quadruple) whose value is (item1, item2, item3, item4). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4) => + new(item1, item2, item3, item4); + + /// Creates a new struct 5-tuple, or quintuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// A 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) => + new(item1, item2, item3, item4, item5); + + /// Creates a new struct 6-tuple, or sextuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// A 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) => + new(item1, item2, item3, item4, item5, item6); + + /// Creates a new struct 7-tuple, or septuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// A 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) => + new(item1, item2, item3, item4, item5, item6, item7); + + /// Creates a new struct 8-tuple, or octuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The type of the eighth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// The value of the eighth component of the tuple. + /// An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). + public static ValueTuple> Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) => + new(item1, item2, item3, item4, item5, item6, item7, ValueTuple.Create(item8)); + + internal static int CombineHashCodes(int h1, int h2) + { + return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2); + } + + internal static int CombineHashCodes(int h1, int h2, int h3) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2), h3); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3), h4); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4), h5); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5), h6); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6), h7); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6, h7), h8); + } + } + + /// Represents a 1-tuple, or singleton, as a value type. + /// The type of the tuple's only component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + public ValueTuple(T1 item1) + { + Item1 = item1; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its field + /// is equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return Comparer.Default.Compare(Item1, objTuple.Item1); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return Comparer.Default.Compare(Item1, other.Item1); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return comparer.Compare(Item1, objTuple.Item1); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return EqualityComparer.Default.GetHashCode(Item1); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1), + /// where Item1 represents the value of . If the field is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ")"; + } + + int ITupleInternal.Size => 1; + } + + /// + /// Represents a 2-tuple, or pair, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// The current instance's first component. + /// + public T2 Item2; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public ValueTuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object based on a specified comparison method. + /// + /// The object to compare with this instance. + /// An object that defines the method to use to evaluate whether the two objects are equal. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// This member is an explicit interface member implementation. It can be used only when the + /// instance is cast to an interface. + /// + /// The implementation is called only if other is not , + /// and if it can be successfully cast (in C#) or converted (in Visual Basic) to a + /// whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method + /// first passes the values of the objects to be compared to the + /// implementation. If this method call returns , the method is + /// called again and passed the values of the two instances. + /// + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other is null or not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + return Comparer.Default.Compare(Item2, other.Item2); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + return comparer.Compare(Item2, objTuple.Item2); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2), + /// where Item1 and Item2 represent the values of the + /// and fields. If either field value is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + int ITupleInternal.Size => 2; + } + + /// + /// Represents a 3-tuple, or triple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + public ValueTuple(T1 item1, T2 item2, T3 item3) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + return Comparer.Default.Compare(Item3, other.Item3); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + return comparer.Compare(Item3, objTuple.Item3); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + int ITupleInternal.Size => 3; + } + + /// + /// Represents a 4-tuple, or quadruple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + return Comparer.Default.Compare(Item4, other.Item4); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + return comparer.Compare(Item4, objTuple.Item4); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + int ITupleInternal.Size => 4; + } + + /// + /// Represents a 5-tuple, or quintuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + return Comparer.Default.Compare(Item5, other.Item5); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + return comparer.Compare(Item5, objTuple.Item5); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + int ITupleInternal.Size => 5; + } + + /// + /// Represents a 6-tuple, or sixtuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + return Comparer.Default.Compare(Item6, other.Item6); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + return comparer.Compare(Item6, objTuple.Item6); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + int ITupleInternal.Size => 6; + } + + /// + /// Represents a 7-tuple, or sentuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + return Comparer.Default.Compare(Item7, other.Item7); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + return comparer.Compare(Item7, objTuple.Item7); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + int ITupleInternal.Size => 7; + } + + /// + /// Represents an 8-tuple, or octuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + /// The type of the tuple's eighth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + where TRest : struct + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + /// + /// The current instance's eighth component. + /// + public TRest Rest; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + /// The value of the tuple's eight component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) + { + if (rest is not ITupleInternal) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleLastArgumentNotAValueTuple); + } + + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + Rest = rest; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7) + && EqualityComparer.Default.Equals(Rest, other.Rest); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7) + && comparer.Equals(Rest, objTuple.Rest); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item7, other.Item7); + if (c != 0) return c; + + return Comparer.Default.Compare(Rest, other.Rest); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + c = comparer.Compare(Item7, objTuple.Item7); + if (c != 0) return c; + + return comparer.Compare(Rest, objTuple.Rest); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 2: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 3: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 4: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 5: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 6: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 7: + case 8: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes( + comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(comparer); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 2: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 3: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 4: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 5: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 6: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 7: + case 8: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Rest). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + if (Rest is not ITupleInternal rest) + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + string ITupleInternal.ToStringEnd() + { + if (Rest is not ITupleInternal rest) + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + int ITupleInternal.Size + { + get + { + //ITupleInternal? rest = Rest as ITupleInternal; + //return rest == null ? 8 : 7 + rest.Size; + return Rest is not ITupleInternal rest ? 8 : 7 + rest.Size; + } + } + } +} + + + + diff --git a/src/IFoxCAD.Basal/DictEx.cs b/src/IFoxCAD.Basal/DictEx.cs new file mode 100644 index 0000000..6fbccc4 --- /dev/null +++ b/src/IFoxCAD.Basal/DictEx.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace IFoxCAD.Basal +{ + public static class DictEx + { + //public static TKey? GetKey(this IDictionary dict!!, TKey key!!) + //{ + // if (dict.ContainsKey(key)) + // { + // foreach (var item in dict.Keys) + // if (key.Equals(item)) + // return item; + // } + // return default; + //} + } +} diff --git a/src/IFoxCAD.Basal/GlobalUsings.cs b/src/IFoxCAD.Basal/GlobalUsings.cs new file mode 100644 index 0000000..15c8a97 --- /dev/null +++ b/src/IFoxCAD.Basal/GlobalUsings.cs @@ -0,0 +1,9 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; + diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj index a15060d..73d940d 100644 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj @@ -1,45 +1,34 @@ - - - - preview - enable - - - 1.0.0.* - 1.0.0.0 - False - net35;net40 - 0.1.1 - - true - true - InspireFunction - xsfhlzh;vicwjb - 基于.NET的二次开发基本类库 - InspireFunction - git - https://gitee.com/inspirefunction/ifoxcad.git - - https://gitee.com/inspirefunction/ifoxcad - IFoxCAD;C#;NET;Common;Basal - 增加在net35支持 - true - true - - LICENSE - true - - - - - - - - - True - - - - + + + + preview + enable + + net35;net40;net45 + true + 0.3.5.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的二次开发基本类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;C#;NET;Common;Basal + 直接集成元组和索引切片. + true + true + LICENSE + true + True + none + + + + + True + + + diff --git a/src/IFoxCAD.Basal/LinkedHashMap.cs b/src/IFoxCAD.Basal/LinkedHashMap.cs new file mode 100644 index 0000000..0e6232c --- /dev/null +++ b/src/IFoxCAD.Basal/LinkedHashMap.cs @@ -0,0 +1,171 @@ +namespace IFoxCAD.Basal; + + +/// +/// A least-recently-used cache stored like a dictionary. +/// +/// +/// The type of the key to the cached item +/// +/// +/// The type of the cached item. +/// +/// +/// Derived from https://stackoverflow.com/a/3719378/240845 +/// https://stackoverflow.com/users/240845/mheyman +/// +public class LinkedHashMap +{ + private readonly Dictionary> cacheMap = new(); + + private readonly LinkedList lruList = new(); + + private readonly Action? dispose; + + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// Maximum number of elements to cache. + /// + /// + /// When elements cycle out of the cache, disposes them. May be null. + /// + public LinkedHashMap(int capacity, Action? dispose = null) + { + this.Capacity = capacity; + this.dispose = dispose; + } + + /// + /// Gets the capacity of the cache. + /// + public int Capacity { get; } + + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// + /// When this method returns, contains the value associated with the specified + /// key, if the key is found; otherwise, the default value for the type of the + /// parameter. This parameter is passed + /// uninitialized. + /// + /// + /// true if the + /// contains an element with the specified key; otherwise, false. + /// + public bool TryGetValue(TKey key, out TValue? value) + { + lock (this.cacheMap) + { + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + return true; + } + + value = default; + return false; + } + } + + /// + /// Looks for a value for the matching . If not found, + /// calls to retrieve the value and add it to + /// the cache. + /// + /// + /// The key of the value to look up. + /// + /// + /// Generates a value if one isn't found. + /// + /// + /// The requested value. + /// + public TValue Get(TKey key, Func valueGenerator) + { + lock (this.cacheMap) + { + TValue value; + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + } + else + { + value = valueGenerator(); + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + node = new LinkedListNode(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + + return value; + } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// + /// The key of the element to add. + /// + /// + /// The value of the element to add. The value can be null for reference types. + /// + public void Add(TKey key, TValue value) + { + lock (this.cacheMap) + { + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + LinkedListNode node = new(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + } + + private void RemoveFirst() + { + // Remove from LRUPriority + LinkedListNode node = this.lruList.First; + this.lruList.RemoveFirst(); + + // Remove from cache + this.cacheMap.Remove(node.Value.Key); + + // dispose + this.dispose?.Invoke(node.Value.Value); + } + + private class MapItem + { + public MapItem(TKey k, TValue v) + { + this.Key = k; + this.Value = v; + } + + public TKey Key { get; } + + public TValue Value { get; } + } +} + diff --git a/src/IFoxCAD.Basal/LinkedHashSet.cs b/src/IFoxCAD.Basal/LinkedHashSet.cs new file mode 100644 index 0000000..8659c24 --- /dev/null +++ b/src/IFoxCAD.Basal/LinkedHashSet.cs @@ -0,0 +1,221 @@ +namespace IFoxCAD.Basal; + +public class LinkedHashSet : ICollection where T : IComparable +{ + private readonly IDictionary> m_Dictionary; + private readonly LoopList m_LinkedList; + + public LinkedHashSet() + { + m_Dictionary = new Dictionary>(); + m_LinkedList = new LoopList(); + } + + public LoopListNode? First => m_LinkedList.First; + + public LoopListNode? Last => m_LinkedList.Last; + + public LoopListNode? MinNode { get; set; } + + public bool Add(T item) + { + if (m_Dictionary.ContainsKey(item)) + return false; + var node = m_LinkedList.AddLast(item); + m_Dictionary.Add(item, node); + + if (MinNode is null) + { + MinNode = node; + } + else + { + if (item.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + + + + return true; + } + + void ICollection.Add(T item) + { + Add(item); + } + + public LoopListNode AddFirst(T value) + { + if (m_Dictionary.ContainsKey(value)) + { + return m_Dictionary[value]; + } + var node = m_LinkedList.AddFirst(value); + m_Dictionary.Add(value, node); + if (MinNode is null) + { + MinNode = node; + } + else + { + if (value.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + return node; + } + + public void AddRange(IEnumerable collection) + { + foreach (var item in collection) + { + Add(item); + } + } + + + public void Clear() + { + m_LinkedList.Clear(); + m_Dictionary.Clear(); + } + + public bool Remove(T item) + { + bool found = m_Dictionary.TryGetValue(item, out LoopListNode node); + if (!found) return false; + m_Dictionary.Remove(item); + m_LinkedList.Remove(node); + return true; + } + + public int Count + { + get { return m_Dictionary.Count; } + } + + public void For(LoopListNode from, Action action) + { + var first = from; + var last = from; + if(first is null) return; + + for (int i = 0; i < Count; i++) + { + + action.Invoke(i,first!.Value, last!.Value); + first = first.Next; + last = last.Previous; + } + } + + public List ToList() + { + return m_LinkedList.ToList(); + } + + public IEnumerator GetEnumerator() + { + return m_LinkedList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + public bool Contains(T item) + { + return m_Dictionary.ContainsKey(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + //m_LinkedList.CopyTo(array, arrayIndex); + return; + } + + public bool SetFirst(LoopListNode node) + { + return m_LinkedList.SetFirst(node); + } + + public LinkedHashSet Clone() + { + var newset = new LinkedHashSet(); + foreach (var item in this) + { + newset.Add(item); + } + return newset; + } + + public virtual bool IsReadOnly + { + get { return m_Dictionary.IsReadOnly; } + } + + public override string ToString() + { + return m_LinkedList.ToString(); + } + + public void UnionWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void IntersectWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void ExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void SymmetricExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool Overlaps(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool SetEquals(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + private static Exception GetNotSupportedDueToSimplification() + { + return new NotSupportedException("This method is not supported due to simplification of example code."); + } +} diff --git a/src/IFoxCAD.Basal/LinqEx.cs b/src/IFoxCAD.Basal/LinqEx.cs index 7b20401..8112e63 100644 --- a/src/IFoxCAD.Basal/LinqEx.cs +++ b/src/IFoxCAD.Basal/LinqEx.cs @@ -1,342 +1,335 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Basal; -namespace IFoxCAD.Linq +/// +/// linq 扩展类 +/// +public static class LinqEx { + #region FindByMax + /// - /// linq 扩展类 + /// 按转换函数找出序列中最大键值的对应值 /// - public static class LinqEx + /// + /// + /// 序列 + /// 转换函数 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, Func func) + where TKey : IComparable { - #region FindByMax - - /// - /// 按转换函数找出序列中最大键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) > 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) > 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - return value; } + return value; + } - /// - /// 按转换函数找出序列中最大键值的对应值 - /// - /// - /// - /// 序列 - /// 对应的最大键值 - /// 转换函数 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, out TKey maxResult, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最大键值的对应值 + /// + /// + /// + /// 序列 + /// 对应的最大键值 + /// 转换函数 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, out TKey maxResult, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) > 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) > 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - maxResult = key; - return value; } + maxResult = key; + return value; + } - /// - /// 按比较器找出序列中最大键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); - - TValue value = itor.Current; + /// + /// 按比较器找出序列中最大键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - while (itor.MoveNext()) - { - if (comparison(itor.Current, value) > 0) - value = itor.Current; - } - return value; - } + TValue value = itor.Current; - #endregion FindByMax - - #region FindByMin - - /// - /// 按转换函数找出序列中最小键值的对应值 - /// - /// - /// - /// 序列 - /// 对应的最小键值 - /// 转换函数 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, out TKey minKey, Func func) - where TKey : IComparable + while (itor.MoveNext()) { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + if (comparison(itor.Current, value) > 0) + value = itor.Current; + } + return value; + } - TValue value = itor.Current; - TKey key = func(value); + #endregion FindByMax - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) < 0) - { - key = tkey; - value = itor.Current; - } - } - minKey = key; - return value; - } + #region FindByMin - /// - /// 按转换函数找出序列中最小键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最小键值的对应值 + /// + /// + /// + /// 序列 + /// 对应的最小键值 + /// 转换函数 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, out TKey minKey, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) < 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) < 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - return value; } + minKey = key; + return value; + } - /// - /// 按比较器找出序列中最小键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最小键值的对应值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) < 0) { - if (comparison(itor.Current, value) < 0) - value = itor.Current; + key = tkey; + value = itor.Current; } - return value; } + return value; + } - #endregion FindByMin + /// + /// 按比较器找出序列中最小键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - #region FindByExt + TValue value = itor.Current; - /// - /// 按转换函数找出序列中最(小/大)键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最(小/大)键值的对应值 - public static TValue[] FindByExt(this IEnumerable source, Func func) - where TKey : IComparable + while (itor.MoveNext()) { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + if (comparison(itor.Current, value) < 0) + value = itor.Current; + } + return value; + } - TValue[] values = new TValue[2]; - values[0] = values[1] = itor.Current; + #endregion FindByMin - TKey[] keys = new TKey[2]; - keys[0] = keys[1] = func(itor.Current); + #region FindByExt - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(keys[0]) < 0) - { - keys[0] = tkey; - values[0] = itor.Current; - } - else if (tkey.CompareTo(keys[1]) > 0) - { - keys[1] = tkey; - values[1] = itor.Current; - } - } - return values; - } + /// + /// 按转换函数找出序列中最(小/大)键值的对应值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最(小/大)键值的对应值 + public static TValue[] FindByExt(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - /// - /// 按比较器找出序列中最(小/大)键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最(小/大)键值的对应值 - public static TValue[] FindByExt(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + TValue[] values = new TValue[2]; + values[0] = values[1] = itor.Current; - TValue[] values = new TValue[2]; - values[0] = values[1] = itor.Current; + TKey[] keys = new TKey[2]; + keys[0] = keys[1] = func(itor.Current); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(keys[0]) < 0) { - if (comparison(itor.Current, values[0]) < 0) - values[0] = itor.Current; - else if (comparison(itor.Current, values[1]) > 0) - values[1] = itor.Current; + keys[0] = tkey; + values[0] = itor.Current; + } + else if (tkey.CompareTo(keys[1]) > 0) + { + keys[1] = tkey; + values[1] = itor.Current; } - return values; } + return values; + } - /// - /// 按转换函数找出序列中最(小/大)键值的对应键值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最(小/大)键值 - public static TKey[] FindExt(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按比较器找出序列中最(小/大)键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最(小/大)键值的对应值 + public static TValue[] FindByExt(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TKey[] keys = new TKey[2]; - keys[0] = keys[1] = func(itor.Current); + TValue[] values = new TValue[2]; + values[0] = values[1] = itor.Current; - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(keys[0]) < 0) - keys[0] = tkey; - else if (tkey.CompareTo(keys[1]) > 0) - keys[1] = tkey; - } - return keys; + while (itor.MoveNext()) + { + if (comparison(itor.Current, values[0]) < 0) + values[0] = itor.Current; + else if (comparison(itor.Current, values[1]) > 0) + values[1] = itor.Current; } + return values; + } - #endregion FindByExt + /// + /// 按转换函数找出序列中最(小/大)键值的对应键值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最(小/大)键值 + public static TKey[] FindExt(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - #region Order + TKey[] keys = new TKey[2]; + keys[0] = keys[1] = func(itor.Current); - /// - /// 自定义的比较泛型类 - /// - /// 泛型 - private class SpecComparer : IComparer + while (itor.MoveNext()) { - private Comparison _comp; - - internal SpecComparer(Comparison comp) - { - _comp = comp; - } + TKey tkey = func(itor.Current); + if (tkey.CompareTo(keys[0]) < 0) + keys[0] = tkey; + else if (tkey.CompareTo(keys[1]) > 0) + keys[1] = tkey; + } + return keys; + } - #region IComparer 成员 + #endregion FindByExt - public int Compare(T x, T y) - { - return _comp(x, y); - } + #region Order - #endregion IComparer 成员 - } + /// + /// 自定义的比较泛型类 + /// + /// 泛型 + private class SpecComparer : IComparer + { + private readonly Comparison _comp; - /// - /// 使用指定的比较器将序列按升序排序 - /// - /// 输入泛型 - /// 输出泛型 - /// 序列 - /// 用于从元素中提取键的函数 - /// 比较器 - /// 排序的序列 - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Comparison comparison) + internal SpecComparer(Comparison comp) { - return source.OrderBy(keySelector, new SpecComparer(comparison)); + _comp = comp; } - /// - /// 使用指定的比较器将其后的序列按升序排序 - /// - /// 输入泛型 - /// 输出泛型 - /// 序列 - /// 用于从元素中提取键的函数 - /// 比较器 - /// 排序的序列 - public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, Comparison comparison) + #region IComparer 成员 + public int Compare(T x, T y) { - return source.ThenBy(keySelector, new SpecComparer(comparison)); + return _comp(x, y); } + #endregion IComparer 成员 + } + + /// + /// 使用指定的比较器将序列按升序排序 + /// + /// 输入泛型 + /// 输出泛型 + /// 序列 + /// 用于从元素中提取键的函数 + /// 比较器 + /// 排序的序列 + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Comparison comparison) + { + return source.OrderBy(keySelector, new SpecComparer(comparison)); + } - #endregion Order + /// + /// 使用指定的比较器将其后的序列按升序排序 + /// + /// 输入泛型 + /// 输出泛型 + /// 序列 + /// 用于从元素中提取键的函数 + /// 比较器 + /// 排序的序列 + public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, Comparison comparison) + { + return source.ThenBy(keySelector, new SpecComparer(comparison)); } + + #endregion Order } diff --git a/src/IFoxCAD.Basal/ListEx.cs b/src/IFoxCAD.Basal/ListEx.cs new file mode 100644 index 0000000..b0d20c0 --- /dev/null +++ b/src/IFoxCAD.Basal/ListEx.cs @@ -0,0 +1,25 @@ + +namespace IFoxCAD.Basal; + +public static class ListEx +{ + public static bool EqualsAll(this IList a, IList b) + { + return EqualsAll(a, b, null); + // there is a slight performance gain in passing null here. + // It is how it is done in other parts of the framework. + } + + public static bool EqualsAll(this IList a!!, IList b!!, IEqualityComparer? comparer) + { + if (a.Count != b.Count) + return false; + + comparer ??= EqualityComparer.Default; + + for (int i = 0; i < a.Count; i++) + if (!comparer.Equals(a[i], b[i])) + return false; + return true; + } +} diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 2c0b088..c0e021c 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -1,281 +1,265 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; +namespace IFoxCAD.Basal; -namespace IFoxCAD.Collections +#line hidden //调试的时候跳过它 + +/// +/// 环链表节点 +/// +/// +public class LoopListNode { + #region 成员 /// - /// 环链表节点 - /// - /// - public class LoopListNode - { - #region 成员 - /// - /// 取值 - /// - public T Value; - - /// - /// 上一个节点 - /// - public LoopListNode? Previous { internal set; get; } - - /// - /// 下一个节点 - /// - public LoopListNode? Next { internal set; get; } - - /// - /// 环链表序列 - /// - public LoopList? List { internal set; get; } - #endregion - #region 构造 - /// - /// 环链表节点构造函数 - /// - /// 节点值 - public LoopListNode(T value, LoopList ts) - { - Value = value; - List = ts; - } + /// 取值 + ///
+ public T Value; - /// - /// 获取当前节点的临近节点 - /// - /// 搜索方向标志,为向前搜索,为向后搜索 - /// - public LoopListNode? GetNext(bool forward) - { - return forward ? Next : Previous; - } - #endregion - #region 方法 - /// - /// 无效化成员 - /// - internal void Invalidate() - { - List = null; - Next = null; - Previous = null; - } - #endregion + /// + /// 上一个节点 + /// + public LoopListNode? Previous { internal set; get; } + + /// + /// 下一个节点 + /// + public LoopListNode? Next { internal set; get; } + + /// + /// 环链表序列 + /// + public LoopList? List { internal set; get; } + #endregion + + #region 构造 + /// + /// 环链表节点构造函数 + /// + /// 节点值 + public LoopListNode(T value, LoopList ts) + { + Value = value; + List = ts; } /// - /// 环链表 + /// 获取当前节点的临近节点 /// - /// - public class LoopList : IEnumerable, IFormattable + /// 搜索方向标志,为向前搜索,为向后搜索 + /// + public LoopListNode? GetNext(bool forward) { - #region 成员 - /// - /// 节点数 - /// - public int Count { get; private set; } + return forward ? Next : Previous; + } + #endregion - /// - /// 首节点 - /// - public LoopListNode? First { get; private set; } + #region 方法 + /// + /// 无效化成员 + /// + internal void Invalidate() + { + List = null; + Next = null; + Previous = null; + } + #endregion +} + +/// +/// 环链表 +/// +/// +public class LoopList : IEnumerable, IFormattable +{ + #region 成员 - /// - /// 尾节点 - /// - public LoopListNode? Last => First?.Previous; + /// + /// 节点数 + /// + public int Count { get; private set; } - #endregion - #region 构造 - /// - /// 默认构造函数 - /// - public LoopList() { } + /// + /// 首节点 + /// + public LoopListNode? First { get; private set; } - /// - /// 环链表构造函数 - /// - /// 节点迭代器 - public LoopList(IEnumerable values) - { - var ge = values.GetEnumerator(); - while (ge.MoveNext()) - Add(ge.Current); - } + /// + /// 尾节点 + /// + public LoopListNode? Last => First?.Previous; - #endregion - #region 方法 - /// - /// 设置首节点 - /// - /// 节点 - /// - public bool SetFirst(LoopListNode node) - { - if (!Contains(node)) - return false; - First = node; - return true; - } - /// - /// 交换两个节点的值 - /// - /// 第一个节点 - /// 第二个节点 - public void Swap(LoopListNode node1, LoopListNode node2) - { -#if NET35 - var value = node1.Value; - node1.Value = node2.Value; - node2.Value = value; -#else - (node2.Value, node1.Value) = (node1.Value, node2.Value); -#endif - } - /// - /// 链内翻转 - /// - public void Reverse() - { - var first = First; - if (first is null) - return; - var last = Last; - for (int i = 0; i < Count / 2; i++) - { - Swap(first!, last!); - first = first!.Next; - last = last!.Previous; - } - } + #endregion - /// - /// 清理 - /// - public void Clear() - { - //移除头部,表示链表再也无法遍历得到 - First = null; - Count = 0; - } + #region 构造 + + /// + /// 默认构造函数 + /// + public LoopList() { } + + /// + /// 环链表构造函数 + /// + /// 节点迭代器 + public LoopList(IEnumerable values) + { + var ge = values.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } - /// - /// 从头遍历_非迭代器 - /// - /// - public void ForEach(Func, bool> action) + #endregion + + #region 方法 + + /// + /// 设置首节点 + /// + /// 节点 + /// + public bool SetFirst(LoopListNode node) + { + if (!Contains(node)) + return false; + + First = node; + return true; + } + + /// + /// 交换两个节点的值 + /// + /// 第一个节点 + /// 第二个节点 + public void Swap(LoopListNode node1, LoopListNode node2) + { + (node2.Value, node1.Value) = (node1.Value, node2.Value); + } + + /// + /// 链内翻转 + /// + public void Reverse() + { + var first = First; + if (first is null) + return; + var last = Last; + for (int i = 0; i < Count / 2; i++) { - var node = First; - if (node is null) - return; - for (int i = 0; i < Count; i++) - { - if (action(node!)) - break; - node = node!.Next; - } + Swap(first!, last!); + first = first!.Next; + last = last!.Previous; } + } - #region Contains - /// - /// 是否包含节点 - /// - /// - /// - public bool Contains(LoopListNode node) + /// + /// 清理 + /// + public void Clear() + { + //移除头部,表示链表再也无法遍历得到 + First = null; + Count = 0; + } + + /// + /// 从头遍历_非迭代器(此处和通用ForEach冲突,所以内部用) + /// + /// + void ForEach(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) { - return node is not null && node.List == this; + if (action(node!)) + break; + node = node!.Next; } + } - /// - /// 是否包含值 - /// - /// - /// - public bool Contains(T value) + /// + /// 从头遍历_非迭代器(扔出计数) + /// + /// + public void For(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) { - bool result = false; - ForEach(node => { - if (node.Value!.Equals(value)) - { - result = true; - return true; - } - return false; - }); - return result; + if (action(i, node!)) + break; + node = node!.Next; } + } - /// - /// 查找第一个出现的节点 - /// - /// - /// - public LoopListNode? Find(T value) - { - //LoopListNode result = null; - //ForEach(node => - //{ - // if (node.Value.Equals(t2)) - // { - // result = node; - // return true; - // } - // return false; - //}); - //return result; - - LoopListNode? node = First; - var c = EqualityComparer.Default; - if (node is not null) + #region Contains + + /// + /// 是否包含节点 + /// + /// + /// + public bool Contains(LoopListNode node) + { + return node is not null && node.List == this; + } + + /// + /// 是否包含值 + /// + /// + /// + public bool Contains(T value) + { + bool result = false; + ForEach(node => { + if (node.Value!.Equals(value)) { - if (value is not null) - { - do - { - if (c.Equals(node!.Value, value)) - return node; - node = node.Next; - } while (node != First); - } - else - { - do - { - if (node!.Value is null) - return node; - node = node.Next; - } while (node != First); - } + result = true; + return true; } - return null; - } + return false; + }); + return result; + } - /// - /// 查找所有出现的节点 - /// - /// - /// - public IEnumerable>? Finds(T value) + /// + /// 查找第一个出现的节点 + /// + /// + /// + public LoopListNode? Find(T value) + { + //LoopListNode result = null; + //ForEach(node => + //{ + // if (node.Value.Equals(t2)) + // { + // result = node; + // return true; + // } + // return false; + //}); + //return result; + + LoopListNode? node = First; + var c = EqualityComparer.Default; + if (node is not null) { - LoopListNode? node = First; - if (node is null) - return null; - - List> result = new(); - var c = EqualityComparer.Default; if (value is not null) { do { if (c.Equals(node!.Value, value)) - result.Add(node); + return node; node = node.Next; } while (node != First); } @@ -284,422 +268,475 @@ public bool Contains(T value) do { if (node!.Value is null) - result.Add(node); + return node; node = node.Next; } while (node != First); } - return result; } + return null; + } - /// - /// 获取节点 - /// - /// - /// - public LoopListNode? GetNode(Func func) - { - LoopListNode? result = null; - ForEach(node => { - if (func(node.Value)) - { - result = node; - return true; - } - return false; - }); - return result; - } + /// + /// 查找所有出现的节点 + /// + /// + /// + public IEnumerable>? Finds(T value) + { + LoopListNode? node = First; + if (node is null) + return null; - #endregion - #region Add - /// - /// 在首节点之前插入节点,并设置新节点为首节点 - /// - /// - /// - public LoopListNode AddFirst(T value) + List> result = new(); + var c = EqualityComparer.Default; + if (value is not null) { - var node = new LoopListNode(value, this); - - if (Count == 0) + do { - First = node; - First.Previous = First.Next = node; - } - else - { - LoopListNode last = Last!; - First!.Previous = last.Next = node; - node.Next = First; - node.Previous = last; - First = node; - } - Count++; - return First; + if (c.Equals(node!.Value, value)) + result.Add(node); + node = node.Next; + } while (node != First); } - - /// - /// 在尾节点之后插入节点,并设置新节点为尾节点 - /// - /// - /// - public LoopListNode Add(T value) + else { - var node = new LoopListNode(value, this); - - if (Count == 0) + do { - First = node; - First.Previous = First.Next = node; - } - else + if (node!.Value is null) + result.Add(node); + node = node.Next; + } while (node != First); + } + return result; + } + + /// + /// 获取节点 + /// + /// + /// + public LoopListNode? GetNode(Func func) + { + LoopListNode? result = null; + ForEach(node => { + if (func(node.Value)) { - var last = First!.Previous!; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; + result = node; + return true; } - Count++; - return Last!; - } + return false; + }); + return result; + } - /// - /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 - /// - /// - /// - public LoopListNode AddLast(T value) - { - return Add(value); - } + #endregion + #region Add - /// - /// 容器内容全部加入到末尾 - /// - /// - public void AddRange(IEnumerable list) - { - var ge = list.GetEnumerator(); - while (ge.MoveNext()) - Add(ge.Current); - } + /// + /// 在首节点之前插入节点,并设置新节点为首节点 + /// + /// + /// + public LoopListNode AddFirst(T value) + { + var node = new LoopListNode(value, this); - /// - /// 前面增加节点 - /// - /// - /// - /// - public LoopListNode AddBefore(LoopListNode node, T value) + if (Count == 0) { - if (node == First) - return AddFirst(value); - - var tnode = new LoopListNode(value, this); - node.Previous!.Next = tnode; - tnode.Previous = node.Previous; - node.Previous = tnode; - tnode.Next = node; - Count++; - return tnode; + First = node; + First.Previous = First.Next = node; } - - /// - /// 后面增加节点 - /// - /// - /// - /// - public LoopListNode AddAfter(LoopListNode node, T value) + else { - var tnode = new LoopListNode(value, this); - node.Next!.Previous = tnode; - tnode.Next = node.Next; - node.Next = tnode; - tnode.Previous = node; - Count++; - return tnode; + LoopListNode last = Last!; + First!.Previous = last.Next = node; + node.Next = First; + node.Previous = last; + First = node; } + Count++; + return First; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点 + /// + /// + /// + public LoopListNode Add(T value) + { + var node = new LoopListNode(value, this); - #endregion - #region Remove - /// - /// 删除首节点 - /// - /// - public bool RemoveFirst() + if (Count == 0) { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last!; - First = First!.Next; - First!.Previous = last; - last.Next = First; - break; - } - return true; + First = node; + First.Previous = First.Next = node; } - - /// - /// 删除尾节点 - /// - /// - public bool RemoveLast() + else { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last!.Previous!; - last.Next = First; - First!.Previous = last; - break; - } - Count--; - return true; + var last = First!.Previous!; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; } + Count++; + return Last!; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 + /// + /// + /// + public LoopListNode AddLast(T value) + { + return Add(value); + } - /// - /// 删除节点 - /// - /// 指定节点 - /// - public bool Remove(LoopListNode node) + /// + /// 容器内容全部加入到末尾 + /// + /// + public void AddRange(IEnumerable list) + { + var ge = list.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } + + /// + /// 前面增加节点 + /// + /// + /// + /// + public LoopListNode AddBefore(LoopListNode node, T value) + { + if (node == First) + return AddFirst(value); + + var tnode = new LoopListNode(value, this); + node.Previous!.Next = tnode; + tnode.Previous = node.Previous; + node.Previous = tnode; + tnode.Next = node; + Count++; + return tnode; + } + + /// + /// 后面增加节点 + /// + /// + /// + /// + public LoopListNode AddAfter(LoopListNode node, T value) + { + var tnode = new LoopListNode(value, this); + node.Next!.Previous = tnode; + tnode.Next = node.Next; + node.Next = tnode; + tnode.Previous = node; + Count++; + return tnode; + } + + #endregion + + #region Remove + + /// + /// 删除首节点 + /// + /// + public bool RemoveFirst() + { + switch (Count) { - if (!Contains(node)) + case 0: return false; - InternalRemove(node); - return true; + + case 1: + First = null; + break; + + default: + LoopListNode last = Last!; + First = First!.Next; + First!.Previous = last; + last.Next = First; + break; } + Count--; + return true; + } - /// - /// 删除节点 - /// - /// 将移除所有含有此值 - /// - public bool Remove(T value) + /// + /// 删除尾节点 + /// + /// + public bool RemoveLast() + { + switch (Count) { - var lst = Finds(value); - if (lst is null) + case 0: return false; - var ge = lst!.GetEnumerator(); - while (ge.MoveNext()) - InternalRemove(ge.Current); - return true; + case 1: + First = null; + break; + + default: + LoopListNode last = Last!.Previous!; + last.Next = First; + First!.Previous = last; + break; } + Count--; + return true; + } - /// - /// 删除节点_内部调用 - /// - /// 此值肯定存在当前链表 - /// - void InternalRemove(LoopListNode node) - { - if (Count == 1 || node == First) - { - RemoveFirst(); - } - else - { - node.Next!.Previous = node.Previous; - node.Previous!.Next = node.Next; - } + /// + /// 删除此参数节点(唯一) + /// + /// 指定节点 + /// + public bool Remove(LoopListNode node) + { + if (!Contains(node)) + return false; + InternalRemove(node); + return true; + } - node.Invalidate(); - Count--; - } + /// + /// 删除含有此参数节点(所有) + /// + /// 将移除所有含有此值 + /// + public bool Remove(T value) + { + var lst = Finds(value); + if (lst is null) + return false; + + var ge = lst!.GetEnumerator(); + while (ge.MoveNext()) + InternalRemove(ge.Current); + return true; + } - #endregion - #region LinkTo - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to) + /// + /// 删除节点_内部调用 + /// + /// 此值肯定存在当前链表 + /// + void InternalRemove(LoopListNode node) + { + if (Count == 1 || node == First) { - if (from != to && Contains(from) && Contains(to)) - { - LoopListNode node = from.Next!; - bool isFirstChanged = false; - int number = 0; + RemoveFirst();//此处会减数字 + } + else + { + node.Next!.Previous = node.Previous; + node.Previous!.Next = node.Next; + Count--; + } + node.Invalidate(); + } - while (node != to) - { - if (node == First) - isFirstChanged = true; + #endregion - node = node.Next!; - number++; - } + #region LinkTo - from.Next = to; - to.Previous = from; + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to) + { + if (from != to && Contains(from) && Contains(to)) + { + LoopListNode node = from.Next!; + bool isFirstChanged = false; + int number = 0; - if (number > 0 && isFirstChanged) - First = to; + while (node != to) + { + if (node == First) + isFirstChanged = true; - Count -= number; + node = node.Next!; + number++; } - } - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; + from.Next = to; + to.Previous = from; + + if (number > 0 && isFirstChanged) First = to; - Count -= number; - } - } - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; - if (isFirstChanged) - First = to; - Count -= number; - } + Count -= number; } + } - #endregion - #endregion - #region IEnumerable - /// - /// 获取节点的查询器 - /// - /// - /// - public IEnumerable> GetNodes(LoopListNode from) + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number) + { + if (from != to && Contains(from) && Contains(to)) { - var node = from; - for (int i = 0; i < Count; i++) - { - yield return node!; - node = node!.Next; - } + from.Next = to; + to.Previous = from; + First = to; + Count -= number; } + } - /// - /// 获取节点的查询器 - /// - /// - public IEnumerable> GetNodes() + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) + { + if (from != to && Contains(from) && Contains(to)) { - LoopListNode node = First!; - for (int i = 0; i < Count; i++) - { - yield return node!; - node = node.Next!; - } + from.Next = to; + to.Previous = from; + if (isFirstChanged) + First = to; + Count -= number; } + } + + #endregion - /// - /// 获取节点值的查询器 - /// - /// - public IEnumerator GetEnumerator() + #endregion + + #region IEnumerable + + /// + /// 获取节点的查询器 + /// + /// + /// + public IEnumerable> GetNodes(LoopListNode from) + { + var node = from; + for (int i = 0; i < Count; i++) { - LoopListNode node = First!; - for (int i = 0; i < Count; i++) - { - yield return node!.Value; - node = node.Next!; - } + yield return node!; + node = node!.Next; } + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #region IEnumerable 成员 - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion IEnumerable 成员 - #endregion - #region IFormattable - /// - /// 转换为字符串_格式化实现 - /// - /// - /// - /// - string IFormattable.ToString(string? format, IFormatProvider? formatProvider) + + + /// + /// 获取节点的查询器 + /// + /// + public IEnumerable> GetNodes() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) { - return ToString(format, formatProvider); + yield return node!; + node = node.Next!; } + } - /// - /// 转换为字符串_无参调用 - /// - /// - public override string ToString() + /// + /// 获取节点值的查询器 + /// + /// + public IEnumerator GetEnumerator() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) { - return ToString(null, null); + yield return node!.Value; + node = node.Next!; } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #region IEnumerable 成员 + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// - /// 转换为字符串_有参调用 - /// - /// - public string ToString(string? format, IFormatProvider? formatProvider = null) + #endregion IEnumerable 成员 + + #endregion + + #region IFormattable + /// + /// 转换为字符串_格式化实现 + /// + /// + /// + /// + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(format, formatProvider); + } + + /// + /// 转换为字符串_无参调用 + /// + /// + public override string ToString() + { + return ToString(null, null); + } + + /// + /// 转换为字符串_有参调用 + /// + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) + { + var s = new StringBuilder(); + s.Append($"Count = {Count};"); + if (format is null) { - var s = new StringBuilder(); - s.Append($"Count = {Count};"); - if (format is null) - { - s.Append("{ "); - foreach (T value in this) - s.Append($"{value} "); - s.Append(" }"); - } - return s.ToString(); + s.Append("{ "); + foreach (T value in this) + s.Append($"{value} "); + s.Append(" }"); } - #endregion - #region ICloneable - /* 山人说无法分辨ICloneable接口是深浅克隆,因此不要在泛型模板实现克隆函数,让用户自己来 - * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; - * public object Clone() - * { - * var lst = new LoopList>(); - * ForEach(node => { - * lst.Add(node); - * return false; - * }); - * return lst; - * } - */ - #endregion - } -} \ No newline at end of file + return s.ToString(); + } + #endregion + + #region ICloneable + /* 山人说无法分辨ICloneable接口是深浅克隆, + * 因此不要在泛型模板实现克隆函数, + * 让用户自己来 new(xx)实现浅克隆,所以也不提供Clone()了 + * + * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; + * public object Clone() + * { + * var lst = new LoopList>(); + * ForEach(node => { + * lst.Add(node); + * return false; + * }); + * return lst; + * } + */ + #endregion +} + +#line default \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/ISet.cs b/src/IFoxCAD.Basal/Sortedset/ISet.cs new file mode 100644 index 0000000..549e9f0 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/ISet.cs @@ -0,0 +1,71 @@ +#if NET35 +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Interface: ISet +** +** kimhamil +** +** +** Purpose: Base interface for all generic sets. +** +** +===========================================================*/ +namespace System.Collections.Generic +{ + + using System; + using System.Runtime.CompilerServices; + + + /// + /// Generic collection that guarantees the uniqueness of its elements, as defined + /// by some comparer. It also supports basic set operations such as Union, Intersection, + /// Complement and Exclusive Complement. + /// + public interface ISet : ICollection + { + + //Add ITEM to the set, return true if added, false if duplicate + new bool Add(T item); + + //Transform this set into its union with the IEnumerable other + void UnionWith(IEnumerable other); + + //Transform this set into its intersection with the IEnumberable other + void IntersectWith(IEnumerable other); + + //Transform this set so it contains no elements that are also in other + void ExceptWith(IEnumerable other); + + //Transform this set so it contains elements initially in this or in other, but not both + void SymmetricExceptWith(IEnumerable other); + + //Check if this set is a subset of other + bool IsSubsetOf(IEnumerable other); + + //Check if this set is a superset of other + bool IsSupersetOf(IEnumerable other); + + //Check if this set is a subset of other, but not the same as it + bool IsProperSupersetOf(IEnumerable other); + + //Check if this set is a superset of other, but not the same as it + bool IsProperSubsetOf(IEnumerable other); + + //Check if this set has any elements in common with other + bool Overlaps(IEnumerable other); + + //Check if this set contains the same and only the same elements as other + bool SetEquals(IEnumerable other); + + + + } + +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/SR.cs b/src/IFoxCAD.Basal/Sortedset/SR.cs new file mode 100644 index 0000000..093de14 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/SR.cs @@ -0,0 +1,907 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +namespace System; + + +using System; +using System.Reflection; +using System.Globalization; +using System.Resources; +using System.Text; +using System.ComponentModel; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDescriptionAttribute : DescriptionAttribute +{ + public SRDescriptionAttribute(string description) + { + DescriptionValue = SR.GetString(description); + } + + public SRDescriptionAttribute(string description, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DescriptionValue = rm.GetString(description); + System.Diagnostics.Debug.Assert(DescriptionValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { description })); + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRCategoryAttribute : CategoryAttribute +{ + private string resourceSet = String.Empty; + + public SRCategoryAttribute(string category) + : base(category) + { + } + + public SRCategoryAttribute(string category, string resourceSet) + : base(category) + { + this.resourceSet = resourceSet; + } + + protected override string GetLocalizedString(string value) + { + if (this.resourceSet.Length > 0) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + String localizedString = rm.GetString(value); + System.Diagnostics.Debug.Assert(localizedString != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { value })); + return localizedString; + } + else + { + return SR.GetString(value); + } + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDisplayNameAttribute : DisplayNameAttribute +{ + public SRDisplayNameAttribute(string name) + { + DisplayNameValue = SR.GetString(name); + } + + public SRDisplayNameAttribute(string name, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DisplayNameValue = rm.GetString(name); + System.Diagnostics.Debug.Assert(DisplayNameValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + } +} + +/// +/// AutoGenerated resource class. Usage: +/// +/// string s = SR.GetString(SR.MyIdenfitier); +/// +internal sealed partial class SR +{ +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + static SR loader = null; +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + ResourceManager resources; + + internal SR() + { + resources = new System.Resources.ResourceManager("System.Workflow.ComponentModel.StringResources", Assembly.GetExecutingAssembly()); + } + + private static SR GetLoader() + { + if (loader == null) + loader = new SR(); + return loader; + } + + private static CultureInfo Culture + { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(string name, params object[] args) + { + return GetString(SR.Culture, name, args); + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(CultureInfo culture, string name, params object[] args) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + if (args != null && args.Length > 0) + { + return string.Format(CultureInfo.CurrentCulture, res, args); + } + else + { + return res; + } + } + + internal static string GetString(string name) + { + return GetString(SR.Culture, name); + } + + internal static string GetString(CultureInfo culture, string name) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + return res; + } + + // All these strings should be present in StringResources.resx + internal const string Activity = "Activity"; + internal const string Handlers = "Handlers"; + internal const string Conditions = "Conditions"; + internal const string ConditionedActivityConditions = "ConditionedActivityConditions"; + internal const string Correlations = "Correlations"; + internal const string CorrelationSet = "CorrelationSet"; + internal const string NameDescr = "NameDescr"; + internal const string EnabledDescr = "EnabledDescr"; + internal const string DescriptionDescr = "DescriptionDescr"; + internal const string UnlessConditionDescr = "UnlessConditionDescr"; + internal const string InitializeDescr = "InitializeDescr"; + internal const string CatchTypeDescr = "CatchTypeDescr"; + internal const string ExceptionTypeDescr = "ExceptionTypeDescr"; + internal const string FaultDescription = "FaultDescription"; + internal const string FaultTypeDescription = "FaultTypeDescription"; + internal const string ContainingAssemblyDescr = "ContainingAssemblyDescr"; + internal const string ExecutionModeDescr = "ExecutionModeDescr"; + internal const string Error_ReadOnlyTemplateActivity = "Error_ReadOnlyTemplateActivity"; + internal const string Error_TypeNotString = "Error_TypeNotString"; + internal const string Error_InvalidErrorType = "Error_InvalidErrorType"; + internal const string Error_LiteralConversionFailed = "Error_LiteralConversionFailed"; + internal const string Error_TypeNotPrimitive = "Error_TypeNotPrimitive"; + internal const string CompletedCaleeDescr = "CompletedCaleeDescr"; + internal const string ProxyClassDescr = "ProxyClassDescr"; + internal const string ActivitySetDescr = "ActivitySetDescr"; + internal const string VersionDescr = "VersionDescr"; + internal const string ActivationDescr = "ActivationDescr"; + internal const string CorrelationSetsDescr = "CorrelationSetsDescr"; + internal const string CompanionClassDescr = "CompanionClassDescr"; + internal const string TransactionTypeDescr = "TransactionTypeDescr"; + internal const string SynchronizedDescr = "SynchronizedDescr"; + internal const string IsolationLevelDescr = "IsolationLevelDescr"; + internal const string TimeoutDescr = "TimeoutDescr"; + internal const string BatchableDescr = "BatchableDescr"; + internal const string LRTTimeoutDescr = "LRTTimeoutDescr"; + internal const string OnGetCalleeCountDescr = "OnGetCalleeCountDescr"; + internal const string CompensatableActivityDescr = "CompensatableActivityDescr"; + internal const string OnAfterEventDescr = "OnAfterEventDescr"; + internal const string OnBeforeMethodInvokeDescr = "OnBeforeMethodInvokeDescr"; + internal const string AssignedToDescr = "AssignedToDescr"; + internal const string TypeDescr = "TypeDescr"; + internal const string TemplateActivityDescr = "TemplateActivityDescr"; + internal const string ErrorMessageDescr = "ErrorMessageDescr"; + internal const string WebServiceSynchronizedDescr = "WebServiceSynchronizedDescr"; + internal const string CorrelationSetDescr = "CorrelationSetDescr"; + internal const string ExecutionTypeDescr = "ExecutionTypeDescr"; + internal const string RoleDescr = "RoleDescr"; + internal const string OnInitializeClonesDescr = "OnInitializeClonesDescr"; + internal const string CorrelationSetDisplayName = "CorrelationSetDisplayName"; + internal const string PastingActivities = "PastingActivities"; + internal const string DeletingActivities = "DeletingActivities"; + internal const string DragDropActivities = "DragDropActivities"; + internal const string ChangingEnabled = "ChangingEnabled"; + internal const string ChangingHandler = "ChangingHandler"; + internal const string ChangingParameter = "ChangingParameter"; + internal const string CollectionItem = "CollectionItem"; + internal const string AddingConditionalBranch = "AddingConditionalBranch"; + internal const string AddingEventActivity = "AddingEventActivity"; + internal const string AddingListenBranch = "AddingListenBranch"; + internal const string AddingParallelBranch = "AddingParallelBranch"; + internal const string CurrentProject = "CurrentProject"; + internal const string ReferencedAssemblies = "ReferencedAssemblies"; + internal const string CollectionText = "CollectionText"; + internal const string ParameterDescription = "ParameterDescription"; + internal const string InvokeParameterDescription = "InvokeParameterDescription"; + internal const string ParametersDescription = "ParametersDescription"; + internal const string ChangingParameters = "ChangingParameters"; + internal const string Condition = "ConditionRule"; + internal const string MovingActivities = "MovingActivities"; + internal const string MemberNameDescr = "MemberNameDescr"; + internal const string OnScopeInitializedDescr = "OnScopeInitializedDescr"; + internal const string OnGeneratorInitializedDescr = "OnGeneratorInitializedDescr"; + internal const string OnScopeCompletedDescr = "OnScopeCompletedDescr"; + internal const string OnGeneratorCompletedDescr = "OnGeneratorCompletedDescr"; + internal const string DataElementRuntimeTypeDescr = "DataElementRuntimeTypeDescr"; + internal const string RuleConditionReferencesDescr = "RuleConditionReferencesDescr"; + internal const string CreateActivityFromToolbox = "CreateActivityFromToolbox"; + internal const string MoveMultipleActivities = "MoveMultipleActivities"; + internal const string MoveSingleActivity = "MoveSingleActivity"; + internal const string CutMultipleActivities = "CutMultipleActivities"; + internal const string CutSingleActivity = "CutSingleActivity"; + internal const string CutActivity = "CutActivity"; + internal const string FaultActivityDescription = "FaultActivityDescription"; + internal const string NullConditionExpression = "NullConditionExpression"; + internal const string ParameterTypeDescription = "ParameterTypeDescription"; + internal const string ParameterCategory = "ParameterCategory"; + internal const string ParameterDirectionDescription = "ParameterDirectionDescription"; + internal const string ParameterElementDescription = "ParameterElementDescription"; + internal const string ParameterDlgDescription = "ParameterDlgDescription"; + internal const string ParameterDlgHeader = "ParameterDlgHeader"; + internal const string SuspendActivityDescription = "SuspendActivityDescription"; + internal const string SuspendErrorMessageDescr = "SuspendErrorMessageDescr"; + internal const string TerminateActivityDescription = "TerminateActivityDescription"; + internal const string TerminateErrorMessageDescr = "TerminateErrorMessageDescr"; + internal const string DeclarationCategory = "DeclarationCategory"; + internal const string NoValidActivityPropertiesAvailable = "NoValidActivityPropertiesAvailable"; + internal const string ChooseActivityDatasource = "ChooseActivityDatasource"; + internal const string Promote = "Promote"; + internal const string Type = "Type"; + internal const string NoMatchingActivityProperties = "NoMatchingActivityProperties"; + internal const string ActivityBindIDDescription = "ActivityBindIDDescription"; + internal const string ActivityBindPathDescription = "ActivityBindPathDescription"; + internal const string XPathDescription = "XPathDescription"; + internal const string TransformerDescription = "TransformerDescription"; + internal const string CustomPropertiesCollectionFormHeader = "CustomPropertiesCollectionFormHeader"; + internal const string CustomPropertiesCollectionFormDescription = "CustomPropertiesCollectionFormDescription"; + internal const string BaseTypePropertyName = "BaseTypePropertyName"; + internal const string CustomActivityBaseClassTypeFilterProviderDesc = "CustomActivityBaseClassTypeFilterProviderDesc"; + internal const string CustomActivityDesignerTypeFilterProviderDesc = "CustomActivityDesignerTypeFilterProviderDesc"; + internal const string CustomActivityValidatorTypeFilterProviderDesc = "CustomActivityValidatorTypeFilterProviderDesc"; + internal const string CustomActivityExecutorTypeFilterProviderDesc = "CustomActivityExecutorTypeFilterProviderDesc"; + internal const string GenericParameters = "GenericParameters"; + internal const string ToolboxItem = "ToolboxItem"; + internal const string ToolboxItemCompanionClassDesc = "ToolboxItemCompanionClassDesc"; + internal const string Error_SerializationInsufficientState = "Error_SerializationInsufficientState"; + internal const string Error_ActivityHasParent = "Error_ActivityHasParent"; + internal const string Error_CompensantionParentNotScope = "Error_CompensantionParentNotScope"; + internal const string Error_ConditionedActivityParentNotCAG = "Error_ConditionedActivityParentNotCAG"; + internal const string Error_CorrelationTypeNotComparable = "Error_CorrelationTypeNotComparable"; + internal const string Error_ArgumentTypeNotMatchParameter = "Error_ArgumentTypeNotMatchParameter"; + internal const string Error_TypeTypeMismatch = "Error_TypeTypeMismatch"; + internal const string Error_ParameterTypeMismatch = "Error_ParameterTypeMismatch"; + internal const string Error_InvokeParameterTypeMismatch = "Error_InvokeParameterTypeMismatch"; + internal const string Error_ParameterPropertyNotSet = "Error_ParameterPropertyNotSet"; + internal const string Error_DataSourceNameNotSet = "Error_DataSourceNameNotSet"; + internal const string Error_DataSourceInvalidIdentifier = "Error_DataSourceInvalidIdentifier"; + internal const string Error_ParameterTypeNotExist = "Error_ParameterTypeNotExist"; + internal const string Error_InvalidParameterName = "Error_InvalidParameterName"; + internal const string Error_InvalidParameterType = "Error_InvalidParameterType"; + internal const string Error_InvalidParameterElement = "Error_InvalidParameterElement"; + internal const string Error_InvalidPropertyType = "Error_InvalidPropertyType"; + internal const string Error_TypeNotResolvedInMethodName = "Error_TypeNotResolvedInMethodName"; + internal const string Error_DelegateNoInvoke = "Error_DelegateNoInvoke"; + internal const string Error_TypeNotDelegate = "Error_TypeNotDelegate"; + internal const string Error_MethodSignatureMismatch = "Error_MethodSignatureMismatch"; + internal const string Error_MethodReturnTypeMismatch = "Error_MethodReturnTypeMismatch"; + internal const string Error_PropertyNotSet = "Error_PropertyNotSet"; + internal const string Error_ScopeCouldNotBeResolved = "Error_ScopeCouldNotBeResolved"; + internal const string Error_IfElseNotAllIfElseBranchDecl = "Error_ConditionalNotAllConditionalBranchDecl"; + internal const string Error_TypeTypeMismatchAmbiguity = "Error_TypeTypeMismatchAmbiguity"; + internal const string Error_InvalidCorrelationSetDatasource = "Error_InvalidCorrelationSetDatasource"; + internal const string Error_InvalidCorrelationSetType = "Error_InvalidCorrelationSetType"; + internal const string Error_MissingCorrelationParameterAttribute = "Error_MissingCorrelationParameterAttribute"; + internal const string Error_CorrelationTypeNotConsistent = "Error_CorrelationTypeNotConsistent"; + internal const string Error_CorrelationInvalid = "Error_CorrelationInvalid"; + internal const string Error_MissingDelegateMethod = "Error_MissingDelegateMethod"; + internal const string Error_MissingHostInterface = "Error_MissingHostInterface"; + internal const string Error_MissingMethodName = "Error_MissingMethodName"; + internal const string Error_NoBoundType = "Error_NoBoundType"; + internal const string Error_PortTypeNotAnInterface = "Error_PortTypeNotAnInterface"; + internal const string Error_MethodNotExists = "Error_MethodNotExists"; + internal const string Error_InvalidRequestResponseMethod = "Error_InvalidRequestResponseMethod"; + internal const string General_MissingService = "General_MissingService"; + internal const string Error_ScopeDuplicatedNameActivity = "Error_ScopeDuplicatedNameActivity"; + internal const string Error_DuplicatedActivityID = "Error_DuplicatedActivityID"; + internal const string Error_DuplicatedParameterName = "Error_DuplicatedParameterName"; + internal const string Error_ScopeMissingSerializableAttribute = "Error_ScopeMissingSerializableAttribute"; + internal const string Error_FieldNotExists = "Error_FieldNotExists"; + internal const string Error_PropertyNotExists = "Error_PropertyNotExists"; + internal const string Error_FieldTypeMismatch = "Error_FieldTypeMismatch"; + internal const string Error_PropertyTypeMismatch = "Error_PropertyTypeMismatch"; + internal const string Error_TypeNotResolvedInFieldName = "Error_TypeNotResolvedInFieldName"; + internal const string Error_TypeNotResolvedInPropertyName = "Error_TypeNotResolvedInPropertyName"; + internal const string Error_FieldGenericParamTypeMismatch = "Error_FieldGenericParamTypeMismatch"; + internal const string Error_TypeNotResolved = "Error_TypeNotResolved"; + internal const string Error_TypeIsUnboundedGeneric = "Error_TypeIsUnboundedGeneric"; + internal const string Error_MissingRootActivity = "Error_MissingRootActivity"; + internal const string Error_PropertyNotReadable = "Error_PropertyNotReadable"; + internal const string Error_PropertyNotWritable = "Error_PropertyNotWritable"; + internal const string Error_NotCompositeActivity = "Error_NotCompositeActivity"; + internal const string Error_TypeNotExist = "Error_TypeNotExist"; + internal const string Error_ActivityRefNotResolved = "Error_ActivityRefNotResolved"; + internal const string Error_ActivityRefNotMatchType = "Error_ActivityRefNotMatchType"; + internal const string Error_ActivityValidation = "Error_ActivityValidation"; + internal const string Error_ActiveChildExist = "Error_ActiveChildExist"; + internal const string Error_ActiveChildContextExist = "Error_ActiveChildContextExist"; + internal const string Error_CannotCompleteContext = "Error_CannotCompleteContext"; + internal const string Error_NoPasteSupport = "Error_NoPasteSupport"; + internal const string Error_UnknownSerializationStore = "Error_UnknownSerializationStore"; + internal const string Error_MissingCorrelationSet = "Error_MissingCorrelationSet"; + internal const string Error_CreateVariable = "Error_CreateVariable"; + internal const string Error_DuplicateCorrelationSetName = "Error_DuplicateCorrelationSetName"; + internal const string Error_DragDropInvalid = "Error_DragDropInvalid"; + internal const string AddingImplicitActivity = "AddingImplicitActivity"; + internal const string Failure_DoDefaultAction = "Failure_DoDefaultAction"; + internal const string Failure_DoDefaultActionCaption = "Failure_DoDefaultActionCaption"; + internal const string Error_FaultInsideAtomicScope = "Error_FaultInsideAtomicScope"; + internal const string Error_ListenNotMoreThanOneDelay = "Error_ListenNotMoreThanOneDelay"; + internal const string Error_AtomicScopeWithFaultHandlersActivityDecl = "Error_AtomicScopeWithFaultHandlersActivityDecl"; + internal const string Error_AtomicScopeWithCancellationHandlerActivity = "Error_AtomicScopeWithCancellationHandlerActivity"; + internal const string Error_ScopeDuplicateFaultHandlerActivityForAll = "Error_ScopeDuplicateFaultHandlerActivityForAll"; + internal const string Error_ScopeDuplicateFaultHandlerActivityFor = "Error_ScopeDuplicateFaultHandlerActivityFor"; + internal const string Error_AtomicScopeNestedInNonLRT = "Error_AtomicScopeNestedInNonLRT"; + internal const string Error_LRTScopeNestedInNonLRT = "Error_LRTScopeNestedInNonLRT"; + internal const string Error_CAGNotAllChildrenConditioned = "Error_CAGNotAllChildrenConditioned"; + internal const string Error_ConditionedActivityChildCount = "Error_ConditionedActivityChildCount"; + internal const string Error_NegativeValue = "Error_NegativeValue"; + internal const string Error_MethodWithReturnType = "Error_MethodWithReturnType"; + internal const string Error_SendReceiveOrderIncorrect = "Error_SendReceiveOrderIncorrect"; + internal const string Error_ReceiveSendOrderIncorrect = "Error_ReceiveSendOrderIncorrect"; + internal const string Error_CompensateBadNesting = "Error_CompensateBadNesting"; + internal const string Error_ReferencedAssemblyIsInvalid = "Error_ReferencedAssemblyIsInvalid"; + internal const string Error_TypeToXsdConversion = "Error_TypeToXsdConversion"; + internal const string Error_FieldTypeNotResolved = "Error_FieldTypeNotResolved"; + internal const string Error_PropertyTypeNotResolved = "Error_PropertyTypeNotResolved"; + internal const string Error_CouldNotDeserializeXomlFile = "Error_CouldNotDeserializeXomlFile"; + internal const string Error_InternalCompilerError = "Error_InternalCompilerError"; + internal const string Error_TypeNotAsseblyQualified = "Error_TypeNotAsseblyQualified"; + internal const string CompilerWarning_StandardAssemlbyInReferences = "CompilerWarning_StandardAssemlbyInReferences"; + internal const string Error_SuspendInAtomicScope = "Error_SuspendInAtomicScope"; + internal const string Error_InvalidActivityExecutionContext = "Error_InvalidActivityExecutionContext"; + internal const string Error_NoRuntimeAvailable = "Error_NoRuntimeAvailable"; + internal const string Error_CanNotChangeAtRuntime = "Error_CanNotChangeAtRuntime"; + internal const string Error_DataContextNotInitialized = "Error_DataContextNotInitialized"; + internal const string Error_DataContextAlreadyInitialized = "Error_DataContextAlreadyInitialized"; + internal const string Error_ParseActivityNameDoesNotExist = "Error_ParseActivityNameDoesNotExist"; + internal const string Error_NoParameterPropertyDeclared = "Error_NoParameterPropertyDeclared"; + internal const string Error_PropertyInvalidIdentifier = "Error_PropertyInvalidIdentifier"; + internal const string Error_WorkflowDefinitionModified = "Error_WorkflowDefinitionModified"; + internal const string Error_FieldAlreadyExist = "Error_FieldAlreadyExist"; + internal const string Failure_FieldAlreadyExist = "Failure_FieldAlreadyExist"; + internal const string Error_DifferentTypeFieldExists = "Error_DifferentTypeFieldExists"; + internal const string Error_RootActivityTypeInvalid = "Error_RootActivityTypeInvalid"; + internal const string Error_RootActivityTypeInvalid2 = "Error_RootActivityTypeInvalid2"; + internal const string Error_CannotCompile_No_XClass = "Error_CannotCompile_No_XClass"; + internal const string Error_TemplateActivityIsNotActivity = "Error_TemplateActivityIsNotActivity"; + internal const string Error_TypeIsNotRootActivity = "Error_TypeIsNotRootActivity"; + internal const string Error_NoTypeProvider = "Error_NoTypeProvider"; + internal const string Error_NotCodeGeneratorType = "Error_NotCodeGeneratorType"; + internal const string Error_NotDataContext = "Error_NotDataContext"; + internal const string Error_MissingDefaultConstructor = "Error_MissingDefaultConstructor"; + internal const string Error_ContextStackItemMissing = "Error_ContextStackItemMissing"; + internal const string Error_UnexpectedArgumentType = "Error_UnexpectedArgumentType"; + internal const string Error_EmptyArgument = "Error_EmptyArgument"; + internal const string Error_DPAlreadyExist = "Error_DPAlreadyExist"; + internal const string Error_DuplicateDynamicProperty = "Error_DuplicateDynamicProperty"; + internal const string Error_DynamicPropertyTypeValueMismatch = "Error_DynamicPropertyTypeValueMismatch"; + internal const string Error_DynamicPropertyNoSupport = "Error_DynamicPropertyNoSupport"; + internal const string Error_NoContextForDatasource = "Error_NoContextForDatasource"; + internal const string Error_NoContextForDatasourceCaption = "Error_NoContextForDatasourceCaption"; + internal const string Error_DataSourceHasParent = "Error_DataSourceHasParent"; + internal const string OnTaskCompletedDescr = "OnTaskCompletedDescr"; + internal const string OnTaskInitializedDescr = "OnTaskInitializedDescr"; + internal const string Error_InvalidXmlData = "Error_InvalidXmlData"; + internal const string Error_HandlerNotOnRoot = "Error_HandlerNotOnRoot"; + internal const string Error_InvalidArgumentIndex = "Error_InvalidArgumentIndex"; + internal const string Error_UITypeEditorTypeNotUITypeEditor = "Error_UITypeEditorTypeNotUITypeEditor"; + internal const string FilterDescription_UITypeEditor = "FilterDescription_UITypeEditor"; + internal const string Error_UserCodeFilesNotAllowed = "Error_UserCodeFilesNotAllowed"; + internal const string Error_CodeWithinNotAllowed = "Error_CodeWithinNotAllowed"; + internal const string Error_TypeNotAuthorized = "Error_TypeNotAuthorized"; + internal const string Error_CantDetermineBaseType = "Error_CantDetermineBaseType"; + internal const string Error_MultipleSelectNotSupportedForBindAndPromote = "Error_MultipleSelectNotSupportedForBindAndPromote"; + internal const string Error_CantDetermineBaseTypeCaption = "Error_CantDetermineBaseTypeCaption"; + internal const string Error_CantDeterminePropertyBaseType = "Error_CantDeterminePropertyBaseType"; + internal const string Error_NullCustomActivityTypeName = "Error_NullCustomActivityTypeName"; + internal const string Error_InvalidAttribute = "Error_InvalidAttribute"; + internal const string Error_InvalidAttributes = "Error_InvalidAttributes"; + internal const string Error_ConfigFileMissingOrInvalid = "Error_ConfigFileMissingOrInvalid"; + internal const string Error_CantHaveContextActivity = "Error_CantHaveContextActivity"; + internal const string Error_SynchronizedNeedsDataContext = "Error_SynchronizedNeedsDataContext"; + internal const string Error_MoreThanOneFaultHandlersActivityDecl = "Error_MoreThanOneFaultHandlersActivityDecl"; + internal const string Error_MoreThanOneEventHandlersDecl = "Error_MoreThanOneEventHandlersDecl"; + internal const string Error_MoreThanOneCancelHandler = "Error_MoreThanOneCancelHandler"; + internal const string Error_MetaDataInterfaceMissing = "Error_MetaDataInterfaceMissing"; + internal const string Error_NonActivityExecutor = "Error_NonActivityExecutor"; + internal const string Error_DynamicUpdateEvaluation = "Error_DynamicUpdateEvaluation"; + internal const string Error_CollectionHasNullEntry = "Error_CollectionHasNullEntry"; + internal const string Error_MissingContextProperty = "Error_MissingContextProperty"; + internal const string Error_AssociatedDesignerMissing = "Error_AssociatedDesignerMissing"; + internal const string Error_MissingContextActivityProperty = "Error_MissingContextActivityProperty"; + internal const string Error_MissingActivityProperty = "Error_MissingActivityProperty"; + internal const string Error_MissingOwnerTypeProperty = "Error_MissingOwnerTypeProperty"; + internal const string Error_DOIsNotAnActivity = "Error_DOIsNotAnActivity"; + internal const string Error_PropertyCanBeOnlyCleared = "Error_PropertyCanBeOnlyCleared"; + internal const string Error_PropertyDefaultTypeMismatch = "Error_PropertyDefaultTypeMismatch"; + internal const string Error_PropertyDefaultIsReference = "Error_PropertyDefaultIsReference"; + // workflow load errors + internal const string Error_WorkflowLoadFailed = "Error_WorkflowLoadFailed"; + internal const string Error_WorkflowLoadValidationFailed = "Error_WorkflowLoadValidationFailed"; + internal const string Error_WorkflowLoadDeserializationFailed = "Error_WorkflowLoadDeserializationFailed"; + internal const string Error_WorkflowLoadTypeMismatch = "Error_WorkflowLoadTypeMismatch"; + internal const string Error_WorkflowLoadInvalidXoml = "Error_WorkflowLoadInvalidXoml"; + internal const string Error_WorkflowLoadNotValidRootType = "Error_WorkflowLoadNotValidRootType"; + internal const string Error_CantCreateInstanceOfComponent = "Error_CantCreateInstanceOfComponent"; + internal const string Error_NotComponentFactoryType = "Error_NotComponentFactoryType"; + internal const string Error_WorkflowTerminated = "Error_WorkflowTerminated"; + + // serializer errrors + internal const string Error_SerializerAttributesFoundInComplexProperty = "Error_SerializerAttributesFoundInComplexProperty"; + internal const string Error_InvalidDataFound = "Error_InvalidDataFound"; + internal const string Error_InvalidDataFoundForType = "Error_InvalidDataFoundForType"; + internal const string Error_InvalidDataFoundForType1 = "Error_InvalidDataFoundForType1"; + internal const string Error_SerializerTypeNotResolved = "Error_SerializerTypeNotResolved"; + internal const string Error_MarkupSerializerTypeNotResolved = "Error_MarkupSerializerTypeNotResolved"; + internal const string Error_SerializerTypeNotResolvedWithInnerError = "Error_SerializerTypeNotResolvedWithInnerError"; + internal const string Error_SerializerNotAvailable = "Error_SerializerNotAvailable"; + internal const string Error_SerializerNotAvailableForSerialize = "Error_SerializerNotAvailableForSerialize"; + internal const string Error_SerializerCreateInstanceFailed = "Error_SerializerCreateInstanceFailed"; + internal const string Error_SerializerAddChildFailed = "Error_SerializerAddChildFailed"; + internal const string Error_SerializerNoPropertyAvailable = "Error_SerializerNoPropertyAvailable"; + internal const string Error_SerializerPrimitivePropertyReadOnly = "Error_SerializerPrimitivePropertyReadOnly"; + internal const string Error_SerializerCantChangeIsLocked = "Error_SerializerCantChangeIsLocked"; + internal const string Error_SerializerPrimitivePropertySetFailed = "Error_SerializerPrimitivePropertySetFailed"; + internal const string Error_SerializerPropertyGetFailed = "Error_SerializerPropertyGetFailed"; + internal const string Error_SerializerPrimitivePropertyNoLogic = "Error_SerializerPrimitivePropertyNoLogic"; + internal const string Error_SerializerPrimitivePropertyParentIsNull = "Error_SerializerPrimitivePropertyParentIsNull"; + internal const string Error_SerializerComplexPropertySetFailed = "Error_SerializerComplexPropertySetFailed"; + internal const string Error_SerializerNoChildNotion = "Error_SerializerNoChildNotion"; + internal const string Error_SerializerNoDynamicPropertySupport = "Error_SerializerNoDynamicPropertySupport"; + internal const string Error_SerializerNoSerializeLogic = "Error_SerializerNoSerializeLogic"; + internal const string Error_SerializerReadOnlyPropertyAndValueIsNull = "Error_SerializerReadOnlyPropertyAndValueIsNull"; + internal const string Error_SerializerReadOnlyParametersNoChild = "Error_SerializerReadOnlyParametersNoChild"; + internal const string Error_SerializerNotParameterBindingObject = "Error_SerializerNotParameterBindingObject"; + internal const string Error_SerializerThrewException = "Error_SerializerThrewException"; + internal const string Error_ActivityCollectionSerializer = "Error_ActivityCollectionSerializer"; + internal const string Error_MissingClassAttribute = "Error_MissingClassAttribute"; + internal const string Error_MissingClassAttributeValue = "Error_MissingClassAttributeValue"; + internal const string ExecutorCreationFailedErrorMessage = "ExecutorCreationFailedErrorMessage"; + internal const string VariableGetterCode_VB = "VariableGetterCode_VB"; + internal const string VariableGetterCode_CS = "VariableGetterCode_CS"; + internal const string VariableSetterCode_VB = "VariableSetterCode_VB"; + internal const string VariableSetterCode_CS = "VariableSetterCode_CS"; + internal const string StaticVariableGetterCode_VB = "StaticVariableGetterCode_VB"; + internal const string StaticVariableGetterCode_CS = "StaticVariableGetterCode_CS"; + internal const string StaticVariableSetterCode_VB = "StaticVariableSetterCode_VB"; + internal const string StaticVariableSetterCode_CS = "StaticVariableSetterCode_CS"; + internal const string EnterCodeBesidesCode_VB = "EnterCodeBesidesCode_VB"; + internal const string EnterCodeBesidesCode_CS = "EnterCodeBesidesCode_CS"; + internal const string LeaveCodeBesides1Code_VB = "LeaveCodeBesides1Code_VB"; + internal const string LeaveCodeBesides2Code_VB = "LeaveCodeBesides2Code_VB"; + internal const string LeaveCodeBesides1Code_CS = "LeaveCodeBesides1Code_CS"; + internal const string LeaveCodeBesides2Code_CS = "LeaveCodeBesides2Code_CS"; + internal const string VariableSetterName = "VariableSetterName"; + internal const string VariableGetterName = "VariableGetterName"; + internal const string HandlerGetterName = "HandlerGetterName"; + internal const string WorkflowCreatorName = "WorkflowCreatorName"; + internal const string ActivityMethod = "ActivityMethod"; + internal const string CustomActivityPrivateField = "CustomActivityPrivateField"; + internal const string InitializedVariableDeclaration_VB = "InitializedVariableDeclaration_VB"; + internal const string InitializedVariableDeclaration_CS = "InitializedVariableDeclaration_CS"; + internal const string In = "In"; + internal const string Out = "Out"; + internal const string Ref = "Ref"; + internal const string Required = "Required"; + internal const string Optional = "Optional"; + internal const string Parameters = "Parameters"; + internal const string Properties = "Properties"; + internal const string Error_RecursionDetected = "Error_RecursionDetected"; + internal const string Warning_UnverifiedRecursion = "Warning_UnverifiedRecursion"; + internal const string AddConstructorCode = "AddConstructorCode"; + internal const string Error_UninitializedCorrelation = "Error_UninitializedCorrelation"; + internal const string Error_CorrelationAlreadyInitialized = "Error_CorrelationAlreadyInitialized"; + internal const string Error_CorrelatedSendReceiveAtomicScope = "Error_CorrelatedSendReceiveAtomicScope"; + internal const string Warning_ActivityValidation = "Warning_ActivityValidation"; + internal const string Warning_EmptyBehaviourActivity = "Warning_EmptyBehaviourActivity"; + internal const string Error_ParallelActivationNoCorrelation = "Error_ParallelActivationNoCorrelation"; + internal const string Error_MethodNotAccessible = "Error_MethodNotAccessible"; + internal const string Error_FieldNotAccessible = "Error_FieldNotAccessible"; + internal const string Error_PropertyNotAccessible = "Error_PropertyNotAccessible"; + internal const string Error_GenericArgumentsNotAllowed = "Error_GenericArgumentsNotAllowed"; + internal const string Error_InvalidIdentifier = "Error_InvalidIdentifier"; + internal const string Error_InvalidLanguageIdentifier = "Error_InvalidLanguageIdentifier"; + internal const string DuplicateActivityIdentifier = "DuplicateActivityIdentifier"; + internal const string Error_MissingAttribute = "Error_MissingAttribute"; + internal const string Error_LoadUIPropertiesFile = "Error_LoadUIPropertiesFile"; + internal const string Error_SerializerEventGetFailed = "Error_SerializerEventGetFailed"; + internal const string Error_SerializerEventFailed = "Error_SerializerEventFailed"; + internal const string Error_SerializerNoMemberFound = "Error_SerializerNoMemberFound"; + internal const string Error_DynamicEventConflict = "Error_DynamicEventConflict"; + internal const string Error_SerializerMemberSetFailed = "Error_SerializerMemberSetFailed"; + internal const string Error_ContentPropertyCouldNotBeFound = "Error_ContentPropertyCouldNotBeFound"; + internal const string Error_ContentPropertyValueInvalid = "Error_ContentPropertyValueInvalid"; + internal const string Error_ContentPropertyNoSetter = "Error_ContentPropertyNoSetter"; + internal const string Error_ContentCanNotBeConverted = "Error_ContentCanNotBeConverted"; + internal const string Error_ContentPropertyCanNotBeNull = "Error_ContentPropertyCanNotBeNull"; + internal const string Error_SerializerTypeMismatch = "Error_SerializerTypeMismatch"; + internal const string Error_CouldNotAddValueInContentProperty = "Error_CouldNotAddValueInContentProperty"; + internal const string Error_SerializerTypeRequirement = "Error_SerializerTypeRequirement"; + internal const string Error_CanNotAddActivityInBlackBoxActivity = "Error_CanNotAddActivityInBlackBoxActivity"; + internal const string Error_ContentPropertyCanNotSupportCompactFormat = "Error_ContentPropertyCanNotSupportCompactFormat"; + internal const string Error_ContentPropertyNoMultipleContents = "Error_ContentPropertyNoMultipleContents"; + internal const string Error_InternalSerializerError = "Error_InternalSerializerError"; + internal const string Error_DictionarySerializerNonDictionaryObject = "Error_DictionarySerializerNonDictionaryObject"; + internal const string Error_DictionarySerializerKeyNotFound = "Error_DictionarySerializerKeyNotFound"; + internal const string Error_InvalidCancelActivityState = "Error_InvalidCancelActivityState"; + internal const string Error_InvalidCompensateActivityState = "Error_InvalidCompensateActivityState"; + internal const string Error_InvalidCloseActivityState = "Error_InvalidCloseActivityState"; + internal const string Error_SealedPropertyMetadata = "Error_SealedPropertyMetadata"; + internal const string Error_MemberNotFound = "Error_MemberNotFound"; + internal const string Error_EmptyPathValue = "Error_EmptyPathValue"; + internal const string Error_InvalidCompensatingState = "Error_InvalidCompensatingState"; + internal const string Error_InvalidCancelingState = "Error_InvalidCancelingState"; + internal const string Error_InvalidClosingState = "Error_InvalidClosingState"; + internal const string Error_InvalidStateToExecuteChild = "Error_InvalidStateToExecuteChild"; + internal const string Error_InvalidExecutionState = "Error_InvalidExecutionState"; + internal const string Error_InvalidInitializingState = "Error_InvalidInitializingState"; + internal const string Error_InvalidInvokingState = "Error_InvalidInvokingState"; + internal const string Error_NotRegisteredAs = "Error_NotRegisteredAs"; + internal const string Error_AlreadyRegisteredAs = "Error_AlreadyRegisteredAs"; + internal const string Error_InsertingChildControls = "Error_InsertingChildControls"; + internal const string Error_EmptyToolTipRectangle = "Error_EmptyToolTipRectangle"; + internal const string Error_EmptyRectangleValue = "Error_EmptyRectangleValue"; + internal const string Error_InvalidShadowRectangle = "Error_InvalidShadowRectangle"; + internal const string Error_InvalidShadowDepth = "Error_InvalidShadowDepth"; + internal const string Error_InvalidLightSource = "Error_InvalidLightSource"; + internal const string Error_ChangingDock = "Error_ChangingDock"; + internal const string Error_NullOrEmptyValue = "Error_NullOrEmptyValue"; + internal const string Error_InvalidStateImages = "Error_InvalidStateImages"; + internal const string Error_InvalidConnectorSegment = "Error_InvalidConnectorSegment"; + internal const string Error_InvalidConnectorSource = "Error_InvalidConnectorSource"; + internal const string Error_CreatingToolTip = "Error_CreatingToolTip"; + internal const string Error_InvalidDockStyle = "Error_InvalidDockStyle"; + internal const string Error_InvalidConnectorValue = "Error_InvalidConnectorValue"; + internal const string Error_InvalidDesignerVerbValue = "Error_InvalidDesignerVerbValue"; + internal const string Error_InvalidRuntimeType = "Error_InvalidRuntimeType"; + internal const string Error_InvalidArgumentValue = "Error_InvalidArgumentValue"; + internal const string Error_InvalidRadiusValue = "Error_InvalidRadiusValue"; + internal const string ToolTipString = "ToolTipString"; + + //Collection Editor Resources + internal const string CollectionEditorCaption = "CollectionEditorCaption"; + internal const string CollectionEditorProperties = "CollectionEditorProperties"; + internal const string CollectionEditorPropertiesMultiSelect = "CollectionEditorPropertiesMultiSelect"; + internal const string CollectionEditorPropertiesNone = "CollectionEditorPropertiesNone"; + internal const string CollectionEditorCantRemoveItem = "CollectionEditorCantRemoveItem"; + internal const string CollectionEditorUndoBatchDesc = "CollectionEditorUndoBatchDesc"; + internal const string CollectionEditorInheritedReadOnlySelection = "CollectionEditorInheritedReadOnlySelection"; + internal const string Error_ParameterAlreadyExists = "Error_ParameterAlreadyExists"; + internal const string Error_PropertyAlreadyExists = "Error_PropertyAlreadyExists"; + internal const string Error_HiddenPropertyAlreadyExists = "Error_HiddenPropertyAlreadyExists"; + internal const string Error_CorrelationInUse = "Error_CorrelationInUse"; + internal const string Error_ItemNotExists = "Error_ItemNotExists"; + internal const string Error_NoHelpAvailable = "Error_NoHelpAvailable"; + internal const string Error_DuplicateWorkflow = "Error_DuplicateWorkflow"; + internal const string Error_Recursion = "Error_Recursion"; + internal const string Error_RootActivity = "Error_RootActivity"; + internal const string Error_ConditionDefinitionDeserializationFailed = "Error_ConditionDefinitionDeserializationFailed"; + internal const string Error_InvalidConditionDefinition = "Error_InvalidConditionDefinition"; + internal const string SR_InvokeTransactionalFromAtomic = "SR_InvokeTransactionalFromAtomic"; + internal const string Error_SuspendInAtomicCallChain = "Error_SuspendInAtomicCallChain"; + internal const string Error_LiteralPassedToOutRef = "Error_LiteralPassedToOutRef"; + internal const string Error_GeneratorShouldContainSingleActivity = "Error_GeneratorShouldContainSingleActivity"; + internal const string Error_DeclaringPropertyNotSupported = "Error_DeclaringPropertyNotSupported"; + internal const string Error_DeclaringEventNotSupported = "Error_DeclaringEventNotSupported"; + internal const string Error_DynamicEventNotSupported = "Error_DynamicEventNotSupported"; + internal const string Error_DynamicPropertyNotSupported = "Error_DynamicPropertyNotSupported"; + internal const string Error_ParameterTypeResolution = "Error_ParameterTypeResolution"; + + // Dynamic Validations + internal const string Error_DynamicActivity = "Error_DynamicActivity"; + internal const string Error_DynamicActivity2 = "Error_DynamicActivity2"; + internal const string Error_CompilerValidationFailed = "Error_CompilerValidationFailed"; + internal const string Error_RuntimeValidationFailed = "Error_RuntimeValidationFailed"; + internal const string Error_TransactionAlreadyCanceled = "Error_TransactionAlreadyCanceled"; + internal const string Error_RemoveExecutingActivity = "Error_RemoveExecutingActivity"; + internal const string Error_InsideAtomicScope = "Error_InsideAtomicScope"; + internal const string SuspendReason_WorkflowChange = "SuspendReason_WorkflowChange"; + + //type filtering + internal const string FilterDescription_ParameterDeclaration = "FilterDescription_ParameterDeclaration"; + internal const string FilterDescription_GenericArgument = "FilterDescription_GenericArgument"; + + + internal const string LibraryPathIsInvalid = "LibraryPathIsInvalid"; + + // Activity Set + internal const string Error_CreateValidator = "Error_CreateValidator"; + internal const string Error_InvalidPackageFile = "Error_InvalidPackageFile"; + internal const string Error_AddAssemblyRef = "Error_AddAssemblyRef"; + internal const string Error_AssemblyBadImage = "Error_AssemblyBadImage"; + internal const string BindPropertySetterName = "BindPropertySetterName"; + + // Bind validations + internal const string Error_CannotResolveActivity = "Error_CannotResolveActivity"; + internal const string Error_CannotResolveRelativeActivity = "Error_CannotResolveRelativeActivity"; + internal const string Error_PathNotSetForActivitySource = "Error_PathNotSetForActivitySource"; + internal const string Error_InvalidMemberPath = "Error_InvalidMemberPath"; + internal const string Error_TargetTypeMismatch = "Error_TargetTypeMismatch"; + internal const string Warning_ParameterBinding = "Warning_ParameterBinding"; + internal const string Error_ReferencedActivityPropertyNotBind = "Error_ReferencedActivityPropertyNotBind"; + internal const string Error_TargetTypeDataSourcePathMismatch = "Error_TargetTypeDataSourcePathMismatch"; + internal const string Bind_ActivityDataSourceRecursionDetected = "Bind_ActivityDataSourceRecursionDetected"; + internal const string Bind_DuplicateDataSourceNames = "Bind_DuplicateDataSourceNames"; + internal const string Error_PathNotSetForXmlDataSource = "Error_PathNotSetForXmlDataSource"; + internal const string Error_XmlDocumentLoadFailed = "Error_XmlDocumentLoadFailed"; + internal const string Error_XmlDataSourceInvalidPath = "Error_XmlDataSourceInvalidPath"; + internal const string Error_XmlDataSourceMultipleNodes = "Error_XmlDataSourceMultipleNodes"; + internal const string Error_XmlDataSourceInvalidXPath = "Error_XmlDataSourceInvalidXPath"; + internal const string Error_InvalidObjectRefFormat = "Error_InvalidObjectRefFormat"; + internal const string Error_ReadOnlyDataSource = "Error_ReadOnlyDataSource"; + internal const string Error_HandlerReadOnly = "Error_HandlerReadOnly"; + internal const string Error_XmlDataSourceReadOnly = "Error_XmlDataSourceReadOnly"; + internal const string Error_DataSourceNotExist = "Error_DataSourceNotExist"; + internal const string Error_PropertyNoGetter = "Error_PropertyNoGetter"; + internal const string Error_PropertyNoSetter = "Error_PropertyNoSetter"; + internal const string Error_PropertyHasNoGetterDefined = "Error_PropertyHasNoGetterDefined"; + internal const string Error_PropertyHasNoSetterDefined = "Error_PropertyHasNoSetterDefined"; + internal const string Error_PropertyReferenceNoGetter = "Error_PropertyReferenceNoGetter"; + internal const string Error_PropertyReferenceGetterNoAccess = "Error_PropertyReferenceGetterNoAccess"; + internal const string Error_PropertyHasIndexParameters = "Error_PropertyHasIndexParameters"; + internal const string Error_ReadOnlyField = "Error_ReadOnlyField"; + internal const string Error_NoEnclosingContext = "Error_NoEnclosingContext"; + internal const string Error_NestedPersistOnClose = "Error_NestedPersistOnClose"; + internal const string Error_NestedCompensatableActivity = "Error_NestedCompensatableActivity"; + internal const string Error_InvalidActivityForObjectDatasource = "Error_InvalidActivityForObjectDatasource"; + internal const string Error_DataSourceTypeConversionFailed = "Error_DataSourceTypeConversionFailed"; + internal const string Error_BindDialogWrongPropertyType = "Error_BindDialogWrongPropertyType"; + internal const string Error_BindDialogNoValidPropertySelected = "Error_BindDialogNoValidPropertySelected"; + internal const string Error_BindDialogBindNotValid = "Error_BindDialogBindNotValid"; + internal const string Error_BindDialogCanNotBindToItself = "Error_BindDialogCanNotBindToItself"; + internal const string Error_BindActivityReference = "Error_BindActivityReference"; + internal const string Error_NoTargetTypeForMethod = "Error_NoTargetTypeForMethod"; + internal const string Error_MethodDataSourceIsReadOnly = "Error_MethodDataSourceIsReadOnly"; + internal const string Error_NotMethodDataSource = "Error_NotMethodDataSource"; + internal const string Error_MethodDataSourceWithPath = "Error_MethodDataSourceWithPath"; + internal const string Error_PathSyntax = "Error_PathSyntax"; + internal const string Error_UnmatchedParen = "Error_UnmatchedParen"; + internal const string Error_UnmatchedBracket = "Error_UnmatchedBracket"; + internal const string Error_MemberWithSameNameExists = "Error_MemberWithSameNameExists"; + internal const string Error_ActivityIdentifierCanNotBeEmpty = "Error_ActivityIdentifierCanNotBeEmpty"; + internal const string Error_InvalidActivityIdentifier = "Error_InvalidActivityIdentifier"; + internal const string Error_ActivityBindTypeConversionError = "Error_ActivityBindTypeConversionError"; + internal const string EmptyValue = "EmptyValue"; + internal const string Error_PropertyTypeNotDefined = "Error_PropertyTypeNotDefined"; + + internal const string Error_CompilationFailed = "Error_CompilationFailed"; + internal const string Error_MissingCompilationContext = "Error_MissingCompilationContext"; + + internal const string InvokeWorkflowReference_VB = "InvokeWorkflowReference_VB"; + internal const string InvokeWorkflowReference_CS = "InvokeWorkflowReference_CS"; + internal const string Error_InvalidListItem = "Error_InvalidListItem"; + + internal const string ParserMapPINoWhitespace = "ParserMapPINoWhitespace"; + internal const string ParserMapPIBadCharEqual = "ParserMapPIBadCharEqual"; + internal const string ParserMapPIBadCharQuote = "ParserMapPIBadCharQuote"; + internal const string ParserMapPIBadKey = "ParserMapPIBadKey"; + internal const string ParserMapPIMissingKey = "ParserMapPIMissingKey"; + internal const string ParserMapPIKeyNotSet = "ParserMapPIKeyNotSet"; + internal const string ParserMismatchDelimiter = "ParserMismatchDelimiter"; + internal const string ParserDanglingClause = "ParserDanglingClause"; + internal const string UnknownDefinitionTag = "UnknownDefinitionTag"; + internal const string CDATASection = "CDATASection"; + internal const string TextSection = "TextSection"; + internal const string IncorrectSyntax = "IncorrectSyntax"; + internal const string IncorrectTypeSyntax = "IncorrectTypeSyntax"; + internal const string Error_MultipleRootActivityCreator = "Error_MultipleRootActivityCreator"; + internal const string Error_MustHaveParent = "Error_MustHaveParent"; + + // Workflow References + internal const string Error_ReferenceObjNotInitialized = "Error_ReferenceObjNotInitialized"; + internal const string Error_ReferenceInitResourceManager = "Error_ReferenceInitResourceManager"; + internal const string Error_ResourceReferenceGetObject = "Error_ResourceReferenceGetObject"; + internal const string Error_RefBindCantFindRef = "Error_RefBindCantFindRef"; + internal const string Error_RefBindMissingReferenceName = "Error_RefBindMissingReferenceName"; + internal const string Error_RefBindMissingAttribute = "Error_RefBindMissingAttribute"; + internal const string Error_ReferenceLoad = "Error_ReferenceLoad"; + internal const string Error_ReferenceMissingAttribute = "Error_ReferenceMissingAttribute"; + internal const string Error_ReferenceInvalidResourceFile = "Error_ReferenceInvalidResourceFile"; + internal const string Error_ReferenceEmptyName = "Error_ReferenceEmptyName"; + + internal const string HandlerInvokerName = "HandlerInvokerName"; + internal const string HandlerInvokerSwitchPrefix_CS = "HandlerInvokerSwitchPrefix_CS"; + internal const string HandlerInvokerSwitchPrefix_VB = "HandlerInvokerSwitchPrefix_VB"; + internal const string HandlerInvokerSwitchSuffix_CS = "HandlerInvokerSwitchSuffix_CS"; + internal const string HandlerInvokerSwitchSuffix_VB = "HandlerInvokerSwitchSuffix_VB"; + internal const string HandlerInvokerCaseBegin_CS = "HandlerInvokerCaseBegin_CS"; + internal const string HandlerInvokerCaseBegin_VB = "HandlerInvokerCaseBegin_VB"; + + // Activity Category + internal const string Standard = "Standard"; + internal const string Base = "Base"; + + //CustomActivityDesigner + internal const string ValidatorCompanionClassDesc = "ValidatorCompanionClassDesc"; + internal const string ExecutorCompanionClassDesc = "ExecutorCompanionClassDesc"; + internal const string DesignerCompanionClassDesc = "DesignerCompanionClassDesc"; + internal const string CustomActivityBaseTypeDesc = "CustomActivityBaseTypeDesc"; + internal const string ActivityProperties = "ActivityProperties"; + internal const string ActivityPropertiesDesc = "ActivityPropertiesDesc"; + internal const string CompanionClasses = "CompanionClasses"; + internal const string ActivityDesc = "Activity"; + internal const string Error_TypeConversionFailed = "Error_TypeConversionFailed"; + internal const string SupportDataContext = "SupportDataContext"; + internal const string AdvancedCategory = "AdvancedCategory"; + internal const string SupportDataContextDesc = "SupportDataContextDesc"; + internal const string BaseCompanionClassName = "BaseCompanionClassName"; + internal const string BaseCompanionClassDesc = "BaseCompanionClassDesc"; + internal const string Designer = "Designer"; + internal const string Validator = "Validator"; + internal const string Executor = "Executor"; + internal const string BaseActivityType = "BaseActivityType"; + internal const string Error_NotBuiltInActivity = "Error_NotBuiltInActivity"; + internal const string NoChildActivities_Message = "NoChildActivities_Message"; + internal const string NoChildActivities_Caption = "NoChildActivities_Caption"; + internal const string Error_CustomActivityCantCreate = "Error_CustomActivityCantCreate"; + internal const string Error_CantChangeBuiltInActivity = "Error_CantChangeBuiltInActivity"; + internal const string Error_CantAddBeforeBuiltInActivity = "Error_CantAddBeforeBuiltInActivity"; + internal const string Error_CantAddAfterNonBuiltInActivity = "Error_CantAddAfterNonBuiltInActivity"; + internal const string Error_CannotAddRemoveChildActivities = "Error_CannotAddRemoveChildActivities"; + internal const string Error_CantFindBuiltInActivity = "Error_CantFindBuiltInActivity"; + internal const string Error_MissingBaseCompanionClassAttribute = "Error_MissingBaseCompanionClassAttribute"; + internal const string Error_CantFindBuiltInParent = "Error_CantFindBuiltInParent"; + internal const string Error_CantCreateInstanceOfBaseType = "Error_CantCreateInstanceOfBaseType"; + internal const string Error_CustomActivityTypeCouldNotBeFound = "Error_CustomActivityTypeCouldNotBeFound"; + internal const string None = "None"; + internal const string AtomicTransaction = "AtomicTransaction"; + internal const string LocalDataContext = "LocalDataContext"; + internal const string LocalDataContextDesc = "LocalDataContextDesc"; + internal const string CompanionClass = "CompanionClass"; + internal const string Error_AlreadyRootActivity = "Error_AlreadyRootActivity"; + internal const string RootActivityName = "RootActivityName"; + internal const string RootActivityNameDesc = "RootActivityNameDesc"; + internal const string CustomProperties = "CustomProperties"; + internal const string VisibleDescr = "VisibleDescr"; + internal const string EditableDescr = "EditableDescr"; + internal const string Error_CantCreateMethod = "Error_CantCreateMethod"; + internal const string Error_CantEditNullValue = "Error_CantEditNullValue"; + internal const string Error_CompanionTypeNotSet = "Error_CompanionTypeNotSet"; + internal const string Error_CompanionClassNameCanNotBeEmpty = "Error_CompanionClassNameCanNotBeEmpty"; + internal const string Error_CouldNotEmitFieldInLocalDataContext = "Error_CouldNotEmitFieldInLocalDataContext"; + internal const string Error_CouldNotEmitMethodInLocalDataContext = "Error_CouldNotEmitMethodInLocalDataContext"; + internal const string Error_DerivationFromTypeWithLocalDataContext = "Error_DerivationFromTypeWithLocalDataContext"; + internal const string Error_CompanionTypeDerivationError = "Error_CompanionTypeDerivationError"; + internal const string Error_CantCreateDataContextClass = "Error_CantCreateDataContextClass"; + internal const string ArrayExistingBind = "ArrayExistingBind"; + internal const string Error_NoMatchingFieldsOrProperties = "Error_NoMatchingFieldsOrProperties"; + internal const string ChooseFieldPropertyDatasource = "ChooseFieldPropertyDatasource"; + + internal const string SupportsTransaction = "SupportsTransaction"; + internal const string SupportsExceptions = "SupportsExceptions"; + internal const string SupportsCancellationHandlerActivity = "SupportsCancellationHandlerActivity"; + internal const string SupportsEvents = "SupportsEvents"; + internal const string SupportsDataSources = "SupportsDataSources"; + internal const string SupportsCompensationHandler = "SupportsCompensationHandler"; + internal const string SupportsCompensationHandlerDesc = "SupportsCompensationHandlerDesc"; + internal const string SupportsTransactionDesc = "SupportsTransactionDesc"; + internal const string SupportsExceptionsDesc = "SupportsExceptionsDesc"; + internal const string SupportsCancelHandlerDesc = "SupportsCancelHandlerDesc"; + internal const string SupportsEventsDesc = "SupportsEventsDesc"; + internal const string TransactionDesc = "TransactionDesc"; + + internal const string Error_BaseTypeMustBeActivity = "Error_BaseTypeMustBeActivity"; + internal const string ExistingActivityBindTitle = "ExistingActivityBindTitle"; + internal const string ExistingActivityBindLabel = "ExistingActivityBindLabel"; + internal const string ExistingFieldPropBindTitle = "ExistingFieldPropBindTitle"; + internal const string ExistingFieldPropBindLabel = "ExistingFieldPropBindLabel"; + internal const string ProvidesSynchronization = "ProvidesSynchronization"; + internal const string ProvidesSynchronizationDesc = "ProvidesSynchronizationDesc"; + internal const string SynchronizationHandles = "SynchronizationHandles"; + internal const string SynchronizationHandlesDesc = "SynchronizationHandlesDesc"; + + internal const string Error_TransactionAlreadyApplied = "Error_TransactionAlreadyApplied"; + internal const string Error_BindBaseTypeNotSpecified = "Error_BindBaseTypeNotSpecified"; + internal const string NonDelegateTargetType = "NonDelegateTargetType"; + internal const string Error_ClassnameNotInRootNamespace = "Error_ClassnameNotInRootNamespace"; + internal const string Error_CantUseCurrentProjectTypeAsBase = "Error_CantUseCurrentProjectTypeAsBase"; + internal const string Error_UnboundGenericType = "Error_UnboundGenericType"; + internal const string Error_UnboundGenericTypeDataSource = "Error_UnboundGenericTypeDataSource"; + internal const string Error_BaseTypeUnknown = "Error_BaseTypeUnknown"; + internal const string Error_UnconfiguredBind = "Error_UnconfiguredBind"; + internal const string Error_CanNotEmitMemberInLocalDataContext = "Error_CanNotEmitMemberInLocalDataContext"; + internal const string Error_DesignedTypeNotFound = "Error_DesignedTypeNotFound"; + internal const string Error_PathCouldNotBeResolvedToMember = "Error_PathCouldNotBeResolvedToMember"; + internal const string Error_EdittingNullCollection = "Error_EdittingNullCollection"; + internal const string Error_MoreThanOneCompensationDecl = "Error_MoreThanOneCompensationDecl"; + internal const string Error_ParentDoesNotSupportCompensation = "Error_ParentDoesNotSupportCompensation"; + internal const string Error_CantResolveEventHandler = "Error_CantResolveEventHandler"; + internal const string Error_XSDObjectTypeNotSerializable = "Error_XSDObjectTypeNotSerializable"; + internal const string AEC_InvalidActivity = "AEC_InvalidActivity"; + internal const string GetDynamicActivities_InvalidActivity = "GetDynamicActivities_InvalidActivity"; + internal const string AEC_InvalidNestedActivity = "AEC_InvalidNestedActivity"; + internal const string Error_IDNotSetForActivitySource = "Error_IDNotSetForActivitySource"; + internal const string Error_InvalidCustomPropertyName = "Error_InvalidCustomPropertyName"; + internal const string Error_InvalidCustomPropertyType = "Error_InvalidCustomPropertyType"; + + internal const string Error_DPReadOnly = "Error_DPReadOnly"; + internal const string Error_DPMetaPropertyBinding = "Error_DPMetaPropertyBinding"; + internal const string Error_DPSetValueBind = "Error_DPSetValueBind"; + internal const string Error_DPSetValueHandler = "Error_DPSetValueHandler"; + internal const string Error_DPGetValueHandler = "Error_DPGetValueHandler"; + internal const string Error_DPAddHandlerNonEvent = "Error_DPAddHandlerNonEvent"; + internal const string Error_DPAddHandlerMetaProperty = "Error_DPAddHandlerMetaProperty"; + internal const string Error_DPRemoveHandlerBind = "Error_DPRemoveHandlerBind"; + internal const string Error_LanguageNeedsToBeVBCSharp = "Error_LanguageNeedsToBeVBCSharp"; + internal const string Error_TargetFxNotSupported = "Error_TargetFxNotSupported"; + internal const string Error_CantConvertValueValue = "Error_CantConvertValueValue"; + internal const string Error_TypeIsNotValid = "Error_TypeIsNotValid"; + internal const string Error_TypePropertyInvalid = "Error_TypePropertyInvalid"; + internal const string Error_EventCantBeMetaProperty = "Error_EventCantBeMetaProperty"; + internal const string Error_EventMustBeDelegate = "Error_EventMustBeDelegate"; + internal const string Error_DPPropertyTypeMissing = "Error_DPPropertyTypeMissing"; + + internal const string TransactionalContextActivityDescription = "TransactionalContextActivityDescription"; + internal const string CompensatableTransactionalContextActivityDescription = "CompensatableTransactionalContextActivityDescription"; + internal const string SynchronizationScopeActivityDescription = "SynchronizationScopeActivityDescription"; + internal const string SequenceActivityDescription = "SequenceActivityDescription"; + internal const string CompensateActivityDescription = "CompensateActivityDescription"; + internal const string Error_CompensateBadTargetTX = "Error_CompensateBadTargetTX"; + internal const string Error_CancelHandlerParentNotScope = "Error_CancelHandlerParentNotScope"; + internal const string FaultHandlerActivityDescription = "FaultHandlerActivityDescription"; + internal const string Error_ExceptionTypeNotException = "Error_ExceptionTypeNotException"; + internal const string Error_FaultIsNotOfFaultType = "Error_FaultIsNotOfFaultType"; + internal const string Error_FaultTypeNoDefaultConstructor = "Error_FaultTypeNoDefaultConstructor"; + internal const string FilterDescription_FaultHandlerActivity = "FilterDescription_FaultHandlerActivity"; + internal const string Error_FaultHandlerActivityParentNotFaultHandlersActivity = "Error_FaultHandlerActivityParentNotFaultHandlersActivity"; + internal const string Error_FaultHandlerActivityAllMustBeLast = "Error_FaultHandlerActivityAllMustBeLast"; + internal const string Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl = "Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl"; + internal const string Error_FaultHandlerActivityWrongOrder = "Error_FaultHandlerActivityWrongOrder"; + internal const string Error_SenderMustBeActivityExecutionContext = "Error_SenderMustBeActivityExecutionContext"; + internal const string Error_XomlWorkflowHasCode = "Error_XomlWorkflowHasCode"; + internal const string Error_WrongParamForActivityResolveEventArgs = "Error_WrongParamForActivityResolveEventArgs"; + internal const string Error_ValidatorThrewException = "Error_ValidatorThrewException"; + internal const string Error_Missing_CanModifyProperties_True = "Error_Missing_CanModifyProperties_True"; + internal const string Error_Missing_CanModifyProperties_False = "Error_Missing_CanModifyProperties_False"; + internal const string Error_ModelingConstructsCanNotContainModelingConstructs = "Error_ModelingConstructsCanNotContainModelingConstructs"; + internal const string Error_RootIsNotEnabled = "Error_RootIsNotEnabled"; + internal const string Error_MissingSetAccessor = "Error_MissingSetAccessor"; + internal const string Error_MissingAddHandler = "Error_MissingAddHandler"; + internal const string Error_MissingCLRProperty = "Error_MissingCLRProperty"; + + internal const string Error_NotReadOnlyProperty = "Error_NotReadOnlyProperty"; + internal const string Error_InvalidDependencyProperty = "Error_InvalidDependencyProperty"; + internal const string Error_ActivityNameExist = "Error_ActivityNameExist"; + internal const string CannotCreateAttribute = "CannotCreateAttribute"; + internal const string NamespaceAndDeclaringTypeCannotBeNull = "NamespaceAndDeclaringTypeCannotBeNull"; + internal const string NotElementType = "NotElementType"; + + //Layout persistence errors + internal const string Error_LayoutSerializationActivityNotFound = "Error_LayoutSerializationActivityNotFound"; + internal const string Error_LayoutSerializationAssociatedActivityNotFound = "Error_LayoutSerializationAssociatedActivityNotFound"; + internal const string Error_LayoutSerializationPersistenceSupport = "Error_LayoutSerializationPersistenceSupport"; + internal const string Error_LayoutSerializationRootDesignerNotFound = "Error_LayoutSerializationRootDesignerNotFound"; + internal const string Error_ParameterCannotBeEmpty = "Error_ParameterCannotBeEmpty"; + internal const string InvalidExecutionStatus = "InvalidExecutionStatus"; + internal const string Error_LayoutDeserialization = "Error_LayoutDeserialization"; + internal const string Error_LayoutSerialization = "Error_LayoutSerialization"; + + internal const string Error_SerializerStackOverflow = "Error_SerializerStackOverflow"; + internal const string Error_InvalidActivityForWorkflowChanges = "Error_InvalidActivityForWorkflowChanges"; + internal const string Error_InvalidMemberType = "Error_InvalidMemberType"; + internal const string Error_BindPathNullValue = "Error_BindPathNullValue"; + internal const string Error_MarkupExtensionMissingTerminatingCharacter = "Error_MarkupExtensionMissingTerminatingCharacter"; + internal const string Error_MarkupExtensionDeserializeFailed = "Error_MarkupExtensionDeserializeFailed"; + internal const string Error_ApplyDynamicChangeFailed = "Error_ApplyDynamicChangeFailed"; + internal const string Error_ActivityCircularReference = "Error_ActivityCircularReference"; + internal const string Error_ValidatorTypeIsInvalid = "Error_ValidatorTypeIsInvalid"; + internal const string Error_InvalidServiceProvider = "Error_InvalidServiceProvider"; + internal const string Error_InvalidRootForWorkflowChanges = "Error_InvalidRootForWorkflowChanges"; + internal const string Error_ExtraCharacterFoundAtEnd = "Error_ExtraCharacterFoundAtEnd"; + internal const string Error_WorkflowChangesNotSupported = "Error_WorkflowChangesNotSupported"; + internal const string Error_TypeSystemAttributeArgument = "Error_TypeSystemAttributeArgument"; + + internal const string Error_InvalidElementFoundForType = "Error_InvalidElementFoundForType"; + internal const string Error_ActivitySaveLoadNotCalled = "Error_ActivitySaveLoadNotCalled"; + internal const string Error_CanNotBindProperty = "Error_CanNotBindProperty"; +} +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/Sortedset.cs b/src/IFoxCAD.Basal/Sortedset/Sortedset.cs new file mode 100644 index 0000000..9405fc1 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/Sortedset.cs @@ -0,0 +1,2935 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8601 // 引用类型赋值可能为 null。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning disable CS8602 // 解引用可能出现空引用。 +#pragma warning disable CS8604 // 引用类型参数可能为 null。 +// #define USING_HASH_SET +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Class: SortedSet +** +** Purpose: A generic sorted set. +** +** Date: August 15, 2008 +** +===========================================================*/ + + +namespace System.Collections.Generic +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + + + + // + // A binary search tree is a red-black tree if it satisfies the following red-black properties: + // 1. Every node is either red or black + // 2. Every leaf (nil node) is black + // 3. If a node is red, then both its children are black + // 4. Every simple path from a node to a descendant leaf contains the same number of black nodes + // + // The basic idea of red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information + // per node to encode 3-nodes and 4-nodes. + // 4-nodes will be represented as: B + // R R + // 3 -node will be represented as: B or B + // R B B R + // + // For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewick. + // + + internal delegate bool TreeWalkPredicate(SortedSet.Node node); + + internal enum TreeRotation + { + LeftRotation = 1, + RightRotation = 2, + RightLeftRotation = 3, + LeftRightRotation = 4, + } + + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "by design name choice")] + [DebuggerTypeProxy(nameof(SortedSet))]/*这句改了*/ + [DebuggerDisplay("Count = {Count}")] +#if !FEATURE_NETCORE + [Serializable] + public class SortedSet : ISet, ICollection, ICollection, ISerializable, IDeserializationCallback//, IReadOnlyCollection + { +#else + public class SortedSet : ISet, ICollection, ICollection, IReadOnlyCollection { +#endif //!FEATURE_NETCORE + #region local variables/constants + Node root; + IComparer comparer; + int count; + int version; + private Object _syncRoot; + + private const String ComparerName = "Comparer"; + private const String CountName = "Count"; + private const String ItemsName = "Items"; + private const String VersionName = "Version"; + //needed for enumerator + private const String TreeName = "Tree"; + private const String NodeValueName = "Item"; + private const String EnumStartName = "EnumStarted"; + private const String ReverseName = "Reverse"; + private const String EnumVersionName = "EnumVersion"; + +#if !FEATURE_NETCORE + //needed for TreeSubset + private const String minName = "Min"; + private const String maxName = "Max"; + private const String lBoundActiveName = "lBoundActive"; + private const String uBoundActiveName = "uBoundActive"; + + private SerializationInfo siInfo; //A temporary variable which we need during deserialization. +#endif + internal const int StackAllocThreshold = 100; + + #endregion + + #region Constructors + public SortedSet() + { + this.comparer = Comparer.Default; + + } + + public SortedSet(IComparer comparer) + { + if (comparer == null) + { + this.comparer = Comparer.Default; + } + else + { + this.comparer = comparer; + } + } + + + public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { } + + public SortedSet(IEnumerable collection, IComparer comparer) + : this(comparer) + { + + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + // these are explicit type checks in the mould of HashSet. It would have worked better + // with something like an ISorted (we could make this work for SortedList.Keys etc) + SortedSet baseSortedSet = collection as SortedSet; + SortedSet baseTreeSubSet = collection as TreeSubSet; + if (baseSortedSet != null && baseTreeSubSet == null && AreComparersEqual(this, baseSortedSet)) + { + //breadth first traversal to recreate nodes + if (baseSortedSet.Count == 0) + { + count = 0; + version = 0; + root = null; + return; + } + + + //pre order way to replicate nodes + Stack theirStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Stack myStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Node theirCurrent = baseSortedSet.root; + Node myCurrent = (theirCurrent != null ? new SortedSet.Node(theirCurrent.Item, theirCurrent.IsRed) : null); + root = myCurrent; + while (theirCurrent != null) + { + theirStack.Push(theirCurrent); + myStack.Push(myCurrent); + myCurrent.Left = (theirCurrent.Left != null ? new SortedSet.Node(theirCurrent.Left.Item, theirCurrent.Left.IsRed) : null); + theirCurrent = theirCurrent.Left; + myCurrent = myCurrent.Left; + } + while (theirStack.Count != 0) + { + theirCurrent = theirStack.Pop(); + myCurrent = myStack.Pop(); + Node theirRight = theirCurrent.Right; + Node myRight = null; + if (theirRight != null) + { + myRight = new SortedSet.Node(theirRight.Item, theirRight.IsRed); + } + myCurrent.Right = myRight; + + while (theirRight != null) + { + theirStack.Push(theirRight); + myStack.Push(myRight); + myRight.Left = (theirRight.Left != null ? new SortedSet.Node(theirRight.Left.Item, theirRight.Left.IsRed) : null); + theirRight = theirRight.Left; + myRight = myRight.Left; + } + } + count = baseSortedSet.count; + version = 0; + } + else + { //As it stands, you're doing an NlogN sort of the collection + + List els = new List(collection); + els.Sort(this.comparer); + for (int i = 1; i < els.Count; i++) + { + if (comparer.Compare(els[i], els[i - 1]) == 0) + { + els.RemoveAt(i); + i--; + } + } + root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); + count = els.Count; + version = 0; + } + } + + +#if !FEATURE_NETCORE + + protected SortedSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + } +#endif + #endregion + + #region Bulk Operation Helpers + private void AddAllElements(IEnumerable collection) + { + + foreach (T item in collection) + { + if (!this.Contains(item)) + Add(item); + } + } + + private void RemoveAllElements(IEnumerable collection) + { + T min = this.Min; + T max = this.Max; + foreach (T item in collection) + { + if (!(comparer.Compare(item, min) < 0 || comparer.Compare(item, max) > 0) && this.Contains(item)) + this.Remove(item); + } + } + + private bool ContainsAllElements(IEnumerable collection) + { + foreach (T item in collection) + { + if (!this.Contains(item)) + { + return false; + } + } + return true; + } + + // + // Do a in order walk on tree and calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal bool InOrderTreeWalk(TreeWalkPredicate action) + { + return InOrderTreeWalk(action, false); + } + + // Allows for the change in traversal direction. Reverse visits nodes in descending order + internal virtual bool InOrderTreeWalk(TreeWalkPredicate action, bool reverse) + { + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + // note: this should be logbase2, but since the stack grows itself, we + // don't want the extra cost + Stack stack = new Stack(2 * (int)(SortedSet.log2(Count + 1))); + Node current = root; + while (current != null) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + } + return true; + } + + // + // Do a left to right breadth first walk on tree and + // calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (!action(current)) + { + return false; + } + if (current.Left != null) + { + processQueue.Add(current.Left); + } + if (current.Right != null) + { + processQueue.Add(current.Right); + } + } + return true; + } + #endregion + + #region Properties + public int Count + { + get + { + VersionCheck(); + return count; + } + } + + public IComparer Comparer + { + get + { + return comparer; + } + } + + bool ICollection.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection.IsSynchronized + { + get + { + return false; + } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + { + System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + #endregion + + #region Subclass helpers + + //virtual function for subclass that needs to update count + internal virtual void VersionCheck() { } + + + //virtual function for subclass that needs to do range checks + internal virtual bool IsWithinRange(T item) + { + return true; + + } + #endregion + + #region ICollection Members + /// + /// Add the value ITEM to the tree, returns true if added, false if duplicate + /// + /// item to be added + public bool Add(T item) + { + return AddIfNotPresent(item); + } + + void ICollection.Add(T item) + { + AddIfNotPresent(item); + } + + + /// + /// Adds ITEM to the tree if not already present. Returns TRUE if value was successfully added + /// or FALSE if it is a duplicate + /// + internal virtual bool AddIfNotPresent(T item) + { + if (root == null) + { // empty tree + root = new Node(item, false); + count = 1; + version++; + return true; + } + + // + // Search for a node at bottom to insert the new node. + // If we can guanratee the node we found is not a 4-node, it would be easy to do insertion. + // We split 4-nodes along the search path. + // + Node current = root; + Node parent = null; + Node grandParent = null; + Node greatGrandParent = null; + + //even if we don't actually add to the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + + int order = 0; + while (current != null) + { + order = comparer.Compare(item, current.Item); + if (order == 0) + { + // We could have changed root node to red during the search process. + // We need to set it to black before we return. + root.IsRed = false; + return false; + } + + // split a 4-node into two 2-nodes + if (Is4Node(current)) + { + Split4Node(current); + // We could have introduced two consecutive red nodes after split. Fix that by rotation. + if (IsRed(parent)) + { + InsertionBalance(current, ref parent, grandParent, greatGrandParent); + } + } + greatGrandParent = grandParent; + grandParent = parent; + parent = current; + current = (order < 0) ? current.Left : current.Right; + } + + Debug.Assert(parent != null, "Parent node cannot be null here!"); + // ready to insert the new node + Node node = new Node(item); + if (order > 0) + { + parent.Right = node; + } + else + { + parent.Left = node; + } + + // the new node will be red, so we will need to adjust the colors if parent node is also red + if (parent.IsRed) + { + InsertionBalance(node, ref parent, grandParent, greatGrandParent); + } + + // Root node is always black + root.IsRed = false; + ++count; + return true; + } + + /// + /// Remove the T ITEM from this SortedSet. Returns true if successfully removed. + /// + /// + /// + public bool Remove(T item) + { + return this.DoRemove(item); // hack so it can be made non-virtual + } + + internal virtual bool DoRemove(T item) + { + + if (root == null) + { + return false; + } + + + // Search for a node and then find its succesor. + // Then copy the item from the succesor to the matching node and delete the successor. + // If a node doesn't have a successor, we can replace it with its left child (if not empty.) + // or delete the matching node. + // + // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node. + // Following code will make sure the node on the path is not a 2 Node. + + //even if we don't actually remove from the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + Node current = root; + Node parent = null; + Node grandParent = null; + Node match = null; + Node parentOfMatch = null; + bool foundMatch = false; + while (current != null) + { + if (Is2Node(current)) + { // fix up 2-Node + if (parent == null) + { // current is root. Mark it as red + current.IsRed = true; + } + else + { + Node sibling = GetSibling(current, parent); + if (sibling.IsRed) + { + // If parent is a 3-node, flip the orientation of the red link. + // We can acheive this by a single rotation + // This case is converted to one of other cased below. + Debug.Assert(!parent.IsRed, "parent must be a black node!"); + if (parent.Right == sibling) + { + RotateLeft(parent); + } + else + { + RotateRight(parent); + } + + parent.IsRed = true; + sibling.IsRed = false; // parent's color + // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root + ReplaceChildOfNodeOrRoot(grandParent, parent, sibling); + // sibling will become grandParent of current node + grandParent = sibling; + if (parent == match) + { + parentOfMatch = sibling; + } + + // update sibling, this is necessary for following processing + sibling = (parent.Left == current) ? parent.Right : parent.Left; + } + Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!"); + + if (Is2Node(sibling)) + { + Merge2Nodes(parent, current, sibling); + } + else + { + // current is a 2-node and sibling is either a 3-node or a 4-node. + // We can change the color of current to red by some rotation. + TreeRotation rotation = RotationNeeded(parent, current, sibling); + Node newGrandParent = null; + switch (rotation) + { + case TreeRotation.RightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + sibling.Left.IsRed = false; + newGrandParent = RotateRight(parent); + break; + case TreeRotation.LeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + sibling.Right.IsRed = false; + newGrandParent = RotateLeft(parent); + break; + + case TreeRotation.RightLeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + newGrandParent = RotateRightLeft(parent); + break; + + case TreeRotation.LeftRightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + newGrandParent = RotateLeftRight(parent); + break; + } + + newGrandParent.IsRed = parent.IsRed; + parent.IsRed = false; + current.IsRed = true; + ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent); + if (parent == match) + { + parentOfMatch = newGrandParent; + } + grandParent = newGrandParent; + } + } + } + + // we don't need to compare any more once we found the match + int order = foundMatch ? -1 : comparer.Compare(item, current.Item); + if (order == 0) + { + // save the matching node + foundMatch = true; + match = current; + parentOfMatch = parent; + } + + grandParent = parent; + parent = current; + + if (order < 0) + { + current = current.Left; + } + else + { + current = current.Right; // continue the search in right sub tree after we find a match + } + } + + // move successor to the matching node position and replace links + if (match != null) + { + ReplaceNode(match, parentOfMatch, parent, grandParent); + --count; + } + + if (root != null) + { + root.IsRed = false; + } + return foundMatch; + } + + public virtual void Clear() + { + root = null; + count = 0; + ++version; + } + + + public virtual bool Contains(T item) + { + + return FindNode(item) != null; + } + + + + + public void CopyTo(T[] array) { CopyTo(array, 0, Count); } + + public void CopyTo(T[] array, int index) { CopyTo(array, index, Count); } + + public void CopyTo(T[] array, int index, int count) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("count", SR.GetString("SR.ArgumentOutOfRange_NeedNonNegNum")); + } + + // will array, starting at arrayIndex, be able to hold elements? Note: not + // checking arrayIndex >= array.Length (consistency with list of allowing + // count of 0; subsequent check takes care of the rest) + if (index > array.Length || count > array.Length - index) + { + throw new ArgumentException(SR.GetString("SR.Arg_ArrayPlusOffTooSmall")); + } + //upper bound + count += index; + + InOrderTreeWalk(delegate (Node node) + { + if (index >= count) + { + return false; + } + else + { + array[index++] = node.Item; + return true; + } + }); + } + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + T[] tarray = array as T[]; + if (tarray != null) + { + CopyTo(tarray, index); + } + else + { + object[] objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try + { + InOrderTreeWalk(delegate (Node node) { objects[index++] = node.Item; return true; }); + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + #endregion + + #region IEnumerable members + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + + + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + #endregion + + #region Tree Specific Operations + + private static Node GetSibling(Node node, Node parent) + { + if (parent.Left == node) + { + return parent.Right; + } + return parent.Left; + } + + // After calling InsertionBalance, we need to make sure current and parent up-to-date. + // It doesn't matter if we keep grandParent and greatGrantParent up-to-date + // because we won't need to split again in the next node. + // By the time we need to split again, everything will be correctly set. + // + private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent) + { + Debug.Assert(grandParent != null, "Grand parent cannot be null here!"); + bool parentIsOnRight = (grandParent.Right == parent); + bool currentIsOnRight = (parent.Right == current); + + Node newChildOfGreatGrandParent; + if (parentIsOnRight == currentIsOnRight) + { // same orientation, single rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent); + } + else + { // different orientaton, double rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent); + // current node now becomes the child of greatgrandparent + parent = greatGrandParent; + } + // grand parent will become a child of either parent of current. + grandParent.IsRed = true; + newChildOfGreatGrandParent.IsRed = false; + + ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent); + } + + private static bool Is2Node(Node node) + { + Debug.Assert(node != null, "node cannot be null!"); + return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right); + } + + private static bool Is4Node(Node node) + { + return IsRed(node.Left) && IsRed(node.Right); + } + + private static bool IsBlack(Node node) + { + return (node != null && !node.IsRed); + } + + private static bool IsNullOrBlack(Node node) + { + return (node == null || !node.IsRed); + } + + private static bool IsRed(Node node) + { + return (node != null && node.IsRed); + } + + private static void Merge2Nodes(Node parent, Node child1, Node child2) + { + Debug.Assert(IsRed(parent), "parent must be be red"); + // combing two 2-nodes into a 4-node + parent.IsRed = false; + child1.IsRed = true; + child2.IsRed = true; + } + + // Replace the child of a parent node. + // If the parent node is null, replace the root. + private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild) + { + if (parent != null) + { + if (parent.Left == child) + { + parent.Left = newChild; + } + else + { + parent.Right = newChild; + } + } + else + { + root = newChild; + } + } + + // Replace the matching node with its succesor. + private void ReplaceNode(Node match, Node parentOfMatch, Node succesor, Node parentOfSuccesor) + { + if (succesor == match) + { // this node has no successor, should only happen if right child of matching node is null. + Debug.Assert(match.Right == null, "Right child must be null!"); + succesor = match.Left; + } + else + { + Debug.Assert(parentOfSuccesor != null, "parent of successor cannot be null!"); + Debug.Assert(succesor.Left == null, "Left child of succesor must be null!"); + Debug.Assert((succesor.Right == null && succesor.IsRed) || (succesor.Right.IsRed && !succesor.IsRed), "Succesor must be in valid state"); + if (succesor.Right != null) + { + succesor.Right.IsRed = false; + } + + if (parentOfSuccesor != match) + { // detach succesor from its parent and set its right child + parentOfSuccesor.Left = succesor.Right; + succesor.Right = match.Right; + } + + succesor.Left = match.Left; + } + + if (succesor != null) + { + succesor.IsRed = match.IsRed; + } + + ReplaceChildOfNodeOrRoot(parentOfMatch, match, succesor); + + } + + internal virtual Node FindNode(T item) + { + Node current = root; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return current; + } + else + { + current = (order < 0) ? current.Left : current.Right; + } + } + + return null; + } + + //used for bithelpers. Note that this implementation is completely different + //from the Subset's. The two should not be mixed. This indexes as if the tree were an array. + //http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees + internal virtual int InternalIndexOf(T item) + { + Node current = root; + int count = 0; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return count; + } + else + { + current = (order < 0) ? current.Left : current.Right; + count = (order < 0) ? (2 * count + 1) : (2 * count + 2); + } + } + return -1; + } + + + + internal Node FindRange(T from, T to) + { + return FindRange(from, to, true, true); + } + internal Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive) + { + Node current = root; + while (current != null) + { + if (lowerBoundActive && comparer.Compare(from, current.Item) > 0) + { + current = current.Right; + } + else + { + if (upperBoundActive && comparer.Compare(to, current.Item) < 0) + { + current = current.Left; + } + else + { + return current; + } + } + } + + return null; + } + + internal void UpdateVersion() + { + ++version; + } + + + private static Node RotateLeft(Node node) + { + Node x = node.Right; + node.Right = x.Left; + x.Left = node; + return x; + } + + private static Node RotateLeftRight(Node node) + { + Node child = node.Left; + Node grandChild = child.Right; + + node.Left = grandChild.Right; + grandChild.Right = node; + child.Right = grandChild.Left; + grandChild.Left = child; + return grandChild; + } + + private static Node RotateRight(Node node) + { + Node x = node.Left; + node.Left = x.Right; + x.Right = node; + return x; + } + + private static Node RotateRightLeft(Node node) + { + Node child = node.Right; + Node grandChild = child.Left; + + node.Right = grandChild.Left; + grandChild.Left = node; + child.Left = grandChild.Right; + grandChild.Right = child; + return grandChild; + } + /// + /// Testing counter that can track rotations + /// + + + private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling) + { + Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child"); + if (IsRed(sibling.Left)) + { + if (parent.Left == current) + { + return TreeRotation.RightLeftRotation; + } + return TreeRotation.RightRotation; + } + else + { + if (parent.Left == current) + { + return TreeRotation.LeftRotation; + } + return TreeRotation.LeftRightRotation; + } + } + + /// + /// Used for deep equality of SortedSet testing + /// + /// + public static IEqualityComparer> CreateSetComparer() + { + return new SortedSetEqualityComparer(); + } + + /// + /// Create a new set comparer for this set, where this set's members' equality is defined by the + /// memberEqualityComparer. Note that this equality comparer's definition of equality must be the + /// same as this set's Comparer's definition of equality + /// + public static IEqualityComparer> CreateSetComparer(IEqualityComparer memberEqualityComparer) + { + return new SortedSetEqualityComparer(memberEqualityComparer); + } + + + /// + /// Decides whether these sets are the same, given the comparer. If the EC's are the same, we can + /// just use SetEquals, but if they aren't then we have to manually check with the given comparer + /// + internal static bool SortedSetEquals(SortedSet set1, SortedSet set2, IComparer comparer) + { + // handle null cases first + if (set1 == null) + { + return (set2 == null); + } + else if (set2 == null) + { + // set1 != null + return false; + } + + if (AreComparersEqual(set1, set2)) + { + if (set1.Count != set2.Count) + return false; + + return set1.SetEquals(set2); + } + else + { + bool found = false; + foreach (T item1 in set1) + { + found = false; + foreach (T item2 in set2) + { + if (comparer.Compare(item1, item2) == 0) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } + + } + + + //This is a little frustrating because we can't support more sorted structures + private static bool AreComparersEqual(SortedSet set1, SortedSet set2) + { + return set1.Comparer.Equals(set2.Comparer); + } + + + private static void Split4Node(Node node) + { + node.IsRed = true; + node.Left.IsRed = false; + node.Right.IsRed = false; + } + + /// + /// Copies this to an array. Used for DebugView + /// + /// + internal T[] ToArray() + { + T[] newArray = new T[Count]; + CopyTo(newArray); + return newArray; + } + + + #endregion + + #region ISet Members + + /// + /// Transform this set into its union with the IEnumerable OTHER + ///Attempts to insert each element and rejects it if it exists. + /// NOTE: The caller object is important as UnionWith uses the Comparator + ///associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void UnionWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + + if (t != null) + VersionCheck(); + + if (s != null && t == null && this.count == 0) + { + SortedSet dummy = new SortedSet(s, this.comparer); + this.root = dummy.root; + this.count = dummy.count; + this.version++; + return; + } + + + if (s != null && t == null && AreComparersEqual(this, s) && (s.Count > this.Count / 2)) + { //this actually hurts if N is much greater than M the /2 is arbitrary + //first do a merge sort to an array. + T[] merged = new T[s.Count + this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + merged[c++] = mine.Current; + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + merged[c++] = theirs.Current; + theirsEnded = !theirs.MoveNext(); + } + } + + if (!mineEnded || !theirsEnded) + { + Enumerator remaining = (mineEnded ? theirs : mine); + do + { + merged[c++] = remaining.Current; + } while (remaining.MoveNext()); + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + AddAllElements(other); + } + } + + + private static Node ConstructRootFromSortedArray(T[] arr, int startIndex, int endIndex, Node redNode) + { + + + + //what does this do? + //you're given a sorted array... say 1 2 3 4 5 6 + //2 cases: + // If there are odd # of elements, pick the middle element (in this case 4), and compute + // its left and right branches + // If there are even # of elements, pick the left middle element, save the right middle element + // and call the function on the rest + // 1 2 3 4 5 6 -> pick 3, save 4 and call the fn on 1,2 and 5,6 + // now add 4 as a red node to the lowest element on the right branch + // 3 3 + // 1 5 -> 1 5 + // 2 6 2 4 6 + // As we're adding to the leftmost of the right branch, nesting will not hurt the red-black properties + // Leaf nodes are red if they have no sibling (if there are 2 nodes or if a node trickles + // down to the bottom + + + //the iterative way to do this ends up wasting more space than it saves in stack frames (at + //least in what i tried) + //so we're doing this recursively + //base cases are described below + int size = endIndex - startIndex + 1; + if (size == 0) + { + return null; + } + Node root = null; + if (size == 1) + { + root = new Node(arr[startIndex], false); + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 2) + { + root = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + root.Right.IsRed = true; + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 3) + { + root = new Node(arr[startIndex + 1], false); + root.Left = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + if (redNode != null) + { + root.Left.Left = redNode; + + } + } + else + { + int midpt = ((startIndex + endIndex) / 2); + root = new Node(arr[midpt], false); + root.Left = ConstructRootFromSortedArray(arr, startIndex, midpt - 1, redNode); + if (size % 2 == 0) + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 2, endIndex, new Node(arr[midpt + 1], true)); + } + else + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 1, endIndex, null); + } + } + return root; + + } + + + /// + /// Transform this set into its intersection with the IEnumerable OTHER + /// NOTE: The caller object is important as IntersectionWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public virtual void IntersectWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return; + + //HashSet optimizations can't be done until equality comparers and comparers are related + + //Technically, this would work as well with an ISorted + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + if (t != null) + VersionCheck(); + //only let this happen if i am also a SortedSet, not a SubSet + if (s != null && t == null && AreComparersEqual(this, s)) + { + + + //first do a merge sort to an array. + T[] merged = new T[this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + T max = Max; + T min = Min; + + while (!mineEnded && !theirsEnded && Comparer.Compare(theirs.Current, max) <= 0) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + theirsEnded = !theirs.MoveNext(); + } + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + IntersectWithEnumerable(other); + } + } + + internal virtual void IntersectWithEnumerable(IEnumerable other) + { + // + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + AddAllElements(toSave); + + } + + + + /// + /// Transform this set into its complement with the IEnumerable OTHER + /// NOTE: The caller object is important as ExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void ExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (count == 0) + return; + + if (other == this) + { + this.Clear(); + return; + } + + SortedSet asSorted = other as SortedSet; + + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + //outside range, no point doing anything + if (!(comparer.Compare(asSorted.Max, this.Min) < 0 || comparer.Compare(asSorted.Min, this.Max) > 0)) + { + T min = this.Min; + T max = this.Max; + foreach (T item in other) + { + if (comparer.Compare(item, min) < 0) + continue; + if (comparer.Compare(item, max) > 0) + break; + Remove(item); + } + } + + } + else + { + RemoveAllElements(other); + } + } + + /// + /// Transform this set so it contains elements in THIS or OTHER but not both + /// NOTE: The caller object is important as SymmetricExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void SymmetricExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + { + this.UnionWith(other); + return; + } + + if (other == this) + { + this.Clear(); + return; + } + + + SortedSet asSorted = other as SortedSet; + +#if USING_HASH_SET + HashSet asHash = other as HashSet; +#endif + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + SymmetricExceptWithSameEC(asSorted); + } +#if USING_HASH_SET + else if (asHash != null && this.comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + SymmetricExceptWithSameEC(asHash); + } +#endif + else + { + //need perf improvement on this + T[] elements = (new List(other)).ToArray(); + Array.Sort(elements, this.Comparer); + SymmetricExceptWithSameEC(elements); + } + } + + //OTHER must be a set + internal void SymmetricExceptWithSameEC(ISet other) + { + foreach (T item in other) + { + //yes, it is classier to say + //if (!this.Remove(item))this.Add(item); + //but this ends up saving on rotations + if (this.Contains(item)) + { + this.Remove(item); + } + else + { + this.Add(item); + } + } + } + + //OTHER must be a sorted array + internal void SymmetricExceptWithSameEC(T[] other) + { + if (other.Length == 0) + { + return; + } + T last = other[0]; + for (int i = 0; i < other.Length; i++) + { + while (i < other.Length && i != 0 && comparer.Compare(other[i], last) == 0) + i++; + if (i >= other.Length) + break; + if (this.Contains(other[i])) + { + this.Remove(other[i]); + } + else + { + this.Add(other[i]); + } + last = other[i]; + } + } + + + /// + /// Checks whether this Tree is a subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return true; + + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count > asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + else + { + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount >= 0); + } + } + + private bool IsSubsetOfSortedSetWithSameEC(SortedSet asSorted) + { + SortedSet prunedOther = asSorted.GetViewBetween(this.Min, this.Max); + foreach (T item in this) + { + if (!prunedOther.Contains(item)) + return false; + } + return true; + + } + + + /// + /// Checks whether this Tree is a proper subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null) + { + if (Count == 0) + return (other as ICollection).Count > 0; + } + + +#if USING_HASH_SET + //do it one way for HashSets + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSupersetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count >= asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN). + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount > 0); + } + + + /// + /// Checks whether this Tree is a super set of the IEnumerable other + /// + /// + /// + public bool IsSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + + //do it one way for HashSets +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsSubsetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count < asSorted.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + //and a third for everything else + return ContainsAllElements(other); + } + + /// + /// Checks whether this Tree is a proper super set of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return false; + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + +#if USING_HASH_SET + //do it one way for HashSets + + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSubsetOf(this); + } +#endif + //another way for sorted sets + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(asSorted, this)) + { + if (asSorted.Count >= this.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + //slight optimization, put it into a HashSet and then check can do it in O(N+M) + //but slower in better cases + wastes space + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount < Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has all elements in common with IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool SetEquals(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.SetEquals(this); + } +#endif + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + IEnumerator mine = this.GetEnumerator(); + IEnumerator theirs = asSorted.GetEnumerator(); + bool mineEnded = !mine.MoveNext(); + bool theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + if (Comparer.Compare(mine.Current, theirs.Current) != 0) + { + return false; + } + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + return mineEnded && theirsEnded; + } + + //worst case: mark every element in my set and see if i've counted all + //O(N) by size of other + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount == Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has any elements in common with IEnumerable other + /// + /// + /// + public bool Overlaps(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + return false; + + if ((other as ICollection != null) && (other as ICollection).Count == 0) + return false; + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted) && (comparer.Compare(Min, asSorted.Max) > 0 || comparer.Compare(Max, asSorted.Min) < 0)) + { + return false; + } +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.Overlaps(this); + } +#endif + foreach (T item in other) + { + if (this.Contains(item)) + { + return true; + } + } + return false; + } + + /// + /// This works similar to HashSet's CheckUniqueAndUnfound (description below), except that the bit + /// array maps differently than in the HashSet. We can only use this for the bulk boolean checks. + /// + /// Determines counts that can be used to determine equality, subset, and superset. This + /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet + /// these properties can be checked faster without use of marking because we can assume + /// other has no duplicates. + /// + /// The following count checks are performed by callers: + /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = Count; i.e. everything + /// in other is in this and everything in this is in other + /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = Count; i.e. other may + /// have elements not in this and everything in this is in other + /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = Count; i.e + /// other must have at least one element not in this and everything in this is in other + /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less + /// than Count; i.e. everything in other was in this and this had at least one element + /// not contained in other. + /// + /// An earlier implementation used delegates to perform these checks rather than returning + /// an ElementCount struct; however this was changed due to the perf overhead of delegates. + /// + /// + /// Allows us to finish faster for equals and proper superset + /// because unfoundCount must be 0. + /// + // + // + // + // + // + // + [System.Security.SecurityCritical] + private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + { + ElementCount result; + + // need special case in case this has no elements. + if (Count == 0) + { + int numElementsInOther = 0; + foreach (T item in other) + { + numElementsInOther++; + // break right away, all we want to know is whether other has 0 or 1 elements + break; + } + result.uniqueCount = 0; + result.unfoundCount = numElementsInOther; + return result; + } + + + int originalLastIndex = Count; + int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); + + BitHelper bitHelper; + if (intArrayLength <= StackAllocThreshold) + { + int* bitArrayPtr = stackalloc int[intArrayLength]; + bitHelper = new BitHelper(bitArrayPtr, intArrayLength); + } + else + { + int[] bitArray = new int[intArrayLength]; + bitHelper = new BitHelper(bitArray, intArrayLength); + } + + // count of items in other not found in this + int unfoundCount = 0; + // count of unique items in other found in this + int uniqueFoundCount = 0; + + foreach (T item in other) + { + int index = InternalIndexOf(item); + if (index >= 0) + { + if (!bitHelper.IsMarked(index)) + { + // item hasn't been seen yet + bitHelper.MarkBit(index); + uniqueFoundCount++; + } + } + else + { + unfoundCount++; + if (returnIfUnfound) + { + break; + } + } + } + + result.uniqueCount = uniqueFoundCount; + result.unfoundCount = unfoundCount; + return result; + } + public int RemoveWhere(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + List matches = new List(this.Count); + + BreadthFirstTreeWalk(delegate (Node n) + { + if (match(n.Item)) + { + matches.Add(n.Item); + } + return true; + }); + // reverse breadth first to (try to) incur low cost + int actuallyRemoved = 0; + for (int i = matches.Count - 1; i >= 0; i--) + { + if (this.Remove(matches[i])) + { + actuallyRemoved++; + } + } + + return actuallyRemoved; + + } + + + #endregion + + #region ISorted Members + + + public T Min + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }); + return ret; + } + } + + public T Max + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }, true); + return ret; + } + } + + public IEnumerable Reverse() + { + Enumerator e = new Enumerator(this, true); + while (e.MoveNext()) + { + yield return e.Current; + } + } + + + /// + /// Returns a subset of this tree ranging from values lBound to uBound + /// Any changes made to the subset reflect in the actual tree + /// + /// Lowest Value allowed in the subset + /// Highest Value allowed in the subset + public virtual SortedSet GetViewBetween(T lowerValue, T upperValue) + { + if (Comparer.Compare(lowerValue, upperValue) > 0) + { + throw new ArgumentException("lowerBound is greater than upperBound"); + } + return new TreeSubSet(this, lowerValue, upperValue, true, true); + } + +#if DEBUG + + /// + /// debug status to be checked whenever any operation is called + /// + /// + internal virtual bool versionUpToDate() + { + return true; + } +#endif + + + /// + /// This class represents a subset view into the tree. Any changes to this view + /// are reflected in the actual tree. Uses the Comparator of the underlying tree. + /// + /// +#if !FEATURE_NETCORE + [Serializable] + internal sealed class TreeSubSet : SortedSet, ISerializable, IDeserializationCallback + { +#else + internal sealed class TreeSubSet : SortedSet { +#endif + SortedSet underlying; + T min, max; + //these exist for unbounded collections + //for instance, you could allow this subset to be defined for i>10. The set will throw if + //anything <=10 is added, but there is no upperbound. These features Head(), Tail(), were punted + //in the spec, and are not available, but the framework is there to make them available at some point. + bool lBoundActive, uBoundActive; + //used to see if the count is out of date + + +#if DEBUG + internal override bool versionUpToDate() + { + return (this.version == underlying.version); + } +#endif + + public TreeSubSet(SortedSet Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive) + : base(Underlying.Comparer) + { + underlying = Underlying; + min = Min; + max = Max; + lBoundActive = lowerBoundActive; + uBoundActive = upperBoundActive; + root = underlying.FindRange(min, max, lBoundActive, uBoundActive); // root is first element within range + count = 0; + version = -1; + VersionCheckImpl(); + } + +#if !FEATURE_NETCORE + /// + /// For serialization and deserialization + /// + private TreeSubSet() + { + comparer = null; + } + + + [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")] + private TreeSubSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + OnDeserializationImpl(info); + } +#endif // !FEATURE_NETCORE + + /// + /// Additions to this tree need to be added to the underlying tree as well + /// + + internal override bool AddIfNotPresent(T item) + { + + if (!IsWithinRange(item)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.collection); + } + + bool ret = underlying.AddIfNotPresent(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + + return ret; + } + + + public override bool Contains(T item) + { + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.Contains(item); + } + + internal override bool DoRemove(T item) + { // todo: uppercase this and others + + if (!IsWithinRange(item)) + { + return false; + } + + bool ret = underlying.Remove(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return ret; + } + + public override void Clear() + { + + + if (count == 0) + { + return; + } + + List toRemove = new List(); + BreadthFirstTreeWalk(delegate (Node n) { toRemove.Add(n.Item); return true; }); + while (toRemove.Count != 0) + { + underlying.Remove(toRemove[toRemove.Count - 1]); + toRemove.RemoveAt(toRemove.Count - 1); + } + root = null; + count = 0; + version = underlying.version; + } + + + internal override bool IsWithinRange(T item) + { + + int comp = (lBoundActive ? Comparer.Compare(min, item) : -1); + if (comp > 0) + { + return false; + } + comp = (uBoundActive ? Comparer.Compare(max, item) : 1); + if (comp < 0) + { + return false; + } + return true; + } + + internal override bool InOrderTreeWalk(TreeWalkPredicate action, Boolean reverse) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + Stack stack = new Stack(2 * (int)SortedSet.log2(count + 1)); //this is not exactly right if count is out of date, but the stack can grow + Node current = root; + while (current != null) + { + if (IsWithinRange(current.Item)) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + else if (lBoundActive && Comparer.Compare(min, current.Item) > 0) + { + current = current.Right; + } + else + { + current = current.Left; + } + } + + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + if (IsWithinRange(node.Item)) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + else if (lBoundActive && Comparer.Compare(min, node.Item) > 0) + { + node = node.Right; + } + else + { + node = node.Left; + } + } + } + return true; + } + + internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (IsWithinRange(current.Item) && !action(current)) + { + return false; + } + if (current.Left != null && (!lBoundActive || Comparer.Compare(min, current.Item) < 0)) + { + processQueue.Add(current.Left); + } + if (current.Right != null && (!uBoundActive || Comparer.Compare(max, current.Item) > 0)) + { + processQueue.Add(current.Right); + } + + } + return true; + } + + internal override SortedSet.Node FindNode(T item) + { + + if (!IsWithinRange(item)) + { + return null; + } + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.FindNode(item); + } + + //this does indexing in an inefficient way compared to the actual sortedset, but it saves a + //lot of space + internal override int InternalIndexOf(T item) + { + int count = -1; + foreach (T i in this) + { + count++; + if (Comparer.Compare(item, i) == 0) + return count; + } +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return -1; + } + /// + /// checks whether this subset is out of date. updates if necessary. + /// + internal override void VersionCheck() + { + VersionCheckImpl(); + } + + private void VersionCheckImpl() + { + Debug.Assert(underlying != null, "Underlying set no longer exists"); + if (this.version != underlying.version) + { + this.root = underlying.FindRange(min, max, lBoundActive, uBoundActive); + this.version = underlying.version; + count = 0; + InOrderTreeWalk(delegate (Node n) { count++; return true; }); + } + } + + + + //This passes functionality down to the underlying tree, clipping edges if necessary + //There's nothing gained by having a nested subset. May as well draw it from the base + //Cannot increase the bounds of the subset, can only decrease it + public override SortedSet GetViewBetween(T lowerValue, T upperValue) + { + + if (lBoundActive && Comparer.Compare(min, lowerValue) > 0) + { + //lBound = min; + throw new ArgumentOutOfRangeException("lowerValue"); + } + if (uBoundActive && Comparer.Compare(max, upperValue) < 0) + { + //uBound = max; + throw new ArgumentOutOfRangeException("upperValue"); + } + TreeSubSet ret = (TreeSubSet)underlying.GetViewBetween(lowerValue, upperValue); + return ret; + } + + internal override void IntersectWithEnumerable(IEnumerable other) + { + + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + this.AddAllElements(toSave); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + } + +#if !FEATURE_NETCORE + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + info.AddValue(maxName, max, typeof(T)); + info.AddValue(minName, min, typeof(T)); + info.AddValue(lBoundActiveName, lBoundActive); + info.AddValue(uBoundActiveName, uBoundActive); + base.GetObjectData(info, context); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + //don't do anything here as its already been done by the constructor + //OnDeserialization(sender); + + } + + protected override void OnDeserialization(Object sender) + { + OnDeserializationImpl(sender); + } + + private void OnDeserializationImpl(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + max = (T)siInfo.GetValue(maxName, typeof(T)); + min = (T)siInfo.GetValue(minName, typeof(T)); + lBoundActive = siInfo.GetBoolean(lBoundActiveName); + uBoundActive = siInfo.GetBoolean(uBoundActiveName); + underlying = new SortedSet(); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + underlying.Add(items[i]); + } + } + underlying.version = siInfo.GetInt32(VersionName); + count = underlying.count; + version = underlying.version - 1; + VersionCheck(); //this should update the count to be right and update root to be right + + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + + } +#endif // !FEATURE_NETCORE + + + + + } + + + #endregion + + #region Serialization methods + +#if !FEATURE_NETCORE + // LinkDemand here is unnecessary as this is a methodimpl and linkdemand from the interface should suffice + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + + info.AddValue(CountName, count); //This is the length of the bucket array. + info.AddValue(ComparerName, comparer, typeof(IComparer)); + info.AddValue(VersionName, version); + + if (root != null) + { + T[] items = new T[Count]; + CopyTo(items, 0); + info.AddValue(ItemsName, items, typeof(T[])); + } + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + protected virtual void OnDeserialization(Object sender) + { + if (comparer != null) + { + return; //Somebody had a dependency on this class and fixed us up before the ObjectManager got to it. + } + + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + Add(items[i]); + } + } + + version = siInfo.GetInt32(VersionName); + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + } +#endif //!FEATURE_NETCORE + #endregion + + #region Helper Classes + internal class Node + { + public bool IsRed; + public T Item; + public Node Left; + public Node Right; + + public Node(T item) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + IsRed = true; + } + + public Node(T item, bool isRed) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + this.IsRed = isRed; + } + } + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] +#if !FEATURE_NETCORE + [Serializable] + public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback + { +#else + public struct Enumerator : IEnumerator, IEnumerator { +#endif + private SortedSet tree; + private int version; + + + private Stack.Node> stack; + private SortedSet.Node current; + static SortedSet.Node dummyNode = new SortedSet.Node(default(T)); + + private bool reverse; + +#if !FEATURE_NETCORE + private SerializationInfo siInfo; +#endif + internal Enumerator(SortedSet set) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + reverse = false; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + } + + internal Enumerator(SortedSet set, bool reverse) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + this.reverse = reverse; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + + } + +#if !FEATURE_NETCORE + private Enumerator(SerializationInfo info, StreamingContext context) + { + tree = null; + version = -1; + current = null; + reverse = false; + stack = null; + this.siInfo = info; + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + private void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + + } + info.AddValue(TreeName, tree, typeof(SortedSet)); + info.AddValue(EnumVersionName, version); + info.AddValue(ReverseName, reverse); + info.AddValue(EnumStartName, !NotStartedOrEnded); + info.AddValue(NodeValueName, (current == null ? dummyNode.Item : current.Item), typeof(T)); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + private void OnDeserialization(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + tree = (SortedSet)siInfo.GetValue(TreeName, typeof(SortedSet)); + version = siInfo.GetInt32(EnumVersionName); + reverse = siInfo.GetBoolean(ReverseName); + bool EnumStarted = siInfo.GetBoolean(EnumStartName); + stack = new Stack.Node>(2 * (int)SortedSet.log2(tree.Count + 1)); + current = null; + if (EnumStarted) + { + T item = (T)siInfo.GetValue(NodeValueName, typeof(T)); + Intialize(); + //go until it reaches the value we want + while (this.MoveNext()) + { + if (tree.Comparer.Compare(this.Current, item) == 0) + break; + } + } + + + } +#endif //!FEATURE_NETCORE + + + private void Intialize() + { + + + current = null; + SortedSet.Node node = tree.root; + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (next == null || !tree.IsWithinRange(next.Item)) + { + node = other; + } + else + { + node = next; + } + } + } + + public bool MoveNext() + { + + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + if (stack.Count == 0) + { + current = null; + return false; + } + + current = stack.Pop(); + SortedSet.Node node = (reverse ? current.Left : current.Right); + + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (other == null || !tree.IsWithinRange(other.Item)) + { + node = next; + } + else + { + node = other; + } + } + return true; + } + + public void Dispose() + { + } + + public T Current + { + get + { + if (current != null) + { + return current.Item; + } + return default(T); + } + } + + object IEnumerator.Current + { + get + { + if (current == null) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return current.Item; + } + } + + internal bool NotStartedOrEnded + { + get + { + return current == null; + } + } + + internal void Reset() + { + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + stack.Clear(); + Intialize(); + } + + void IEnumerator.Reset() + { + Reset(); + } + + + + + } + + + + internal struct ElementCount + { + internal int uniqueCount; + internal int unfoundCount; + } + #endregion + + #region misc + + /// + /// Searches the set for a given value and returns the equal value it finds, if any. + /// + /// The value to search for. + /// The value from the set that the search found, or the default value of when the search yielded no match. + /// A value indicating whether the search was successful. + /// + /// This can be useful when you want to reuse a previously stored reference instead of + /// a newly constructed one (so that more sharing of references can occur) or to look up + /// a value that has more complete data than the value you currently have, although their + /// comparer functions indicate they are equal. + /// + public bool TryGetValue(T equalValue, out T actualValue) + { + Node node = FindNode(equalValue); + if (node != null) + { + actualValue = node.Item; + return true; + } + actualValue = default(T); + return false; + } + + // used for set checking operations (using enumerables) that rely on counting + private static int log2(int value) + { + //Contract.Requires(value>0) + int c = 0; + while (value > 0) + { + c++; + value >>= 1; + } + return c; + } + #endregion + + + } + + /// + /// A class that generates an IEqualityComparer for this SortedSet. Requires that the definition of + /// equality defined by the IComparer for this SortedSet be consistent with the default IEqualityComparer + /// for the type T. If not, such an IEqualityComparer should be provided through the constructor. + /// + internal class SortedSetEqualityComparer : IEqualityComparer> + { + private IComparer comparer; + private IEqualityComparer e_comparer; + + public SortedSetEqualityComparer() : this(null, null) { } + + public SortedSetEqualityComparer(IComparer comparer) : this(comparer, null) { } + + public SortedSetEqualityComparer(IEqualityComparer memberEqualityComparer) : this(null, memberEqualityComparer) { } + + /// + /// Create a new SetEqualityComparer, given a comparer for member order and another for member equality (these + /// must be consistent in their definition of equality) + /// + public SortedSetEqualityComparer(IComparer comparer, IEqualityComparer memberEqualityComparer) + { + if (comparer == null) + this.comparer = Comparer.Default; + else + this.comparer = comparer; + if (memberEqualityComparer == null) + e_comparer = EqualityComparer.Default; + else + e_comparer = memberEqualityComparer; + } + + + // using comparer to keep equals properties in tact; don't want to choose one of the comparers + public bool Equals(SortedSet x, SortedSet y) + { + return SortedSet.SortedSetEquals(x, y, comparer); + } + //IMPORTANT: this part uses the fact that GetHashCode() is consistent with the notion of equality in + //the set + public int GetHashCode(SortedSet obj) + { + int hashCode = 0; + if (obj != null) + { + foreach (T t in obj) + { + hashCode = hashCode ^ (e_comparer.GetHashCode(t) & 0x7FFFFFFF); + } + } // else returns hashcode of 0 for null HashSets + return hashCode; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) + { + SortedSetEqualityComparer comparer = obj as SortedSetEqualityComparer; + if (comparer == null) + { + return false; + } + return (this.comparer == comparer.comparer); + } + + public override int GetHashCode() + { + return comparer.GetHashCode() ^ e_comparer.GetHashCode(); + } + + + } + +} + + + +#pragma warning restore CS8604 // 引用类型参数可能为 null。 +#pragma warning restore CS8602 // 解引用可能出现空引用。 +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8601 // 引用类型赋值可能为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs b/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs new file mode 100644 index 0000000..205dd1c --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs @@ -0,0 +1,382 @@ +#if NET35 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 + +namespace System +{ + // This file defines an internal class used to throw exceptions in BCL code. + // The main purpose is to reduce code size. + // + // The old way to throw an exception generates quite a lot IL code and assembly code. + // Following is an example: + // C# source + // throw new ArgumentNullException("key", SR.GetString("ArgumentNull_Key")); + // IL code: + // IL_0003: ldstr "key" + // IL_0008: ldstr "ArgumentNull_Key" + // IL_000d: call string System.Environment::GetResourceString(string) + // IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string) + // IL_0017: throw + // which is 21bytes in IL. + // + // So we want to get rid of the ldstr and call to Environment.GetResource in IL. + // In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the + // argument name and resource name in a small integer. The source code will be changed to + // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key); + // + // The IL code will be 7 bytes. + // IL_0008: ldc.i4.4 + // IL_0009: ldc.i4.4 + // IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument) + // IL_000f: ldarg.0 + // + // This will also reduce the Jitted code size a lot. + // + // It is very important we do this for generic classes because we can easily generate the same code + // multiple times for different instantiation. + // + // < + + + + + + + + + + +#if !SILVERLIGHT + using System.Runtime.Serialization; +#endif + + using System.Diagnostics; + internal static class ThrowHelper + { + internal static void ThrowWrongKeyTypeArgumentException(object key, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", key, targetType), "key"); + } + + internal static void ThrowWrongValueTypeArgumentException(object value, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", value, targetType), "value"); + } + + internal static void ThrowKeyNotFoundException() + { + throw new System.Collections.Generic.KeyNotFoundException(); + } + + internal static void ThrowArgumentException(ExceptionResource resource) + { + throw new ArgumentException(SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw new ArgumentNullException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument), SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowInvalidOperationException(ExceptionResource resource) + { + throw new InvalidOperationException(SR.GetString(GetResourceName(resource))); + } + +#if !SILVERLIGHT + internal static void ThrowSerializationException(ExceptionResource resource) + { + throw new SerializationException(SR.GetString(GetResourceName(resource))); + } +#endif + + internal static void ThrowNotSupportedException(ExceptionResource resource) + { + throw new NotSupportedException(SR.GetString(GetResourceName(resource))); + } + + // Allow nulls for reference types and Nullable, but not for value types. + internal static void IfNullAndNullsAreIllegalThenThrow(object value, ExceptionArgument argName) + { + // Note that default(T) is not equal to null for value types except when T is Nullable. + if (value == null && !(default(T) == null)) + ThrowHelper.ThrowArgumentNullException(argName); + } + + // + // This function will convert an ExceptionArgument enum value to the argument name string. + // + internal static string GetArgumentName(ExceptionArgument argument) + { + string argumentName = null; + + switch (argument) + { + case ExceptionArgument.array: + argumentName = "array"; + break; + + case ExceptionArgument.arrayIndex: + argumentName = "arrayIndex"; + break; + + case ExceptionArgument.capacity: + argumentName = "capacity"; + break; + + case ExceptionArgument.collection: + argumentName = "collection"; + break; + + case ExceptionArgument.converter: + argumentName = "converter"; + break; + + case ExceptionArgument.count: + argumentName = "count"; + break; + + case ExceptionArgument.dictionary: + argumentName = "dictionary"; + break; + + case ExceptionArgument.index: + argumentName = "index"; + break; + + case ExceptionArgument.info: + argumentName = "info"; + break; + + case ExceptionArgument.key: + argumentName = "key"; + break; + + case ExceptionArgument.match: + argumentName = "match"; + break; + + case ExceptionArgument.obj: + argumentName = "obj"; + break; + + case ExceptionArgument.queue: + argumentName = "queue"; + break; + + case ExceptionArgument.stack: + argumentName = "stack"; + break; + + case ExceptionArgument.startIndex: + argumentName = "startIndex"; + break; + + case ExceptionArgument.value: + argumentName = "value"; + break; + + case ExceptionArgument.item: + argumentName = "item"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return argumentName; + } + + // + // This function will convert an ExceptionResource enum value to the resource string. + // + internal static string GetResourceName(ExceptionResource resource) + { + string resourceName = null; + + switch (resource) + { + case ExceptionResource.Argument_ImplementIComparable: + resourceName = "SR.Argument_ImplementIComparable"; + break; + + case ExceptionResource.Argument_AddingDuplicate: + resourceName = "SR.Argument_AddingDuplicate"; + break; + + case ExceptionResource.ArgumentOutOfRange_Index: + resourceName = "SR.ArgumentOutOfRange_Index"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNum"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNumRequired"; + break; + + case ExceptionResource.ArgumentOutOfRange_SmallCapacity: + resourceName = "SR.ArgumentOutOfRange_SmallCapacity"; + break; + + case ExceptionResource.Arg_ArrayPlusOffTooSmall: + resourceName = "SR.Arg_ArrayPlusOffTooSmall"; + break; + + case ExceptionResource.Arg_RankMultiDimNotSupported: + resourceName = "SR.Arg_MultiRank"; + break; + + case ExceptionResource.Arg_NonZeroLowerBound: + resourceName = "SR.Arg_NonZeroLowerBound"; + break; + + case ExceptionResource.Argument_InvalidArrayType: + resourceName = "SR.Invalid_Array_Type"; + break; + + case ExceptionResource.Argument_InvalidOffLen: + resourceName = "SR.Argument_InvalidOffLen"; + break; + + case ExceptionResource.InvalidOperation_CannotRemoveFromStackOrQueue: + resourceName = "SR.InvalidOperation_CannotRemoveFromStackOrQueue"; + break; + + case ExceptionResource.InvalidOperation_EmptyCollection: + resourceName = "SR.InvalidOperation_EmptyCollection"; + break; + + case ExceptionResource.InvalidOperation_EmptyQueue: + resourceName = "SR.InvalidOperation_EmptyQueue"; + break; + + case ExceptionResource.InvalidOperation_EnumOpCantHappen: + resourceName = "SR.InvalidOperation_EnumOpCantHappen"; + break; + + case ExceptionResource.InvalidOperation_EnumFailedVersion: + resourceName = "SR.InvalidOperation_EnumFailedVersion"; + break; + + case ExceptionResource.InvalidOperation_EmptyStack: + resourceName = "SR.InvalidOperation_EmptyStack"; + break; + + case ExceptionResource.InvalidOperation_EnumNotStarted: + resourceName = "SR.InvalidOperation_EnumNotStarted"; + break; + + case ExceptionResource.InvalidOperation_EnumEnded: + resourceName = "SR.InvalidOperation_EnumEnded"; + break; + + case ExceptionResource.NotSupported_KeyCollectionSet: + resourceName = "SR.NotSupported_KeyCollectionSet"; + break; + + case ExceptionResource.NotSupported_SortedListNestedWrite: + resourceName = "SR.NotSupported_SortedListNestedWrite"; + break; + +#if !SILVERLIGHT + case ExceptionResource.Serialization_InvalidOnDeser: + resourceName = "SR.Serialization_InvalidOnDeser"; + break; + + case ExceptionResource.Serialization_MissingValues: + resourceName = "SR.Serialization_MissingValues"; + break; + + case ExceptionResource.Serialization_MismatchedCount: + resourceName = "SR.Serialization_MismatchedCount"; + break; +#endif + + case ExceptionResource.NotSupported_ValueCollectionSet: + resourceName = "SR.NotSupported_ValueCollectionSet"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return resourceName; + } + + } + + // + // The convention for this enum is using the argument name as the enum name + // + internal enum ExceptionArgument + { + obj, + dictionary, + array, + info, + key, + collection, + match, + converter, + queue, + stack, + capacity, + index, + startIndex, + value, + count, + arrayIndex, + item, + } + + // + // The convention for this enum is using the resource name as the enum name + // + internal enum ExceptionResource + { + Argument_ImplementIComparable, + ArgumentOutOfRange_NeedNonNegNum, + ArgumentOutOfRange_NeedNonNegNumRequired, + Arg_ArrayPlusOffTooSmall, + Argument_AddingDuplicate, + Serialization_InvalidOnDeser, + Serialization_MismatchedCount, + Serialization_MissingValues, + Arg_RankMultiDimNotSupported, + Arg_NonZeroLowerBound, + Argument_InvalidArrayType, + NotSupported_KeyCollectionSet, + ArgumentOutOfRange_SmallCapacity, + ArgumentOutOfRange_Index, + Argument_InvalidOffLen, + NotSupported_ReadOnlyCollection, + InvalidOperation_CannotRemoveFromStackOrQueue, + InvalidOperation_EmptyCollection, + InvalidOperation_EmptyQueue, + InvalidOperation_EnumOpCantHappen, + InvalidOperation_EnumFailedVersion, + InvalidOperation_EmptyStack, + InvalidOperation_EnumNotStarted, + InvalidOperation_EnumEnded, + NotSupported_SortedListNestedWrite, + NotSupported_ValueCollectionSet, + } +} + +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/bithelper.cs b/src/IFoxCAD.Basal/Sortedset/bithelper.cs new file mode 100644 index 0000000..d68c4d8 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/bithelper.cs @@ -0,0 +1,173 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 + + +using System; +using System.Collections; +using System.Text; + +namespace System.Collections.Generic +{ + + /// + /// ABOUT: + /// Helps with operations that rely on bit marking to indicate whether an item in the + /// collection should be added, removed, visited already, etc. + /// + /// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the + /// stack or heap. ToIntArrayLength() tells you the int array size you must allocate. + /// + /// USAGE: + /// Suppose you need to represent a bit array of length (i.e. logical bit array length) + /// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper: + /// *************************************************************************** + /// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH); + /// BitHelper bitHelper; + /// if (intArrayLength less than stack alloc threshold) + /// int* m_arrayPtr = stackalloc int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// else + /// int[] m_arrayPtr = new int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// *************************************************************************** + /// + /// IMPORTANT: + /// The second ctor args, length, should be specified as the length of the int array, not + /// the logical bit array. Because length is used for bounds checking into the int array, + /// it's especially important to get this correct for the stackalloc version. See the code + /// samples above; this is the value gotten from ToIntArrayLength(). + /// + /// The length ctor argument is the only exception; for other methods -- MarkBit and + /// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped + /// to the position within the array of ints. + /// + /// + + + + + unsafe internal class BitHelper + { // should not be serialized + + private const byte MarkedBitFlag = 1; + private const byte IntSize = 32; + + // m_length of underlying int array (not logical bit array) + private int m_length; + + // ptr to stack alloc'd array of ints + [System.Security.SecurityCritical] + private int* m_arrayPtr; + + // array of ints + private int[] m_array; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + // + // + // + // + [System.Security.SecurityCritical] + internal BitHelper(int* bitArrayPtr, int length) + { + this.m_arrayPtr = bitArrayPtr; + this.m_length = length; + useStackAlloc = true; + } + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + internal BitHelper(int[] bitArray, int length) + { + this.m_array = bitArray; + this.m_length = length; + } + + /// + /// Mark bit at specified position + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe void MarkBit(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_arrayPtr[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_array[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + } + + /// + /// Is bit at specified position marked? + /// + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe bool IsMarked(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_arrayPtr[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_array[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + } + + /// + /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but + /// avoids overflow + /// + /// + /// + internal static int ToIntArrayLength(int n) + { + return n > 0 ? ((n - 1) / IntSize + 1) : 0; + } + + } +} + +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs new file mode 100644 index 0000000..ad1efb5 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs @@ -0,0 +1,647 @@ +namespace IFoxCAD.Cad; +using Exception = System.Exception; + +/// +/// 无权无向图实现 +/// IEnumerable 枚举所有顶点; +/// +public sealed class Graph : IGraph, IEnumerable +{ + #region 字段及属性 + /// + /// 存储所有节点的字典,key为顶点的类型,value为邻接表,类型是hashset,不可重复添加点 + /// + /// + readonly Dictionary> vertices = new(); + /// + /// 邻接边表,key为顶点的类型,value为邻接边表,类型是hashset,不可重复添加边 + /// + readonly Dictionary> edges = new(); + /// + /// 为加快索引,引入hash检索 + /// + readonly Dictionary vertexs = new(); + + public int VerticesCount => vertices.Count; + + /// + /// Returns a reference vertex. + /// Time complexity: O(1). + /// + private IGraphVertex? ReferenceVertex + { + get + { + using (var enumerator = vertexs.GetEnumerator()) + { + if (enumerator.MoveNext()) + { + return enumerator.Current.Value; + } + } + + return null; + } + } + IGraphVertex? IGraph.ReferenceVertex => ReferenceVertex; + /// + /// 目前点增加点的顺序号,这个点号不随删点而减少的 + /// + private int insertCount; + #endregion + + #region 构造函数 + public Graph() + { + insertCount = 0; // 每次新建对象就将顶点顺序号归零 + } + #endregion + + #region 顶点及边_增 + /// + /// 向该图添加一个新顶点,但是无边; + /// + /// 点 + /// 创建的顶点 + public IGraphVertex AddVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + return vertexs[str]; + + var vertex = new GraphVertex(pt, insertCount++); + vertices.Add(vertex, new HashSet()); + edges.Add(vertex, new HashSet()); + + vertexs[str] = vertex; + + return vertex; + } + + /// + /// 向该图添加一个边; + /// + /// + public void AddEdge(Curve3d curve!!) + { + var start = AddVertex(curve.StartPoint); + var end = AddVertex(curve.EndPoint); + + // 添加起点的邻接表和邻接边 + vertices[start].Add(end); + edges[start].Add(new GraphEdge(end, curve)); + + // 为了保证点顺序,每个点的邻接边必须按起点-终点,所以添加曲线终点时,将添加一个方向的曲线 + var curtmp = (Curve3d)curve.Clone(); + curtmp = curtmp.GetReverseParameterCurve(); + + // 添加终点的邻接表和邻接边 + vertices[end].Add(start); + edges[end].Add(new GraphEdge(start, curtmp)); + } + #endregion + + #region 顶点及边_删 + /// + /// 从此图中删除现有顶点; + /// + /// 点 + public void RemoveVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + { + var vertex = vertexs[str]; + + // 删除邻接表里的vertex点,先删除后面的遍历可以少一轮 + vertices.Remove(vertex!); + + // 删除其他顶点的邻接表里的vertex点 + foreach (var item in vertices.Values) + item.Remove(vertex!); + + // 删除邻接边表里的vertex点,先删除后面的遍历可以少一轮 + edges.Remove(vertex!); + + // 删除其他顶点的邻接边表的指向vertex的边 + foreach (var item in edges.Values) + { + item.RemoveWhere(x => vertex.Equals(x.TargetVertex)); + //foreach (var edge in item) + //{ + // if (vertex.Equals(edge.TargetVertex)) + // item.Remove(edge); + //} + } + vertexs.Remove(str); + } + } + + /// + /// 从此图中删除一条边; + /// + /// 曲线 + public void RemoveEdge(Curve3d curve!!) + { + RemoveVertex(curve.StartPoint); + RemoveVertex(curve.EndPoint); + } + #endregion + + #region 顶点和边_查 + /// + /// 我们在给定的来源和目的地之间是否有边? + /// + /// 起点 + /// 终点 + /// 有边返回 ,反之返回 + public bool HasEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex == dest) + return true; + } + return false; + } + + /// + /// 获取边 + /// + /// 起点 + /// 终点 + /// + /// 传入的点不在图中时抛出参数异常 + public IEdge? GetEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex.Equals(dest)) + return item; + } + return null; + } + + /// + /// 是否存在顶点,此函数目前未发现有啥用 + /// + /// 顶点 + /// 存在顶点返回 ,反之返回 + public bool ContainsVertex(IGraphVertex value) + { + return vertices.ContainsKey(value); + } + #endregion + + #region 获取邻接表和曲线 + /// + /// 获取顶点的邻接表 + /// + /// 顶点 + /// 邻接表 + public HashSet GetAdjacencyList(IGraphVertex vertex) + { + return vertices[vertex]; + } + + /// + /// 获取顶点的邻接边表 + /// + /// 顶点 + /// 邻接边表 + public HashSet GetAdjacencyEdge(IGraphVertex vertex) + { + return edges[vertex]; + } + + /// + /// 根据顶点表获取曲线集合 + /// + /// 顶点表 + /// 曲线表 + public List GetCurves(List graphVertices) + { + var curves = new List(); + for (int i = 0; i < graphVertices.Count - 1; i++) + { + var cur = graphVertices[i]; + var next = graphVertices[i + 1]; + var edge = GetEdge(cur, next); + if (edge is not null) + curves.Add(edge.TargetEdge); + } + var lastedge = GetEdge(graphVertices[^1], graphVertices[0]); + if (lastedge is not null) + curves.Add(lastedge.TargetEdge); + + return curves; + } + #endregion + + #region 克隆及接口实现 + /// + /// 克隆此图;目测是深克隆 + /// + public Graph Clone() + { + var newGraph = new Graph(); + + foreach (var vertex in edges.Values) + foreach (var item in vertex) + newGraph.AddEdge(item.TargetEdge); + + return newGraph; + } + + IGraph IGraph.Clone() + { + return Clone(); + } + + public IEnumerator GetEnumerator() + { + return VerticesAsEnumberable.GetEnumerator(); + } + + IEnumerator? IEnumerable.GetEnumerator() + { + return GetEnumerator() as IEnumerator; + } + + public IEnumerable VerticesAsEnumberable => + vertices.Select(x => x.Key); + #endregion + + #region 方法 + /// + /// 输出点的邻接表的可读字符串 + /// + /// + public string ToReadable() + { + int i = 1; + string output = string.Empty; + foreach (var node in vertices) + { + var adjacents = string.Empty; + + output = string.Format("{1}\r\n{0}-{2}: [", i, output, node.Key.Data.ToString()); + + foreach (var adjacentNode in node.Value) + adjacents = string.Format("{0}{1},", adjacents, adjacentNode.Data.ToString()); + + if (adjacents.Length > 0) + adjacents = adjacents.TrimEnd(new char[] { ',', ' ' }); + + output = string.Format("{0}{1}]", output, adjacents); + i++; + } + return output; + } + #endregion +} + + +/// +/// 邻接表图实现的顶点; +/// IEnumerable 枚举所有邻接点; +/// +public sealed class GraphVertex : IGraphVertex, IEquatable, IComparable, IComparable +{ + #region 属性 + public Point3d Data { get; set; } + public int Index { get; set; } + #endregion + + #region 构造 + /// + /// 邻接表图实现的顶点 + /// + /// 点 + /// 所在节点索引 + public GraphVertex(Point3d value, int index) + { + Data = value; + Index = index; + } + #endregion + + #region 重载运算符_比较 + public bool Equals(IGraphVertex other) + { + return Index == other.Index; + } + + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not IGraphVertex vertex) + return false; + else + return Equals(vertex); + } + + public override int GetHashCode() + { + return Index; + } + + public int CompareTo(IGraphVertex other) + { + if (Equals(other)) + return 0; + + if (Index < other.Index) + return -1; + else + return 1; + } + + int IComparable.CompareTo(IGraphVertex other) + { + return CompareTo(other); + } + + public int CompareTo(object obj) + { + if (obj is null) + return 1; + + try + { + var other = (GraphVertex)obj; + return CompareTo(other); + } + catch (Exception) + { + throw new ArgumentException("Object is not a IGraphVertex"); + } + } + + public static bool operator ==(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + + public static bool operator !=(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 无向图中边的定义 +/// +public sealed class GraphEdge : IEdge, IEquatable +{ + #region 属性 + public IGraphVertex TargetVertex { get; set; } + public Curve3d TargetEdge { get; set; } + #endregion + + #region 构造 + /// + /// 无向图中边的定义 + /// + /// 下一点 + /// 下一点之间的曲线 + public GraphEdge(IGraphVertex target, Curve3d edge) + { + TargetVertex = target; + TargetEdge = edge; + } + #endregion + + #region 重载运算符_比较 + public bool Equals(GraphEdge other) + { + if (other is null) + return false; + return TargetVertex == other.TargetVertex && + TargetEdge == other.TargetEdge; + } + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not GraphEdge personObj) + return false; + else + return Equals(personObj); + } + + public override int GetHashCode() + { + return (TargetVertex.GetHashCode(), TargetEdge.GetHashCode()).GetHashCode(); + } + public static bool operator ==(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + public static bool operator !=(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 深度优先搜索; +/// +public sealed class DepthFirst +{ + #region 公共方法 +/// +/// 存储所有的边 +/// +#if true + public List> Curve3ds { get; } = new(); +#else + public List> Curve3ds { get; } = new(); +#endif + private HashSet Curved { get; } = new(); + + + /// + /// 找出所有的路径 + /// + /// 图 + public void FindAll(IGraph graph) + { + var total = new HashSet(); + //var graphtmp = graph.Clone(); + foreach (var item in graph.VerticesAsEnumberable) + { + Dfs(graph, new LinkedHashSet { item },total); + total.Add(item); + } + } +#endregion + +#region 内部方法 + /// + /// 递归 DFS; + /// + /// 图 + /// 已经遍历的路径 +#if true + void Dfs(IGraph graph, LinkedHashSet visited, HashSet totalVisited) + { + var adjlist = graph.GetAdjacencyList(/*startNode*/ visited.First!.Value); // O(1) + foreach (var nextNode in adjlist) // O(n) + { + if (totalVisited.Contains(nextNode)) + { + continue; + } + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(1) + { + // 将下一点加入路径集合,并进行下一次递归 + var sub = new LinkedHashSet { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub,totalVisited); + } + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited.Last!.Value)) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var curstr = Gethashstring(visited); // O(n) + if (Isnew(curstr)) // O(1) + { + Curve3ds.Add(visited); + Curved.Add(curstr.Item1); + } + } + } + } + + + + +#else + + void Dfs(IGraph graph, List visited) + { + var startNode = visited[0]; + IGraphVertex nextNode; + List sub; + + var adjlist = graph.GetAdjacencyList(startNode).ToList(); // O(n) + for (int i = 0; i < adjlist.Count; i++) // O(n) + { + nextNode = adjlist[i]; + + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(n) + { + // 将下一点加入路径集合,并进行下一次递归 + sub = new List { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub); + } + + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited[^1])) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var cur = RotateToSmallest(visited); // O(n) + var inv = Invert(cur,cur[0]); // O(n) + + var curstr = Gethashstring(cur,inv); + //Env.Print(curstr); + if (Isnew(curstr)) + { + Curve3ds.Add(cur); + Curved.Add(curstr.Item1); + } + } + } + } +#endif + + + + + + + + + /// + /// 将列表旋转到最小的值为列表起点 + /// + /// + /// + static List RotateToSmallest(List lst) + { + var index = lst.IndexOf(lst.Min()); + return lst.Skip(index).Concat(lst.Take(index)).ToList(); + } + + /// + /// 将列表反向,并旋转到起点为最小值 + /// + /// + /// + static List Invert(List lst, IGraphVertex vertex) + { + var tmp = lst.ToList(); + tmp.Reverse(); + var index = tmp.IndexOf(vertex); + return tmp.Skip(index).Concat(lst.Take(index)).ToList(); + } + + static (string,string) Gethashstring(List pathone, List pathtwo) + { + var one = new string[pathone.Count]; + var two = new string[pathtwo.Count]; + for (int i = 0; i < pathone.Count; i++) + { + one[i] = pathone[i].Index.ToString(); + two[i] = pathtwo[i].Index.ToString(); + } + return (string.Join("-", one), string.Join("-", two)); + } + + static (string, string) Gethashstring(LinkedHashSet path) + { + var one = new string[path.Count]; + var two = new string[path.Count]; + path.For(path.MinNode!, (i, ver1, ver2) => { + one[i] = ver1.Index.ToString(); + two[i] = ver2.Index.ToString(); + }); + return (string.Join("-", one), string.Join("-", two)); + } + + + bool Isnew((string,string) path) + { + return !Curved.Contains(path.Item1) && !Curved.Contains(path.Item2); + } + + +#endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs b/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs new file mode 100644 index 0000000..73547b7 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs @@ -0,0 +1,98 @@ +namespace IFoxCAD.Cad; + +/// +/// 无向图 +/// +public interface IGraph +{ + /// + /// 顶点的数量 + /// + /// + int VerticesCount { get; } + + /// + /// 是否存在顶点 + /// + /// 顶点键 + /// + bool ContainsVertex(IGraphVertex key); + + /// + /// 顶点的迭代器 + /// + /// + IEnumerable VerticesAsEnumberable { get; } + + /// + /// 是否有边 + /// + /// 源顶点 + /// 目的顶点 + /// + bool HasEdge(IGraphVertex source, IGraphVertex destination); + /// + /// 图克隆函数 + /// + /// + IGraph Clone(); + /// + /// 获取边 + /// + /// + /// + /// + IEdge? GetEdge(IGraphVertex source, IGraphVertex dest); + /// + /// 邻接表 + /// + /// + /// + HashSet GetAdjacencyList(IGraphVertex vertex); + /// + /// 邻接边表 + /// + /// + /// + HashSet GetAdjacencyEdge(IGraphVertex vertex); + IGraphVertex? ReferenceVertex { get; } + + void RemoveVertex(Point3d pt); + void RemoveEdge(Curve3d curve); + +} + +/// +/// 无向图顶点 +/// +/// 顶点数据类型 +public interface IGraphVertex : IComparable +{ + /// + /// 顶点的键 + /// + /// + int Index { get; set; } + + /// + /// 顶点的数据 + /// + Point3d Data { get; } +} +/// +/// 无向图边 +/// +public interface IEdge +{ + /// + /// 边 + /// + Curve3d TargetEdge { get; } + /// + /// 目标顶点 + /// + IGraphVertex TargetVertex { get; } +} + + + diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs new file mode 100644 index 0000000..c5f7f11 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs @@ -0,0 +1,25 @@ +namespace IFoxCAD.Cad; + +/* + * 这个类存在的意义是为了不暴露Rect类字段 + * 同时利用了Rect类字段的快速 + * 提供到外面去再继承 + */ + +/// +/// 四叉树图元 +/// +public class QuadEntity : Rect +{ + /// + /// 四叉树图元 + /// + /// 包围盒 + public QuadEntity(Rect box) + { + _X = box._X; + _Y = box._Y; + _Top = box._Top; + _Right = box._Right; + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs new file mode 100644 index 0000000..0eb1bf8 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs @@ -0,0 +1,259 @@ +/* + * 四叉树维基百科 http://en.wikipedia.org/wiki/Quadtree + * 四叉树是一种分区空间的算法,更快找出内部或外部给定区域. + * 通过一个正交矩形边界进行中心点分裂四个正交矩形, + * 插入时候会一直分裂四个正交矩形, + * 当分裂四个节点都无法单独拥有 图元包围盒 就停止分裂,并且你属于这四个节点的父亲. + * (不包含就是面积少了,就这么一句话看代码看半天), + * 还可以通过限制树的深度实现加速. + * + * 第一版: https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=30535 + * + * 第二版: 找邻居 + * https://blog.csdn.net/dive_shallow/article/details/112438050 + * https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/ + * + * 1.根节点:控制根节点从而控制所有节点 + * 2.子节点:包含自身根节点,插入矩形的时候进行递归分裂自身,和实现查找. + * 3.接口:约束都要有正交矩形,否则无法调用"包含"方法 + * 4.选择模式:模仿cad的窗选和框选 + */ +namespace IFoxCAD.Cad; + +/// +/// 根节点控制器 +/// +/// 类型接口约束必须有正交矩形 +public class QuadTree where TEntity : QuadEntity +{ + #region 成员 + /// + /// 根节点 + /// + QuadTreeNode _rootNode; + + /// + /// 四叉树节点的数目 + /// + public int Count { get => _rootNode.CountSubTree; } + + /// + /// 点容器(红黑树) + /// + SortedSet _points; + #endregion + + #region 构造 + /// + /// 四叉树根节点控制器 + /// + /// 四叉树矩形范围 + public QuadTree(Rect rect) + { + _rootNode = new QuadTreeNode(rect, null, 0);//初始化根节点 + _points = new(); + } + #endregion + + #region 方法 + /// + /// 通过根节点插入数据项 + /// + /// + public void Insert(TEntity ent) + { + /* + * 图元点 是不分裂空间的,加入一个红黑树内部. + */ + if (ent.IsPoint) + { + _points.Add(ent); + return; + } + + while (!_rootNode.Contains(ent)) + { + /* + * 四叉树插入时候,如果超出根边界,就需要扩展 + * 扩展时候有一个要求,当前边界要作为扩展边界的一个象限,也就是反向分裂 + * + * 创建新根,计算原根在新根的位置, + * 替换指针:获取新分裂的节点的父节点,判断它哪个儿子是它, + * 替换之后可能仍然不包含图元边界,再循环计算. + */ + var sq_Left = _rootNode._X; + var sq_Botton = _rootNode._Y; + var sq_Right = _rootNode._Right; + var sq_Top = _rootNode._Top; + if (ent._Y >= _rootNode._Y)//上↑增殖 + { + if (ent._X >= _rootNode._X) + { + //右上↗增殖 + sq_Right += _rootNode.Width; + sq_Top += _rootNode.Height; + } + else + { + //左上↖增殖 + sq_Left -= _rootNode.Width; + sq_Top += _rootNode.Height; + } + } + else//在下↓ + { + if (ent._X >= _rootNode._X) + { + //右下↘增殖 + sq_Right += _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + else + { + //左下↙增殖 + sq_Left -= _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + } + //扩大2次方 + var rectSquare = new Rect(sq_Left, sq_Botton, sq_Right, sq_Top); + + //四叉树的旧根要作为四分之一插入 + //新根中计算原根 + //把 旧根节点 连接到 新根节点 上面,然后新根成为根 + var newRoot = new QuadTreeNode(rectSquare, null, 0); + var insert = newRoot.Insert(_rootNode); + if (insert is null) + throw new("四叉树:新根尺寸不对"); + if (!insert.Equals(_rootNode)) + throw new("四叉树:新旧节点大小不一致,无法连接"); + + var insPar = insert.Parent; + _rootNode.Parent = insPar; + if (insPar is null) + return; + + if (_rootNode.Equals(insPar.RightTopTree)) + insPar.RightTopTree = _rootNode; + else if (_rootNode.Equals(insPar.RightBottomTree)) + insPar.RightBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftBottomTree)) + insPar.LeftBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftTopTree)) + insPar.LeftTopTree = _rootNode; + else + throw new("四叉树:新节点不对,无法连接"); + + //其后的子节点层数全部增加层数, + //要加多少层取决于当前根边界属于新根边界的所在层 + var depth = insert.Depth; + if (depth == 0) + throw new("四叉树:插入节点是0,造成错误"); + _rootNode.ForEach(node => { + node.Depth += depth; + return false; + }); + + //交换根控制 + _rootNode = newRoot; + } + + _rootNode.Insert(ent); + } + + + /// + /// 查询四叉树,返回给定区域的数据项 + /// + /// 矩形选区查询 + /// + public List Query(Rect rect, QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith) + { + QuadTreeEvn.SelectMode = selectMode; + + var results = new List(); + //选择图元 + _rootNode.Query(rect, results); + //选择点 + var ptge = _points.GetEnumerator(); + switch (selectMode) + { + case QuadTreeSelectMode.IntersectsWith: + case QuadTreeSelectMode.Contains: + /* 由于红黑树的方法 _points.GetViewBetween() + * 过滤只能过滤X区间,Y区间还是要过滤, + * 那么我就只能用这样的方法加速了 + * + * 而更好的方式是不用红黑树,去加入一个点云数据来进行,可谓是编程无极限.... + */ + while (ptge.MoveNext()) + { + var ptEnt = ptge.Current; + if (rect._X <= ptEnt._X && ptEnt._X <= rect._Right) + { + if (rect._Y <= ptEnt._Y && ptEnt._Y <= rect.Top) + results.Add(ptEnt); + } + else if (ptEnt._X > rect._Right) + break;//超过后面范围就break,因为红黑树已经排序 + } + break; + default: + throw new("四叉树:" + nameof(selectMode)); + } + return results; + } + + /// + /// 删除子节点 + /// + /// 根据范围删除 + public void Remove(Rect rect) + { + _rootNode.Remove(rect); + } + + /// + /// 删除子节点 + /// + /// 根据图元删除 + public void Remove(TEntity ent) + { + _rootNode.Remove(ent); + } + + /// + /// 找到附近节点图元 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect rect, QuadTreeFindMode findMode) + { + return _rootNode.FindNeibor(rect, findMode); + } + + /// + /// 找到附近图元 + /// + /// + /// + public TEntity? FindNearEntity(Rect rect) + { + return _rootNode.FindNearEntity(rect); + } + + /// + /// 执行四叉树中特定的行为 + /// + /// + public void ForEach(QTAction action) + { + _rootNode.ForEach(action); + } + + /// + /// 委托:四叉树节点上执行一个操作 + /// + /// + public delegate bool QTAction(QuadTreeNode obj); + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs new file mode 100644 index 0000000..16d518b --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs @@ -0,0 +1,26 @@ +#pragma warning disable CA2211 // 非常量字段应当不可见 +namespace IFoxCAD.Cad; + +public class QuadTreeEvn +{ + /// + /// 最小的节点有一个面积(一定要大于0) + /// + public static double MinArea = 1e-6; + + /// + /// 选择模式 + /// + public static QuadTreeSelectMode SelectMode; + + /// + /// 最大深度 + /// + public static int QuadTreeMaximumDepth = 2046; + + /// + /// 节点内容超过就分裂 + /// + public static int QuadTreeContentsCountSplit = 20; +} +#pragma warning restore CA2211 // 非常量字段应当不可见 \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs new file mode 100644 index 0000000..86cb5b4 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs @@ -0,0 +1,818 @@ +namespace IFoxCAD.Cad; + +/// +/// 子节点 +/// +/// +public class QuadTreeNode + : Rect + where TEntity : QuadEntity +{ + #region 成员 + /// + /// 子节点:第一象限:右上↗ + /// + public QuadTreeNode? RightTopTree; + /// + /// 子节点:第二象限:左上↖ + /// + public QuadTreeNode? LeftTopTree; + /// + /// 子节点:第三象限:左下↙ + /// + public QuadTreeNode? LeftBottomTree; + /// + /// 子节点:第四象限:右下↘ + /// + public QuadTreeNode? RightBottomTree; + /// + /// 所有子节点 + /// + QuadTreeNode[] Nodes + { + get + { + return new QuadTreeNode[] + { + RightTopTree!, + LeftTopTree!, + LeftBottomTree!, + RightBottomTree!, + }; + } + } + /// + /// 所有子节点是空的 + /// + bool NodesIsEmpty => RightTopTree is null && LeftTopTree is null && LeftBottomTree is null && RightBottomTree is null; + + /// + /// 父节点 + /// + public QuadTreeNode? Parent; + /// + /// 节点的在四叉树的深度 + /// + public int Depth; + + // 注意,内容没有限制:这不是 impement 四叉树的标准方法 + /// (节点图元是交叉线压着的,并不是矩形范围内全部,因为这是四叉树的特性决定) + /// + /// 本节点:内容 + /// + public List Contents; + + /// + /// 本节点和旗下所有子节点:内容群 + /// + public void ContentsSubTree(List results) + { + if (Contents is null) + return; + results.AddRange(Contents); + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.ContentsSubTree(results); + } + + /// + /// 本节点和旗下所有子节点:内容群数量 + /// + public int CountSubTree + { + get + { + if (Contents is null) + return 0; + int count = Contents.Count; + + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + count += node.CountSubTree; + } + return count; + } + } + #endregion + + #region 构造 + /// + /// 四叉树节点 + /// + /// 当前节点边界 + /// 父节点 + /// 节点深度 + public QuadTreeNode(Rect box, QuadTreeNode? parent, int depth) + { + _X = box._X; + _Y = box._Y; + _Right = box._Right; + _Top = box._Top; + + Parent = parent; + Depth = depth; + Contents = new(); + } + #endregion + + #region 增 + /// + /// 将原有节点插入用 + /// + /// + internal QuadTreeNode? Insert(Rect rect) + { + if (!Contains(rect)) + return null; + + //四叉树分裂,将当前节点分为四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + //当前节点边界 包含 图元包围盒 就插入 + //退出递归:4个节点都不完全包含 + //4个节点的上层 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + if (node.Equals(rect)) + { + rect = node; + return node.Insert(rect); + } + } + return this; + } + + /// + /// 将数据项递归插入四叉树 + /// + /// + public QuadTreeNode? Insert(TEntity ent) + { + if (!Contains(ent)) + { + //Debug.WriteLine("不在四叉树边界范围"); + //Trace.WriteLine("不在四叉树边界范围"); + return null; + } + + // if (ent.IsPoint) + // { + // //找到最后一层包含它的节点,然后加入它 + // //因此是跳过分裂矩形的,以免造成无限递归 + // var minNode = GetMinNode(ent); + // minNode.Contents.Add(ent); + // return minNode; + // } + +#if true2 + //方案二: + //内容数超过才分裂,防止树深度过高,但是多选过滤时候慢一点 + if (Contents.Count > QuadTreeEvn.QuadTreeContentsCountSplit) + { + //分裂出四个子节点 + if (_nodesIsEmpty) + { + CreateChildren(); + //分裂之后将当前层的内容扔到四个子节点, + //如果被压着,那么就不会扔到下面 + for (int i = Contents.Count - 1; i >= 0; i--) + { + var minNode = GetMinNode(Contents[i].Box); + minNode.Contents.Add(Contents[i]); + Contents.RemoveAt(i); + } + } + else + { + //没有分裂的话,就递归 + //退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = _Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } + } + } +#else + //方案一:分裂到最细节点 + + //分裂出四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + //4个子节点开始递归 + //退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } +#endif + + //为什么要用容器? + //相同包围盒或者四叉树分割线压着多个. + this.Contents.Add(ent); + return this; + } + + /// + /// 创建子节点 + /// + void CreateChildren() + { + // 最小面积控制节点深度,但是这样可能导致树分成高,引起爆栈 + if (Depth > QuadTreeEvn.QuadTreeMaximumDepth) + return; + var recs = RectSplit(this); + var de = Depth + 1; + RightTopTree = new QuadTreeNode(recs[0], this, de); + LeftTopTree = new QuadTreeNode(recs[1], this, de); + LeftBottomTree = new QuadTreeNode(recs[2], this, de); + RightBottomTree = new QuadTreeNode(recs[3], this, de); + } + + /// + /// 矩形分裂为四个 + /// + /// + /// + Rect[] RectSplit(Rect box) + { + var halfWidth = box.Width / 2.0; + var halfHeight = box.Height / 2.0; + + var upperRight = new Rect(box._X + halfWidth, box._Y + halfHeight, box._Right, box._Top); + var upperLeft = new Rect(box._X, box._Y + halfHeight, box._Right - halfWidth, box._Top); + var lowerleft = new Rect(box._X, box._Y, box._Right - halfWidth, box._Top - halfHeight);//基础 + var lowerRight = new Rect(box._X + halfWidth, box._Y, box._Right, box._Top - halfHeight); + + //依照象限顺序输出 + return new Rect[] { upperRight, upperLeft, lowerleft, lowerRight }; + } + #endregion + + #region 删 + /// + /// 删除图元 + /// + /// 根据图元删除 + public bool Remove(TEntity easeEnt) + { + //通过图元id删除无疑是非常低效的, + //1.相当于在所有的容器查找它,但是移除只会移除一次, + // 因此必须要求图元只会加入一次,才能中断检索剩余分支. + //2.这个代价还是太高,因此我们还是要默认条件,图元载入一次之后,不再改动. + //3.不再改动也不太合理,因为cad图元还是可以修改的 + + //1.处理内容 + if (Contents.Remove(easeEnt)) + { + if (CountSubTree == 0) + this.Clear(this); + return true; + } + + //2.递归子节点移除 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.Remove(easeEnt)) //递归进入子节点删除内容 + return true; //删除成功就中断其他节点的搜索 + } + return false; + } + + /// + /// 递归进入最下层节点,然后开始清理 + /// + /// + void Clear(QuadTreeNode node) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.Clear(nodes[i]); + + node.Contents.Clear(); + //node.Contents = null;//重复加入时候会出错 + node.RightTopTree = null; + node.LeftTopTree = null; + node.LeftBottomTree = null; + node.RightBottomTree = null; + node.Parent = null; + //node.Box = zoreRect; + } + + /// + /// 删除子节点内容 + /// + /// 根据范围删除 + public void Remove(Rect queryArea) + { + //本节点内容移除 + if (Contents is not null && Contents.Count > 0)//从最上层的根节点开始进入 + { + for (int i = Contents.Count - 1; i >= 0; i--) + { + var ent = Contents[i]; + //移除之后,如果容器是0,那么这里不能直接 Contents=null, + //因为此节点下面可能还有节点, + //需要判断了其后数量0才可以清理. + //否则其后还有内容,那么此节点就是仍然可以用的. + if (queryArea.Contains(ent)) + Contents.Remove(ent); + } + } + + //同插入一样 + //跳到指定节点再搜索这个节点下面的图元 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.NodesIsEmpty) + continue; + + //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Remove(queryArea); + break; + } + //查询区域 完全包含 此节点边界,提取此节点全部内容 + //跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.Clear(node); + continue; + } + //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + //1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Remove(queryArea); + } + + //本节点内容移除之后,旗下还有内容的话, + //会跳过此处,再进入子节点进行递归,直到最后一个节点 + if (CountSubTree == 0) + Clear(this); + } + #endregion + + #region 查 + /// + /// 查询范围内的实体 + /// + /// 查询矩形 + /// + public void Query(Rect queryArea, List results) + { + GetCurrentContents(queryArea, results); + + //遍历子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + //子节点的4个子节点都是空的, + //那么表示元素会在子节点这一层啊... + if (node.NodesIsEmpty) + continue; + + //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Query(queryArea, results); + break; + } + //查询区域 完全包含 此节点边界,提取此节点全部内容 + //跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.ContentsSubTree(results); + continue; + } + //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + //1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Query(queryArea, results); + } + } + + /// + /// 获取本节点内容 + /// + /// + /// + void GetCurrentContents(Rect queryArea, List results) + { + //遍历当前节点内容,加入方式取决于碰撞模式 + if (QuadTreeEvn.SelectMode == QuadTreeSelectMode.IntersectsWith) + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.IntersectsWith(ent)) + results.Add(ent); + } + } + else + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.Contains(ent)) + results.Add(ent); + } + } + } + + /// + /// 找临近图元 + /// + /// 查找矩形 + /// + public TEntity? FindNearEntity(Rect queryArea) + { + TEntity? resultEntity = default; + //1.找到 查找矩形 所在的节点,利用此节点的矩形. + var queryNode = GetMinNode(queryArea); + var queryAreaCenter = queryArea.CenterPoint; + + //2.从根开始搜索 + // 如果搜索父亲的父亲的...内容群,它不是距离最近的,只是节点(亲属关系)最近 + // 储存找过的<图元,距离> + var entDic = new Dictionary(); + + var old = QuadTreeEvn.SelectMode; + QuadTreeEvn.SelectMode = QuadTreeSelectMode.IntersectsWith; + while (true) + { + //循环找父节点大小 + var hw = queryNode.Width / 2.0; + var hh = queryNode.Height / 2.0; + //3.利用选区中心扩展一个节点边界大小的矩形.从而选择图元 + // 再判断图元的与目标的距离,找到最小距离,即为最近 + var minPt = new Point2d(queryAreaCenter.X - hw, queryAreaCenter.Y - hh); + var maxPt = new Point2d(queryAreaCenter.X + hw, queryAreaCenter.Y + hh); + var ents = new List(); + Query(new Rect(minPt, maxPt), ents); + for (int i = 0; i < ents.Count; i++) + { + var ent = ents[i]; + if (entDic.ContainsKey(ent)) + continue; + var dis = ent.CenterPoint.GetDistanceTo(queryAreaCenter); + if (dis > 1e-6)//剔除本身 + entDic.Add(ent, dis); + } + if (entDic.Count > 0) + { + resultEntity = entDic.OrderBy(a => a.Value).First().Key; + break; + } + if (queryNode.Parent is null)//最顶层就退出 + break; + queryNode = queryNode.Parent;//利用父节点矩形进行变大选区 + } + QuadTreeEvn.SelectMode = old; + return resultEntity; + } + + /// + /// 找临近节点的图元 + /// + /// 查找矩形 + /// 查找什么方向 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect queryArea, QuadTreeFindMode findMode) + { + TEntity? resultEntity = default; + //1.找到 查找矩形 所在的节点,利用此节点的矩形. + //2.利用节点矩形是分裂的特点,边和边必然贴合. + //3.找到方向 findMode 拥有的节点,然后查找节点的内容 + var queryNode = GetMinNode(queryArea); + + bool whileFlag = true; + //同一个节点可能包含邻居,因为四叉树的加入是图元压线, + //那么就在这里搜就得了,用中心点决定空间位置 + //但是本空间的图元可能都比它矮,无法满足条件 + if (queryNode.CountSubTree > 1) + { + resultEntity = GetNearestNeighbor(queryNode, findMode, queryArea); + if (resultEntity is null || resultEntity.CenterPoint == queryArea.CenterPoint) + whileFlag = true; + else + whileFlag = false; + } + + while (whileFlag) + { + //同一个父节点是临近的,优先获取 兄弟节点 的内容. + //循环了第二次是北方兄弟的节点, + //但是这不是一个找到临近图元的方法, + //因为临近的可能是父亲的父亲的父亲...另一个函数 FindNearEntity 写 + //本方案也仅仅作为找北方节点 + var parent = queryNode.Parent; + if (parent is not null) + { + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //下格才获取上格,否则导致做了无用功,上格就直接获取邻居了 + if (parent.LeftBottomTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Bottom: + { + if (parent.LeftTopTree == queryNode || parent.RightTopTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Left: + { + if (parent.RightTopTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Right: + { + if (parent.LeftTopTree == queryNode || parent.LeftBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + } + } + if (resultEntity is not null) + break; + + //通过 所在节点 找 邻居节点, + //拿到 邻居节点 下面的所有内容(图元) + //内容可能是空的,再从邻居那往北找...如果找到了四叉树最外层,仍然没有内容,退出循环 + var neiborNode = FindNeiborNode(queryNode, findMode); + if (neiborNode is null) + continue; + if (neiborNode.CountSubTree > 0) + { + resultEntity = GetNearestNeighbor(neiborNode, findMode, queryArea); + break; + } + if (neiborNode.Parent is null)//如果找到了四叉树最外层,仍然没有内容,退出循环 + break; + queryNode = neiborNode; + } + + return resultEntity; + } + + /// + /// 查找节点的(本内容和子内容)与(查找面积)矩形中点对比,找到最近一个内容 + /// + /// 查找面积 + /// 查找方向 + /// 查找节点 + /// + TEntity? GetNearestNeighbor(QuadTreeNode queryNode, QuadTreeFindMode findMode, Rect queryArea) + { + TEntity? results = default; + + var lst = new List(); + var qcent = queryArea.CenterPoint; + + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //取出Y比queryArea的还大的一个,是最近的那个 + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.Y > qy) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Bottom: + { + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.Y < qy) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Left: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.X > qx) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.X).ToList(); + } + break; + case QuadTreeFindMode.Right: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.X < qx) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.X).ToList(); + } + break; + } + + if (lst.Count > 0) + return lst[0];//可能就是本体重叠 + return results; + } + + /// + /// 找包含它的最小分支 + /// + /// 查询的矩形 + /// 节点 + QuadTreeNode GetMinNode(Rect queryArea) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + //边界包含查询面积,那么再递归查询, + //直到最后四个都不包含,那么上一个就是图元所在节点 + if (node.Contains(queryArea)) + return node.GetMinNode(queryArea);//中断后面的范围,才可以返回正确的 + } + return this; + } + + /// + /// 四叉树找邻居节点(相同或更大) + /// + /// 源节点 + /// 方向 + /// + QuadTreeNode? FindNeiborNode(QuadTreeNode tar, QuadTreeFindMode findMode) + { + var parent = tar.Parent; + if (parent is null) + return null; + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //判断当前节点在父节点的位置,如果是在 下格 就取对应的 上格 + if (tar == parent.LeftBottomTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.RightTopTree; + //否则就是上格 + //找父节点的北方邻居..也就是在爷节点上面找 + //递归,此时必然是 下格,就必然返回 上格,然后退出递归 + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Top); + //父节点的北方邻居 无 子节点 + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor;//返回父节点的北方邻居,比较大 + //父节点的北方邻居 有 子节点,剩下条件就只有这两 + + // 如果直接返回,那么是(相同或更大), + // 而找邻近图元需要的是这个(相同或更大)下面的图元,在外面对这个格子内图元用坐标分析就好了 + if (tar == parent.LeftTopTree) + return parentNeibor.LeftBottomTree; + return parentNeibor.RightBottomTree; + } + case QuadTreeFindMode.Bottom: + { + if (tar == parent.LeftTopTree) + return parent.LeftBottomTree; + if (tar == parent.RightTopTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Bottom); + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor; + if (tar == parent.LeftBottomTree) + return parentNeibor.LeftTopTree; + return parentNeibor.RightTopTree; + } + case QuadTreeFindMode.Right: + { + if (tar == parent.LeftTopTree) + return parent.RightTopTree; + if (tar == parent.LeftBottomTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Right); + if (tar == parent.RightTopTree) + return parentNeibor?.LeftTopTree; + return parentNeibor?.LeftBottomTree; + } + case QuadTreeFindMode.Left: + { + if (tar == parent.RightTopTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.LeftBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Left); + if (tar == parent.LeftTopTree) + return parentNeibor?.RightTopTree; + return parentNeibor?.RightBottomTree; + } + } + return null; + } + #endregion + + #region 改 + /// + /// 所有的点归类到最小包围它的空间 + /// + //public void PointsToMinNode() + //{ + // ForEach(node => + // { + // for (int i = 0; i < node.Contents.Count; i++) + // { + // var ent = node.Contents[i]; + // if (ent.IsPoint) + // { + // //如果最小包含!=当前,就是没有放在最适合的位置 + // var queryNode = GetMinNode(ent); + // if (queryNode != node) + // { + // node.Remove(ent); + // queryNode.Contents.Add(ent); + // } + // } + // } + // return false; + // }); + //} + #endregion + + #region 方法 + /// + /// 递归全部节点(提供给根用的,所以是全部) + /// + /// QTAction + public bool ForEach(QuadTree.QTAction action) + { + //执行本节点 + if (action(this)) + return true; + + //递归执行本节点的子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.ForEach(action)) + break; + } + return false; + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs new file mode 100644 index 0000000..d180236 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs @@ -0,0 +1,21 @@ +namespace IFoxCAD.Cad; + +/// +/// 四叉树选择模式 +/// +public enum QuadTreeSelectMode +{ + IntersectsWith, //碰撞到就选中 + Contains, //全包含才选中 +} + +/// +/// 四叉树查找方向 +/// +public enum QuadTreeFindMode +{ + Top = 1, //上 + Bottom = 2, //下 + Left = 4, //左 + Right = 8, //右 +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs new file mode 100644 index 0000000..c533ddb --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs @@ -0,0 +1,605 @@ +using System.Diagnostics; + +namespace IFoxCAD.Cad; + +/// +/// Linq Distinct 消重比较两点在容差范围内就去除 +/// +public class TolerancePoint2d : IEqualityComparer +{ + readonly double _tolerance; + public TolerancePoint2d(double tolerance = 1e-6) + { + _tolerance = tolerance; + } + + public bool Equals(Point2d a, Point2d b)//Point3d是struct不会为null + { + /*默认规则是==是0容差,Eq是有容差*/ + // 方形限定 + // 在 0~1e-6 范围实现 圆形限定 则计算部分在浮点数6位后,没有啥意义 + // 在 0~1e-6 范围实现 从时间和CPU消耗来说,圆形限定 都没有 方形限定 的好 + if (_tolerance <= 1e-6) + return Math.Abs(a.X - b.X) <= _tolerance && Math.Abs(a.Y - b.Y) <= _tolerance; + + // 圆形限定 + // DistanceTo 分别对XYZ进行了一次乘法,也是总数3次乘法,然后求了一次平方根 + // (X86.CPU.FSQRT指令用的牛顿迭代法/软件层面可以使用快速平方根....我还以为CPU会采取快速平方根这样的取表操作) + return a.IsEqualTo(b, new Tolerance(_tolerance, _tolerance)); + } + + public int GetHashCode(Point2d obj) + { + //结构体直接返回 obj.GetHashCode(); Point3d ToleranceDistinct3d + //因为结构体是用可值叠加来判断?或者因为结构体兼备了一些享元模式的状态? + //而类是构造的指针,所以取哈希值要改成x+y+z..s给Equals判断用,+是会溢出,所以用^ + return (int)obj.X ^ (int)obj.Y;// ^ (int)obj.Z; + } +} + + +[Serializable] +[StructLayout(LayoutKind.Sequential)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +[DebuggerTypeProxy(typeof(Rect))] +public class Rect : IEquatable, IComparable +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString("f4"); + +#pragma warning disable CA2211 // 非常量字段应当不可见 + public static TolerancePoint2d RectTolerance = new(1e-6); + public static Tolerance CadTolerance = new(1e-6, 1e-6); +#pragma warning restore CA2211 // 非常量字段应当不可见 + + #region 字段 + //这里的成员不要用{get}封装成属性,否则会导致跳转了一次函数, + //10w图元将会从187毫秒变成400毫秒 + //不用 protected 否则子类传入Rect对象进来无法用 + internal double _X; + internal double _Y; + internal double _Right; + internal double _Top; + #endregion + + #region 成员 + public double X => _X; + public double Y => _Y; + public double Left => _X; + public double Bottom => _Y; + public double Right => _Right; + public double Top => _Top; + + public double Width => _Right - _X; + public double Height => _Top - _Y; + public double Area + { + get + { + var ar = (_Right - _X) * (_Top - _Y); + return ar < 1e-10 ? 0 : ar; + } + } + + public Point2d MinPoint => LeftLower; + public Point2d MaxPoint => RightUpper; + public Point2d CenterPoint => Midst; + + /// + /// 左下Min + /// + public Point2d LeftLower => new(_X, _Y); + + /// + /// 左中 + /// + public Point2d LeftMidst => new(_X, Midst.Y); + + /// + /// 左上 + /// + public Point2d LeftUpper => new(_X, _Top); + + /// + /// 右上Max + /// + public Point2d RightUpper => new(_Right, _Top); + + /// + /// 右中 + /// + public Point2d RightMidst => new(_Right, Midst.Y); + + /// + /// 右下 + /// + public Point2d RightBottom => new(_Right, _Y); + + /// + /// 中间 + /// + public Point2d Midst => new(((_Right - _X) * 0.5) + _X, ((_Top - _Y) * 0.5) + _Y); + + /// + /// 中上 + /// + public Point2d MidstUpper => new(Midst.X, _Top); + + /// + /// 中下 + /// + public Point2d MidstBottom => new(Midst.X, _Y); + + /// + /// 是一个点 + /// 水平或垂直直线包围盒是面积是0,所以面积是0不一定是点 + /// + public bool IsPoint => Math.Abs(_X - _Right) < 1e-10 && Math.Abs(_Y - _Top) < 1e-10; + #endregion + + #region 构造 + public Rect() { } + + /// + /// 矩形类 + /// + /// 左 + /// 下 + /// 右 + /// 上 + public Rect(double left, double bottom, double right, double top) + { + _X = left; + _Y = bottom; + _Right = right; + _Top = top; + } + + /// + /// 构造矩形类 + /// + /// + /// + /// 是否检查大小 + public Rect(Point2d p1, Point2d p3, bool check = false) + { + if (check) + { + _X = Math.Min(p1.X, p3.X); + _Y = Math.Min(p1.Y, p3.Y); + _Right = Math.Max(p1.X, p3.X); + _Top = Math.Max(p1.Y, p3.Y); + } + else + { + _X = p1.X; + _Y = p1.Y; + _Right = p3.X; + _Top = p3.Y; + } + } + #endregion + + #region 重载运算符_比较 + public override bool Equals(object? obj) + { + return this.Equals(obj as Rect); + } + public bool Equals(Rect? b) + { + return this.Equals(b, 1e-6);/*默认规则是==是0容差,Eq是有容差*/ + } + public static bool operator !=(Rect? a, Rect? b) + { + return !(a == b); + } + public static bool operator ==(Rect? a, Rect? b) + { + //此处地方不允许使用==null,因为此处是定义 + if (b is null) + return a is null; + else if (a is null) + return false; + if (ReferenceEquals(a, b))//同一对象 + return true; + + return a.Equals(b, 0); + } + + /// + /// 比较核心 + /// + public bool Equals(Rect? b, double tolerance = 1e-6) + { + if (b is null) + return false; + if (ReferenceEquals(this, b)) //同一对象 + return true; + + return Math.Abs(_X - b._X) < tolerance && + Math.Abs(_Right - b._Right) < tolerance && + Math.Abs(_Top - b._Top) < tolerance && + Math.Abs(_Y - b._Y) < tolerance; + } + + public override int GetHashCode() + { + return (((int)_X ^ (int)_Y).GetHashCode() ^ (int)_Right).GetHashCode() ^ (int)_Top; + } + #endregion + + #region 包含 + public bool Contains(Point2d Point2d) + { + return Contains(Point2d.X, Point2d.Y); + } + public bool Contains(double x, double y) + { + return _X <= x && x <= _Right && + _Y <= y && y <= _Top; + } + + /// + /// 四个点都在内部就是包含 + /// + /// + /// + public bool Contains(Rect rect) + { + return _X <= rect._X && rect._Right <= _Right && + _Y <= rect._Y && rect._Top <= _Top; + } + + /// + /// 一个点在内部就是碰撞 + /// + /// + /// + public bool IntersectsWith(Rect rect) + { + return rect._X <= _Right && _X <= rect._Right && + rect._Top >= _Y && rect._Y <= _Top; + } + #endregion + + #region 方法 + /// + /// 获取共点 + /// + /// + public Point2d[] GetCommonPoint(Rect other) + { + return ToPoints().Intersect(other.ToPoints(), RectTolerance).ToArray(); + } + + public Point2d[] ToPoints() + { + Point2d a = MinPoint;//min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;//max + Point2d d = new(_X, _Top); + return new Point2d[] { a, b, c, d }; + } + + public (Point2d boxMin, Point2d boxRigthDown, Point2d boxMax, Point2d boxLeftUp) ToPoints4() + { + Point2d a = MinPoint;//min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;//max + Point2d d = new(_X, _Top); + return (a, b, c, d); + } + + /// + /// 四周膨胀 + /// + /// + public Rect Expand(double d) + { + return new Rect(_X - d, _Y - d, _Right + d, _Top + d); + } + + /// + /// 是否矩形(带角度) + /// + /// + /// + public static bool IsRectAngle(List? ptList, double tolerance = 1e-8) + { + if (ptList == null) + throw new ArgumentNullException(nameof(ptList)); + + var pts = ptList.ToList(); + /* 消重,不这里设置,否则这不是一个正确的单元测试 + * //var ptList = pts.Distinct().ToList(); + * var ptList = pts.DistinctExBy((a, b) => a.DistanceTo(b) < 1e-6).ToList(); + */ + if (ptList.Count == 5) + { + //首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + //最快的方案 + //点乘求值法:(为了处理 正梯形/平行四边形 需要三次) + //这里的容差要在1e-8内,因为点乘的三次浮点数乘法会令精度变低 + var dot = DotProductValue(pts[0], pts[1], pts[3]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[1], pts[2], pts[0]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[2], pts[3], pts[1]); + return Math.Abs(dot) < tolerance; + } + } + return false; + } + + /// + /// 点积,求值 + /// 1.是两个向量的长度与它们夹角余弦的积 + /// 2.求四个点是否矩形使用 + /// + /// 原点 + /// 点 + /// 点 + /// 0方向相同,夹角0~90度;=0相互垂直;<0方向相反,夹角90~180度]]> + static double DotProductValue(Point2d o, Point2d a, Point2d b) + { + var oa = o.GetVectorTo(a); + var ob = o.GetVectorTo(b); + return (oa.X * ob.X) + (oa.Y * ob.Y); + } + + /// + /// 是否轴向矩形(无角度) + /// + public static bool IsRect(List? ptList, double tolerance = 1e-10) + { + if (ptList == null) + throw new ArgumentNullException(nameof(ptList)); + + var pts = ptList.ToList(); + if (ptList.Count == 5) + { + //首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + return Math.Abs(pts[0].X - pts[3].X) < tolerance && + Math.Abs(pts[0].Y - pts[1].Y) < tolerance && + Math.Abs(pts[1].X - pts[2].X) < tolerance && + Math.Abs(pts[2].Y - pts[3].Y) < tolerance; + } + + /// + /// 获取点集的包围盒的最小点和最大点(无角度) + /// + /// + public static (Point2d boxMin, Point2d boxMax) GetMinMax(IEnumerable pts) + { + var xMin = double.MaxValue; + var xMax = double.MinValue; + var yMin = double.MaxValue; + var yMax = double.MinValue; + //var zMin = double.MaxValue; + //var zMax = double.MinValue; + + pts.ForEach(p => { + xMin = Math.Min(p.X, xMin); + xMax = Math.Max(p.X, xMax); + yMin = Math.Min(p.Y, yMin); + yMax = Math.Max(p.Y, yMax); + //zMin = Math.Min(p.Z, zMin); + //zMax = Math.Max(p.Z, zMax); + }); + return (new Point2d(xMin, yMin), new Point2d(xMax, yMax)); + } + + /// + /// 矩形点序逆时针排列,将min点[0],max点是[3](带角度) + /// + /// + /// + public static bool RectAnglePointOrder(List? pts) + { + if (pts == null) + throw new ArgumentNullException(nameof(pts)); + + if (!Rect.IsRectAngle(pts)) + return false; + + //获取min和max点(非包围盒) + pts = pts.OrderBy(a => a.X).ThenBy(a => a.Y).ToList(); + var minPt = pts.First(); + var maxPt = pts.Last(); + var link = new LoopList(); + link.AddRange(pts); + + pts.Clear(); + //排序这四个点,左下/右下/右上/左上 + var node = link.Find(minPt); + for (int i = 0; i < 4; i++) + { + pts.Add(node!.Value); + node = node.Next; + } + //保证是逆时针 + var isAcw = CrossAclockwise(pts[0], pts[1], pts[2]); + if (!isAcw) + (pts[3], pts[1]) = (pts[1], pts[3]); + return true; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 传参是向量,表示原点是0,0 + /// 传参是向量,表示原点是0,0 + /// 其模为a与b构成的平行四边形面积 + static double Cross(Vector2d a, Vector2d b) + { + return a.X * b.Y - a.Y * b.X; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 原点 + /// oa向量 + /// ob向量,此为判断点 + /// 返回值有正负,表示绕原点四象限的位置变换,也就是有向面积 + static double Cross(Point2d o, Point2d a, Point2d b) + { + return Cross(o.GetVectorTo(a), o.GetVectorTo(b)); + } + + /// + /// 叉积,逆时针方向为真 + /// + /// 直线点1 + /// 直线点2 + /// 判断点 + /// b点在oa的逆时针 + static bool CrossAclockwise(Point2d o, Point2d a, Point2d b) + { + return Cross(o, a, b) > -1e-6;//浮点数容差考虑 + } + +#if !WinForm + public Entity ToPolyLine() + { + var bv = new List(); + var pts = ToPoints(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((i, vertex) => { + pl.AddVertexAt(i, vertex, 0, 0, 0); + }); + return pl; + } +#endif + + /// + /// 列扫碰撞检测(碰撞算法) + /// 比四叉树还快哦~ + /// + /// + /// 继承Rect的集合 + /// 先处理集合每一个成员;返回true就跳过后续委托 + /// 碰撞,返回两个碰撞的成员;返回true就跳过后续委托 + /// 后处理集合每一个成员 + public static void XCollision(List box, + Func firstProcessing, + Func collisionProcessing, + Action lastProcessing) where T : Rect + { + //先排序X:不需要Y排序,因为Y的上下浮动不共X .ThenBy(a => a.Box.Y) + //因为先排序就可以有序遍历x区间,超过就break,达到更快 + box = box.OrderBy(a => a._X).ToList(); + + //遍历所有图元 + for (int i = 0; i < box.Count; i++) + { + var oneRect = box[i]; + if (firstProcessing(oneRect)) + continue; + + bool actionlast = true; + + //搜索范围要在 one 的头尾中间的部分 + for (int j = i + 1; j < box.Count; j++) + { + var twoRect = box[j]; + //x碰撞:矩形2的Left 在 矩形1[Left-Right]闭区间;穿过的话,也必然有自己的Left因此不需要处理 + if (oneRect._X <= twoRect._X && twoRect._X <= oneRect._Right) + { + //y碰撞,那就是真的碰撞了 + if ((oneRect._Top >= twoRect._Top && twoRect._Top >= oneRect._Y) /*包容上边*/ + || (oneRect._Top >= twoRect._Y && twoRect._Y >= oneRect._Y) /*包容下边*/ + || (twoRect._Top >= oneRect._Top && oneRect._Y >= twoRect._Y)) /*穿过*/ + { + if (collisionProcessing(oneRect, twoRect)) + actionlast = false; + } + //这里想中断y高过它的无意义比较, + //但是必须排序Y,而排序Y必须同X,而这里不是同X(而是同X区间),所以不能中断 + //而做到X区间排序,就必须创造一个集合,再排序这个集合, + //会导致每个图元都拥有一次X区间集合,开销更巨大(因此放弃). + } + else + break;//因为已经排序了,后续的必然超过 x碰撞区间 + } + + if (actionlast) + lastProcessing(oneRect); + } + } + + #endregion + + #region 转换类型 +#if !WinForm + // 隐式转换(相当于是重载赋值运算符) + //public static implicit operator Rect(System.Windows.Rect rect) + //{ + // return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + //} + public static implicit operator Rect(System.Drawing.RectangleF rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } + public static implicit operator Rect(System.Drawing.Rectangle rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } +#endif + + #region ToString + public sealed override string ToString() + { + return ToString(null, null); + } + public string ToString(IFormatProvider? provider) + { + return ToString(null, provider); + } + public string ToString(string? format = null, IFormatProvider? formatProvider = null) + { + return $"({_X.ToString(format, formatProvider)},{_Y.ToString(format, formatProvider)})," + + $"({_Right.ToString(format, formatProvider)},{_Top.ToString(format, formatProvider)})"; + + // return $"X={_X.ToString(format, formatProvider)}," + + // $"Y={_Y.ToString(format, formatProvider)}," + + // $"Right={_Right.ToString(format, formatProvider)}," + + // $"Top={_Top.ToString(format, formatProvider)}"; + } + + /*为了红黑树,加入这个*/ + public int CompareTo(Rect rect) + { + if (rect == null) + return -1; + if (_X < rect._X) + return -1; + else if (_X > rect._X) + return 1; + else if (_Y < rect._Y)/*x是一样的*/ + return -1; + else if (_Y > rect._Y) + return 1; + return 0;/*全部一样*/ + } + #endregion + + #endregion + + +} diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs new file mode 100644 index 0000000..2a0be8e --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs @@ -0,0 +1,60 @@ +using System; + +namespace IFoxCAD.Cad; + +public static class Utility +{ + /// + /// 带有随机种子的随机数 + /// 为什么这样写随机种子呢 + /// + /// + public static Random GetRandom() + { + var tick = DateTime.Now.Ticks; + + /* + * 知识准备: + * | 高位64位 | 低位32位 | + * Convert.ToString(int.MaxValue, 2)输出二进制 "1111111111111111111111111111111" 31个;最高位是符号位,所以少1位 + * Convert.ToString(long.MaxValue,2)输出二进制,刚好长一倍 "11111111111111111111111111111111 1111111111111111111111111111111" 63个;最高位是符号位,所以少1位 + * Convert.ToString(0xffffffffL, 2)int.MaxValue再按位多1 "1 1111111111111111111111111111111" 32个;前面的0不会打印出来 + * + * Convert.ToString(long.MaxValue>>32, 2)相当于平移高位的到低位范围,也就是上面少打印的二进制 + * 验证右移是不是高位保留,答案是 + * var a = Convert.ToInt64("101111111111111111111111111111111111111111111111111111111111111", 2); + * Convert.ToString(a >> 32,2); + * + * 解释代码: + * 0x01: + * (int)(long.MaxValue & 0xffffffffL) | (int)(long.MaxValue >> 32); + * Convert.ToString(long.MaxValue & 0xffffffffL, 2)//去掉高位:"11111111111111111111111111111111" 32个,再强转int + * 按位与&是保证符号位肯定是1,其他尽可能为0,高位被去掉只是MaxValue&0的原因,强转才是去掉高位..."尽可能"一词带来第一次随机性 + * 0x02: + * Convert.ToString((long.MaxValue >> 32), 2) //去掉低位: "1111111111111111111111111111111" 31个,再强转int + * 按位或|是尽可能为1..."尽可能"一词带来第二次随机性 + * + */ + + var tickSeeds = (int)(tick & 0xffffffffL) | (int)(tick >> 32); + return new Random(tickSeeds); + } + + /// + /// 随机颜色 + /// + /// + public static System.Drawing.Color RandomColor + { + get + { + var ran = GetRandom(); + int R = ran.Next(255); + int G = ran.Next(255); + int B = ran.Next(255); + B = (R + G > 400) ? R + G - 400 : B;//0 : 380 - R - G; + B = (B > 255) ? 255 : B; + return System.Drawing.Color.FromArgb(R, G, B); + } + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs b/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs new file mode 100644 index 0000000..dc7abd3 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs @@ -0,0 +1,85 @@ +namespace IFoxCAD; + +/// +/// 多段线的顶点,凸度,头宽,尾宽 +/// +[Serializable] +public class BulgeVertexWidth +{ + /// + /// 顶点X + /// + public double X; + /// + /// 顶点Y + /// + public double Y; + /// + /// 凸度 + /// + public double Bulge; + /// + /// 头宽 + /// + public double StartWidth; + /// + /// 尾宽 + /// + public double EndWidth; + + public Point2d Vertex => new(X, Y); + + public BulgeVertexWidth() { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(double vertex_X, double vertex_Y, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + { + X = vertex_X; + Y = vertex_Y; + Bulge = bulge; + StartWidth = startWidth; + EndWidth = endWidth; + } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(Point2d vertex, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + : this(vertex.X, vertex.Y, bulge, startWidth, endWidth) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(BulgeVertex bv) + : this(bv.Vertex.X, bv.Vertex.Y, bv.Bulge) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + /// 多段线 + /// 子段编号 + public BulgeVertexWidth(Polyline pl, int index) + { + var pt = pl.GetPoint2dAt(index);//这里可以3d + X = pt.X; + Y = pt.Y; + Bulge = pl.GetBulgeAt(index); + StartWidth = pl.GetStartWidthAt(index); + EndWidth = pl.GetEndWidthAt(index); + } + + public BulgeVertex ToBulgeVertex() + { + return new BulgeVertex(Vertex, Bulge); + } +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs index 406a33d..bfbed62 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs @@ -1,135 +1,198 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 集合扩展类 +/// +public static class CollectionEx { /// - /// 集合扩展类 + /// 对象id迭代器转换为集合 /// - public static class CollectionEx + /// 对象id的迭代器 + /// 对象id集合 + public static ObjectIdCollection ToCollection(this IEnumerable ids) { - /// - /// 对象id迭代器转换为集合 - /// - /// 对象id的迭代器 - /// 对象id集合 - public static ObjectIdCollection ToCollection(this IEnumerable ids) - { - return new ObjectIdCollection(ids.ToArray()); - } + return new ObjectIdCollection(ids.ToArray()); + } - /// - /// 实体迭代器转换为集合 - /// - /// 对象类型 - /// 实体对象的迭代器 - /// 实体集合 - public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject - { - DBObjectCollection objCol = new(); - foreach (T obj in objs) - objCol.Add(obj); - return objCol; - } + /// + /// 实体迭代器转换为集合 + /// + /// 对象类型 + /// 实体对象的迭代器 + /// 实体集合 + public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject + { + DBObjectCollection objCol = new(); + foreach (T obj in objs) + objCol.Add(obj); + return objCol; + } - /// - /// double 数值迭代器转换为 double 数值集合 - /// - /// double 数值迭代器 - /// double 数值集合 - public static DoubleCollection ToCollection(this IEnumerable doubles) - { - return new DoubleCollection(doubles.ToArray()); - } + /// + /// double 数值迭代器转换为 double 数值集合 + /// + /// double 数值迭代器 + /// double 数值集合 + public static DoubleCollection ToCollection(this IEnumerable doubles) + { + return new DoubleCollection(doubles.ToArray()); + } - /// - /// 二维点迭代器转换为二维点集合 - /// - /// 二维点迭代器 - /// 二维点集合 - public static Point2dCollection ToCollection(this IEnumerable pts) - { - return new Point2dCollection(pts.ToArray()); - } + /// + /// 二维点迭代器转换为二维点集合 + /// + /// 二维点迭代器 + /// 二维点集合 + public static Point2dCollection ToCollection(this IEnumerable pts) + { + return new Point2dCollection(pts.ToArray()); + } - /// - /// 三维点迭代器转换为三维点集合 - /// - /// 三维点迭代器 - /// 三维点集合 - public static Point3dCollection ToCollection(this IEnumerable pts) - { - return new Point3dCollection(pts.ToArray()); - } + /// + /// 三维点迭代器转换为三维点集合 + /// + /// 三维点迭代器 + /// 三维点集合 + public static Point3dCollection ToCollection(this IEnumerable pts) + { + return new Point3dCollection(pts.ToArray()); + } + + /// + /// 对象id集合转换为对象id列表 + /// + /// 对象id集合 + /// 对象id列表 + public static List ToList(this ObjectIdCollection ids) + { + return ids.Cast().ToList(); + } - /// - /// 对象id集合转换为对象id列表 - /// - /// 对象id集合 - /// 对象id列表 - public static List ToList(this ObjectIdCollection ids) - { - return ids.Cast().ToList(); - } - public static List ToList(this StringCollection strs) + /// + /// 遍历集合的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public static void ForEach(this IEnumerable source, Action action) + { + foreach (var element in source) { - return strs.Cast().ToList(); + action?.Invoke(element); } - - /* Cast不进行过滤,而是直接强转 - var s = new System.Collections.ArrayList(); - s.Add("1"); - s.Add("a"); - s.Add(5666); - - var aaa = s.Cast().ToList(); - System.InvalidCastException: 指定的转换无效。 - + List..ctor(IEnumerable) - + System.Linq.Enumerable.ToList(IEnumerable) - - var aaa = s.Cast().ToList(); - System.InvalidCastException: 无法将类型为“System.Int32”的对象强制转换为类型“System.String”。 - + List..ctor(IEnumerable) - + System.Linq.Enumerable.ToList(IEnumerable) - */ - - /// - /// 遍历集合的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) + } + /// + /// 同时遍历集合索引和值的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public static void ForEach(this IEnumerable source, Action action) + { + int i = 0; + foreach (var item in source) { - if (action is null) - throw new ArgumentNullException(nameof(action)); - foreach (var element in source) - action.Invoke(element); + action?.Invoke(i, item); + i++; } + } + - /// - /// 同时遍历集合索引和值的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) + #region 关键字集合 + public enum KeywordName + { + GlobalName, + LocalName, + DisplayName, + } + + /// + /// 含有关键字 + /// + /// 关键字集合 + /// 关键字 + /// 关键字容器字段名 + /// true含有 + public static bool Contains(this KeywordCollection collection, string name, + KeywordName keywordName = KeywordName.GlobalName) + { + bool contains = false; + switch (keywordName) { - if (action is null) - throw new ArgumentNullException(nameof(action)); - int i = 0; - foreach (var item in source) - { - action.Invoke(i, item); - i++; - } + case KeywordName.GlobalName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].GlobalName == name) + { + contains = true; + break; + } + break; + case KeywordName.LocalName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].LocalName == name) + { + contains = true; + break; + } + break; + case KeywordName.DisplayName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].DisplayName == name) + { + contains = true; + break; + } + break; + default: + break; } + return contains; + } + + /// + /// 获取词典, + /// KeywordCollection是允许重复关键字的,没有哈希索引,在多次判断时候会遍历多次O(n),所以生成一个词典进行O(1) + /// + /// + /// + public static Dictionary GetDict(this KeywordCollection collection) + { + Dictionary map = new(); + for (int i = 0; i < collection.Count; i++) + map.Add(collection[i].GlobalName, collection[i].DisplayName); + return map; + } + #endregion + + #region IdMapping + /// + /// 旧块名 + /// + /// + /// + public static List GetKeys(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Key); + return ids; } + + /// + /// 新块名 + /// + /// + /// + public static List GetValues(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Value); + return ids; + } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs index 7acb61e..51ff4e2 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs @@ -1,318 +1,312 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 二维解析类曲线转换为二维实体曲线扩展类 +/// +public static class Curve2dEx { + #region Curve2d + /// - /// 二维解析类曲线转换为二维实体曲线扩展类 + /// 按矩阵转换Ge2d曲线为Db曲线 /// - - public static class Curve2dEx + /// Ge2d曲线 + /// 曲线转换矩阵 + /// Db曲线 + public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) { - #region Curve2d - - /// - /// 按矩阵转换Ge2d曲线为Db曲线 - /// - /// Ge2d曲线 - /// 曲线转换矩阵 - /// Db曲线 - public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) + return curve switch { - return curve switch - { - LineSegment2d li => ToCurve(li, mat), - NurbCurve2d nu => ToCurve(nu, mat), - EllipticalArc2d el => ToCurve(el, mat), - CircularArc2d ci => ToCurve(ci, mat), - PolylineCurve2d po => ToCurve(po, mat), - Line2d l2 => ToCurve(l2, mat), - CompositeCurve2d co => ToCurve(co, mat), - _ => null - }; - } + LineSegment2d li => ToCurve(li, mat), + NurbCurve2d nu => ToCurve(nu, mat), + EllipticalArc2d el => ToCurve(el, mat), + CircularArc2d ci => ToCurve(ci, mat), + PolylineCurve2d po => ToCurve(po, mat), + Line2d l2 => ToCurve(l2, mat), + CompositeCurve2d co => ToCurve(co, mat), + _ => null + }; + } + + #endregion Curve2d - #endregion Curve2d + #region CircularArc2d - #region CircularArc2d + /// + /// 判断点是否位于圆内及圆上 + /// + /// 二维解析类圆弧对象 + /// 二维点 + /// 位于圆内及圆上返回 ,反之返回 + public static bool IsIn(this CircularArc2d ca2d, Point2d pnt) + { + return ca2d.IsOn(pnt) || ca2d.IsInside(pnt); + } - /// - /// 判断点是否位于圆内及圆上 - /// - /// 二维解析类圆弧对象 - /// 二维点 - /// 位于圆内及圆上返回 ,反之返回 - public static bool IsIn(this CircularArc2d ca2d, Point2d pnt) + /// + /// 将二维解析类圆弧转换为实体圆或者圆弧,然后进行矩阵变换 + /// + /// 二维解析类圆弧对象 + /// 变换矩阵 + /// 实体圆或者圆弧 + public static Curve ToCurve(this CircularArc2d ca2d, Matrix3d mat) + { + Curve c = ToCurve(ca2d); + c.TransformBy(mat); + return c; + } + + /// + /// 将二维解析类圆弧转换为实体圆或者圆弧 + /// + /// 二维解析类圆弧对象 + /// 实体圆或者圆弧 + public static Curve ToCurve(this CircularArc2d ca2d) + { + if (ca2d.IsClosed()) { - return ca2d.IsOn(pnt) || ca2d.IsInside(pnt); + return ToCircle(ca2d); } - - /// - /// 将二维解析类圆弧转换为实体圆或者圆弧,然后进行矩阵变换 - /// - /// 二维解析类圆弧对象 - /// 变换矩阵 - /// 实体圆或者圆弧 - public static Curve ToCurve(this CircularArc2d ca2d, Matrix3d mat) + else { - Curve c = ToCurve(ca2d); - c.TransformBy(mat); - return c; + return ToArc(ca2d); } + } + + /// + /// 将二维解析类圆弧转换为实体圆 + /// + /// 二维解析类圆弧对象 + /// 实体圆 + public static Circle ToCircle(this CircularArc2d c2d) + { + return + new Circle( + new Point3d(new Plane(), c2d.Center), + new Vector3d(0, 0, 1), + c2d.Radius); + } + + /// + /// 将二维解析类圆弧转换为实体圆弧 + /// + /// 二维解析类圆弧对象 + /// 圆弧 + public static Arc ToArc(this CircularArc2d a2d) + { + double startangle, endangle; + double refangle = a2d.ReferenceVector.Angle; - /// - /// 将二维解析类圆弧转换为实体圆或者圆弧 - /// - /// 二维解析类圆弧对象 - /// 实体圆或者圆弧 - public static Curve ToCurve(this CircularArc2d ca2d) + if (a2d.IsClockWise) { - if (ca2d.IsClosed()) - { - return ToCircle(ca2d); - } - else - { - return ToArc(ca2d); - } + startangle = -a2d.EndAngle - refangle; + endangle = -a2d.StartAngle - refangle; } - - /// - /// 将二维解析类圆弧转换为实体圆 - /// - /// 二维解析类圆弧对象 - /// 实体圆 - public static Circle ToCircle(this CircularArc2d c2d) + else { - return - new Circle( - new Point3d(new Plane(), c2d.Center), - new Vector3d(0, 0, 1), - c2d.Radius); + startangle = a2d.StartAngle + refangle; + endangle = a2d.EndAngle + refangle; } - /// - /// 将二维解析类圆弧转换为实体圆弧 - /// - /// 二维解析类圆弧对象 - /// 圆弧 - public static Arc ToArc(this CircularArc2d a2d) - { - double startangle, endangle; - double refangle = a2d.ReferenceVector.Angle; + return + new Arc( + new Point3d(new Plane(), a2d.Center), + Vector3d.ZAxis, + a2d.Radius, + startangle, + endangle); + } - if (a2d.IsClockWise) + #endregion CircularArc2d + + #region EllipticalArc2d + + //椭圆弧 + /// + /// 将二维解析类椭圆弧转换为实体椭圆弧,然后进行矩阵变换 + /// + /// 二维解析类椭圆弧对象 + /// 变换矩阵 + /// 实体椭圆弧 + public static Ellipse ToCurve(this EllipticalArc2d ea2d, Matrix3d mat) + { + Ellipse e = ToCurve(ea2d); + e.TransformBy(mat); + return e; + } + + /// + /// 将二维解析类椭圆弧转换为实体椭圆弧 + /// + /// 二维解析类椭圆弧对象 + /// 实体椭圆弧 + public static Ellipse ToCurve(this EllipticalArc2d ea2d) + { + Plane plane = new(); + Ellipse ell = + new( + new Point3d(plane, ea2d.Center), + new Vector3d(0, 0, 1), + new Vector3d(plane, ea2d.MajorAxis) * ea2d.MajorRadius, + ea2d.MinorRadius / ea2d.MajorRadius, + 0, + Math.PI * 2); + if (!ea2d.IsClosed()) + { + if (ea2d.IsClockWise) { - startangle = -a2d.EndAngle - refangle; - endangle = -a2d.StartAngle - refangle; + ell.StartAngle = -ell.GetAngleAtParameter(ea2d.EndAngle); + ell.EndAngle = -ell.GetAngleAtParameter(ea2d.StartAngle); } else { - startangle = a2d.StartAngle + refangle; - endangle = a2d.EndAngle + refangle; + ell.StartAngle = ell.GetAngleAtParameter(ea2d.StartAngle); + ell.EndAngle = ell.GetAngleAtParameter(ea2d.EndAngle); } - - return - new Arc( - new Point3d(new Plane(), a2d.Center), - Vector3d.ZAxis, - a2d.Radius, - startangle, - endangle); } + return ell; + } - #endregion CircularArc2d - - #region EllipticalArc2d + #endregion EllipticalArc2d - //椭圆弧 - /// - /// 将二维解析类椭圆弧转换为实体椭圆弧,然后进行矩阵变换 - /// - /// 二维解析类椭圆弧对象 - /// 变换矩阵 - /// 实体椭圆弧 - public static Ellipse ToCurve(this EllipticalArc2d ea2d, Matrix3d mat) - { - Ellipse e = ToCurve(ea2d); - e.TransformBy(mat); - return e; - } + #region Line2d - /// - /// 将二维解析类椭圆弧转换为实体椭圆弧 - /// - /// 二维解析类椭圆弧对象 - /// 实体椭圆弧 - public static Ellipse ToCurve(this EllipticalArc2d ea2d) - { - Plane plane = new Plane(); - Ellipse ell = - new Ellipse( - new Point3d(plane, ea2d.Center), - new Vector3d(0, 0, 1), - new Vector3d(plane, ea2d.MajorAxis) * ea2d.MajorRadius, - ea2d.MinorRadius / ea2d.MajorRadius, - 0, - Math.PI * 2); - if (!ea2d.IsClosed()) + /// + /// 将二维解析类直线转换为实体类构造线 + /// + /// 二维解析类直线 + /// 实体类构造线 + public static Xline ToCurve(this Line2d line2d) + { + Plane plane = new(); + return + new Xline { - if (ea2d.IsClockWise) - { - ell.StartAngle = -ell.GetAngleAtParameter(ea2d.EndAngle); - ell.EndAngle = -ell.GetAngleAtParameter(ea2d.StartAngle); - } - else - { - ell.StartAngle = ell.GetAngleAtParameter(ea2d.StartAngle); - ell.EndAngle = ell.GetAngleAtParameter(ea2d.EndAngle); - } - } - return ell; - } - - #endregion EllipticalArc2d - - #region Line2d + BasePoint = new Point3d(plane, line2d.PointOnLine), + SecondPoint = new Point3d(plane, line2d.PointOnLine + line2d.Direction) + }; + } - /// - /// 将二维解析类直线转换为实体类构造线 - /// - /// 二维解析类直线 - /// 实体类构造线 - public static Xline ToCurve(this Line2d line2d) - { - var plane = new Plane(); - return - new Xline - { - BasePoint = new Point3d(plane, line2d.PointOnLine), - SecondPoint = new Point3d(plane, line2d.PointOnLine + line2d.Direction) - }; - } + /// + /// 将二维解析类直线转换为实体类构造线,然后进行矩阵变换 + /// + /// 二维解析类直线 + /// 变换矩阵 + /// 实体类构造线 + public static Xline ToCurve(this Line2d line2d, Matrix3d mat) + { + Xline xl = ToCurve(line2d); + xl.TransformBy(mat); + return xl; + } - /// - /// 将二维解析类直线转换为实体类构造线,然后进行矩阵变换 - /// - /// 二维解析类直线 - /// 变换矩阵 - /// 实体类构造线 - public static Xline ToCurve(this Line2d line2d, Matrix3d mat) - { - Xline xl = ToCurve(line2d); - xl.TransformBy(mat); - return xl; - } + /// + /// 将二维解析类构造线转换为二维解析类线段 + /// + /// 二维解析类构造线 + /// 起点参数 + /// 终点参数 + /// 二维解析类线段 + public static LineSegment2d ToLineSegment2d(this Line2d line2d, double fromParameter, double toParameter) + { + return + new LineSegment2d + ( + line2d.EvaluatePoint(fromParameter), + line2d.EvaluatePoint(toParameter) + ); + } - /// - /// 将二维解析类构造线转换为二维解析类线段 - /// - /// 二维解析类构造线 - /// 起点参数 - /// 终点参数 - /// 二维解析类线段 - public static LineSegment2d ToLineSegment2d(this Line2d line2d, double fromParameter, double toParameter) - { - return - new LineSegment2d - ( - line2d.EvaluatePoint(fromParameter), - line2d.EvaluatePoint(toParameter) - ); - } + #endregion Line2d - #endregion Line2d + #region LineSegment2d - #region LineSegment2d + /// + /// 将二维解析类线段转换为实体类直线,并进行矩阵变换 + /// + /// 二维解析类线段 + /// 变换矩阵 + /// 实体类直线 + public static Line ToCurve(this LineSegment2d ls2d, Matrix3d mat) + { + Line l = ToCurve(ls2d); + l.TransformBy(mat); + return l; + } - /// - /// 将二维解析类线段转换为实体类直线,并进行矩阵变换 - /// - /// 二维解析类线段 - /// 变换矩阵 - /// 实体类直线 - public static Line ToCurve(this LineSegment2d ls2d, Matrix3d mat) - { - Line l = ToCurve(ls2d); - l.TransformBy(mat); - return l; - } + /// + /// 将二维解析类线段转换为实体类直线 + /// + /// 二维解析类线段 + /// 实体类直线 + public static Line ToCurve(this LineSegment2d ls2d) + { + Plane plane = new(); + return + new Line( + new Point3d(plane, ls2d.StartPoint), + new Point3d(plane, ls2d.EndPoint)); - /// - /// 将二维解析类线段转换为实体类直线 - /// - /// 二维解析类线段 - /// 实体类直线 - public static Line ToCurve(this LineSegment2d ls2d) - { - Plane plane = new Plane(); - return - new Line( - new Point3d(plane, ls2d.StartPoint), - new Point3d(plane, ls2d.EndPoint)); + } - } + #endregion LineSegment2d - #endregion LineSegment2d + #region NurbCurve2d - #region NurbCurve2d + /// + /// 将二维解析类BURB曲线转换为实体类样条曲线,并进行矩阵变换 + /// + /// 二维解析类BURB曲线 + /// 变换矩阵 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve2d nc2d, Matrix3d mat) + { + Spline spl = ToCurve(nc2d); + spl.TransformBy(mat); + return spl; + } - /// - /// 将二维解析类BURB曲线转换为实体类样条曲线,并进行矩阵变换 - /// - /// 二维解析类BURB曲线 - /// 变换矩阵 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve2d nc2d, Matrix3d mat) + /// + /// 将二维解析类BURB曲线转换为实体类样条曲线 + /// + /// 二维解析类BURB曲线 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve2d nc2d) + { + int i; + Plane plane = new(); + Point3dCollection ctlpnts = new(); + for (i = 0; i < nc2d.NumControlPoints; i++) { - Spline spl = ToCurve(nc2d); - spl.TransformBy(mat); - return spl; + ctlpnts.Add(new Point3d(plane, nc2d.GetControlPointAt(i))); } - /// - /// 将二维解析类BURB曲线转换为实体类样条曲线 - /// - /// 二维解析类BURB曲线 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve2d nc2d) + DoubleCollection knots = new(); + foreach (double knot in nc2d.Knots) { - int i; - Plane plane = new Plane(); - Point3dCollection ctlpnts = new Point3dCollection(); - for (i = 0; i < nc2d.NumControlPoints; i++) - { - ctlpnts.Add(new Point3d(plane, nc2d.GetControlPointAt(i))); - } - - DoubleCollection knots = new DoubleCollection(); - foreach (double knot in nc2d.Knots) - { - knots.Add(knot); - } - - DoubleCollection weights = new DoubleCollection(); - for (i = 0; i < nc2d.NumWeights; i++) - { - weights.Add(nc2d.GetWeightAt(i)); - } + knots.Add(knot); + } - NurbCurve2dData ncdata = nc2d.DefinitionData; - - return - new Spline( - ncdata.Degree, - ncdata.Rational, - nc2d.IsClosed(), - ncdata.Periodic, - ctlpnts, - knots, - weights, - 0, - nc2d.Knots.Tolerance); + DoubleCollection weights = new(); + for (i = 0; i < nc2d.NumWeights; i++) + { + weights.Add(nc2d.GetWeightAt(i)); } - #endregion NurbCurve2d + NurbCurve2dData ncdata = nc2d.DefinitionData; + + return + new Spline( + ncdata.Degree, + ncdata.Rational, + nc2d.IsClosed(), + ncdata.Periodic, + ctlpnts, + knots, + weights, + 0, + nc2d.Knots.Tolerance); } + + #endregion NurbCurve2d } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs index 9d477ba..57ca8fd 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs @@ -1,567 +1,557 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 三维解析类曲线转换为三维实体曲线扩展类 +/// +public static class Curve3dEx { /// - /// 三维解析类曲线转换为三维实体曲线扩展类 + /// 判断两个浮点数是否相等 /// - public static class Curve3dEx + /// 容差 + /// 第一个数 + /// 第二个数 + /// 两个数的差值的绝对值小于容差返回 ,反之返回 + public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) { - /// - /// 判断两个浮点数是否相等 - /// - /// 容差 - /// 第一个数 - /// 第二个数 - /// 两个数的差值的绝对值小于容差返回 ,反之返回 - public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) - { - return Math.Abs(d1 - d2) < tol.EqualPoint; - } + return Math.Abs(d1 - d2) < tol.EqualPoint; + } - #region Curve3d + #region Curve3d - /// - /// 获取三维解析类曲线(自交曲线)的交点参数 - /// - /// 三维解析类曲线 - /// 曲线参数的列表 - public static List GetParamsAtIntersectionPoints(this Curve3d c3d) - { - CurveCurveIntersector3d cci = new CurveCurveIntersector3d(c3d, c3d, Vector3d.ZAxis); - List pars = new List(); - for (int i = 0; i < cci.NumberOfIntersectionPoints; i++) - { - pars.AddRange(cci.GetIntersectionParameters(i)); - } - pars.Sort(); - return pars; - } + /// + /// 获取三维解析类曲线(自交曲线)的交点参数 + /// + /// 三维解析类曲线 + /// 曲线参数的列表 + public static List GetParamsAtIntersectionPoints(this Curve3d c3d) + { + CurveCurveIntersector3d cci = new(c3d, c3d, Vector3d.ZAxis); + List pars = new(); + for (int i = 0; i < cci.NumberOfIntersectionPoints; i++) + pars.AddRange(cci.GetIntersectionParameters(i)); - /// - /// 获取三维解析类子曲线 - /// - /// 三维解析类曲线 - /// 子段曲线起点参数 - /// 子段曲线终点参数 - /// 三维解析类曲线 - public static Curve3d GetSubCurve(this Curve3d curve, double from, double to) + pars.Sort(); + return pars; + } + + /// + /// 获取三维解析类子曲线 + /// + /// 三维解析类曲线 + /// 子段曲线起点参数 + /// 子段曲线终点参数 + /// 三维解析类曲线 + public static Curve3d GetSubCurve(this Curve3d curve, double from, double to) + { + Interval inter = curve.GetInterval(); + bool atStart = Tolerance.Global.IsEqualPoint(inter.LowerBound, from); + bool atEnd = Tolerance.Global.IsEqualPoint(inter.UpperBound, to); + if (atStart && atEnd) + return (Curve3d)curve.Clone(); + if (curve is NurbCurve3d) { - Interval inter = curve.GetInterval(); - bool atStart = Tolerance.Global.IsEqualPoint(inter.LowerBound, from); - bool atEnd = Tolerance.Global.IsEqualPoint(inter.UpperBound, to); - if (atStart && atEnd) - return (Curve3d)curve.Clone(); - if (curve is NurbCurve3d) + if (from < to) { - if (from < to) + NurbCurve3d clone = (NurbCurve3d)curve.Clone(); + if (atStart || atEnd) { - NurbCurve3d clone = (NurbCurve3d)curve.Clone(); - if (atStart || atEnd) - { - clone.HardTrimByParams(from, to); - return clone; - } - else - { - clone.HardTrimByParams(inter.LowerBound, to); - clone.HardTrimByParams(from, to); - return clone; - } + clone.HardTrimByParams(from, to); + return clone; } else { - NurbCurve3d clone1 = (NurbCurve3d)curve.Clone(); - clone1.HardTrimByParams(from, inter.UpperBound); - NurbCurve3d clone2 = (NurbCurve3d)curve.Clone(); - clone2.HardTrimByParams(inter.LowerBound, to); - clone1.JoinWith(clone2); - return clone1; + clone.HardTrimByParams(inter.LowerBound, to); + clone.HardTrimByParams(from, to); + return clone; } } else { - Curve3d clone = (Curve3d)curve.Clone(); - clone.SetInterval(new Interval(from, to, Tolerance.Global.EqualPoint)); - return clone; + NurbCurve3d clone1 = (NurbCurve3d)curve.Clone(); + clone1.HardTrimByParams(from, inter.UpperBound); + NurbCurve3d clone2 = (NurbCurve3d)curve.Clone(); + clone2.HardTrimByParams(inter.LowerBound, to); + clone1.JoinWith(clone2); + return clone1; } } - - /// - /// 将三维解析类曲线转换为三维实体类曲线 - /// - /// 三维解析类曲线 - /// 三维实体类曲线 - public static Curve ToCurve(this Curve3d curve) + else { - return curve switch - { - CompositeCurve3d co => ToCurve(co), - LineSegment3d li => ToCurve(li), - EllipticalArc3d el => ToCurve(el), - CircularArc3d ci => ToCurve(ci), - NurbCurve3d nu => ToCurve(nu), - PolylineCurve3d pl => ToCurve(pl), - Line3d l3 => ToCurve(l3), - _ => null - }; + Curve3d clone = (Curve3d)curve.Clone(); + clone.SetInterval(new Interval(from, to, Tolerance.Global.EqualPoint)); + return clone; } + } - /// - /// 将三维解析类曲线转换为三维解析类Nurb曲线 - /// - /// 三维解析类曲线 - /// 三维解析类Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Curve3d curve) + /// + /// 将三维解析类曲线转换为三维实体类曲线 + /// + /// 三维解析类曲线 + /// 三维实体类曲线 + public static Curve? ToCurve(this Curve3d curve) + { + return curve switch { - return curve switch - { - LineSegment3d line => new NurbCurve3d(line), - EllipticalArc3d el => new NurbCurve3d(el), - CircularArc3d cir => new NurbCurve3d(ToEllipticalArc3d(cir)), - NurbCurve3d nur => nur, - PolylineCurve3d pl => new NurbCurve3d(3, pl, false), - _ => null - }; - } + CompositeCurve3d co => ToCurve(co), + LineSegment3d li => ToCurve(li), + EllipticalArc3d el => ToCurve(el), + CircularArc3d ci => ToCurve(ci), + NurbCurve3d nu => ToCurve(nu), + PolylineCurve3d pl => ToCurve(pl), + Line3d l3 => ToCurve(l3), + _ => null + }; + } + + /// + /// 将三维解析类曲线转换为三维解析类Nurb曲线 + /// + /// 三维解析类曲线 + /// 三维解析类Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Curve3d curve) + { + return curve switch + { + LineSegment3d line => new NurbCurve3d(line), + EllipticalArc3d el => new NurbCurve3d(el), + CircularArc3d cir => new NurbCurve3d(ToEllipticalArc3d(cir)), + NurbCurve3d nur => nur, + PolylineCurve3d pl => new NurbCurve3d(3, pl, false), + _ => null + }; + } - #endregion Curve3d + #endregion Curve3d - #region CompositeCurve3d + #region CompositeCurve3d - /// - /// 判断是否为圆和椭圆 - /// - /// 三维解析类曲线 - /// 完整圆及完整的椭圆返回 ,反之返回 - public static bool IsCircular(this Curve3d curve) + /// + /// 判断是否为圆和椭圆 + /// + /// 三维解析类曲线 + /// 完整圆及完整的椭圆返回 ,反之返回 + public static bool IsCircular(this Curve3d curve) + { + return curve switch { - return curve switch - { - CircularArc3d or EllipticalArc3d => curve.IsClosed(), - _ => false - }; - } + CircularArc3d or EllipticalArc3d => curve.IsClosed(), + _ => false + }; + } - /// - /// 将三维复合曲线按曲线参数分割 - /// - /// 三维复合曲线 - /// 曲线参数列表 - /// 三维复合曲线列表 - public static List GetSplitCurves(this CompositeCurve3d c3d, List pars) + /// + /// 将三维复合曲线按曲线参数分割 + /// + /// 三维复合曲线 + /// 曲线参数列表 + /// 三维复合曲线列表 + public static List GetSplitCurves(this CompositeCurve3d c3d, List pars) + { + //曲线参数剔除重复的 + pars.Sort(); + for (int i = pars.Count - 1; i > 0; i--) { - Interval inter = c3d.GetInterval(); - Curve3d[] c3ds = c3d.GetCurves(); - pars.Sort(); - for (int i = pars.Count - 1; i > 0; i--) - { - if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) - pars.RemoveAt(i); - } + if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) + pars.RemoveAt(i); + } - if (pars.Count == 0) - return new List(); - if (c3ds.Length == 1 && c3ds[0].IsClosed()) + if (pars.Count == 0) + return new List(); + + //这个是曲线参数类 + var inter = c3d.GetInterval(); + //曲线们 + var c3ds = c3d.GetCurves(); + if (c3ds.Length == 1 && c3ds[0].IsClosed()) + { + //闭合曲线不允许打断于一点 + if (pars.Count > 1) { - //闭合曲线不允许打断于一点 - if (pars.Count > 1) + //如果包含起点 + if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) { - //如果包含起点 - if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) - { - pars[0] = inter.LowerBound; - //又包含终点,去除终点 - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - { - pars.RemoveAt(pars.Count - 1); - if (pars.Count == 1) - return new List(); - } - } - else if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) + pars[0] = inter.LowerBound; + //又包含终点,去除终点 + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - pars[pars.Count - 1] = inter.UpperBound; + pars.RemoveAt(pars.Count - 1); + if (pars.Count == 1) + return new List(); } - //加入第一点以支持反向打断 - pars.Add(pars[0]); } - else + else if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - return new List(); + pars[^1] = inter.UpperBound; } + //加入第一点以支持反向打断 + pars.Add(pars[0]); } else { - //非闭合曲线加入起点和终点 - if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) - pars[0] = inter.LowerBound; - else - pars.Insert(0, inter.LowerBound); - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - pars[pars.Count - 1] = inter.UpperBound; - else - pars.Add(inter.UpperBound); - } - - List curves = new List(); - for (int i = 0; i < pars.Count - 1; i++) - { - List cc3ds = new List(); - //复合曲线参数转换到包含曲线参数 - CompositeParameter cp1 = c3d.GlobalToLocalParameter(pars[i]); - CompositeParameter cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); - if (cp1.SegmentIndex == cp2.SegmentIndex) - { - cc3ds.Add( - c3ds[cp1.SegmentIndex].GetSubCurve( - cp1.LocalParameter, - cp2.LocalParameter)); - } - else - { - inter = c3ds[cp1.SegmentIndex].GetInterval(); - cc3ds.Add( - c3ds[cp1.SegmentIndex].GetSubCurve( - cp1.LocalParameter, - inter.UpperBound)); - for (int j = cp1.SegmentIndex + 1; j < cp2.SegmentIndex; j++) - { - cc3ds.Add((Curve3d)c3ds[j].Clone()); - } - inter = c3ds[cp2.SegmentIndex].GetInterval(); - cc3ds.Add( - c3ds[cp2.SegmentIndex].GetSubCurve( - inter.LowerBound, - cp2.LocalParameter)); - } - curves.Add(new CompositeCurve3d(cc3ds.ToArray())); - } - - if (c3d.IsClosed() && c3ds.Length > 1) - { - var cs = curves[curves.Count - 1].GetCurves().ToList(); - cs.AddRange(curves[0].GetCurves()); - curves[curves.Count - 1] = new CompositeCurve3d(cs.ToArray()); - curves.RemoveAt(0); + return new List(); } - - return curves; + } + else + { + //非闭合曲线加入起点和终点 + if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) + pars[0] = inter.LowerBound; + else + pars.Insert(0, inter.LowerBound); + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) + pars[^1] = inter.UpperBound; + else + pars.Add(inter.UpperBound); } - /// - /// 将复合曲线转换为实体类曲线 - /// - /// 三维复合曲线 - /// 实体曲线 - public static Curve ToCurve(this CompositeCurve3d curve) + List curves = new(); + for (int i = 0; i < pars.Count - 1; i++) { - Curve3d[] cs = curve.GetCurves(); - if (cs.Length == 0) + List cc3ds = new(); + //复合曲线参数转换到包含曲线参数 + var cp1 = c3d.GlobalToLocalParameter(pars[i]); + var cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); + if (cp1.SegmentIndex == cp2.SegmentIndex) { - return null; - } - else if (cs.Length == 1) - { - return ToCurve(cs[0]); + cc3ds.Add( + c3ds[cp1.SegmentIndex].GetSubCurve( + cp1.LocalParameter, + cp2.LocalParameter)); } else { - bool hasNurb = false; - - foreach (var c in cs) - { - if (c is NurbCurve3d || c is EllipticalArc3d) - { - hasNurb = true; - break; - } - } - if (hasNurb) - { - NurbCurve3d nc3d = cs[0].ToNurbCurve3d(); - for (int i = 1; i < cs.Length; i++) - nc3d.JoinWith(cs[i].ToNurbCurve3d()); - return nc3d.ToCurve(); - } - else + inter = c3ds[cp1.SegmentIndex].GetInterval(); + cc3ds.Add( + c3ds[cp1.SegmentIndex].GetSubCurve( + cp1.LocalParameter, + inter.UpperBound)); + for (int j = cp1.SegmentIndex + 1; j < cp2.SegmentIndex; j++) { - return ToPolyline(curve); + cc3ds.Add((Curve3d)c3ds[j].Clone()); } + inter = c3ds[cp2.SegmentIndex].GetInterval(); + cc3ds.Add( + c3ds[cp2.SegmentIndex].GetSubCurve( + inter.LowerBound, + cp2.LocalParameter)); } + curves.Add(new CompositeCurve3d(cc3ds.ToArray())); } - /// - /// 将三维复合曲线转换为实体类多段线 - /// - /// 三维复合曲线 - /// 实体类多段线 - public static Polyline ToPolyline(this CompositeCurve3d cc3d) + if (c3d.IsClosed() && c3ds.Length > 1) { - Polyline pl = new Polyline(); - pl.Elevation = cc3d.StartPoint[2]; - Plane plane = pl.GetPlane(); - Point2d endver = Point2d.Origin; - int i = 0; - foreach (Curve3d c3d in cc3d.GetCurves()) - { - if (c3d is CircularArc3d) - { - CircularArc3d ca3d = (CircularArc3d)c3d; - double b = Math.Tan(0.25 * (ca3d.EndAngle - ca3d.StartAngle)) * ca3d.Normal[2]; - pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), b, 0, 0); - endver = c3d.EndPoint.Convert2d(plane); - } - else - { - pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), 0, 0, 0); - endver = c3d.EndPoint.Convert2d(plane); - } - i++; - } - pl.AddVertexAt(i, endver, 0, 0, 0); - return pl; + var cus1 = curves[curves.Count - 1].GetCurves(); + var cus2 = curves[0].GetCurves(); + var cs = cus1.Combine2(cus2); + curves[^1] = new CompositeCurve3d(cs); + curves.RemoveAt(0); } + return curves; + } - #endregion CompositeCurve3d - - #region Line3d + /// + /// 将复合曲线转换为实体类曲线 + /// + /// 三维复合曲线 + /// 实体曲线 + public static Curve? ToCurve(this CompositeCurve3d curve) + { + Curve3d[] cs = curve.GetCurves(); + if (cs.Length == 0) + return null; + if (cs.Length == 1) + return ToCurve(cs[0]); - /// - /// 将解析类三维构造线转换为实体类构造线 - /// - /// 解析类三维构造线 - /// 实体类构造线 - public static Xline ToCurve(this Line3d line3d) - { - return - new Xline - { - BasePoint = line3d.PointOnLine, - SecondPoint = line3d.PointOnLine + line3d.Direction - }; - } + bool hasNurb = false; - /// - /// 将三维解析类构造线转换为三维解析类线段 - /// - /// 三维解析类构造线 - /// 起点参数 - /// 终点参数 - /// 三维解析类线段 - public static LineSegment3d ToLineSegment3d(this Line3d line3d, double fromParameter, double toParameter) + for (int i = 0; i < cs.Length; i++) { - return - new LineSegment3d - ( - line3d.EvaluatePoint(fromParameter), - line3d.EvaluatePoint(toParameter) - ); + var c = cs[i]; + if (c is NurbCurve3d || c is EllipticalArc3d) + { + hasNurb = true; + break; + } } - - #endregion Line3d - - #region LineSegment3d - - /// - /// 将三维解析类线段转换为实体类直线 - /// - /// 三维解析类线段 - /// 实体类直线 - public static Line ToCurve(this LineSegment3d lineSeg3d) + if (hasNurb) { - return new Line(lineSeg3d.StartPoint, lineSeg3d.EndPoint); + var nc3d = cs[0].ToNurbCurve3d(); + for (int i = 1; i < cs.Length; i++) + nc3d?.JoinWith(cs[i].ToNurbCurve3d()); + return nc3d?.ToCurve(); } - #endregion LineSegment3d - - #region CircularArc3d + return ToPolyline(curve); + } - /// - /// 将三维解析类圆/弧转换为实体圆/弧 - /// - /// 三维解析类圆/弧 - /// 实体圆/弧 - public static Curve ToCurve(this CircularArc3d ca3d) + /// + /// 将三维复合曲线转换为实体类多段线 + /// + /// 三维复合曲线 + /// 实体类多段线 + public static Polyline ToPolyline(this CompositeCurve3d cc3d) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.Elevation = cc3d.StartPoint[2]; + + Plane plane = pl.GetPlane(); + Point2d endver = Point2d.Origin; + int i = 0; + foreach (Curve3d c3d in cc3d.GetCurves()) { - if (ca3d.IsClosed()) + if (c3d is CircularArc3d ca3d) { - return ToCircle(ca3d); + double b = Math.Tan(0.25 * (ca3d.EndAngle - ca3d.StartAngle)) * ca3d.Normal[2]; + pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), b, 0, 0); + endver = c3d.EndPoint.Convert2d(plane); } else { - return ToArc(ca3d); + pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), 0, 0, 0); + endver = c3d.EndPoint.Convert2d(plane); } + i++; } + pl.AddVertexAt(i, endver, 0, 0, 0); + return pl; + } - /// - /// 将三维解析类圆/弧转换为实体圆 - /// - /// 三维解析类圆/弧 - /// 实体圆 - public static Circle ToCircle(this CircularArc3d ca3d) => - new Circle(ca3d.Center, ca3d.Normal, ca3d.Radius); - - /// - /// 将三维解析类圆/弧转换为实体圆弧 - /// - /// 三维解析类圆/弧 - /// 实体圆弧 - public static Arc ToArc(this CircularArc3d ca3d) - { - //必须新建,而不能直接使用GetPlane()获取 - double angle = ca3d.ReferenceVector.AngleOnPlane(new Plane(ca3d.Center, ca3d.Normal)); - return new Arc(ca3d.Center, ca3d.Normal, ca3d.Radius, ca3d.StartAngle + angle, ca3d.EndAngle + angle); - } + #endregion CompositeCurve3d + + #region Line3d - /// - /// 将三维解析类圆/弧转换为三维解析类椭圆弧 - /// - /// 三维解析类圆/弧 - /// 三维解析类椭圆弧 - public static EllipticalArc3d ToEllipticalArc3d(this CircularArc3d ca3d) + /// + /// 将解析类三维构造线转换为实体类构造线 + /// + /// 解析类三维构造线 + /// 实体类构造线 + public static Xline ToCurve(this Line3d line3d) + { + return + new Xline + { + BasePoint = line3d.PointOnLine, + SecondPoint = line3d.PointOnLine + line3d.Direction + }; + } + + /// + /// 将三维解析类构造线转换为三维解析类线段 + /// + /// 三维解析类构造线 + /// 起点参数 + /// 终点参数 + /// 三维解析类线段 + public static LineSegment3d ToLineSegment3d(this Line3d line3d, double fromParameter, double toParameter) + { + return + new LineSegment3d + ( + line3d.EvaluatePoint(fromParameter), + line3d.EvaluatePoint(toParameter) + ); + } + + #endregion Line3d + + #region LineSegment3d + + /// + /// 将三维解析类线段转换为实体类直线 + /// + /// 三维解析类线段 + /// 实体类直线 + public static Line ToCurve(this LineSegment3d lineSeg3d) + { + return new Line(lineSeg3d.StartPoint, lineSeg3d.EndPoint); + } + + #endregion LineSegment3d + + #region CircularArc3d + + /// + /// 将三维解析类圆/弧转换为实体圆/弧 + /// + /// 三维解析类圆/弧 + /// 实体圆/弧 + public static Curve ToCurve(this CircularArc3d ca3d) + { + if (ca3d.IsClosed()) { - Vector3d zaxis = ca3d.Normal; - Vector3d xaxis = ca3d.ReferenceVector; - Vector3d yaxis = zaxis.CrossProduct(xaxis); - - return - new EllipticalArc3d( - ca3d.Center, - xaxis, - yaxis, - ca3d.Radius, - ca3d.Radius, - ca3d.StartAngle, - ca3d.EndAngle); + return ToCircle(ca3d); } - - /// - /// 将三维解析类圆/弧转换为三维解析类Nurb曲线 - /// - /// 三维解析类圆/弧 - /// 三维解析类Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this CircularArc3d ca3d) + else { - EllipticalArc3d ea3d = ToEllipticalArc3d(ca3d); - NurbCurve3d nc3d = new NurbCurve3d(ea3d); - return nc3d; + return ToArc(ca3d); } + } + + /// + /// 将三维解析类圆/弧转换为实体圆 + /// + /// 三维解析类圆/弧 + /// 实体圆 + public static Circle ToCircle(this CircularArc3d ca3d) => + new(ca3d.Center, ca3d.Normal, ca3d.Radius); - #endregion CircularArc3d + /// + /// 将三维解析类圆/弧转换为实体圆弧 + /// + /// 三维解析类圆/弧 + /// 实体圆弧 + public static Arc ToArc(this CircularArc3d ca3d) + { + //必须新建,而不能直接使用GetPlane()获取 + double angle = ca3d.ReferenceVector.AngleOnPlane(new Plane(ca3d.Center, ca3d.Normal)); + return new Arc(ca3d.Center, ca3d.Normal, ca3d.Radius, ca3d.StartAngle + angle, ca3d.EndAngle + angle); + } - #region EllipticalArc3d + /// + /// 将三维解析类圆/弧转换为三维解析类椭圆弧 + /// + /// 三维解析类圆/弧 + /// 三维解析类椭圆弧 + public static EllipticalArc3d ToEllipticalArc3d(this CircularArc3d ca3d) + { + Vector3d zaxis = ca3d.Normal; + Vector3d xaxis = ca3d.ReferenceVector; + Vector3d yaxis = zaxis.CrossProduct(xaxis); + + return + new EllipticalArc3d( + ca3d.Center, + xaxis, + yaxis, + ca3d.Radius, + ca3d.Radius, + ca3d.StartAngle, + ca3d.EndAngle); + } - /// - /// 将三维解析类椭圆弧转换为实体类椭圆弧 - /// - /// 三维解析类椭圆弧 - /// 实体类椭圆弧 - public static Ellipse ToCurve(this EllipticalArc3d ea3d) + /// + /// 将三维解析类圆/弧转换为三维解析类Nurb曲线 + /// + /// 三维解析类圆/弧 + /// 三维解析类Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this CircularArc3d ca3d) + { + EllipticalArc3d ea3d = ToEllipticalArc3d(ca3d); + NurbCurve3d nc3d = new(ea3d); + return nc3d; + } + + #endregion CircularArc3d + + #region EllipticalArc3d + + /// + /// 将三维解析类椭圆弧转换为实体类椭圆弧 + /// + /// 三维解析类椭圆弧 + /// 实体类椭圆弧 + public static Ellipse ToCurve(this EllipticalArc3d ea3d) + { + Ellipse ell = + new( + ea3d.Center, + ea3d.Normal, + ea3d.MajorAxis * ea3d.MajorRadius, + ea3d.MinorRadius / ea3d.MajorRadius, + 0, + Math.PI * 2); + //Ge椭圆角度就是Db椭圆的参数 + if (!ea3d.IsClosed()) { - Ellipse ell = - new Ellipse( - ea3d.Center, - ea3d.Normal, - ea3d.MajorAxis * ea3d.MajorRadius, - ea3d.MinorRadius / ea3d.MajorRadius, - 0, - Math.PI * 2); - //Ge椭圆角度就是Db椭圆的参数 - if (!ea3d.IsClosed()) - { - ell.StartAngle = ell.GetAngleAtParameter(ea3d.StartAngle); - ell.EndAngle = ell.GetAngleAtParameter(ea3d.EndAngle); - } - return ell; + ell.StartAngle = ell.GetAngleAtParameter(ea3d.StartAngle); + ell.EndAngle = ell.GetAngleAtParameter(ea3d.EndAngle); } + return ell; + } - #endregion EllipticalArc3d + #endregion EllipticalArc3d - #region NurbCurve3d + #region NurbCurve3d - /// - /// 将三维解析类Nurb曲线转换为实体类样条曲线 - /// - /// 三维解析类Nurb曲线 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve3d nc3d) + /// + /// 将三维解析类Nurb曲线转换为实体类样条曲线 + /// + /// 三维解析类Nurb曲线 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve3d nc3d) + { + Spline spl; + if (nc3d.HasFitData) { - Spline spl = null; - if (nc3d.HasFitData) + NurbCurve3dFitData fdata = nc3d.FitData; + if (fdata.TangentsExist) { - NurbCurve3dFitData fdata = nc3d.FitData; - if (fdata.TangentsExist) - { - spl = new Spline( - fdata.FitPoints, - fdata.StartTangent, - fdata.EndTangent, - nc3d.Order, - fdata.FitTolerance.EqualPoint); - } - else - { - spl = new Spline( - fdata.FitPoints, - nc3d.Order, - fdata.FitTolerance.EqualPoint); - } + spl = new Spline( + fdata.FitPoints, + fdata.StartTangent, + fdata.EndTangent, + nc3d.Order, + fdata.FitTolerance.EqualPoint); } else { - DoubleCollection knots = new DoubleCollection(); - foreach (double knot in nc3d.Knots) - knots.Add(knot); - - NurbCurve3dData ncdata = nc3d.DefinitionData; - spl = new Spline( - ncdata.Degree, - ncdata.Rational, - nc3d.IsClosed(), - ncdata.Periodic, - ncdata.ControlPoints, - knots, - ncdata.Weights, - Tolerance.Global.EqualPoint, - ncdata.Knots.Tolerance); + fdata.FitPoints, + nc3d.Order, + fdata.FitTolerance.EqualPoint); } - return spl; } + else + { + DoubleCollection knots = new(); + foreach (double knot in nc3d.Knots) + knots.Add(knot); + + NurbCurve3dData ncdata = nc3d.DefinitionData; + + spl = new Spline( + ncdata.Degree, + ncdata.Rational, + nc3d.IsClosed(), + ncdata.Periodic, + ncdata.ControlPoints, + knots, + ncdata.Weights, + Tolerance.Global.EqualPoint, + ncdata.Knots.Tolerance); + } + return spl; + } - #endregion NurbCurve3d - - #region PolylineCurve3d + #endregion NurbCurve3d - /// - /// 将三维解析类多段线转换为实体类三维多段线 - /// - /// 三维解析类多段线 - /// 实体类三维多段线 - public static Polyline3d ToCurve(this PolylineCurve3d pl3d) - { - Point3dCollection pnts = new Point3dCollection(); + #region PolylineCurve3d - for (int i = 0; i < pl3d.NumberOfControlPoints; i++) - { - pnts.Add(pl3d.ControlPointAt(i)); - } + /// + /// 将三维解析类多段线转换为实体类三维多段线 + /// + /// 三维解析类多段线 + /// 实体类三维多段线 + public static Polyline3d ToCurve(this PolylineCurve3d pl3d) + { + Point3dCollection pnts = new(); - bool closed = false; - int n = pnts.Count - 1; - if (pnts[0] == pnts[n]) - { - pnts.RemoveAt(n); - closed = true; - } - return new Polyline3d(Poly3dType.SimplePoly, pnts, closed); + for (int i = 0; i < pl3d.NumberOfControlPoints; i++) + { + pnts.Add(pl3d.ControlPointAt(i)); } - #endregion PolylineCurve3d + bool closed = false; + int n = pnts.Count - 1; + if (pnts[0] == pnts[n]) + { + pnts.RemoveAt(n); + closed = true; + } + return new Polyline3d(Poly3dType.SimplePoly, pnts, closed); } + + #endregion PolylineCurve3d } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs index 26fbd18..4056bc5 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs @@ -1,984 +1,662 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using IFoxCAD.Collections; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 实体类曲线扩展类 +/// +public static class CurveEx { /// - /// 实体类曲线扩展类 + /// 曲线长度 /// - public static class CurveEx + /// 曲线 + /// 长度 + public static double GetLength(this Curve curve) { - /// - /// 曲线长度 - /// - /// 曲线 - /// 长度 - public static double GetLength(this Curve curve) - { - return - curve.GetDistanceAtParameter(curve.EndParam); - } + return curve.GetDistanceAtParameter(curve.EndParam); + } - /// - /// 获取分割曲线集合 - /// - /// 曲线 - /// 打断参数表 - /// 打断后曲线的集合 - public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars) + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断参数表 + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars) + { + return + curve + .GetSplitCurves(new DoubleCollection(pars.ToArray())) + .Cast(); + } + + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断点表 + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) + { + return + curve + .GetSplitCurves(new Point3dCollection(points.ToArray())) + .Cast(); + } + + /// + /// 获取曲线集所围成的封闭区域的曲线集,注意此函数不能处理平行边(两个点及两条线组成的闭合环) + /// + /// 曲线集合 + /// 所有的闭合环的曲线集合 + public static IEnumerable GetAllCycle(this IEnumerable curves) + { + // 新建图 + var graph = new Graph(); + foreach (var curve in curves) { - return - curve - .GetSplitCurves(new DoubleCollection(pars.ToArray())) - .Cast(); +#if NET35 + graph.AddEdge(curve.ToCurve3d()!); +#else + graph.AddEdge(curve.GetGeCurve()); +#endif } - - /// - /// 获取分割曲线集合 - /// - /// 曲线 - /// 打断点表 - /// 打断后曲线的集合 - public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) + //新建 dfs + var dfs = new DepthFirst(); + // 查询全部的 闭合环 + dfs.FindAll(graph); + // 遍历闭合环的列表,将每个闭合环转换为实体曲线 + var res = new List(); + foreach (var item in dfs.Curve3ds) { - return - curve - .GetSplitCurves(new Point3dCollection(points.ToArray())) - .Cast(); + var curve = graph.GetCurves(item.ToList()).ToArray(); + var comcur = new CompositeCurve3d(curve).ToCurve(); + if (comcur is not null) + res.Add(comcur); } + return res; + } + /// + /// 曲线打断 + /// + /// 曲线列表 + /// 打断后的曲线列表 + public static List BreakCurve(this List curves) + { + var geCurves = new List(); // 存储曲线转换后的复合曲线 + var paramss = new List>(); // 存储每个曲线的交点参数值 - private struct EdgeItem : IEquatable + for (int i = 0; i < curves.Count; i++) { - public Edge Edge; - public bool Forward; - - public EdgeItem(Edge edge, bool forward) + var cc3d = curves[i].ToCompositeCurve3d(); + if (cc3d is not null) { - Edge = edge; - Forward = forward; + geCurves.Add(cc3d); + paramss.Add(new List()); } + } + + //var oldCurves = new List(); + var newCurves = new List(); + var cci3d = new CurveCurveIntersector3d(); - public CompositeCurve3d GetCurve() + for (int i = 0; i < curves.Count; i++) + { + var gc1 = geCurves[i]; + var pars1 = paramss[i]; //引用 + for (int j = i; j < curves.Count; j++) { - var cc3d = Edge.Curve; - if (Forward) - return cc3d; - else - { - cc3d = (CompositeCurve3d)cc3d.Clone(); - return cc3d.GetReverseParameterCurve() as CompositeCurve3d; - } - } + var gc2 = geCurves[j]; + var pars2 = paramss[j]; // 引用 - public bool Equals(EdgeItem other) - { - return - Edge == other.Edge && - Forward == other.Forward; - } + cci3d.Set(gc1, gc2, Vector3d.ZAxis); - public void FindRegion(List edges, List> regions) - { - var region = new LoopList(); - var edgeItem = this; - region.Add(edgeItem); - var edgeItem2 = this.GetNext(edges); - if (edgeItem2.Edge is not null) + for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) { - bool hasList = false; - foreach (var edgeList2 in regions) - { - var node = edgeList2.GetNode(e => e.Equals(edgeItem)); - if (node is not null) - { - if (node.Next.Value.Equals(edgeItem2)) - { - hasList = true; - break; - } - } - } - if (!hasList) - { - while (edgeItem2.Edge is not null) - { - if (edgeItem2.Edge == edgeItem.Edge) - break; - region.Add(edgeItem2); - edgeItem2 = edgeItem2.GetNext(edges); - } - if (edgeItem2.Edge == edgeItem.Edge) - regions.Add(region); - } + var pars = cci3d.GetIntersectionParameters(k); + pars1.Add(pars[0]); // 引用修改会同步到源对象 + pars2.Add(pars[1]); // 引用修改会同步到源对象 } } - public EdgeItem GetNext(List edges) + if (pars1.Count > 0) { - Vector3d vec; - int next; - if (Forward) + var c3ds = gc1.GetSplitCurves(pars1); + if (c3ds.Count > 1) { - vec = Edge.GetEndVector(); - next = Edge.EndIndex; - } - else - { - vec = Edge.GetStartVector(); - next = Edge.StartIndex; - } - - EdgeItem item = new EdgeItem(); - Vector3d vec2, vec3 = new Vector3d(); - double angle = 0; - bool hasNext = false; - bool forward = false; - foreach (var edge in edges) - { - if (edge.IsNext(Edge, next, ref vec3, ref forward)) + foreach (var c3d in c3ds) { - if (hasNext) - { - var angle2 = vec.GetAngleTo(vec3, Vector3d.ZAxis); - if (angle2 < angle) - { - vec2 = vec3; - angle = angle2; - item.Edge = edge; - item.Forward = forward; - } - } - else + var c3dCur = c3d.ToCurve(); + if (c3dCur is not null) { - vec2 = vec3; - angle = vec.GetAngleTo(vec2, Vector3d.ZAxis); - item.Edge = edge; - item.Forward = forward; - hasNext = true; + c3dCur.SetPropertiesFrom(curves[i]); + newCurves.Add(c3dCur); } } + //oldCurves.Add(curves[i]); } - return item; - } - - public override string ToString() - { - return - Forward ? - string.Format("{0}-{1}", Edge.StartIndex, Edge.EndIndex) : - string.Format("{0}-{1}", Edge.EndIndex, Edge.StartIndex); } } - private class Edge - { - public CompositeCurve3d Curve; - public int StartIndex; - public int EndIndex; + return newCurves; + } - public Vector3d GetStartVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new PointOnCurve3d(Curve, inter.LowerBound); - return poc.GetDerivative(1); - } + //转换DBCurve为GeCurved - public Vector3d GetEndVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new PointOnCurve3d(Curve, inter.UpperBound); - return -poc.GetDerivative(1); - } + #region Curve - public bool IsNext(Edge edge, int index, ref Vector3d vec, ref bool forward) - { - if (edge != this) - { - if (StartIndex == index) - { - vec = GetStartVector(); - forward = true; - return true; - } - else if (EndIndex == index) - { - vec = GetEndVector(); - forward = false; - return true; - } - } - return false; - } - } - - public static List Topo(List curves) + /// + /// 将曲线转换为ge曲线,此函数将在未来淘汰,二惊加油 + /// + /// 曲线 + /// ge曲线 + public static Curve3d? ToCurve3d(this Curve curve) + { + return curve switch { - //首先按交点分解为Ge曲线集 - List geCurves = new List(); - List> paramss = new List>(); - - foreach (var curve in curves) - { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d is not null) - { - geCurves.Add(cc3d); - paramss.Add(new List()); - } - } - - List edges = new List(); - CurveCurveIntersector3d cci3d = new CurveCurveIntersector3d(); - List newCurves = new List(); - - for (int i = 0; i < curves.Count; i++) - { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; - for (int j = i; j < curves.Count; j++) - { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; + Line li => ToCurve3d(li), + Circle ci => ToCurve3d(ci), + Arc arc => ToCurve3d(arc), + Ellipse el => ToCurve3d(el), + Polyline pl => ToCurve3d(pl), + Polyline2d pl2 => ToCurve3d(pl2), + Polyline3d pl3 => ToCurve3d(pl3), + Spline sp => ToCurve3d(sp), + _ => null + }; + } - cci3d.Set(gc1, gc2, Vector3d.ZAxis); + /// + /// 将曲线转换为复合曲线 + /// + /// 曲线 + /// 复合曲线 + public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) + { + return curve switch + { + Line li => new CompositeCurve3d(new Curve3d[] { ToCurve3d(li) }), + Circle ci => new CompositeCurve3d(new Curve3d[] { ToCurve3d(ci) }), + Arc arc => new CompositeCurve3d(new Curve3d[] { ToCurve3d(arc) }), + Ellipse el => new CompositeCurve3d(new Curve3d[] { ToCurve3d(el) }), + Polyline pl => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl) }), + + Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2)! }), + Polyline3d pl3 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl3) }), + Spline sp => new CompositeCurve3d(new Curve3d[] { ToCurve3d(sp) }), + _ => null + }; + } - for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) - { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); - } - } + /// + /// 将曲线转换为Nurb曲线 + /// + /// 曲线 + /// Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Curve curve) + { + return curve switch + { + Line li => ToNurbCurve3d(li), + Circle ci => ToNurbCurve3d(ci), + Arc arc => ToNurbCurve3d(arc), + Ellipse el => ToNurbCurve3d(el), + Polyline pl => ToNurbCurve3d(pl), + Polyline2d pl2 => ToNurbCurve3d(pl2), + Polyline3d pl3 => ToNurbCurve3d(pl3), + Spline sp => ToNurbCurve3d(sp), + _ => null + }; + } - if (pars1.Count > 0) - { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 0) - { - edges.AddRange( - c3ds.Select(c => new Edge { Curve = c })); - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - else - { - edges.Add(new Edge { Curve = gc1 }); - } - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - } + #region Line - //构建边的邻接表 - var knots = new List(); - var nums = new List(); - var closedEdges = new List(); + /// + /// 将直线转换为ge直线 + /// + /// 直线 + /// ge直线 + public static LineSegment3d ToCurve3d(this Line line) + { + return new LineSegment3d(line.StartPoint, line.EndPoint); + } - foreach (var edge in edges) - { - if (edge.Curve.IsClosed()) - { - closedEdges.Add(edge); - } - else - { - if (knots.Contains(edge.Curve.StartPoint)) - { - edge.StartIndex = - knots.IndexOf(edge.Curve.StartPoint); - nums[edge.StartIndex]++; - } - else - { - knots.Add(edge.Curve.StartPoint); - nums.Add(1); - edge.StartIndex = knots.Count - 1; - } + /// + /// 将直线转换为Nurb曲线 + /// + /// 直线 + /// Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Line line) + { + return new NurbCurve3d(ToCurve3d(line)); + } - if (knots.Contains(edge.Curve.EndPoint)) - { - edge.EndIndex = - knots.IndexOf(edge.Curve.EndPoint); - nums[edge.EndIndex]++; - } - else - { - knots.Add(edge.Curve.EndPoint); - nums.Add(1); - edge.EndIndex = knots.Count - 1; - } - } - } + #endregion Line - newCurves.AddRange(closedEdges.Select(e => e.Curve.ToCurve())); + #region Circle - edges = - edges - .Except(closedEdges) - .Where(e => nums[e.StartIndex] > 1 && nums[e.EndIndex] > 1) - .ToList(); + /// + /// 将圆转换为ge圆弧曲线 + /// + /// 圆 + /// ge圆弧曲线 + public static CircularArc3d ToCurve3d(this Circle cir) + { + return + new CircularArc3d( + cir.Center, + cir.Normal, + cir.Radius); + } - foreach (var edge in edges.Except(closedEdges)) - { - if (nums[edge.StartIndex] == 1 || nums[edge.EndIndex] == 1) - { - if (nums[edge.StartIndex] == 1 && nums[edge.EndIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[edge.EndIndex] = 0; - } - else - { - int next = -1; - if (nums[edge.StartIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[next = edge.EndIndex]--; - } - else - { - nums[edge.EndIndex] = 0; - nums[next = edge.StartIndex]--; - } - } - } - } + /// + /// 将圆转换为ge椭圆曲线 + /// + /// 圆 + /// ge椭圆曲线 + public static EllipticalArc3d ToEllipticalArc3d(this Circle cir) + { + return ToCurve3d(cir).ToEllipticalArc3d(); + } - List> regions = new List>(); - foreach (var edge in edges) - { - var edgeItem = new EdgeItem(edge, true); - edgeItem.FindRegion(edges, regions); - edgeItem = new EdgeItem(edge, false); - edgeItem.FindRegion(edges, regions); - } + /// + /// 将圆转换为Nurb曲线 + /// + /// 圆 + /// Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Circle cir) + { + return new NurbCurve3d(ToEllipticalArc3d(cir)); + } - for (int i = 0; i < regions.Count; i++) - { - for (int j = i + 1; j < regions.Count;) - { - bool eq = false; - if (regions[i].Count == regions[j].Count) - { - var node = regions[i].First; - var curve = node.Value.Edge.Curve; - var node2 = regions[j].GetNode(e => e.Edge.Curve == curve); - if (eq = node2 is not null) - { - var b = node.Value.Forward; - var b2 = node2.Value.Forward; - for (int k = 1; k < regions[i].Count; k++) - { - node = node.GetNext(b); - node2 = node2.GetNext(b2); - if (node.Value.Edge.Curve != node2.Value.Edge.Curve) - { - eq = false; - break; - } - } - } - } - if (eq) - regions.RemoveAt(j); - else - j++; - } - } + #endregion Circle - foreach (var region in regions) - { - var cs3ds = - region - .Select(e => e.GetCurve()) - .ToArray(); - newCurves.Add(new CompositeCurve3d(cs3ds.ToArray()).ToCurve()); - } + #region Arc - return newCurves; - } + /// + /// 将圆弧转换为ge圆弧曲线 + /// + /// 圆弧 + /// ge圆弧曲线 + public static CircularArc3d ToCurve3d(this Arc arc) + { + Plane plane = new(arc.Center, arc.Normal); + + return + new CircularArc3d( + arc.Center, + arc.Normal, + plane.GetCoordinateSystem().Xaxis, + arc.Radius, + arc.StartAngle, + arc.EndAngle + ); + } - /// - /// 曲线打断 - /// - /// 曲线列表 - /// 打断后的曲线列表 - public static List BreakCurve(List curves) - { - List geCurves = new List(); - List> paramss = new List>(); + /// + /// 将圆弧转换为ge椭圆曲线 + /// + /// 圆弧 + /// ge椭圆曲线 + public static EllipticalArc3d ToEllipticalArc3d(this Arc arc) + { + return ToCurve3d(arc).ToEllipticalArc3d(); + } - foreach (var curve in curves) - { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d is not null) - { - geCurves.Add(cc3d); - paramss.Add(new List()); - } - } + /// + /// 将圆弧转换为三维Nurb曲线 + /// + /// 圆弧 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Arc arc) + { + return new NurbCurve3d(ToEllipticalArc3d(arc)); + } - List oldCurves = new List(); - List newCurves = new List(); - CurveCurveIntersector3d cci3d = new CurveCurveIntersector3d(); + #endregion Arc - for (int i = 0; i < curves.Count; i++) - { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; - for (int j = i; j < curves.Count; j++) - { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; + #region Ellipse - cci3d.Set(gc1, gc2, Vector3d.ZAxis); + /// + /// 将椭圆转换为三维ge椭圆曲线 + /// + /// 椭圆 + /// 三维ge椭圆曲线 + public static EllipticalArc3d ToCurve3d(this Ellipse ell) + { + return + new EllipticalArc3d( + ell.Center, + ell.MajorAxis.GetNormal(), + ell.MinorAxis.GetNormal(), + ell.MajorRadius, + ell.MinorRadius, + ell.StartParam, + ell.EndParam); + } - for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) - { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); - } - } + /// + /// 将椭圆转换为三维Nurb曲线 + /// + /// 椭圆 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Ellipse ell) + { + return new NurbCurve3d(ToCurve3d(ell)); + } - if (pars1.Count > 0) - { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 1) - { - foreach (CompositeCurve3d c3d in c3ds) - { - Curve c = c3d.ToCurve(); - if (c is not null) - { - c.SetPropertiesFrom(curves[i]); - newCurves.Add(c); - } - } - oldCurves.Add(curves[i]); - } - } - } - curves = oldCurves; - return newCurves; - } + #endregion Ellipse - //转换DBCurve为GeCurved + #region Spline - #region Curve + /// + /// 将样条曲线转换为三维Nurb曲线 + /// + /// 样条曲线 + /// 三维Nurb曲线 + public static NurbCurve3d ToCurve3d(this Spline spl) + { + NurbCurve3d nc3d; + NurbsData ndata = spl.NurbsData; + KnotCollection knots = new(); + foreach (Double knot in ndata.GetKnots()) + knots.Add(knot); - /// - /// 将曲线转换为ge曲线,此函数将在未来淘汰,二惊加油 - /// - /// 曲线 - /// ge曲线 - [Obsolete("请使用Cad自带的 GetGeCurve 函数!")] - public static Curve3d? ToCurve3d(this Curve curve) + if (ndata.Rational) { - return curve switch - { - Line li => ToCurve3d(li), - Circle ci => ToCurve3d(ci), - Arc arc => ToCurve3d(arc), - Ellipse el => ToCurve3d(el), - Polyline pl => ToCurve3d(pl), - Polyline2d pl2 => ToCurve3d(pl2), - Polyline3d pl3 => ToCurve3d(pl3), - Spline sp => ToCurve3d(sp), - _ => null - }; + nc3d = + new NurbCurve3d( + ndata.Degree, + knots, + ndata.GetControlPoints(), + ndata.GetWeights(), + ndata.Periodic); } - - /// - /// 将曲线转换为复合曲线 - /// - /// 曲线 - /// 复合曲线 - public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) + else { - return curve switch - { - Line li => new CompositeCurve3d(new Curve3d[] { ToCurve3d(li) }), - Circle ci => new CompositeCurve3d(new Curve3d[] { ToCurve3d(ci) }), - Arc arc => new CompositeCurve3d(new Curve3d[] { ToCurve3d(arc) }), - Ellipse el => new CompositeCurve3d(new Curve3d[] { ToCurve3d(el) }), - Polyline pl => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl) }), - - Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2) }), - Polyline3d pl3 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl3) }), - Spline sp => new CompositeCurve3d(new Curve3d[] { ToCurve3d(sp) }), - _ => null - }; + nc3d = + new NurbCurve3d( + ndata.Degree, + knots, + ndata.GetControlPoints(), + ndata.Periodic); } - /// - /// 将曲线转换为Nurb曲线 - /// - /// 曲线 - /// Nurb曲线 - public static NurbCurve3d? ToNurbCurve3d(this Curve curve) + if (spl.HasFitData) { - return curve switch - { - Line li => ToNurbCurve3d(li), - Circle ci => ToNurbCurve3d(ci), - Arc arc => ToNurbCurve3d(arc), - Ellipse el => ToNurbCurve3d(el), - Polyline pl => ToNurbCurve3d(pl), - Polyline2d pl2 => ToNurbCurve3d(pl2), - Polyline3d pl3 => ToNurbCurve3d(pl3), - Spline sp => ToNurbCurve3d(sp), - _ => null - }; + var fdata = spl.FitData; + var vec = new Vector3d(); + if (fdata.TangentsExist && (fdata.StartTangent != vec || fdata.EndTangent != vec)) + nc3d.SetFitData(fdata.GetFitPoints(), fdata.StartTangent, fdata.EndTangent); } + return nc3d; + } - #region Line + #endregion Spline - /// - /// 将直线转换为ge直线 - /// - /// 直线 - /// ge直线 - public static LineSegment3d ToCurve3d(this Line line) - { - return new LineSegment3d(line.StartPoint, line.EndPoint); - } + #region Polyline2d - /// - /// 将直线转换为Nurb曲线 - /// - /// 直线 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Line line) + /// + /// 将二维多段线转换为三维ge曲线 + /// + /// 二维多段线 + /// 三维ge曲线 + public static Curve3d? ToCurve3d(this Polyline2d pl2d) + { + switch (pl2d.PolyType) { - return new NurbCurve3d(ToCurve3d(line)); + case Poly2dType.SimplePoly: + case Poly2dType.FitCurvePoly: + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToCurve3d(pl); + default: + return ToNurbCurve3d(pl2d); } - #endregion Line - - #region Circle - - /// - /// 将圆转换为ge圆弧曲线 - /// - /// 圆 - /// ge圆弧曲线 - public static CircularArc3d ToCurve3d(this Circle cir) - { - return - new CircularArc3d( - cir.Center, - cir.Normal, - cir.Radius); - } + //Polyline pl = new Polyline(); + //pl.ConvertFrom(pl2d, false); + //return ToCurve3d(pl); + } - /// - /// 将圆转换为ge椭圆曲线 - /// - /// 圆 - /// ge椭圆曲线 - public static EllipticalArc3d ToEllipticalArc3d(this Circle cir) + /// + /// 将二维多段线转换为三维Nurb曲线 + /// + /// 二维多段线 + /// 三维Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Polyline2d pl2d) + { + switch (pl2d.PolyType) { - return ToCurve3d(cir).ToEllipticalArc3d(); + case Poly2dType.SimplePoly: + case Poly2dType.FitCurvePoly: + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToNurbCurve3d(pl); + + default: + return ToCurve3d(pl2d.Spline); } + } - /// - /// 将圆转换为Nurb曲线 - /// - /// 圆 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Circle cir) + /// + /// 将二维多段线转换为三维ge多段线 + /// + /// 二维多段线 + /// 三维ge多段线 + public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) + { + Point3dCollection pnts = new(); + foreach (Vertex2d ver in pl) { - return new NurbCurve3d(ToEllipticalArc3d(cir)); + pnts.Add(ver.Position); } + return new PolylineCurve3d(pnts); + } - #endregion Circle - - #region Arc + #endregion Polyline2d - /// - /// 将圆弧转换为ge圆弧曲线 - /// - /// 圆弧 - /// ge圆弧曲线 - public static CircularArc3d ToCurve3d(this Arc arc) - { - Plane plane = new Plane(arc.Center, arc.Normal); - - return - new CircularArc3d( - arc.Center, - arc.Normal, - plane.GetCoordinateSystem().Xaxis, - arc.Radius, - arc.StartAngle, - arc.EndAngle - ); - } + #region Polyline3d - /// - /// 将圆弧转换为ge椭圆曲线 - /// - /// 圆弧 - /// ge椭圆曲线 - public static EllipticalArc3d ToEllipticalArc3d(this Arc arc) - { - return ToCurve3d(arc).ToEllipticalArc3d(); - } - - /// - /// 将圆弧转换为三维Nurb曲线 - /// - /// 圆弧 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Arc arc) + /// + /// 将三维多段线转换为三维曲线 + /// + /// 三维多段线 + /// 三维曲线 + public static Curve3d ToCurve3d(this Polyline3d pl3d) + { + return pl3d.PolyType switch { - return new NurbCurve3d(ToEllipticalArc3d(arc)); - } - - #endregion Arc + Poly3dType.SimplePoly => ToPolylineCurve3d(pl3d), + _ => ToNurbCurve3d(pl3d), + }; + } - #region Ellipse + /// + /// 将三维多段线转换为三维Nurb曲线 + /// + /// 三维多段线 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Polyline3d pl3d) + { + return ToCurve3d(pl3d.Spline); + } - /// - /// 将椭圆转换为三维ge椭圆曲线 - /// - /// 椭圆 - /// 三维ge椭圆曲线 - public static EllipticalArc3d ToCurve3d(this Ellipse ell) + /// + /// 将三维多段线转换为三维ge多段线 + /// + /// 三维多段线 + /// 三维ge多段线 + public static PolylineCurve3d ToPolylineCurve3d(this Polyline3d pl) + { + Point3dCollection pnts = new(); + foreach (ObjectId id in pl) { - return - new EllipticalArc3d( - ell.Center, - ell.MajorAxis.GetNormal(), - ell.MinorAxis.GetNormal(), - ell.MajorRadius, - ell.MinorRadius, - ell.StartParam, - ell.EndParam); + PolylineVertex3d ver = (PolylineVertex3d)id.GetObject(OpenMode.ForRead); + pnts.Add(ver.Position); } + return new PolylineCurve3d(pnts); + } - /// - /// 将椭圆转换为三维Nurb曲线 - /// - /// 椭圆 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Ellipse ell) - { - return new NurbCurve3d(ToCurve3d(ell)); - } + #endregion Polyline3d - #endregion Ellipse + #region Polyline - #region Spline + /// + /// 多段线转换为复合曲线 + /// + /// 多段线对象 + /// 复合曲线对象 + public static CompositeCurve3d ToCurve3d(this Polyline pl) + { + List c3ds = new(); - /// - /// 将样条曲线转换为三维Nurb曲线 - /// - /// 样条曲线 - /// 三维Nurb曲线 - public static NurbCurve3d ToCurve3d(this Spline spl) + for (int i = 0; i < pl.NumberOfVertices; i++) { - NurbCurve3d nc3d; - NurbsData ndata = spl.NurbsData; - KnotCollection knots = new KnotCollection(); - foreach (Double knot in ndata.GetKnots()) - knots.Add(knot); - - if (ndata.Rational) + switch (pl.GetSegmentType(i)) { - nc3d = - new NurbCurve3d( - ndata.Degree, - knots, - ndata.GetControlPoints(), - ndata.GetWeights(), - ndata.Periodic); - } - else - { - nc3d = - new NurbCurve3d( - ndata.Degree, - knots, - ndata.GetControlPoints(), - ndata.Periodic); - } - - if (spl.HasFitData) - { - var fdata = spl.FitData; - var vec = new Vector3d(); - if (fdata.TangentsExist && (fdata.StartTangent != vec || fdata.EndTangent != vec)) - nc3d.SetFitData(fdata.GetFitPoints(), fdata.StartTangent, fdata.EndTangent); - } - return nc3d; - } - - #endregion Spline + case SegmentType.Line: + c3ds.Add(pl.GetLineSegmentAt(i)); + break; - #region Polyline2d - - /// - /// 将二维多段线转换为三维ge曲线 - /// - /// 二维多段线 - /// 三维ge曲线 - public static Curve3d ToCurve3d(this Polyline2d pl2d) - { - switch (pl2d.PolyType) - { - case Poly2dType.SimplePoly: - case Poly2dType.FitCurvePoly: - Polyline pl = new Polyline(); - pl.ConvertFrom(pl2d, false); - return ToCurve3d(pl); + case SegmentType.Arc: + c3ds.Add(pl.GetArcSegmentAt(i)); + break; default: - return ToNurbCurve3d(pl2d); + break; } - - //Polyline pl = new Polyline(); - //pl.ConvertFrom(pl2d, false); - //return ToCurve3d(pl); } + return new CompositeCurve3d(c3ds.ToArray()); + } - /// - /// 将二维多段线转换为三维Nurb曲线 - /// - /// 二维多段线 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline2d pl2d) + /// + /// 多段线转换为Nurb曲线 + /// + /// 多段线 + /// Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Polyline pl) + { + NurbCurve3d? nc3d = null; + for (int i = 0; i < pl.NumberOfVertices; i++) { - switch (pl2d.PolyType) + NurbCurve3d? nc3dtemp = null; + switch (pl.GetSegmentType(i)) { - case Poly2dType.SimplePoly: - case Poly2dType.FitCurvePoly: - Polyline pl = new Polyline(); - pl.ConvertFrom(pl2d, false); - return ToNurbCurve3d(pl); + case SegmentType.Line: + nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); + break; + + case SegmentType.Arc: + nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); + break; default: - return ToCurve3d(pl2d.Spline); + break; } - } - - /// - /// 将二维多段线转换为三维ge多段线 - /// - /// 二维多段线 - /// 三维ge多段线 - public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) - { - var pnts = new Point3dCollection(); - foreach (Vertex2d ver in pl) - pnts.Add(ver.Position); - return new PolylineCurve3d(pnts); - } - #endregion Polyline2d - - #region Polyline3d - - /// - /// 将三维多段线转换为三维曲线 - /// - /// 三维多段线 - /// 三维曲线 - public static Curve3d ToCurve3d(this Polyline3d pl3d) - { - return pl3d.PolyType switch - { - Poly3dType.SimplePoly => ToPolylineCurve3d(pl3d), - _ => ToNurbCurve3d(pl3d), - }; - } - - /// - /// 将三维多段线转换为三维Nurb曲线 - /// - /// 三维多段线 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline3d pl3d) - { - return ToCurve3d(pl3d.Spline); - } - - /// - /// 将三维多段线转换为三维ge多段线 - /// - /// 三维多段线 - /// 三维ge多段线 - public static PolylineCurve3d ToPolylineCurve3d(this Polyline3d pl) - { - Point3dCollection pnts = new Point3dCollection(); - foreach (ObjectId id in pl) + if (nc3d is null) { - PolylineVertex3d ver = (PolylineVertex3d)id.GetObject(OpenMode.ForRead); - pnts.Add(ver.Position); + nc3d = nc3dtemp; } - return new PolylineCurve3d(pnts); - } - - #endregion Polyline3d - - #region Polyline - - /// - /// 多段线转换为复合曲线 - /// - /// 多段线对象 - /// 复合曲线对象 - public static CompositeCurve3d ToCurve3d(this Polyline pl) - { - List c3ds = new List(); - - for (int i = 0; i < pl.NumberOfVertices; i++) + else if (nc3dtemp is not null) { - switch (pl.GetSegmentType(i)) - { - case SegmentType.Line: - c3ds.Add(pl.GetLineSegmentAt(i)); - break; - - case SegmentType.Arc: - c3ds.Add(pl.GetArcSegmentAt(i)); - break; - - default: - break; - } + nc3d.JoinWith(nc3dtemp); } - return new CompositeCurve3d(c3ds.ToArray()); } + return nc3d; + } - /// - /// 多段线转换为Nurb曲线 - /// - /// 多段线 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline pl) - { - NurbCurve3d nc3d = null; - for (int i = 0; i < pl.NumberOfVertices; i++) - { - NurbCurve3d nc3dtemp = null; - switch (pl.GetSegmentType(i)) - { - case SegmentType.Line: - nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); - break; + /// + /// 为优化多段线倒角 + /// + /// 优化多段线 + /// 顶点索引号 + /// 倒角半径 + /// 倒角类型 + public static void ChamferAt(this Polyline polyline, int index, double radius, bool isFillet) + { + if (index < 1 || index > polyline.NumberOfVertices - 2) + throw new System.Exception("错误的索引号"); - case SegmentType.Arc: - nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); - break; + if (SegmentType.Line != polyline.GetSegmentType(index - 1) || + SegmentType.Line != polyline.GetSegmentType(index)) + throw new System.Exception("非直线段不能倒角"); - default: - break; - } - if (nc3d is null) + //获取当前索引号的前后两段直线,并组合为Ge复合曲线 + Curve3d[] c3ds = + new Curve3d[] { - nc3d = nc3dtemp; - } - else if (nc3dtemp is not null) - { - nc3d.JoinWith(nc3dtemp); - } - } - return nc3d; - } - - /// - /// 为优化多段线倒角 - /// - /// 优化多段线 - /// 顶点索引号 - /// 倒角半径 - /// 倒角类型 - public static void ChamferAt(this Polyline polyline, int index, double radius, bool isFillet) - { - if (index < 1 || index > polyline.NumberOfVertices - 2) - throw new System.Exception("错误的索引号"); - - if (polyline.GetSegmentType(index - 1) != SegmentType.Line || - polyline.GetSegmentType(index) != SegmentType.Line) - throw new System.Exception("非直线段不能倒角"); - - //获取当前索引号的前后两段直线,并组合为Ge复合曲线 - var c3ds = new Curve3d[] - { - polyline.GetLineSegmentAt(index - 1), - polyline.GetLineSegmentAt(index) - }; - var cc3d = new CompositeCurve3d(c3ds); - - //试倒直角 - //子曲线的个数有三种情况: - //1、=3时倒角方向正确 - //2、=2时倒角方向相反 - //3、=0或为直线时失败 - c3ds = cc3d.GetTrimmedOffset + polyline.GetLineSegmentAt(index - 1), + polyline.GetLineSegmentAt(index) + }; + CompositeCurve3d cc3d = new(c3ds); + + //试倒直角 + //子曲线的个数有三种情况: + //1、=3时倒角方向正确 + //2、=2时倒角方向相反 + //3、=0或为直线时失败 + c3ds = + cc3d.GetTrimmedOffset ( radius, Vector3d.ZAxis, OffsetCurveExtensionType.Chamfer ); - if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d newcc3d) - { - c3ds = newcc3d.GetCurves(); - if (c3ds.Length == 3) - { - c3ds = cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Chamfer - ); - if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) - throw new System.Exception("倒角半径过大"); - } - else if (c3ds.Length == 2) - { - radius = -radius; - } + if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d) + { + var newcc3d = c3ds[0] as CompositeCurve3d; + c3ds = newcc3d!.GetCurves(); + if (c3ds.Length == 3) + { + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Chamfer + ); + if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) + throw new System.Exception("倒角半径过大"); } - else + else if (c3ds.Length == 2) { - throw new System.Exception("倒角半径过大"); + radius = -radius; } - - //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 - c3ds = cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Extend - ); - var type = isFillet ? OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; - c3ds = c3ds[0].GetTrimmedOffset - ( - radius, - Vector3d.ZAxis, - type - ); - - //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 - var plTemp = c3ds[0].ToCurve() as Polyline; - polyline.RemoveVertexAt(index); - polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); - polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); + } + else + { + throw new System.Exception("倒角半径过大"); } - #endregion Polyline - - #endregion Curve + //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Extend + ); + OffsetCurveExtensionType type = + isFillet ? + OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; + c3ds = c3ds[0].GetTrimmedOffset + ( + radius, + Vector3d.ZAxis, + type + ); + + //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 + var plTemp = c3ds[0].ToCurve() as Polyline; + if (plTemp == null) + return; + polyline.RemoveVertexAt(index); + polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); + polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); } -} + + #endregion Polyline + + #endregion Curve +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs index d4fba82..2ae0879 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs @@ -1,389 +1,372 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; +using Group = Autodesk.AutoCAD.DatabaseServices.Group; + +/// +/// 字典扩展类 +/// +public static class DBDictionaryEx { /// - /// 字典扩展类 + /// 获取字典里的全部对象 /// - public static class DBDictionaryEx + /// 对象类型的泛型 + /// 字典 + /// 事务 + /// 对象迭代器 + public static IEnumerable GetAllObjects(this DBDictionary dict, DBTrans? trans = null) where T : DBObject { - /// - /// 获取字典里的全部对象 - /// - /// 对象类型的泛型 - /// 字典 - /// 事务 - /// 对象迭代器 - public static IEnumerable GetAllObjects(this DBDictionary dict, Transaction trans = null) where T : DBObject + trans ??= DBTrans.Top; + foreach (DBDictionaryEntry e in dict) { - trans ??= DBTrans.Top.Transaction; - foreach (DBDictionaryEntry e in dict) - { - yield return trans.GetObject(e.Value, OpenMode.ForRead) as T; - } + var ent = trans.GetObject(e.Value); + if (ent is not null) + yield return ent; } + } - /// - /// 获取字典内指定key的对象 - /// - /// 对象类型的泛型 - /// 字典 - /// 事务 - /// 指定的键值 - /// T 类型的对象 - public static T GetAt(this DBDictionary dict, string key, Transaction trans = null) where T : DBObject + /// + /// 获取字典内指定key的对象 + /// + /// 对象类型的泛型 + /// 字典 + /// 事务 + /// 指定的键值 + /// T 类型的对象 + public static T? GetAt(this DBDictionary dict, string key, DBTrans? trans = null) where T : DBObject + { + trans ??= DBTrans.Top; + if (dict.Contains(key)) { - trans ??= DBTrans.Top.Transaction; - if (dict.Contains(key)) - { - ObjectId id = dict.GetAt(key); - if (!id.IsNull) - { - return trans.GetObject(id, OpenMode.ForRead) as T; - } - } - return null; + ObjectId id = dict.GetAt(key); + if (!id.IsNull) + return trans.GetObject(id); } + return null; + } - /// - /// 添加条目(键值对)到字典 - /// - /// 对象类型 - /// 字典 - /// 事务 - /// 键 - /// 值 - public static void SetAt(this DBDictionary dict, string key, T obj, Transaction tr = null) where T : DBObject + /// + /// 添加条目(键值对)到字典 + /// + /// 对象类型 + /// 字典 + /// 事务 + /// 键 + /// 值 + public static void SetAt(this DBDictionary dict, string key, T obj, Transaction? trans = null) where T : DBObject + { + trans ??= DBTrans.Top.Transaction; + using (dict.ForWrite()) { - tr ??= DBTrans.Top.Transaction; - using (dict.ForWrite()) - { - dict.SetAt(key, obj); - tr.AddNewlyCreatedDBObject(obj, true); - } + dict.SetAt(key, obj); + trans.AddNewlyCreatedDBObject(obj, true); } + } - #region XRecord + #region XRecord - /// - /// 从字典中获取扩展数据 - /// - /// 字典 - /// 键值 - /// 扩展数据 - public static XRecordDataList GetXRecord(this DBDictionary dict, string key) - { - Xrecord rec = dict.GetAt(key); - if (rec is not null) - return rec.Data; - return null; - } + /// + /// 从字典中获取扩展数据 + /// + /// 字典 + /// 键值 + /// 扩展数据 + public static XRecordDataList? GetXRecord(this DBDictionary dict, string key) + { + Xrecord? rec = dict.GetAt(key); + if (rec is not null) + return rec.Data; + return null; + } - /// - /// 保存扩展数据到字典 - /// - /// 扩展数据 - /// 字典 - /// 键值 - public static void SetXRecord(this DBDictionary dict, string key, XRecordDataList rb) - { - using var data = new Xrecord { Data = rb }; - dict.SetAt(key, data); - } - #endregion - - /// - /// 获取扩展字典 - /// - /// 对象 - /// 事务 - /// 扩展字典对象 - public static DBDictionary GetXDictionary(this DBObject obj, Transaction trans = null) + /// + /// 保存扩展数据到字典 + /// + /// 扩展数据 + /// 字典 + /// 键值 + public static void SetXRecord(this DBDictionary dict, string key, XRecordDataList rb) + { + using var data = new Xrecord { Data = rb }; + dict.SetAt(key, data); + } + #endregion + + /// + /// 获取扩展字典 + /// + /// 对象 + /// 事务 + /// 扩展字典对象 + public static DBDictionary? GetXDictionary(this DBObject obj, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + ObjectId id = obj.ExtensionDictionary; + if (id.IsNull) { - trans ??= DBTrans.Top.Transaction; - ObjectId id = obj.ExtensionDictionary; - if (id.IsNull) + using (obj.ForWrite()) { - using (obj.ForWrite()) - { - obj.CreateExtensionDictionary(); - } - - id = obj.ExtensionDictionary; + obj.CreateExtensionDictionary(); } - return id.GetObject(tr: trans); + + id = obj.ExtensionDictionary; } + return id.GetObject(tr: trans); + } - #region 数据表 + #region 数据表 - /// - /// 创建数据表 - /// - /// 原数据类型的字典 - /// 表元素(二维数组) - /// 数据表 - public static DataTable CreateDataTable(Dictionary colTypes, object[,] content) + /// + /// 创建数据表 + /// + /// 原数据类型的字典 + /// 表元素(二维数组) + /// 数据表 + public static DataTable CreateDataTable(Dictionary colTypes, object[,] content) + { + DataTable table = new(); + foreach (var t in colTypes) + table.AppendColumn(t.Value, t.Key); + var ncol = colTypes.Count; + var nrow = content.GetLength(0); + var types = new CellType[ncol]; + colTypes.Values.CopyTo(types, 0); + for (int i = 0; i < nrow; i++) { - DataTable table = new(); - foreach (var t in colTypes) - table.AppendColumn(t.Value, t.Key); - var ncol = colTypes.Count; - var nrow = content.GetLength(0); - var types = new CellType[ncol]; - colTypes.Values.CopyTo(types, 0); - for (int i = 0; i < nrow; i++) + DataCellCollection row = new(); + for (int j = 0; j < ncol; j++) { - DataCellCollection row = new(); - for (int j = 0; j < ncol; j++) - { - var cell = new DataCell(); - cell.SetValue(types[j], content[i, j]); - row.Add(cell); - } - table.AppendRow(row, true); + var cell = new DataCell(); + cell.SetValue(types[j], content[i, j]); + row.Add(cell); } - return table; + table.AppendRow(row, true); } + return table; + } - /// - /// 设定单元格数据 - /// - /// 单元格 - /// 类型 - /// 数据 - public static void SetValue(this DataCell cell, CellType type, object value) - { + /// + /// 设定单元格数据 + /// + /// 单元格 + /// 类型 + /// 数据 + public static void SetValue(this DataCell cell, CellType type, object value) + { - switch (type) - { - case CellType.Bool: - cell.SetBool((bool)value); - break; + switch (type) + { + case CellType.Bool: + cell.SetBool((bool)value); + break; - case CellType.CharPtr: - cell.SetString((string)value); - break; + case CellType.CharPtr: + cell.SetString((string)value); + break; - case CellType.Integer: - cell.SetInteger((int)value); - break; + case CellType.Integer: + cell.SetInteger((int)value); + break; - case CellType.Double: - cell.SetDouble((double)value); - break; + case CellType.Double: + cell.SetDouble((double)value); + break; - case CellType.ObjectId: - cell.SetObjectId((ObjectId)value); - break; + case CellType.ObjectId: + cell.SetObjectId((ObjectId)value); + break; - case CellType.Point: - cell.SetPoint((Point3d)value); - break; + case CellType.Point: + cell.SetPoint((Point3d)value); + break; - case CellType.Vector: - cell.SetVector((Vector3d)value); - break; + case CellType.Vector: + cell.SetVector((Vector3d)value); + break; - case CellType.HardOwnerId: - cell.SetHardOwnershipId((ObjectId)value); - break; + case CellType.HardOwnerId: + cell.SetHardOwnershipId((ObjectId)value); + break; - case CellType.HardPtrId: - cell.SetHardPointerId((ObjectId)value); - break; + case CellType.HardPtrId: + cell.SetHardPointerId((ObjectId)value); + break; - case CellType.SoftOwnerId: - cell.SetSoftOwnershipId((ObjectId)value); - break; + case CellType.SoftOwnerId: + cell.SetSoftOwnershipId((ObjectId)value); + break; - case CellType.SoftPtrId: - cell.SetSoftPointerId((ObjectId)value); - break; - } + case CellType.SoftPtrId: + cell.SetSoftPointerId((ObjectId)value); + break; } - #endregion - - #region 子字典 - /// - /// 获取子字典 - /// - /// 根字典 - /// 事务 - /// 是否创建子字典 - /// 键值列表 - /// 字典 - public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createSubDictionary, IEnumerable dictNames, Transaction trans = null) + } + #endregion + + #region 子字典 + /// + /// 获取子字典 + /// + /// 根字典 + /// 事务 + /// 是否创建子字典 + /// 键值列表 + /// 字典 + public static DBDictionary? GetSubDictionary(this DBDictionary dict, bool createSubDictionary, IEnumerable dictNames, DBTrans? trans = null) + { + DBDictionary? newdict = null; + trans ??= DBTrans.Top; + if (createSubDictionary) { - trans ??= DBTrans.Top.Transaction; - if (createSubDictionary) - { - using (dict.ForWrite()) - dict.TreatElementsAsHard = true; + using (dict.ForWrite()) + dict.TreatElementsAsHard = true; - foreach (string name in dictNames) - { - if (dict.Contains(name)) - { - dict = dict.GetAt(name, trans); - } - else - { - DBDictionary subDict = new(); - dict.SetAt(name, subDict, trans); - dict = subDict; - dict.TreatElementsAsHard = true; - } - } - } - else + foreach (string name in dictNames) { - foreach (string name in dictNames) + if (dict!.Contains(name)) { - if (dict.Contains(name)) - dict = dict.GetAt(name, trans); - else - return null; + newdict = dict.GetAt(name, trans); } - } - - return dict; - } - - - ///// - ///// 获取对象扩展字典的子字典 - ///// - ///// 对象 - ///// 事务 - ///// 是否创建子字典 - ///// 键值列表 - ///// 字典 - //public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) - //{ - // return obj.GetXDictionary().GetSubDictionary(createSubDictionary, dictNames); - //} - - #endregion - - #region 组字典 - /// - /// 添加编组 - /// - /// 组名 - /// 实体Id集合 - /// 编组Id - public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCollection ids) - { - if (dict.Contains(name)) - { - return ObjectId.Null; - } - else - { - using (dict.ForWrite()) + else { - Group g = new(); - g.Append(ids); - dict.SetAt(name, g); - DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); - return g.ObjectId; + DBDictionary subDict = new(); + dict.SetAt(name, subDict, trans); + newdict = subDict; + newdict.TreatElementsAsHard = true; } } } - - /// - /// 添加编组 - /// - /// 组名 - /// 实体Id集合 - /// 编组Id - public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable ids) + else { - if (dict.Contains(name)) + foreach (string name in dictNames) { - return ObjectId.Null; + if (dict.Contains(name)) + newdict = dict.GetAt(name, trans); + else + return null; } - return dict.AddGroup(name, new ObjectIdCollection(ids.ToArray())); } + return newdict; + } - /// - /// 按选择条件获取编组集合 - /// - /// 选择条件,过滤函数 - /// g.NumEntities < 2);]]> - /// 编组集合 - public static IEnumerable GetGroups(this DBDictionary dict, Func func) + + ///// + ///// 获取对象扩展字典的子字典 + ///// + ///// 对象 + ///// 事务 + ///// 是否创建子字典 + ///// 键值列表 + ///// 字典 + //public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) + //{ + // return obj.GetXDictionary().GetSubDictionary(createSubDictionary, dictNames); + //} + + #endregion + + #region 组字典 + /// + /// 添加编组 + /// + /// 组名 + /// 实体Id集合 + /// 编组Id + public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCollection ids) + { + if (dict.Contains(name)) + return ObjectId.Null; + + using (dict.ForWrite()) { - return - dict - .GetAllObjects() - .Where(func); + Group g = new(); + g.Append(ids); + dict.SetAt(name, g); + DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); + return g.ObjectId; } + } + + /// + /// 添加编组 + /// + /// 组名 + /// 实体Id集合 + /// 编组Id + public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable ids) + { + if (dict.Contains(name)) + return ObjectId.Null; + + return dict.AddGroup(name, new ObjectIdCollection(ids.ToArray())); + } - /// - /// 返回实体的所在编组的集合 - /// - /// 图元实体 - /// 编组集合 - public static IEnumerable GetGroups(this Entity ent) + + /// + /// 按选择条件获取编组集合 + /// + /// 选择条件,过滤函数 + /// g.NumEntities < 2);]]> + /// 编组集合 + public static IEnumerable GetGroups(this DBDictionary dict, Func func) + { + return dict.GetAllObjects() + .Where(func); + } + + /// + /// 返回实体的所在编组的集合 + /// + /// 图元实体 + /// 编组集合 + public static IEnumerable GetGroups(this Entity ent) + { + return ent.GetPersistentReactorIds() + .Cast() + .Select(id => id.GetObject()) + .OfType(); + } + + /// + /// 移除所有的空组 + /// + /// 被移除编组的名称集合 + public static List RemoveNullGroup(this DBDictionary dict) + { + var groups = dict.GetGroups(g => g.NumEntities < 2); + List names = new(); + foreach (Group g in groups) { - return - ent.GetPersistentReactorIds() - .Cast() - .Select(id => id.GetObject()) - .OfType(); + names.Add(g.Name); + using (g.ForWrite()) + g.Erase(); } + return names; + } - /// - /// 移除所有的空组 - /// - /// 被移除编组的名称集合 - public static List RemoveNullGroup(this DBDictionary dict) + /// + /// 移除所有空组 + /// + /// 过滤条件,过滤要删除的组名的规则函数 + /// RemoveNullGroup(g => g.StartsWith("hah")) + /// 被移除编组的名称集合 + public static List RemoveNullGroup(this DBDictionary dict, Func func) + { + var groups = dict.GetGroups(g => g.NumEntities < 2); + List names = new(); + foreach (Group g in groups) { - var groups = dict.GetGroups(g => g.NumEntities < 2); - List names = new(); - foreach (Group g in groups) + if (func(g.Name)) { names.Add(g.Name); using (g.ForWrite()) - { g.Erase(); - } - } - return names; - } - - /// - /// 移除所有空组 - /// - /// 过滤条件,过滤要删除的组名的规则函数 - /// RemoveNullGroup(g => g.StartsWith("hah")) - /// 被移除编组的名称集合 - public static List RemoveNullGroup(this DBDictionary dict, Func func) - { - var groups = dict.GetGroups(g => g.NumEntities < 2); - List names = new(); - foreach (Group g in groups) - { - if (func(g.Name)) - { - names.Add(g.Name); - using (g.ForWrite()) - { - g.Erase(); - } - } } - return names; } - #endregion + return names; + } + #endregion @@ -404,5 +387,4 @@ public static List RemoveNullGroup(this DBDictionary dict, Func +/// 实体对象扩展类 +/// +public static class DBObjectEx { + #region Xdata扩展 /// - /// 实体对象扩展类 + /// 删除扩展数据 /// - public static class DBObjectEx + /// 对象实例 + /// 应用程序名称 + /// 要删除数据的组码 + public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCode) { - #region Xdata扩展 - /// - /// 删除扩展数据 - /// - /// 对象实例 - /// 应用程序名称 - /// 要删除数据的组码 - public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCode) + XDataList data = obj.XData; + var indexlst = new List(); + bool flag = false; + for (int i = 0; i < data.Count; i++) { - XDataList data = obj.XData; - var indexlst = new List(); - bool flag = false; - for (int i = 0; i < data.Count; i++) + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - indexlst.Add(i); - } - } + flag = true; } - foreach (var item in indexlst) + if (flag) { - data.RemoveAt(item); + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) + break; + if (data[i].TypeCode == (int)dxfCode) + { + indexlst.Add(i); + } } + } + foreach (var item in indexlst) + { + data.RemoveAt(item); + } - using (obj.ForWrite()) - { - obj.XData = data; - } + using (obj.ForWrite()) + { + obj.XData = data; } - /// - /// 修改扩展数据 - /// - /// 对象实例 - /// 应用程序名称 - /// 要修改数据的组码 - /// 新的数据 - public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCode, object newvalue) + } + /// + /// 修改扩展数据 + /// + /// 对象实例 + /// 应用程序名称 + /// 要修改数据的组码 + /// 新的数据 + public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCode, object newvalue) + { + XDataList data = obj.XData; + bool flag = false; + for (int i = 0; i < data.Count; i++) { - XDataList data = obj.XData; - bool flag = false; - for (int i = 0; i < data.Count; i++) + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) + flag = true; + } + if (flag) + { + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) + break; + if (data[i].TypeCode == (int)dxfCode) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - data[i] = new TypedValue((int)dxfCode, newvalue); - } + data[i] = new TypedValue((int)dxfCode, newvalue); } } + } - using (obj.ForWrite()) - { - obj.XData = data; - } + using (obj.ForWrite()) + { + obj.XData = data; } - #endregion + } + #endregion - #region 读写模式切换 + #region 读写模式切换 - /// - /// 实体自动管理读写函数 - /// - /// 实体类型 - /// 实体对象 - /// 操作委托 - public static void ForWrite(this T obj, Action action) where T : DBObject + /// + /// 实体自动管理读写函数 + /// + /// 实体类型 + /// 实体对象 + /// 操作委托 + public static void ForWrite(this T obj, Action action) where T : DBObject + { + var _isNotifyEnabled = obj.IsNotifyEnabled; + var _isWriteEnabled = obj.IsWriteEnabled; + if (_isNotifyEnabled) { - var _isNotifyEnabled = obj.IsNotifyEnabled; - var _isWriteEnabled = obj.IsWriteEnabled; - if (_isNotifyEnabled) - { - obj.UpgradeFromNotify(); - } - else if (!_isWriteEnabled) - { - obj.UpgradeOpen(); - } - action?.Invoke(obj); - if (_isNotifyEnabled) - { - obj.DowngradeToNotify(_isWriteEnabled); - } - else if (!_isWriteEnabled) - { - obj.DowngradeOpen(); - } + obj.UpgradeFromNotify(); } - - /// - /// 打开模式提权 - /// - /// 实体对象 - /// 提权类对象 - public static UpgradeOpenManager ForWrite(this DBObject obj) + else if (!_isWriteEnabled) { - return new UpgradeOpenManager(obj); + obj.UpgradeOpen(); } - - /// - /// 提权类 - /// - public class UpgradeOpenManager : IDisposable + action?.Invoke(obj); + if (_isNotifyEnabled) + { + obj.DowngradeToNotify(_isWriteEnabled); + } + else if (!_isWriteEnabled) { - private readonly DBObject _obj; - private readonly bool _isNotifyEnabled; - private readonly bool _isWriteEnabled; + obj.DowngradeOpen(); + } + } - internal UpgradeOpenManager(DBObject obj) - { - _obj = obj; - _isNotifyEnabled = _obj.IsNotifyEnabled; - _isWriteEnabled = _obj.IsWriteEnabled; - if (_isNotifyEnabled) - _obj.UpgradeFromNotify(); - else if (!_isWriteEnabled) - _obj.UpgradeOpen(); - } + /// + /// 打开模式提权 + /// + /// 实体对象 + /// 提权类对象 + public static UpgradeOpenManager ForWrite(this DBObject obj) + { + return new UpgradeOpenManager(obj); + } - #region IDisposable 成员 + /// + /// 提权类 + /// + public class UpgradeOpenManager : IDisposable + { + private readonly DBObject _obj; + private readonly bool _isNotifyEnabled; + private readonly bool _isWriteEnabled; - /// - /// 注销函数 - /// - public void Dispose() - { - if (_isNotifyEnabled) - _obj.DowngradeToNotify(_isWriteEnabled); - else if (!_isWriteEnabled) - _obj.DowngradeOpen(); - GC.SuppressFinalize(this); - } + internal UpgradeOpenManager(DBObject obj) + { + _obj = obj; + _isNotifyEnabled = _obj.IsNotifyEnabled; + _isWriteEnabled = _obj.IsWriteEnabled; + if (_isNotifyEnabled) + _obj.UpgradeFromNotify(); + else if (!_isWriteEnabled) + _obj.UpgradeOpen(); + } - #endregion IDisposable 成员 + #region IDisposable 成员 + + /// + /// 注销函数 + /// + public void Dispose() + { + if (_isNotifyEnabled) + _obj.DowngradeToNotify(_isWriteEnabled); + else if (!_isWriteEnabled) + _obj.DowngradeOpen(); + GC.SuppressFinalize(this); } - #endregion + + #endregion IDisposable 成员 } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs new file mode 100644 index 0000000..dff7a9e --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs @@ -0,0 +1,23 @@ +namespace IFoxCAD.Cad; + +public static class DatabaseEx +{ + /// + /// 后台开图文字偏移处理 + /// 0x01 此方案利用前台数据库进行处理 + /// 0x02 当关闭所有前台文档时,会出现无时,不能使用(惊惊没有测试过此状态) + /// 0x03 当关闭所有前台文档时,如何发送命令呢?那就是利用跨进程通讯 + /// + /// 后台打开的数据库 + /// 处理后台的任务 + public static void DBTextDeviation(this Database backstageOpenDwg, Action action) + { + var wdb = HostApplicationServices.WorkingDatabase; + if (wdb != null) + { + HostApplicationServices.WorkingDatabase = backstageOpenDwg; + action?.Invoke(); + HostApplicationServices.WorkingDatabase = wdb; + } + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index 9745eb9..d1cc6a8 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -1,752 +1,1100 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 命令行扩展类 +/// +public static class EditorEx { + #region 选择集 /// - /// 命令行扩展类 + /// 选择穿过一个点的对象 /// - public static class EditorEx + /// 命令行对象 + /// 点 + /// 过滤器 + /// 选择集结果类 + public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, SelectionFilter? filter = default) { - #region 选择集 - /// - /// 选择穿过一个点的对象 - /// - /// 命令行对象 - /// 点 - /// 过滤器 - /// 选择集结果类 - public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, SelectionFilter filter = default) - { - return editor.SelectCrossingWindow(point, point, filter); - } + return editor.SelectCrossingWindow(point, point, filter); + } - /// - /// 根据线宽创建图层选择集 - /// - /// 命令行对象 - /// 线宽 - /// 图层选择集 - public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lineWeight) - { - OpFilter filter = new OpEqual(370, lineWeight); + /// + /// 根据线宽创建图层选择集 + /// + /// 命令行对象 + /// 线宽 + /// 图层选择集 + public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lineWeight) + { + OpFilter filter = new OpEqual(370, lineWeight); - var lays = - DBTrans.Top.LayerTable - .GetRecords() - .Where(ltr => ltr.LineWeight == lineWeight) - .Select(ltr => ltr.Name) - .ToArray(); + var lays = + DBTrans.Top.LayerTable + .GetRecords() + .Where(ltr => ltr.LineWeight == lineWeight) + .Select(ltr => ltr.Name) + .ToArray(); - if (lays.Length > 0) - { - filter = - new OpOr - { + if (lays.Length > 0) + { + filter = + new OpOr + { filter, new OpAnd { { 8, string.Join(",", lays) }, { 370, LineWeight.ByLayer } } - }; - } - - PromptSelectionResult res = editor.SelectAll(filter); - return res.Value; + }; } - #endregion - #region Info + PromptSelectionResult res = editor.SelectAll(filter); + return res.Value; + } - /// - /// 带错误提示对话框的打印信息函数 - /// - /// 带格式项的字符串 - /// 指定格式化的对象数组 - public static void StreamMessage(string format, params object[] args) + public static PromptSelectionResult? SSGet(this Editor editor, string? mode = null, SelectionFilter? filter = null, string[]? messages = null, Dictionary? keywords = null) + { + var pso = new PromptSelectionOptions(); + PromptSelectionResult? ss = null; + if (mode is not null) { - StreamMessage(string.Format(format, args)); - }// + mode = mode.ToUpper(); + pso.SinglePickInSpace = mode.Contains(":A"); + pso.RejectObjectsFromNonCurrentSpace = mode.Contains(":C"); + pso.AllowDuplicates = mode.Contains(":D"); + pso.SelectEverythingInAperture = mode.Contains(":E"); + pso.RejectObjectsOnLockedLayers = mode.Contains(":L"); + pso.PrepareOptionalDetails = mode.Contains(":N"); + pso.SingleOnly = mode.Contains(":S"); + pso.RejectPaperspaceViewport = mode.Contains(":V"); + pso.AllowSubSelections = mode.Contains("-A"); + pso.ForceSubSelections = mode.Contains("-F"); - /// - /// 带错误提示对话框的打印信息函数 - /// - /// 打印信息 - public static void StreamMessage(string message) + } + if (messages is not null) { - try - { - if (HasEditor()) - WriteMessage(message); - else - InfoMessageBox(message); - } - catch (System.Exception ex) - { - Message(ex); - } - }// + pso.MessageForAdding = messages[0]; + pso.MessageForRemoval = messages[1]; + } - /// - /// 异常信息对话框 - /// - /// 异常 - public static void Message(System.Exception ex) + if (keywords is not null) { - try - { - System.Windows.Forms.MessageBox.Show( - ex.ToString(), - "Error", - System.Windows.Forms.MessageBoxButtons.OK, - System.Windows.Forms.MessageBoxIcon.Error); - } - catch - { - } - }// + foreach (var keyword in keywords.Keys) + pso.Keywords.Add(keyword); + if (pso.MessageForRemoval is null) + pso.MessageForAdding = "选择对象"; + pso.MessageForAdding += $"[{string.Join(" / ", keywords.Keys.ToArray())}]"; + pso.KeywordInput += (s, e) => { + if (keywords.ContainsKey(e.Input)) + keywords[e.Input].Invoke(); + }; - /// - /// 提示信息对话框 - /// - /// 对话框的标题 - /// 对话框文本 - public static void InfoMessageBox(string caption, string message) + } + try { - try - { - System.Windows.Forms.MessageBox.Show( - message, - caption, - System.Windows.Forms.MessageBoxButtons.OK, - System.Windows.Forms.MessageBoxIcon.Information); - } - catch (System.Exception ex) - { - Message(ex); - } - }// - - /// - /// 提示信息对话框 - /// - /// 对话框的标题 - /// 带格式化项的对话框文本 - /// 指定格式化的对象数组 - public static void InfoMessageBox(string caption, string format, params object[] args) + if (filter is not null) + ss = editor.GetSelection(pso, filter); + else + ss = editor.GetSelection(pso); + } + catch (Autodesk.AutoCAD.Runtime.Exception e) { - InfoMessageBox(caption, string.Format(format, args)); + + editor.WriteMessage($"\nKey is {e.Message}"); } + return ss; + } - /// - /// 提示信息对话框,默认标题为NFox.Cad - /// - /// 对话框文本 - public static void InfoMessageBox(string message) - { - InfoMessageBox("NFox.Cad", message); - }// - /// - /// 提示信息对话框 - /// - /// 带格式化项的对话框文本 - /// 指定格式化的对象数组 - public static void InfoMessageBox(string format, params object[] args) - { - InfoMessageBox(string.Format(format, args)); - }// + /* + * //定义选择集选项 + * var pso = new PromptSelectionOptions + * { + * AllowDuplicates = false, //重复选择 + * }; + * + * //getai遍历全图选择块有用到 + * var dic = new Dictionary() { + * { "Z,全部同名", ()=> { + * getai = BlockHelper.EnumAttIdentical.AllBlockName; + * SendEsc.Esc(); + * }}, + * { "X,动态块显示", ()=> { + * getai = BlockHelper.EnumAttIdentical.Display; + * }}, + * { "V,属性值-默认", ()=> { + * getai = BlockHelper.EnumAttIdentical.DisplayAndTagText; + * }}, + * //允许以下操作,相同的会加入前面的 + * //{ "V,属性值-默认|X,啊啊啊啊", ()=> { + * + * //}}, + * }; + * pso.SsgetAddKeys(dic); + * + * //创建选择集过滤器,只选择块对象 + * var filList = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") }; + * var filter = new SelectionFilter(filList); + * ssPsr = ed.GetSelection(pso, filter); + */ - /// - /// 命令行打印字符串 - /// - /// 字符串 - public static void WriteMessage(string message) + /// + /// 添加选择集关键字和回调 + /// + /// 选择集配置 + /// 关键字,回调委托 + /// + public static void SsgetAddKeys(this PromptSelectionOptions pso, + Dictionary dicActions) + { + Dictionary tmp = new(); + // 后缀名的|号切割,移除掉,组合成新的加入tmp + for (int i = dicActions.Count - 1; i >= 0; i--) { - try + var pair = dicActions.ElementAt(i); + var key = pair.Key; + var keySp = key.Split('|'); + if (keySp.Length < 2) + continue; + + for (int j = 0; j < keySp.Length; j++) { - if (Acceptable()) - Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); + var item = keySp[j]; + // 防止多个后缀通过|符越过词典约束同名 + // 后缀(key)含有,而且Action(value)不同,就把Action(value)累加到后面. + if (dicActions.ContainsKey(item)) + { + if (dicActions[item] != dicActions[key]) + dicActions[item] += dicActions[key]; + } else - return; + tmp.Add(item, dicActions[key]); } - catch (System.Exception ex) + dicActions.Remove(key); + } + + foreach (var item in tmp) + dicActions.Add(item.Key, item.Value); + + //去除关键字重复的,把重复的执行动作移动到前面 + for (int i = 0; i < dicActions.Count; i++) + { + var pair1 = dicActions.ElementAt(i); + var key1 = pair1.Key; + + for (int j = dicActions.Count - 1; j > i; j--) { - Message(ex); + var pair2 = dicActions.ElementAt(j); + var key2 = pair2.Key; + + if (key1.Split(',')[0] == key2.Split(',')[0]) + { + if (dicActions[key1] != dicActions[key2]) + dicActions[key1] += dicActions[key2]; + dicActions.Remove(key2); + } } - }// + } - /// - /// 命令行打印字符串 - /// - /// 带格式化项的文本 - /// 指定格式化的对象数组 - public static void WriteMessage(string format, params object[] args) + foreach (var item in dicActions) { - WriteMessage(string.Format(format, args)); + var keySplitS = item.Key.Split(new string[] { ",", "|" }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < keySplitS.Length; i += 2) + pso.Keywords.Add(keySplitS[i], keySplitS[i], + keySplitS[i + 1] + "(" + keySplitS[i] + ")"); } - /// - /// 判断是否有活动的编辑器对象 - /// - /// 有,没有 - public static bool HasEditor() + //回调的时候我想用Dict的O(1)索引, + //但是此函数内进行new Dictionary() 在函数栈释放的时候,它被释放掉了. + //因此 dicActions 参数的生命周期 + tmp = new(dicActions); + dicActions.Clear(); + foreach (var item in tmp) + dicActions.Add(item.Key.Split(',')[0], item.Value); + + var keyWords = pso.Keywords; + //从选择集命令中显示关键字 + pso.MessageForAdding = keyWords.GetDisplayString(true); + //关键字回调事件 ssget关键字 + pso.KeywordInput += (sender, e) => { + dicActions[e.Input].Invoke(); + }; + } + + + + + + + //#region 即时选择样板 + + ///// + ///// 即时选择,框选更新关键字 + ///// + //public static void SelectTest() + //{ + // Env.Editor.WriteMessage("\n[白嫖工具]--测试"); + // //激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + // //初始化坐标系 + // Env.Editor.CurrentUserCoordinateSystem = Matrix3d.Identity; + + // //创建过滤器 + // var sf = new OpEqual(0, "arc"); + // var pso = new PromptSelectionOptions + // { + // MessageForAdding = "\n请选择对象:" + // }; + + // pso.Keywords.Add("Z"); + // pso.Keywords.Add("X"); + // pso.Keywords.Add("Q"); + // //注册关键字 + // pso.KeywordInput += SelectTest_KeywordInput; + // try + // { + // //用户选择 + // var psr = Env.Editor.GetSelection(pso, sf); + // //处理代码 + + + // } + // catch (Exception ex)//捕获关键字 + // { + // if (ex.Message == "XuError") + // { + // //关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // //重新调用自身 + // ZengLiangYuanJiao(); + // } + // } + // //关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + //} + + ///// + ///// 即时选择 + ///// + ///// + ///// + //private static void SelectTest_SelectionAdded(object sender, SelectionAddedEventArgs e) + //{ + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // using (var tr = new DBTrans()) + // { + // //处理代码 + // for (int i = 0; i < e.AddedObjects.Count; i++) + // { + + + // //处理完移除已处理的对象 + // e.Remove(i); + // } + // } + // //激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + //} + + ///// + ///// 关键字响应 + ///// + ///// + ///// + //private static void SelectTest_KeywordInput(object sender, SelectionTextInputEventArgs e) + //{ + // //获取关键字 + // switch (e.Input) + // { + // case "Z": + // { + // break; + // } + // case "X": + // { + // break; + // } + + // case "Q": + // { + // break; + // } + // } + // //抛出异常,用于更新提示信息 + // throw new ArgumentException("XuError"); + //} + + + //#endregion + #endregion + + #region Info + + /// + /// 带错误提示对话框的打印信息函数 + /// + /// 带格式项的字符串 + /// 指定格式化的对象数组 + public static void StreamMessage(string format, params object[] args) + { + StreamMessage(string.Format(format, args)); + } + + /// + /// 带错误提示对话框的打印信息函数 + /// + /// 打印信息 + public static void StreamMessage(string message) + { + try { - return Application.DocumentManager.MdiActiveDocument is not null - && Application.DocumentManager.Count != 0 - && Application.DocumentManager.MdiActiveDocument.Editor is not null; - }// + if (HasEditor()) + WriteMessage(message); + else + InfoMessageBox(message); + } + catch (System.Exception ex) + { + Message(ex); + } + } - /// - /// 判断是否可以打印字符串 - /// - /// 可以打印,不可以打印 - public static bool Acceptable() + /// + /// 异常信息对话框 + /// + /// 异常 + public static void Message(System.Exception ex) + { + try { - return HasEditor() - && !Application.DocumentManager.MdiActiveDocument.Editor.IsDragging; - }// + System.Windows.Forms.MessageBox.Show( + ex.ToString(), + "Error", + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Error); + } + catch + { + } + } + + /// + /// 提示信息对话框 + /// + /// 对话框的标题 + /// 对话框文本 + public static void InfoMessageBox(string caption, string message) + { + try + { + System.Windows.Forms.MessageBox.Show( + message, + caption, + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Information); + } + catch (System.Exception ex) + { + Message(ex); + } + } - #endregion Info + /// + /// 提示信息对话框 + /// + /// 对话框的标题 + /// 带格式化项的对话框文本 + /// 指定格式化的对象数组 + public static void InfoMessageBox(string caption, string format, params object[] args) + { + InfoMessageBox(caption, string.Format(format, args)); + } + + /// + /// 提示信息对话框,默认标题为NFox.Cad + /// + /// 对话框文本 + public static void InfoMessageBox(string message) + { + InfoMessageBox("NFox.Cad", message); + } - #region 画矢量线 + /// + /// 提示信息对话框 + /// + /// 带格式化项的对话框文本 + /// 指定格式化的对象数组 + public static void InfoMessageBox(string format, params object[] args) + { + InfoMessageBox(string.Format(format, args)); + } - /// - /// 根据点表返回矢量线的列表 - /// - /// 点表 - /// 是否闭合, 为闭合, 为不闭合 - /// - public static List GetLines(IEnumerable pnts, bool isClosed) + /// + /// 命令行打印字符串 + /// + /// 字符串 + public static void WriteMessage(string message) + { + try + { + if (Acceptable()) + Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); + else + return; + } + catch (System.Exception ex) { + Message(ex); + } + } - var itor = pnts.GetEnumerator(); - if (!itor.MoveNext()) - return new List(); + /// + /// 命令行打印字符串 + /// + /// 带格式化项的文本 + /// 指定格式化的对象数组 + public static void WriteMessage(string format, params object[] args) + { + WriteMessage(string.Format(format, args)); + } - List values = new(); + /// + /// 判断是否有活动的编辑器对象 + /// + /// 有,没有 + public static bool HasEditor() + { + return Application.DocumentManager.MdiActiveDocument is not null + && Application.DocumentManager.Count != 0 + && Application.DocumentManager.MdiActiveDocument.Editor is not null; + } - TypedValue tvFirst = new((int)LispDataType.Point2d, itor.Current); - TypedValue tv1; - TypedValue tv2 = tvFirst; + /// + /// 判断是否可以打印字符串 + /// + /// 可以打印,不可以打印 + public static bool Acceptable() + { + return HasEditor() + && !Application.DocumentManager.MdiActiveDocument.Editor.IsDragging; + } - while (itor.MoveNext()) - { - tv1 = tv2; - tv2 = new TypedValue((int)LispDataType.Point2d, itor.Current); - values.Add(tv1); - values.Add(tv2); - } + #endregion Info - if (isClosed) - { - values.Add(tv2); - values.Add(tvFirst); - } + #region 画矢量线 - return values; - } + /// + /// 根据点表返回矢量线的列表 + /// + /// 点表 + /// 是否闭合, 为闭合, 为不闭合 + /// + public static List GetLines(IEnumerable pnts, bool isClosed) + { - /// - /// 画矢量线 - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - /// 是否闭合, 为闭合, 为不闭合 - public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex, bool isClosed) - { - var rlst = - new LispList { { LispDataType.Int16, colorIndex } }; - rlst.AddRange(GetLines(pnts, isClosed)); - editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); - } + var itor = pnts.GetEnumerator(); + if (!itor.MoveNext()) + return new List(); + + List values = new(); + + TypedValue tvFirst = new((int)LispDataType.Point2d, itor.Current); + TypedValue tv1; + TypedValue tv2 = tvFirst; - /// - /// 画矢量线 - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex) + while (itor.MoveNext()) { - editor.DrawVectors(pnts, colorIndex, false); + tv1 = tv2; + tv2 = new TypedValue((int)LispDataType.Point2d, itor.Current); + values.Add(tv1); + values.Add(tv2); } - /// - /// 用矢量线画近似圆(正多边形) - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - /// 半径 - /// 多边形边的个数 - public static void DrawCircles(this Editor editor, IEnumerable pnts, short colorIndex, double radius, int numEdges) + if (isClosed) { - var rlst = - new LispList { { LispDataType.Int16, colorIndex } }; + values.Add(tv2); + values.Add(tvFirst); + } - foreach (Point2d pnt in pnts) - { - Vector2d vec = Vector2d.XAxis * radius; - double angle = Math.PI * 2 / numEdges; + return values; + } - List tpnts = new() - { - pnt + vec - }; - for (int i = 1; i < numEdges; i++) - { - tpnts.Add(pnt + vec.RotateBy(angle * i)); - } + /// + /// 画矢量线 + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + /// 是否闭合, 为闭合, 为不闭合 + public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex, bool isClosed) + { + var rlst = + new LispList { { LispDataType.Int16, colorIndex } }; + rlst.AddRange(GetLines(pnts, isClosed)); + editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); + } - rlst.AddRange(GetLines(tpnts, true)); - } - editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); - } + /// + /// 画矢量线 + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex) + { + editor.DrawVectors(pnts, colorIndex, false); + } - /// - /// 用矢量线画近似圆(正多边形) - /// - /// 编辑器对象 - /// 点 - /// 颜色码 - /// 半径 - /// 多边形边的个数 - public static void DrawCircle(this Editor editor, Point2d pnt, short colorIndex, double radius, int numEdges) + /// + /// 用矢量线画近似圆(正多边形) + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + /// 半径 + /// 多边形边的个数 + public static void DrawCircles(this Editor editor, IEnumerable pnts, short colorIndex, double radius, int numEdges) + { + var rlst = + new LispList { { LispDataType.Int16, colorIndex } }; + + foreach (Point2d pnt in pnts) { Vector2d vec = Vector2d.XAxis * radius; double angle = Math.PI * 2 / numEdges; - List pnts = new() + List tpnts = new() { pnt + vec }; for (int i = 1; i < numEdges; i++) { - pnts.Add(pnt + vec.RotateBy(angle * i)); + tpnts.Add(pnt + vec.RotateBy(angle * i)); } - editor.DrawVectors(pnts, colorIndex, true); + rlst.AddRange(GetLines(tpnts, true)); } + editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); + } - #endregion - - #region 矩阵 + /// + /// 用矢量线画近似圆(正多边形) + /// + /// 编辑器对象 + /// 点 + /// 颜色码 + /// 半径 + /// 多边形边的个数 + public static void DrawCircle(this Editor editor, Point2d pnt, short colorIndex, double radius, int numEdges) + { + Vector2d vec = Vector2d.XAxis * radius; + double angle = Math.PI * 2 / numEdges; - /// - /// 获取UCS到WCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromUcsToWcs(this Editor editor) + List pnts = new() { - return editor.CurrentUserCoordinateSystem; - } - - /// - /// 获取WCS到UCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromWcsToUcs(this Editor editor) + pnt + vec + }; + for (int i = 1; i < numEdges; i++) { - return editor.CurrentUserCoordinateSystem.Inverse(); + pnts.Add(pnt + vec.RotateBy(angle * i)); } - /// - /// 获取MDCS(模型空间)到WCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromMDcsToWcs(this Editor editor) - { - Matrix3d mat; - using ViewTableRecord vtr = editor.GetCurrentView(); - mat = Matrix3d.PlaneToWorld(vtr.ViewDirection); - mat = Matrix3d.Displacement(vtr.Target - Point3d.Origin) * mat; - return Matrix3d.Rotation(-vtr.ViewTwist, vtr.ViewDirection, vtr.Target) * mat; - } + editor.DrawVectors(pnts, colorIndex, true); + } - /// - /// 获取WCS到MDCS(模型空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) - { - return editor.GetMatrixFromMDcsToWcs().Inverse(); - } + #endregion - /// - /// 获取MDCS(模型空间)到PDCS(图纸空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) - { - if ((short)Env.GetVar("TILEMODE") == 1) - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Espace papier uniquement"); + #region 矩阵 + + /// + /// 获取UCS到WCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromUcsToWcs(this Editor editor) + { + return editor.CurrentUserCoordinateSystem; + } + + /// + /// 获取WCS到UCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromWcsToUcs(this Editor editor) + { + return editor.CurrentUserCoordinateSystem.Inverse(); + } + + /// + /// 获取MDCS(模型空间)到WCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromMDcsToWcs(this Editor editor) + { + Matrix3d mat; + using ViewTableRecord vtr = editor.GetCurrentView(); + mat = Matrix3d.PlaneToWorld(vtr.ViewDirection); + mat = Matrix3d.Displacement(vtr.Target - Point3d.Origin) * mat; + return Matrix3d.Rotation(-vtr.ViewTwist, vtr.ViewDirection, vtr.Target) * mat; + } + + /// + /// 获取WCS到MDCS(模型空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) + { + return editor.GetMatrixFromMDcsToWcs().Inverse(); + } + + /// + /// 获取MDCS(模型空间)到PDCS(图纸空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) + { + if ((short)Env.GetVar("TILEMODE") == 1) + throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Espace papier uniquement"); - Database db = editor.Document.Database; - Matrix3d mat; - using (Transaction tr = db.TransactionManager.StartTransaction()) + Database db = editor.Document.Database; + Matrix3d mat; + using (Transaction tr = db.TransactionManager.StartTransaction()) + { + Viewport? vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; + if (vp?.Number == 1) { - Viewport vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - if (vp.Number == 1) + try { - try - { - editor.SwitchToModelSpace(); - vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - editor.SwitchToPaperSpace(); - } - catch - { - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Aucun fenêtre active"); - } + editor.SwitchToModelSpace(); + vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; + editor.SwitchToPaperSpace(); + } + catch + { + throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Aucun fenêtre active"); } - Point3d vCtr = new(vp.ViewCenter.X, vp.ViewCenter.Y, 0.0); - mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); - mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; - mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; - tr.Commit(); } - return mat; + Point3d vCtr = new(vp!.ViewCenter.X, vp.ViewCenter.Y, 0.0); + mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); + mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; + mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; + tr.Commit(); } + return mat; + } + + /// + /// 获取PDCS(图纸空间)到MDCS(模型空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromPDcsToMDcs(this Editor editor) + { + return editor.GetMatrixFromMDcsToPDcs().Inverse(); + } - /// - /// 获取PDCS(图纸空间)到MDCS(模型空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromPDcsToMDcs(this Editor editor) + /// + /// 获取变换矩阵 + /// + /// 命令行对象 + /// 源坐标系 + /// 目标坐标系 + /// 变换矩阵 + public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, CoordinateSystemCode to) + { +#if ac2009 + switch (from) { - return editor.GetMatrixFromMDcsToPDcs().Inverse(); - } + case CoordinateSystemCode.Wcs: + switch (to) + { + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.PDcs: + throw new Autodesk.AutoCAD.Runtime.Exception( + ErrorStatus.InvalidInput, + "To be used only with DCS"); + } + break; + case CoordinateSystemCode.Ucs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromUcsToWcs(); - /// - /// 获取变换矩阵 - /// - /// 命令行对象 - /// 源坐标系 - /// 目标坐标系 - /// 变换矩阵 - public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, CoordinateSystemCode to) + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); + + case CoordinateSystemCode.PDcs: + throw new Autodesk.AutoCAD.Runtime.Exception( + ErrorStatus.InvalidInput, + "To be used only with DCS"); + } + break; + case CoordinateSystemCode.MDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.PDcs: + return editor.GetMatrixFromMDcsToPDcs(); + } + break; + case CoordinateSystemCode.PDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + throw new Autodesk.AutoCAD.Runtime.Exception( + ErrorStatus.InvalidInput, + "To be used only with DCS"); + case CoordinateSystemCode.Ucs: + throw new Autodesk.AutoCAD.Runtime.Exception( + ErrorStatus.InvalidInput, + "To be used only with DCS"); + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromPDcsToMDcs(); + } + break; + } + return Matrix3d.Identity; +#else + return (from, to) switch { -#if ac2009 - switch (from) - { - case CoordinateSystemCode.Wcs: - switch (to) - { - case CoordinateSystemCode.Ucs: - return editor.GetMatrixFromWcsToUcs(); - - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromMDcsToWcs(); - - case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - } - break; - case CoordinateSystemCode.Ucs: - switch (to) - { - case CoordinateSystemCode.Wcs: - return editor.GetMatrixFromUcsToWcs(); - - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); - - case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - } - break; - case CoordinateSystemCode.MDcs: - switch (to) - { - case CoordinateSystemCode.Wcs: - return editor.GetMatrixFromMDcsToWcs(); - - case CoordinateSystemCode.Ucs: - return editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(); - - case CoordinateSystemCode.PDcs: - return editor.GetMatrixFromMDcsToPDcs(); - } - break; - case CoordinateSystemCode.PDcs: - switch (to) - { - case CoordinateSystemCode.Wcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - case CoordinateSystemCode.Ucs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromPDcsToMDcs(); - } - break; - } - return Matrix3d.Identity; -#elif ac2013 - return (from, to) switch - { - (CoordinateSystemCode.Wcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromWcsToUcs(), - (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromMDcsToWcs(), - (CoordinateSystemCode.Ucs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromUcsToWcs(), - (CoordinateSystemCode.Ucs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromMDcsToWcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), - (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), - (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) - or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput,"To be used only with DCS"), - (_, _) => Matrix3d.Identity - }; + (CoordinateSystemCode.Wcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromWcsToUcs(), + (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromWcsToMDcs(), + (CoordinateSystemCode.Ucs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromUcsToWcs(), + (CoordinateSystemCode.Ucs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromMDcsToWcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), + (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), + (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) + or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "To be used only with DCS"), + (_, _) => Matrix3d.Identity + }; #endif - } + } - #endregion + #endregion - #region 缩放 + #region 缩放 - /// - /// 缩放窗口范围 - /// - /// 命令行对象 - /// 窗口左下点 - /// 窗口右上点 - public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint) + /// + /// 缩放窗口范围 + /// + /// 命令行对象 + /// 窗口左下点 + /// 窗口右上点 + public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint) + { + ViewTableRecord cvtr = ed.GetCurrentView(); + ViewTableRecord vtr = new(); + vtr.CopyFrom(cvtr); + + Point3d[] oldpnts = new Point3d[] { minPoint, maxPoint }; + Point3d[] pnts = new Point3d[8]; + Point3d[] dpnts = new Point3d[8]; + for (int i = 0; i < 2; i++) { - ViewTableRecord cvtr = ed.GetCurrentView(); - ViewTableRecord vtr = new(); - vtr.CopyFrom(cvtr); - - Point3d[] oldpnts = new Point3d[] { minPoint, maxPoint }; - Point3d[] pnts = new Point3d[8]; - Point3d[] dpnts = new Point3d[8]; - for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) { - for (int j = 0; j < 2; j++) + for (int k = 0; k < 2; k++) { - for (int k = 0; k < 2; k++) - { - int n = i * 4 + j * 2 + k; - pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); - dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); - } + int n = i * 4 + j * 2 + k; + pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); + dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); } } - double xmin, xmax, ymin, ymax; - xmin = xmax = dpnts[0][0]; - ymin = ymax = dpnts[0][1]; - for (int i = 1; i < 8; i++) - { - xmin = Math.Min(xmin, dpnts[i][0]); - xmax = Math.Max(xmax, dpnts[i][0]); - ymin = Math.Min(ymin, dpnts[i][1]); - ymax = Math.Max(ymax, dpnts[i][1]); - } - - vtr.Width = xmax - xmin; - vtr.Height = ymax - ymin; - vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2).Convert2d(new Plane()); - - ed.SetCurrentView(vtr); - ed.Regen(); } - - /// - /// 缩放窗口范围 - /// - /// 命令行对象 - /// 窗口范围点 - public static void ZoomWindow(this Editor ed, Extents3d ext) + double xmin, xmax, ymin, ymax; + xmin = xmax = dpnts[0][0]; + ymin = ymax = dpnts[0][1]; + for (int i = 1; i < 8; i++) { - ZoomWindow(ed, ext.MinPoint, ext.MaxPoint); + xmin = Math.Min(xmin, dpnts[i][0]); + xmax = Math.Max(xmax, dpnts[i][0]); + ymin = Math.Min(ymin, dpnts[i][1]); + ymax = Math.Max(ymax, dpnts[i][1]); } - /// - /// 缩放比例 - /// - /// 命令行对象 - /// 中心点 - /// 窗口宽 - /// 窗口高 - public static void Zoom(this Editor ed, Point3d CenPt, double width, double height) - { - using ViewTableRecord view = ed.GetCurrentView(); - view.Width = width; - view.Height = height; - view.CenterPoint = new Point2d(CenPt.X, CenPt.Y); - ed.SetCurrentView(view);//更新当前视图 - } + vtr.Width = xmax - xmin; + vtr.Height = ymax - ymin; + vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2).Convert2d(new Plane()); - /// - ///缩放窗口范围 - /// - /// 命令行对象 - /// 第一点 - /// 对角点 - /// 偏移距离 - public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double offsetDist = 0.00) - { - Extents3d extents = new(); - extents.AddPoint(lpt); - extents.AddPoint(rpt); - rpt = extents.MaxPoint + new Vector3d(offsetDist, offsetDist, 0); - lpt = extents.MinPoint - new Vector3d(offsetDist, offsetDist, 0); - Vector3d ver = rpt - lpt; - ed.Zoom(lpt + ver / 2, ver.X, ver.Y); - } + ed.SetCurrentView(vtr); + ed.Regen(); + } - /// - /// 动态缩放 - /// - /// 命令行对象 - /// 偏移距离 - public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) - { - var db = ed.Document.Database; - db.UpdateExt(true); - ed.ZoomWindow(db.Extmax, db.Extmin, offsetDist); - } + /// + /// 缩放窗口范围 + /// + /// 命令行对象 + /// 窗口范围点 + public static void ZoomWindow(this Editor ed, Extents3d ext) + { + ZoomWindow(ed, ext.MinPoint, ext.MaxPoint); + } - /// - /// 根据实体对象的范围显示视图 - /// - /// 命令行对象 - /// Entity对象 - /// 偏移距离 - public static void ZoomObject(this Editor ed, Entity ent, double offsetDist = 0.00) - { - Extents3d ext = ent.GeometricExtents; - ed.ZoomWindow(ext.MinPoint, ext.MaxPoint, offsetDist); - } + /// + /// 缩放比例 + /// + /// 命令行对象 + /// 中心点 + /// 窗口宽 + /// 窗口高 + public static void Zoom(this Editor ed, Point3d CenPt, double width, double height) + { + using ViewTableRecord view = ed.GetCurrentView(); + view.Width = width; + view.Height = height; + view.CenterPoint = new Point2d(CenPt.X, CenPt.Y); + ed.SetCurrentView(view);//更新当前视图 + } + + /// + ///缩放窗口范围 + /// + /// 命令行对象 + /// 第一点 + /// 对角点 + /// 偏移距离 + public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double offsetDist = 0.00) + { + Extents3d extents = new(); + extents.AddPoint(lpt); + extents.AddPoint(rpt); + rpt = extents.MaxPoint + new Vector3d(offsetDist, offsetDist, 0); + lpt = extents.MinPoint - new Vector3d(offsetDist, offsetDist, 0); + Vector3d ver = rpt - lpt; + ed.Zoom(lpt + ver / 2, ver.X, ver.Y); + } + + + /// + /// 获取有效的数据库范围 + /// + /// 数据库 + /// 容差值:图元包围盒会超过数据库边界,用此参数扩大边界 + /// + public static Extents3d? GetValidExtents3d(this Database db, double extention = 1e-6) + { + db.UpdateExt(true);//更新当前模型空间的范围 + var ve = new Vector3d(extention, extention, extention); + // 数据库没有图元的时候,min是大,max是小,导致新建出错 + // 数据如下: + // min.X == 1E20 && min.Y == 1E20 && min.Z == 1E20 && + // max.X == -1E20 && max.Y == -1E20 && max.Z == -1E20) + var a = db.Extmin; + var b = db.Extmax; + if (a.X < b.X && a.Y < b.Y) + return new Extents3d(db.Extmin - ve, db.Extmax + ve); + + return null; + } + + /// + /// 动态缩放 + /// + /// 命令行对象 + /// 偏移距离 + public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) + { + Database db = ed.Document.Database; + // db.UpdateExt(true); //GetValidExtents3d内提供了 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + ed.ZoomWindow(Point3d.Origin, new Point3d(1, 1, 0), offsetDist); + else + ed.ZoomWindow(db.Extmin, db.Extmax, offsetDist); + } + + /// + /// 根据实体对象的范围显示视图 + /// + /// 命令行对象 + /// Entity对象 + /// 偏移距离 + public static void ZoomObject(this Editor ed, Entity ent, double offsetDist = 0.00) + { + Extents3d ext = ent.GeometricExtents; + ed.ZoomWindow(ext.MinPoint, ext.MaxPoint, offsetDist); + } - #endregion + #endregion - #region Get交互类 + #region Get交互类 - /// - /// 获取Point - /// - /// 命令行对象 - /// 提示信息 - /// 提示使用的基点 - /// - public static PromptPointResult GetPoint(this Editor ed, string Message, Point3d BasePoint) + /// + /// 获取Point + /// + /// 命令行对象 + /// 提示信息 + /// 提示使用的基点 + /// + public static PromptPointResult GetPoint(this Editor ed, string Message, Point3d BasePoint) + { + PromptPointOptions ptOp = new(Message) { - PromptPointOptions ptOp = new(Message) - { - BasePoint = BasePoint, - UseBasePoint = true - }; - return ed.GetPoint(ptOp); - } + BasePoint = BasePoint, + UseBasePoint = true + }; + return ed.GetPoint(ptOp); + } - /// - /// 获取double值 - /// - /// 命令行对象 - /// 提示信息 - /// double默认值 - /// - public static PromptDoubleResult GetDouble(this Editor ed, string Message, double DefaultValue = 1.0) + /// + /// 获取double值 + /// + /// 命令行对象 + /// 提示信息 + /// double默认值 + /// + public static PromptDoubleResult GetDouble(this Editor ed, string Message, double DefaultValue = 1.0) + { + PromptDoubleOptions douOp = new(Message) { - PromptDoubleOptions douOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetDouble(douOp); - } + DefaultValue = DefaultValue + }; + return ed.GetDouble(douOp); + } - /// - /// 获取int值 - /// - /// 命令行对象 - /// 提示信息 - /// double默认值 - /// - public static PromptIntegerResult GetInteger(this Editor ed, string Message, int DefaultValue = 1) + /// + /// 获取int值 + /// + /// 命令行对象 + /// 提示信息 + /// double默认值 + /// + public static PromptIntegerResult GetInteger(this Editor ed, string Message, int DefaultValue = 1) + { + PromptIntegerOptions douOp = new(Message) { - PromptIntegerOptions douOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetInteger(douOp); - } + DefaultValue = DefaultValue + }; + return ed.GetInteger(douOp); + } - /// - /// 获取string值 - /// - /// 命令行对象 - /// 提示信息 - /// string默认值 - /// - public static PromptResult GetString(this Editor ed, string Message, string DefaultValue = "") + /// + /// 获取string值 + /// + /// 命令行对象 + /// 提示信息 + /// string默认值 + /// + public static PromptResult GetString(this Editor ed, string Message, string DefaultValue = "") + { + PromptStringOptions strOp = new(Message) { - PromptStringOptions strOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetString(strOp); - } + DefaultValue = DefaultValue + }; + return ed.GetString(strOp); + } - #endregion Get交互类 + #endregion - #region 执行lisp + #region 执行lisp +#if NET35 + [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] +#else + [DllImport("accore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] +#endif + static extern int AcedInvoke(IntPtr args, out IntPtr result); -#if ac2009 - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] #else - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); + // 高版本此接口不能使用lisp(command "xx"),但是可以直接在自动运行接口上 + [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] +#endif + [System.Security.SuppressUnmanagedCodeSecurity]//初始化默认值 + static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - [DllImport("accore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "ads_queueexpr")] +#else + [DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "ads_queueexpr")] #endif - /// - /// 发送lisp语句字符串到cad执行 - /// - /// 编辑器对象 - /// lisp语句 - /// 缓冲结果,返回值 - public static ResultBuffer RunLisp(this Editor ed, string arg) - { - AcedEvaluateLisp(arg, out IntPtr rb); - try - { - if (rb != IntPtr.Zero) - return DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; - } - catch - { } - return null; + static extern int Ads_queueexpr(string strExpr); + + public enum RunLispFlag : byte + { + AdsQueueexpr = 1, + AcedEvaluateLisp = 2, + SendStringToExecute = 4, + } + + /// + /// 发送lisp语句字符串到cad执行 + /// + /// 编辑器对象 + /// lisp语句 + /// 运行方式 + /// 缓冲结果,返回值 +#pragma warning disable IDE0060 // 删除未使用的参数 + public static ResultBuffer? RunLisp(this Editor ed, + string lispCode, + RunLispFlag flag = RunLispFlag.AdsQueueexpr) +#pragma warning restore IDE0060 // 删除未使用的参数 + { + /* + * 测试命令: + * [CommandMethod("CmdTest_RunLisp")] + * public static void CmdTest_RunLisp() + * { + * var res = SendLisp.RunLisp("(setq abc 10)"); + * } + * 调用方式: + * (command "CmdTest_RunLisp1") + * bug说明: + * AcedEvaluateLisp接口在高版本调用时候没有运行成功,使得 !abc 没有值 + * 经过测试,cad08调用成功,此bug与CommandFlags无关 + * 解决方案: + * 0x01 用异步接口,但是这样是显式调用了: + * (setq thisdrawing (vla-get-activedocument (vlax-get-acad-object)))(vla-SendCommand thisdrawing "CmdTest_RunLisp1 ") + * 0x02 使用 Ads_queueexpr 接口 + */ + if ((flag & RunLispFlag.AdsQueueexpr) == RunLispFlag.AdsQueueexpr) + { + // 这个在08/12发送lisp不会出错,但是发送bo命令出错了. + // 0x01 设置 CommandFlags.Session 可以同步, + // 0x02 自执行发送lisp都是异步,(用来发送 含有(command)的lisp的) + _ = Ads_queueexpr(lispCode + "\n"); + } + if ((flag & RunLispFlag.AcedEvaluateLisp) == RunLispFlag.AcedEvaluateLisp) + { + _ = AcedEvaluateLisp(lispCode, out IntPtr rb); + if (rb != IntPtr.Zero) + return DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; + } + if ((flag & RunLispFlag.SendStringToExecute) == RunLispFlag.SendStringToExecute) + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + doc.SendStringToExecute(lispCode + "\n", false, false, false); } - #endregion 执行lisp + return null; } -} + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs index c14b566..9f9fc55 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs @@ -1,409 +1,415 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.DatabaseServices.Filters; -using Autodesk.AutoCAD.Geometry; +namespace IFoxCAD.Cad; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace IFoxCAD.Cad +/// +/// 实体图元类扩展函数 +/// +public static class EntityEx { + #region 实体刷新 /// - /// 实体图元类扩展函数 + /// 刷新实体显示 /// - public static class EntityEx + /// 实体对象 + public static void Flush(this Entity entity, DBTrans? trans = null) { - #region 实体刷新 - /// - /// 刷新实体显示 - /// - /// 实体对象 - public static void Flush(this Entity entity, Transaction trans = null) - { - if (entity is null) - { - throw new ArgumentNullException(nameof(entity)); - } - if (trans is null) - { - trans = DBTrans.Top.Transaction; - } - entity.RecordGraphicsModified(true); - trans.TransactionManager.QueueForGraphicsFlush(); - } + //if (entity is null) + //{ + // throw new ArgumentNullException(nameof(entity)); + //} + trans ??= DBTrans.Top; + entity.RecordGraphicsModified(true); + trans.Transaction.TransactionManager.QueueForGraphicsFlush(); + trans.Document?.TransactionManager.FlushGraphics(); + } - /// - /// 刷新实体显示 - /// - /// 实体id - public static void Flush(this ObjectId id) => Flush(DBTrans.Top.GetObject(id)); - #endregion - - #region 多段线端点坐标 - /// - /// 获取二维多段线的端点坐标 - /// - /// 二维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline2d pl2d, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl2d) - { - yield return ((Vertex2d)tr.GetObject(id, OpenMode.ForRead)).Position; - } - } + /// + /// 刷新实体显示 + /// + /// 实体id + public static void Flush(this ObjectId id) => Flush(DBTrans.Top.GetObject(id)!); + #endregion - /// - /// 获取三维多段线的端点坐标 - /// - /// 三维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline3d pl3d, Transaction tr = null) + #region 多段线端点坐标 + /// + /// 获取二维多段线的端点坐标 + /// + /// 二维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline2d pl2d, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + foreach (ObjectId id in pl2d) { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl3d) - { - yield return ((PolylineVertex3d)tr.GetObject(id, OpenMode.ForRead)).Position; - } + yield return tr.GetObject(id)!.Position; } + } - /// - /// 获取多段线的端点坐标 - /// - /// 多段线 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline pl) + /// + /// 获取三维多段线的端点坐标 + /// + /// 三维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline3d pl3d, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + foreach (ObjectId id in pl3d) { - return - Enumerable - .Range(0, pl.NumberOfVertices) - .Select(i => pl.GetPoint3dAt(i)); + yield return tr.GetObject(id, OpenMode.ForRead)!.Position; } - #endregion + } - #region TransformBy + /// + /// 获取多段线的端点坐标 + /// + /// 多段线 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline pl) + { + return + Enumerable + .Range(0, pl.NumberOfVertices) + .Select(i => pl.GetPoint3dAt(i)); + } + #endregion - /// - /// 移动实体 - /// - /// 实体 - /// 基点 - /// 目标点 - public static void Move(this Entity ent, Point3d from, Point3d to) - { - ent.TransformBy(Matrix3d.Displacement(to - from)); - } + #region 实体线性变换 - /// - /// 缩放实体 - /// - /// 实体 - /// 缩放基点坐标 - /// 缩放比例 - public static void Scale(this Entity ent, Point3d center, double scaleValue) - { - ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); - } + /// + /// 移动实体 + /// + /// 实体 + /// 基点 + /// 目标点 + public static void Move(this Entity ent, Point3d from, Point3d to) + { + ent.TransformBy(Matrix3d.Displacement(to - from)); + } - /// - /// 旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - /// 旋转平面的法向矢量 - public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) - { - ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); - } + /// + /// 缩放实体 + /// + /// 实体 + /// 缩放基点坐标 + /// 缩放比例 + public static void Scale(this Entity ent, Point3d center, double scaleValue) + { + ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); + } - /// - /// 在XY平面内旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - public static void Rotation(this Entity ent, Point3d center, double angle) - { - ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); - } + /// + /// 旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + /// 旋转平面的法向矢量 + public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) + { + ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); + } - /// - /// 按对称轴镜像实体 - /// - /// 实体 - /// 对称轴起点 - /// 对称轴终点 - public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) - { - ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); - } + /// + /// 在XY平面内旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + public static void Rotation(this Entity ent, Point3d center, double angle) + { + ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); + } - /// - /// 按对称面镜像实体 - /// - /// 实体 - /// 对称平面 - public static void Mirror(this Entity ent, Plane plane) - { - ent.TransformBy(Matrix3d.Mirroring(plane)); - } + /// + /// 按对称轴镜像实体 + /// + /// 实体 + /// 对称轴起点 + /// 对称轴终点 + public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) + { + ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); + } - /// - /// 按对称点镜像实体 - /// - /// 实体 - /// 对称点 - public static void Mirror(this Entity ent, Point3d basePoint) - { - ent.TransformBy(Matrix3d.Mirroring(basePoint)); - } + /// + /// 按对称面镜像实体 + /// + /// 实体 + /// 对称平面 + public static void Mirror(this Entity ent, Plane plane) + { + ent.TransformBy(Matrix3d.Mirroring(plane)); + } - #endregion + /// + /// 按对称点镜像实体 + /// + /// 实体 + /// 对称点 + public static void Mirror(this Entity ent, Point3d basePoint) + { + ent.TransformBy(Matrix3d.Mirroring(basePoint)); + } + + #endregion - #region 实体范围 - /// - /// 获取实体集合的范围 - /// - /// 实体迭代器 - /// 实体集合的范围 - public static Extents3d GetExtents(this IEnumerable ents) + #region 实体范围 + /// + /// 获取实体集合的范围 + /// + /// 实体迭代器 + /// 实体集合的范围 + public static Extents3d GetExtents(this IEnumerable ents) + { + var ext = new Extents3d(); + foreach (var item in ents) { - var it = ents.GetEnumerator(); - var ext = it.Current.GeometricExtents; - while (it.MoveNext()) - ext.AddExtents(it.Current.GeometricExtents); - return ext; + ext.AddExtents(item.GeometricExtents); } - #endregion + return ext; + } + #endregion - #region 单行文字 + #region 单行文字 - /// - /// 更正单行文字的镜像属性 - /// - /// 单行文字 - public static void ValidateMirror(this DBText txt) - { - if (!txt.Database.Mirrtext) - { - txt.IsMirroredInX = false; - txt.IsMirroredInY = false; - } - } - #endregion - - #region 多行文字 - - /// - /// 炸散多行文字 - /// - /// 存储多行文字炸散之后的对象的类型 - /// 多行文字 - /// 存储对象变量 - /// 回调函数,用于处理炸散之后的对象 - /// MTextFragment -- 多行文字炸散后的对象 - /// MTextFragmentCallbackStatus -- 回调函数处理的结果 - /// - public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) + /// + /// 更正单行文字的镜像属性 + /// + /// 单行文字 + public static void ValidateMirror(this DBText txt) + { + if (!txt.Database.Mirrtext) { - mt.ExplodeFragments((f, o) => mTextFragmentCallback(f, (T)o), obj); + txt.IsMirroredInX = false; + txt.IsMirroredInY = false; } + } + #endregion - /// - /// 获取多行文字的无格式文本 - /// - /// 多行文字 - /// 文本 - public static string GetUnFormatString(this MText mt) - { - List strs = new(); - mt.ExplodeFragments( - strs, - (f, o) => { - o.Add(f.Text); - return MTextFragmentCallbackStatus.Continue; - }); - return string.Join("", strs.ToArray()); - } - #endregion - - #region 圆弧 - - /// - /// 根据圆心、起点和终点来创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 终点 - /// 圆弧 - public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); - //计算起始和终止角度 - arc.StartAngle = startVector.Angle; - arc.EndAngle = endVector.Angle; - return arc; - } - /// - /// 三点法创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆弧上的点 - /// 终点 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); - //将几何类圆弧对象的圆心和半径赋值给圆弧 -#if ac2009 - return geArc.ToArc(); -#elif ac2013 - return (Arc)Curve.CreateFromGeCurve(geArc); + #region 多行文字 + + /// + /// 炸散多行文字 + /// + /// 存储多行文字炸散之后的对象的类型 + /// 多行文字 + /// 存储对象变量 + /// 回调函数,用于处理炸散之后的对象 + /// 多行文字炸散后的对象 + /// 回调函数处理的结果 + /// + public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) + { + mt.ExplodeFragments((f, o) => mTextFragmentCallback(f, (T)o), obj); + } + + /// + /// 获取多行文字的无格式文本 + /// + /// 多行文字 + /// 文本 + public static string GetUnFormatString(this MText mt) + { + List strs = new(); + mt.ExplodeFragments( + strs, + (f, o) => { + o.Add(f.Text); + return MTextFragmentCallbackStatus.Continue; + }); + return string.Join("", strs.ToArray()); + } + #endregion + + #region 圆弧 + + /// + /// 根据圆心、起点、终点来创建圆弧(二维) + /// + /// 起点 + /// 圆心 + /// 终点 + /// 圆弧 + public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); + //计算起始和终止角度 + arc.StartAngle = startVector.Angle; + arc.EndAngle = endVector.Angle; + return arc; + } + /// + /// 三点法创建圆弧(二维) + /// + /// 圆弧对象 + /// 起点 + /// 圆弧上的点 + /// 终点 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) + { + //创建一个几何类的圆弧对象 + CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); + //将几何类圆弧对象的圆心和半径赋值给圆弧 +#if NET35 + return (Arc)geArc.ToCurve(); +#else + return (Arc)Curve.CreateFromGeCurve(geArc); #endif - } + } - /// - /// 根据起点、圆心和圆弧角度创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 圆弧角度 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - arc.StartAngle = startVector.Angle; - arc.EndAngle = startVector.Angle + angle; - return arc; - } + /// + /// 根据起点、圆心和圆弧角度创建圆弧(二维) + /// + /// 圆弧对象 + /// 起点 + /// 圆心 + /// 圆弧角度 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + arc.StartAngle = startVector.Angle; + arc.EndAngle = startVector.Angle + angle; + return arc; + } - #endregion + #endregion - #region 圆 + #region 圆 - /// - /// 两点创建圆(两点中点为圆心) - /// - /// 起点 - /// 终点 - /// - public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) - { - Circle circle = new(); - circle.Center = startPoint.GetMidPointTo(endPoint); - circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; - return circle; - } + /// + /// 两点创建圆(两点中点为圆心) + /// + /// 起点 + /// 终点 + /// + public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) + { + Circle circle = new(); + circle.SetDatabaseDefaults(); + circle.Center = startPoint.GetMidPointTo(endPoint); + circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; + return circle; + } - /// - /// 三点法创建圆(失败则返回Null) - /// - /// 第一点 - /// 第二点 - /// 第三点 - /// - public static Circle CreateCircle(Point3d pt1, Point3d PointV, Point3d pt3) - { - //先判断三点是否共线,得到pt1点指向PointV、PointV点的矢量 - Vector3d va = pt1.GetVectorTo(PointV); - Vector3d vb = pt1.GetVectorTo(pt3); - //如两矢量夹角为0或180度(π弧度),则三点共线. - if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) - { - return null; - } - else - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(pt1, PointV, pt3); - geArc.ToCircle(); - return geArc.ToCircle(); - } - } + /// + /// 三点法创建圆(失败则返回Null) + /// + /// 第一点 + /// 第二点 + /// 第三点 + /// + public static Circle? CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) + { + //先判断三点是否共线,得到pt1点指向pt2、pt2点的矢量 + Vector3d va = pt1.GetVectorTo(pt2); + Vector3d vb = pt1.GetVectorTo(pt3); + //如两矢量夹角为0或180度(π弧度),则三点共线. + if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) + return null; + + //创建一个几何类的圆弧对象 + CircularArc3d geArc = new(pt1, pt2, pt3); + geArc.ToCircle(); + return geArc.ToCircle(); + } + + /// + /// 通过圆心,半径绘制圆形 + /// + /// 圆心 + /// 半径 + /// 图形的ObjectId + public static Circle? CreateCircle(Point3d center, double radius, double vex = 0, double vey = 0, double vez = 1) + { + return new Circle(center, new Vector3d(vex, vey, vez), radius);//平面法向量XY方向 + } - #endregion + #endregion - #region 块参照 + #region 块参照 - #region 裁剪块参照 + #region 裁剪块参照 - private const string filterDictName = "ACAD_FILTER"; - private const string spatialName = "SPATIAL"; + private const string filterDictName = "ACAD_FILTER"; + private const string spatialName = "SPATIAL"; - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 裁剪多边形点表 - public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 裁剪多边形点表 + public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + var pts = + pt3ds + .Select(p => p.TransformBy(mat)) + .Select(p => new Point2d(p.X, p.Y)) + .ToCollection(); + + SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); + using SpatialFilter sf = new() { Definition = sfd }; + var dict = bref.GetXDictionary()!.GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + //SetToDictionary(dict, spatialName, sf); + } + + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 第一角点 + /// 第二角点 + public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d pt2) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + pt1 = pt1.TransformBy(mat); + pt2 = pt2.TransformBy(mat); + + Point2dCollection pts = new() { - if (bref is null) - { - throw new ArgumentNullException(nameof(bref)); - } - if (pt3ds is null) - { - throw new ArgumentNullException(nameof(pt3ds)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - var pts = - pt3ds - .Select(p => p.TransformBy(mat)) - .Select(p => new Point2d(p.X, p.Y)) - .ToCollection(); - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); - } + new Point2d(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), + new Point2d(Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y)) + }; + + SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); + using SpatialFilter sf = new() { Definition = sfd }; + var dict = bref.GetXDictionary()!.GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + //SetToDictionary(dict, spatialName, sf); + } + #endregion + + /// + /// 更新动态块属性值 + /// + /// 动态块 + /// 属性值字典 + public static void ChangeBlockProperty(this BlockReference blockReference, + Dictionary propertyNameValues) + { + if (!blockReference.IsDynamicBlock) + return; - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 第一角点 - /// 第二角点 - public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d PointV) + using (blockReference.ForWrite()) { - if (bref is null) - { - throw new ArgumentNullException(nameof(bref)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - pt1 = pt1.TransformBy(mat); - PointV = PointV.TransformBy(mat); - Point2dCollection pts = new() - { - new Point2d(Math.Min(pt1.X, PointV.X), Math.Min(pt1.Y, PointV.Y)), - new Point2d(Math.Max(pt1.X, PointV.X), Math.Max(pt1.Y, PointV.Y)) - }; - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); + foreach (DynamicBlockReferenceProperty item in blockReference.DynamicBlockReferencePropertyCollection) + if (propertyNameValues.ContainsKey(item.PropertyName)) + item.Value = propertyNameValues[item.PropertyName]; } - #endregion - #endregion } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs b/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs index 55b108c..fc0fbaf 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs @@ -1,80 +1,107 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 坐标系类型枚举 +/// +public enum CoordinateSystemCode { /// - /// 坐标系类型枚举 + /// 世界坐标系 /// - public enum CoordinateSystemCode - { - /// - /// 世界坐标系 - /// - Wcs = 0, - - /// - /// 用户坐标系 - /// - Ucs, - - /// - /// 模型空间坐标系 - /// - MDcs, - - /// - /// 图纸空间坐标系 - /// - PDcs - } + Wcs = 0, /// - /// 方向的枚举 + /// 用户坐标系 /// - public enum OrientationType - { - /// - /// 左转或逆时针 - /// - CounterClockWise, - /// - /// 右转或顺时针 - /// - ClockWise, - /// - /// 重合或平行 - /// - Parallel - } + Ucs, /// - /// 点与多边形的关系类型枚举 + /// 模型空间坐标系 /// - public enum PointOnRegionType - { - /// - /// 多边形内部 - /// - Inside, - - /// - /// 多边形上 - /// - On, - - /// - /// 多边形外 - /// - Outside, - - /// - /// 错误 - /// - Error - } + MDcs, + /// + /// 图纸空间坐标系 + /// + PDcs +} +/// +/// 方向的枚举 +/// +public enum OrientationType +{ + /// + /// 左转或逆时针 + /// + CounterClockWise, + /// + /// 右转或顺时针 + /// + ClockWise, + /// + /// 重合或平行 + /// + Parallel } + +/// +/// 点与多边形的关系类型枚举 +/// +public enum PointOnRegionType +{ + /// + /// 多边形内部 + /// + Inside, + + /// + /// 多边形上 + /// + On, + + /// + /// 多边形外 + /// + Outside, + + /// + /// 错误 + /// + Error +} + + + +public enum FontTTF +{ + [Description("宋体.ttf")] + 宋体, + [Description("simfang.ttf")] + 仿宋, + [Description("FSGB2312.ttf")] + 仿宋GB2312, + [Description("Arial.ttf")] + Arial, + [Description("Romans")] + Romans +} + + + +public static class EnumHelper +{ + public static string GetDesc(this Enum val) + { + var type = val.GetType(); + var memberInfo = type.GetMember(val.ToString()); + var attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + //如果没有定义描述,就把当前枚举值的对应名称返回 + if (attributes is null || attributes.Length != 1) + { + return val.ToString(); + } + return ((DescriptionAttribute)attributes.Single()).Description; + } +} + diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs index 8f2a7a1..eca3a15 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs @@ -1,665 +1,682 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; +namespace IFoxCAD.Cad; + using System.Drawing; -using System.Linq; -using IFoxCAD.Collections; -using IFoxCAD.Linq; -namespace IFoxCAD.Cad + +/// +/// 图形扩展类 +/// +public static class GeometryEx { + + #region Point&Circle + /// - /// 图形扩展类 + /// 判断点与多边形的关系 /// - public static class GeometryEx + /// 多边形顶点集合 + /// 点 + /// 点与多边形的关系 + public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point2d pt) { - public static double DistanceTo(this Point2d pt1, Point2d PointV) - { - return pt1.GetDistanceTo(PointV); - } - - #region Point&Circle + //遍历点集并生成首尾连接的多边形 + var ptlst = new LoopList(pts); + if (ptlst.Count < 3) + return PointOnRegionType.Error; + + var ls2ds = new List(); + foreach (var node in ptlst.GetNodes()) + { + ls2ds.Add(new LineSegment2d(node.Value, node.Next!.Value)); + } + var cc2d = new CompositeCurve2d(ls2ds.ToArray()); + + //在多边形上? + if (cc2d.IsOn(pt)) + return PointOnRegionType.On; + + //在最小包围矩形外? + var bb2d = cc2d.BoundBlock; + if (!bb2d.Contains(pt)) + return PointOnRegionType.Outside; + + // + bool flag = false; + foreach (var node in ptlst.GetNodes()) + { + var pt1 = node.Value; + var pt2 = node.Next!.Value; + if (pt.Y < pt1.Y && pt.Y < pt2.Y) + continue; + if (pt1.X < pt.X && pt2.X < pt.X) + continue; + Vector2d vec = pt2 - pt1; + double t = (pt.X - pt1.X) / vec.X; + double y = t * vec.Y + pt1.Y; + if (y < pt.Y && t >= 0 && t <= 1) + flag = !flag; + } + return + flag ? + PointOnRegionType.Inside : PointOnRegionType.Outside; + } - /// - /// 判断点与多边形的关系 - /// - /// 多边形顶点集合 - /// 点 - /// 点与多边形的关系 - public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point2d pt) - { - //遍历点集并生成首尾连接的多边形 - var ptlst = new LoopList(pts); - if (ptlst.Count < 3) - return PointOnRegionType.Error; + /// + /// 判断点与多边形的关系 + /// + /// 多边形顶点集合 + /// 点 + /// 点与多边形的关系 + public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point3d pt) + { + //遍历点集并生成首尾连接的多边形 + var ptlst = new LoopList(pts); + if (ptlst.First!.Value == ptlst.Last!.Value) + ptlst.RemoveLast(); + if (ptlst.Count < 3) + return PointOnRegionType.Error; + + var ls3ds = new List(); + foreach (var node in ptlst.GetNodes()) + { + ls3ds.Add(new LineSegment3d(node.Value, node.Next!.Value)); + } + var cc3d = new CompositeCurve3d(ls3ds.ToArray()); + + //在多边形上? + if (cc3d.IsOn(pt)) + return PointOnRegionType.On; + + //在最小包围矩形外? + var bb2d = cc3d.BoundBlock; + if (!bb2d.Contains(pt)) + return PointOnRegionType.Outside; + + // + bool flag = false; + foreach (var node in ptlst.GetNodes()) + { + var pt1 = node.Value; + var pt2 = node.Next!.Value; + if (pt.Y < pt1.Y && pt.Y < pt2.Y) + continue; + if (pt1.X < pt.X && pt2.X < pt.X) + continue; + Vector3d vec = pt2 - pt1; + double t = (pt.X - pt1.X) / vec.X; + double y = t * vec.Y + pt1.Y; + if (y < pt.Y && t >= 0 && t <= 1) + flag = !flag; + } + return + flag ? + PointOnRegionType.Inside : PointOnRegionType.Outside; + } - var ls2ds = new List(); - foreach (var node in ptlst.GetNodes()) - { - ls2ds.Add(new LineSegment2d(node.Value, node.Next.Value)); - } - var cc2d = new CompositeCurve2d(ls2ds.ToArray()); + /// + /// 按两点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, out LoopList ptlst) + { + ptlst = new LoopList { pt1, pt2 }; + return + new CircularArc2d + ( + (pt1 + pt2.GetAsVector()) / 2, + pt1.GetDistanceTo(pt2) / 2 + ); + } - //在多边形上? - if (cc2d.IsOn(pt)) - return PointOnRegionType.On; + /// + /// 按三点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, out LoopList ptlst) + { + ptlst = new LoopList { pt1, pt2, pt3 }; - //在最小包围矩形外? - var bb2d = cc2d.BoundBlock; - if (!bb2d.Contains(pt)) - return PointOnRegionType.Outside; + //遍历各点与下一点的向量长度,找到距离最大的两个点 + LoopListNode maxNode = + ptlst.GetNodes().FindByMax + ( + out double maxLength, + node => node.Value.GetDistanceTo(node.Next!.Value) + ); - // - bool flag = false; - foreach (var node in ptlst.GetNodes()) - { - var pt1 = node.Value; - var PointV = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < PointV.Y) - continue; - if (pt1.X < pt.X && PointV.X < pt.X) - continue; - Vector2d vec = PointV - pt1; - double t = (pt.X - pt1.X) / vec.X; - double y = t * vec.Y + pt1.Y; - if (y < pt.Y && t >= 0 && t <= 1) - flag = !flag; - } - return - flag ? - PointOnRegionType.Inside : PointOnRegionType.Outside; - } + //以两点做最小包围圆 + CircularArc2d ca2d = + GetMinCircle(maxNode.Value, maxNode.Next!.Value, out LoopList tptlst); - /// - /// 判断点与多边形的关系 - /// - /// 多边形顶点集合 - /// 点 - /// 点与多边形的关系 - public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point3d pt) + //如果另一点属于该圆 + if (ca2d.IsIn(maxNode.Previous!.Value)) { - //遍历点集并生成首尾连接的多边形 - var ptlst = new LoopList(pts); - if (ptlst.First.Value == ptlst.Last.Value) - ptlst.RemoveLast(); - if (ptlst.Count < 3) - return PointOnRegionType.Error; - - var ls3ds = new List(); - foreach (var node in ptlst.GetNodes()) - { - ls3ds.Add(new LineSegment3d(node.Value, node.Next.Value)); - } - var cc3d = new CompositeCurve3d(ls3ds.ToArray()); + //返回 + ptlst = tptlst; + return ca2d; + } + //否则按三点做圆 + //ptlst.SetFirst(maxNode); + ptlst = new LoopList { maxNode.Value, maxNode.Next.Value, maxNode.Previous.Value }; + ca2d = new CircularArc2d(pt1, pt2, pt3); + ca2d.SetAngles(0, Math.PI * 2); + return ca2d; + } - //在多边形上? - if (cc3d.IsOn(pt)) - return PointOnRegionType.On; + /// + /// 按四点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d? GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, Point2d pt4, out LoopList? ptlst) + { + var iniptlst = new LoopList() { pt1, pt2, pt3, pt4 }; + ptlst = null; + CircularArc2d? ca2d = null; - //在最小包围矩形外? - var bb2d = cc3d.BoundBlock; - if (!bb2d.Contains(pt)) - return PointOnRegionType.Outside; + //遍历C43的组合,环链表的优势在这里 + foreach (LoopListNode firstNode in iniptlst.GetNodes()) + { + //获取各组合下三点的最小包围圆 + var secondNode = firstNode.Next; + var thirdNode = secondNode!.Next; + CircularArc2d tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode!.Value, out LoopList tptlst); - // - bool flag = false; - foreach (var node in ptlst.GetNodes()) + //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 + if (tca2d.IsIn(firstNode.Previous!.Value)) { - var pt1 = node.Value; - var PointV = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < PointV.Y) - continue; - if (pt1.X < pt.X && PointV.X < pt.X) - continue; - Vector3d vec = PointV - pt1; - double t = (pt.X - pt1.X) / vec.X; - double y = t * vec.Y + pt1.Y; - if (y < pt.Y && t >= 0 && t <= 1) - flag = !flag; + if (ca2d is null || tca2d.Radius < ca2d.Radius) + { + ca2d = tca2d; + ptlst = tptlst; + } } - return - flag ? - PointOnRegionType.Inside : PointOnRegionType.Outside; } - /// - /// 按两点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, out LoopList ptlst) - { - ptlst = new LoopList { pt1, PointV }; - return - new CircularArc2d - ( - (pt1 + PointV.GetAsVector()) / 2, - pt1.DistanceTo(PointV) / 2 - ); - } + //返回直径最小的圆 + return ca2d; + } - /// - /// 按三点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, out LoopList ptlst) - { - ptlst = new LoopList { pt1, PointV, pt3 }; - - //遍历各点与下一点的向量长度,找到距离最大的两个点 - double maxLength; - LoopListNode maxNode = - ptlst.GetNodes().FindByMax - ( - out maxLength, - node => node.Value.DistanceTo(node.Next.Value) - ); - - //以两点做最小包围圆 - LoopList tptlst; - CircularArc2d ca2d = - GetMinCircle(maxNode.Value, maxNode.Next.Value, out tptlst); - - //如果另一点属于该圆 - if (ca2d.IsIn(maxNode.Previous.Value)) - { - //返回 - ptlst = tptlst; - return ca2d; - } + /// + /// 计算三点围成的有向面积 + /// + /// 基准点 + /// 第一点 + /// 第二点 + /// 三点围成的三角形的有向面积 + private static double CalArea(Point2d ptBase, Point2d pt1, Point2d pt2) + { + return (pt2 - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; + } + /// + /// 计算三点围成的三角形的真实面积 + /// + /// 基准点 + /// 第一点 + /// 第二点 + /// 三点围成的三角形的真实面积 + public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d pt2) + { + return Math.Abs(CalArea(ptBase, pt1, pt2)); + } - //否则按三点做圆 - ptlst.SetFirst(maxNode); - ca2d = new CircularArc2d(pt1, PointV, pt3); - ca2d.SetAngles(0, Math.PI * 2); - return ca2d; - } + /// + /// 判断三点是否为逆时针,也就是说判断三点是否为左转 + /// + /// 基点 + /// 第一点 + /// 第二点 + /// OrientationType 类型值 + public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d pt2) + { - /// - /// 按四点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, Point2d pt4, out LoopList ptlst) + return CalArea(ptBase, pt1, pt2) switch { - LoopList iniptlst = - new LoopList { pt1, PointV, pt3, pt4 }; - ptlst = null; - CircularArc2d ca2d = null; - - //遍历C43的组合,环链表的优势在这里 - foreach (LoopListNode firstNode in iniptlst.GetNodes()) - { - //获取各组合下三点的最小包围圆 - LoopListNode secondNode = firstNode.Next; - LoopListNode thirdNode = secondNode.Next; - LoopList tptlst; - CircularArc2d tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode.Value, out tptlst); - - //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 - if (tca2d.IsIn(firstNode.Previous.Value)) - { - if (ca2d is null || tca2d.Radius < ca2d.Radius) - { - ca2d = tca2d; - ptlst = tptlst; - } - } - } + > 0 => OrientationType.CounterClockWise, + < 0 => OrientationType.ClockWise, + _ => OrientationType.Parallel + }; + } - //返回直径最小的圆 - return ca2d; - } + /// + /// 计算两个二维向量围成的平行四边形的有向面积 + /// + /// 基向量 + /// 向量 + /// 有向面积 + private static double CalArea(Vector2d vecBase, Vector2d vec) + { + return vec.DotProduct(vecBase.GetPerpendicularVector()) / 2; + } - /// - /// 计算三点围成的有向面积 - /// - /// 基准点 - /// 第一点 - /// 第二点 - /// 三点围成的三角形的有向面积 - private static double CalArea(Point2d ptBase, Point2d pt1, Point2d PointV) - { - return (PointV - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; - } - /// - /// 计算三点围成的三角形的真实面积 - /// - /// 基准点 - /// 第一点 - /// 第二点 - /// 三点围成的三角形的真实面积 - public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d PointV) - { - return Math.Abs(CalArea(ptBase, pt1, PointV)); - } + /// + /// 计算两个二维向量围成的平行四边形的真实面积 + /// + /// 基向量 + /// 向量 + /// 真实面积 + public static double GetArea(Vector2d vecBase, Vector2d vec) + { + return Math.Abs(CalArea(vecBase, vec)); + } - /// - /// 判断三点是否为逆时针,也就是说判断三点是否为左转 - /// - /// 基点 - /// 第一点 - /// 第二点 - /// OrientationType 类型值 - public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d PointV) + /// + /// 判断两个二维向量是否左转 + /// + /// 基向量 + /// 向量 + /// OrientationType 类型值 + public static OrientationType IsClockWise(Vector2d vecBase, Vector2d vec) + { + return CalArea(vecBase, vec) switch { + > 0 => OrientationType.CounterClockWise, + < 0 => OrientationType.ClockWise, + _ => OrientationType.Parallel + }; + } - return CalArea(ptBase, pt1, PointV) switch - { + #region PointList - > 0 => OrientationType.CounterClockWise, - < 0 => OrientationType.ClockWise, - _ => OrientationType.Parallel - }; - } + /// + /// 计算点集的有向面积 + /// + /// 点集 + /// 有向面积 + private static double CalArea(IEnumerable pnts) + { + IEnumerator itor = pnts.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(pnts)); + Point2d start = itor.Current; + Point2d p1, p2 = start; + double area = 0; - /// - /// 计算两个二维向量围成的平行四边形的有向面积 - /// - /// 基向量 - /// 向量 - /// 有向面积 - private static double CalArea(Vector2d vecBase, Vector2d vec) + while (itor.MoveNext()) { - return vec.DotProduct(vecBase.GetPerpendicularVector()) / 2; + p1 = p2; + p2 = itor.Current; + area += (p1.X * p2.Y - p2.X * p1.Y); } - /// - /// 计算两个二维向量围成的平行四边形的真实面积 - /// - /// 基向量 - /// 向量 - /// 真实面积 - public static double GetArea(Vector2d vecBase, Vector2d vec) + area = (area + (p2.X * start.Y - start.X * p2.Y)) / 2.0; + return area; + } + /// + /// 计算点集的真实面积 + /// + /// 点集 + /// 面积 + public static double GetArea(this IEnumerable pnts) + { + return Math.Abs(CalArea(pnts)); + } + + /// + /// 判断点集的点序 + /// + /// 点集 + /// OrientationType 类型值 + public static OrientationType IsClockWise(this IEnumerable pnts) + { + return CalArea(pnts) switch { - return Math.Abs(CalArea(vecBase, vec)); - } + < 0 => OrientationType.ClockWise, + > 0 => OrientationType.CounterClockWise, + _ => OrientationType.Parallel + }; + } - /// - /// 判断两个二维向量是否左转 - /// - /// 基向量 - /// 向量 - /// OrientationType 类型值 - public static OrientationType IsClockWise(Vector2d vecBase, Vector2d vec) + /// + /// 按点集返回最小包围圆 + /// + /// 点集 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d? GetMinCircle(this List pnts, out LoopList? ptlst) + { + //点数较小时直接返回 + switch (pnts.Count) { - return CalArea(vecBase, vec) switch - { - > 0 => OrientationType.CounterClockWise, - < 0 => OrientationType.ClockWise, - _ => OrientationType.Parallel - }; - } + case 0: + ptlst = null; + return null; - #region PointList + case 1: + ptlst = new LoopList { pnts[0] }; + return new CircularArc2d(pnts[0], 0); - /// - /// 计算点集的有向面积 - /// - /// 点集 - /// 有向面积 - private static double CalArea(IEnumerable pnts) - { - IEnumerator itor = pnts.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(nameof(pnts)); - Point2d start = itor.Current; - Point2d p1, p2 = start; - double area = 0; - - while (itor.MoveNext()) - { - p1 = p2; - p2 = itor.Current; - area += (p1.X * p2.Y - p2.X * p1.Y); - } + case 2: + return GetMinCircle(pnts[0], pnts[1], out ptlst); - area = (area + (p2.X * start.Y - start.X * p2.Y)) / 2.0; - return area; - } - /// - /// 计算点集的真实面积 - /// - /// 点集 - /// 面积 - public static double GetArea(this IEnumerable pnts) - { - return Math.Abs(CalArea(pnts)); - } + case 3: + return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); - /// - /// 判断点集的点序 - /// - /// 点集 - /// OrientationType 类型值 - public static OrientationType IsClockWise(this IEnumerable pnts) - { - return CalArea(pnts) switch - { - < 0 => OrientationType.ClockWise, - > 0 => OrientationType.CounterClockWise, - _ => OrientationType.Parallel - }; + case 4: + return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); } - /// - /// 按点集返回最小包围圆 - /// - /// 点集 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(this List pnts, out LoopList ptlst) - { - //点数较小时直接返回 - switch (pnts.Count) - { - case 0: - ptlst = null; - return null; + //按前三点计算最小包围圆 + Point2d[] tpnts = new Point2d[4]; + pnts.CopyTo(0, tpnts, 0, 3); + CircularArc2d? ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - case 1: - ptlst = new LoopList { pnts[0] }; - return new CircularArc2d(pnts[0], 0); + //找到点集中距离圆心的最远点为第四点 + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); - case 2: - return GetMinCircle(pnts[0], pnts[1], out ptlst); - - case 3: - return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); + //如果最远点属于圆结束 + while (!ca2d.IsIn(tpnts[3])) + { + //如果最远点不属于圆,按此四点计算最小包围圆 + ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], tpnts[3], out ptlst); - case 4: - return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); + //将结果作为新的前三点 + if (ptlst!.Count == 3) + { + tpnts[2] = ptlst.Last!.Value; } - - //按前三点计算最小包围圆 - Point2d[] tpnts = new Point2d[4]; - pnts.CopyTo(0, tpnts, 0, 3); - CircularArc2d ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - - //找到点集中距离圆心的最远点为第四点 - tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); - - //如果最远点属于圆结束 - while (!ca2d.IsIn(tpnts[3])) + else { - //如果最远点不属于圆,按此四点计算最小包围圆 - ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], tpnts[3], out ptlst); - - //将结果作为新的前三点 - if (ptlst.Count == 3) - { - tpnts[2] = ptlst.Last.Value; - } - else - { - //第三点取另两点中距离圆心较远的点 - //按算法中描述的任选其中一点的话,还是无法收敛...... - tpnts[2] = - tpnts.Except(ptlst) - .FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); - } - tpnts[0] = ptlst.First.Value; - tpnts[1] = ptlst.First.Next.Value; - - //按此三点计算最小包围圆 - ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - - //找到点集中圆心的最远点为第四点 - tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); + //第三点取另两点中距离圆心较远的点 + //按算法中描述的任选其中一点的话,还是无法收敛...... + tpnts[2] = + tpnts.Except(ptlst) + .FindByMax(pnt => ca2d!.Center.GetDistanceTo(pnt)); } + tpnts[0] = ptlst.First!.Value; + tpnts[1] = ptlst.First.Next!.Value; - return ca2d; + //按此三点计算最小包围圆 + ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); + + //找到点集中圆心的最远点为第四点 + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); } - /// - /// 获取点集的凸包 - /// - /// 点集 - /// 凸包 - public static List ConvexHull(this List points) - { - if (points is null) - return null; + return ca2d; + } - if (points.Count <= 1) - return points; + /// + /// 获取点集的凸包 + /// + /// 点集 + /// 凸包 + public static List? ConvexHull(this List points) + { + if (points is null) + return null; - int n = points.Count, k = 0; - List H = new(new Point2d[2 * n]); + if (points.Count <= 1) + return points; - points.Sort((a, b) => - a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + int n = points.Count, k = 0; + List H = new(new Point2d[2 * n]); - // Build lower hull - for (int i = 0; i < n; ++i) - { - while (k >= 2 && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) - k--; - H[k++] = points[i]; - } + points.Sort((a, b) => + a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); - // Build upper hull - for (int i = n - 2, t = k + 1; i >= 0; i--) - { - while (k >= t && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) - k--; - H[k++] = points[i]; - } - return H.Take(k - 1).ToList(); + // Build lower hull + for (int i = 0; i < n; ++i) + { + while (k >= 2 && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) + k--; + H[k++] = points[i]; } + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; i--) + { + while (k >= t && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) + k--; + H[k++] = points[i]; + } + return H.Take(k - 1).ToList(); + } - #endregion PointList - #endregion Point&Circle + #endregion PointList - #region Ucs + #endregion Point&Circle - /// - /// ucs到wcs的点变换 - /// - /// 点 - /// 变换后的点 - public static Point3d Ucs2Wcs(this Point3d point) - { - return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem); - } + #region Ucs - /// - /// wcs到ucs的点变换 - /// - /// 点 - /// 变换后的点 - public static Point3d Wcs2Ucs(this Point3d point) - { - return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); - } + /// + /// ucs到wcs的点变换 + /// + /// 点 + /// 变换后的点 + public static Point3d Ucs2Wcs(this Point3d point) + { + return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem); + } - /// - /// ucs到wcs的向量变换 - /// - /// 向量 - /// 变换后的向量 - public static Vector3d Ucs2Wcs(this Vector3d vec) - { - return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem); - } + /// + /// wcs到ucs的点变换 + /// + /// 点 + /// 变换后的点 + public static Point3d Wcs2Ucs(this Point3d point) + { + return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); + } - /// - /// wcs到ucs的向量变换 - /// - /// 向量 - /// 变换后的向量 - public static Vector3d Wcs2Ucs(this Vector3d vec) - { - return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); - } + /// + /// ucs到wcs的向量变换 + /// + /// 向量 + /// 变换后的向量 + public static Vector3d Ucs2Wcs(this Vector3d vec) + { + return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem); + } - /// - /// 模拟 trans 函数 - /// - /// 点 - /// 源坐标系 - /// 目标坐标系 - /// 变换后的点 - public static Point3d Trans(this Point3d point, CoordinateSystemCode from, CoordinateSystemCode to) - { - return Env.Editor.GetMatrix(from, to) * point; - } + /// + /// wcs到ucs的向量变换 + /// + /// 向量 + /// 变换后的向量 + public static Vector3d Wcs2Ucs(this Vector3d vec) + { + return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); + } - /// - /// 模拟 trans 函数 - /// - /// 向量 - /// 源坐标系 - /// 目标坐标系 - /// 变换后的向量 - public static Vector3d Trans(this Vector3d vec, CoordinateSystemCode from, CoordinateSystemCode to) - { - return vec.TransformBy(Env.Editor.GetMatrix(from, to)); - } + /// + /// 模拟 trans 函数 + /// + /// 点 + /// 源坐标系 + /// 目标坐标系 + /// 变换后的点 + public static Point3d Trans(this Point3d point, CoordinateSystemCode from, CoordinateSystemCode to) + { + return Env.Editor.GetMatrix(from, to) * point; + } - /// - /// wcs到dcs的点变换 - /// - /// 点 - /// 是否为图纸空间 - /// 变换后的点 - public static Point3d Wcs2Dcs(this Point3d point, bool atPaperSpace) - { - return - Trans( - point, - CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs - ); - } + /// + /// 模拟 trans 函数 + /// + /// 向量 + /// 源坐标系 + /// 目标坐标系 + /// 变换后的向量 + public static Vector3d Trans(this Vector3d vec, CoordinateSystemCode from, CoordinateSystemCode to) + { + return vec.TransformBy(Env.Editor.GetMatrix(from, to)); + } - /// - /// wcs到dcs的向量变换 - /// - /// 向量 - /// 是否为图纸空间 - /// 变换后的向量 - public static Vector3d Wcs2Dcs(this Vector3d vec, bool atPaperSpace) - { - return - Trans( - vec, - CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs - ); - } + /// + /// wcs到dcs的点变换 + /// + /// 点 + /// 是否为图纸空间 + /// 变换后的点 + public static Point3d Wcs2Dcs(this Point3d point, bool atPaperSpace) + { + return + Trans( + point, + CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs + ); + } + + /// + /// wcs到dcs的向量变换 + /// + /// 向量 + /// 是否为图纸空间 + /// 变换后的向量 + public static Vector3d Wcs2Dcs(this Vector3d vec, bool atPaperSpace) + { + return + Trans( + vec, + CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs + ); + } - #endregion Ucs + #endregion Ucs - /// - /// 返回不等比例变换矩阵 - /// - /// 基点 - /// x方向比例 - /// y方向比例 - /// z方向比例 - /// 三维矩阵 - public static Matrix3d GetScaleMatrix(this Point3d point, double x, double y, double z) - { - double[] matdata = new double[16]; - matdata[0] = x; - matdata[3] = point.X * (1 - x); - matdata[5] = y; - matdata[7] = point.Y * (1 - y); - matdata[10] = z; - matdata[11] = point.Z * (1 - z); - matdata[15] = 1; - return new Matrix3d(matdata); - } + /// + /// 返回不等比例变换矩阵 + /// + /// 基点 + /// x方向比例 + /// y方向比例 + /// z方向比例 + /// 三维矩阵 + public static Matrix3d GetScaleMatrix(this Point3d point, double x, double y, double z) + { + double[] matdata = new double[16]; + matdata[0] = x; + matdata[3] = point.X * (1 - x); + matdata[5] = y; + matdata[7] = point.Y * (1 - y); + matdata[10] = z; + matdata[11] = point.Z * (1 - z); + matdata[15] = 1; + return new Matrix3d(matdata); + } - /// - /// 获取坐标范围的大小 - /// - /// 坐标范围 - /// 尺寸对象 - public static Size GetSize(this Extents3d ext) - { - int width = (int)Math.Floor(ext.MaxPoint.X - ext.MinPoint.X); - int height = (int)Math.Ceiling(ext.MaxPoint.Y - ext.MinPoint.Y); - return new Size(width, height); - } + /// + /// 获取坐标范围的大小 + /// + /// 坐标范围 + /// 尺寸对象 + public static Size GetSize(this Extents3d ext) + { + int width = (int)Math.Floor(ext.MaxPoint.X - ext.MinPoint.X); + int height = (int)Math.Ceiling(ext.MaxPoint.Y - ext.MinPoint.Y); + return new Size(width, height); + } - /// - /// 将三维点转换为二维点 - /// - /// 三维点 - /// 二维点 - public static Point2d Point2d(this Point3d pt) - { - return new Point2d(pt.X, pt.Y); - } - /// - /// 将三维点集转换为二维点集 - /// - /// 三维点集 - /// 二维点集 - public static IEnumerable Point2d(this IEnumerable pts) - { - return pts.Select(pt => pt.Point2d()); - } - /// - /// 将二维点转换为三维点 - /// - /// 二维点 - /// 三维点 - public static Point3d Point3d(this Point2d pt) - { - return new Point3d(pt.X, pt.Y, 0); - } + /// + /// 将三维点转换为二维点 + /// + /// 三维点 + /// 二维点 + public static Point2d Point2d(this Point3d pt) + { + return new Point2d(pt.X, pt.Y); + } + /// + /// 将三维点集转换为二维点集 + /// + /// 三维点集 + /// 二维点集 + public static IEnumerable Point2d(this IEnumerable pts) + { + return pts.Select(pt => pt.Point2d()); + } + /// + /// 将二维点转换为三维点 + /// + /// 二维点 + /// 三维点 + public static Point3d Point3d(this Point2d pt) + { + return new Point3d(pt.X, pt.Y, 0); + } + /// + /// 将二维点转换为三维点 + /// + /// 二维点 + /// Z值 + /// 三维点 + public static Point3d Point3d(this Point2d pt,double z) + { + return new Point3d(pt.X, pt.Y, z); + } - /// - /// 获取两个点之间的中点 - /// - /// 第一点 - /// 第二点 - /// 返回两个点之间的中点 - public static Point3d GetMidPointTo(this Point3d pt1, Point3d PointV) - { - return new Point3d((pt1.X + PointV.X) * 0.5, (pt1.Y + PointV.Y) * 0.5, (pt1.Z + PointV.Z) * 0.5); - } + /// + /// 获取两个点之间的中点 + /// + /// 第一点 + /// 第二点 + /// 返回两个点之间的中点 + public static Point3d GetMidPointTo(this Point3d pt1, Point3d pt2) + { + return new Point3d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5, (pt1.Z + pt2.Z) * 0.5); + } + + /// + /// 获取两个点之间的中点 + /// + /// 第一点 + /// 第二点 + /// 返回两个点之间的中点 + public static Point2d GetMidPointTo(this Point2d pt1, Point2d pt2) + { + return new Point2d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5); + } - /// - /// 根据世界坐标计算用户坐标 - /// - /// 基点世界坐标 - /// 基点用户坐标 - /// 目标世界坐标 - /// 坐标网旋转角,按x轴正向逆时针弧度 - /// 目标用户坐标 - public static Point3d TransPoint(this Point3d basePt, Point3d userPt, Point3d transPt, double ang) - { - Matrix3d transMat = Matrix3d.Displacement(userPt - basePt); - Matrix3d roMat = Matrix3d.Rotation(-ang, Vector3d.ZAxis, userPt); - return transPt.TransformBy(roMat * transMat); - } - /// - /// 计算指定距离和角度的点 - /// - /// 本函数仅适用于x-y平面 - /// 基点 - /// 角度,x轴正向逆时针弧度 - /// 距离 - /// 目标点 - public static Point3d Polar(this Point3d pt, double ang, double len) - { - return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; - } + /// + /// 根据世界坐标计算用户坐标 + /// + /// 基点世界坐标 + /// 基点用户坐标 + /// 目标世界坐标 + /// 坐标网旋转角,按x轴正向逆时针弧度 + /// 目标用户坐标 + public static Point3d TransPoint(this Point3d basePt, Point3d userPt, Point3d transPt, double ang) + { + Matrix3d transMat = Matrix3d.Displacement(userPt - basePt); + Matrix3d roMat = Matrix3d.Rotation(-ang, Vector3d.ZAxis, userPt); + return transPt.TransformBy(roMat * transMat); + } + /// + /// 计算指定距离和角度的点 + /// + /// 本函数仅适用于x-y平面 + /// 基点 + /// 角度,x轴正向逆时针弧度 + /// 距离 + /// 目标点 + public static Point3d Polar(this Point3d pt, double ang, double len) + { + return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; + } + /// + /// 计算指定距离和角度的点 + /// + /// 本函数仅适用于x-y平面 + /// 基点 + /// 角度,x轴正向逆时针弧度 + /// 距离 + /// 目标点 + public static Point2d Polar(this Point2d pt, double ang, double len) + { + return pt + Vector2d.XAxis.RotateBy(ang) * len; } } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs new file mode 100644 index 0000000..5485e6d --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs @@ -0,0 +1,343 @@ +/* 封装jig + * 20220726 隐藏事件,利用函数进行数据库图元重绘 + * 20220710 修改SetOption()的空格结束,并添加例子到IFox + * 20220503 cad22需要防止刷新过程中更改队列,是因为允许函数重入导致,08不会有. + * 20220326 重绘图元的函数用错了,现在修正过来 + * 20211216 加入块表时候做一个差集,剔除临时图元 + * 20211209 补充正交变量设置和回收设置 + * 作者: 惊惊⎛⎝◕⏝⏝◕。⎠⎞ ⎛⎝≥⏝⏝0⎠⎞ ⎛⎝⓿⏝⏝⓿。⎠⎞ ⎛⎝≥⏝⏝≤⎠⎞ + * 博客: https://www.cnblogs.com/JJBox/p/15650770.html + */ + +namespace IFoxCAD.Cad; + +//此命名空间容易引起Polyline等等重义,因此不放入全局空间 +using Autodesk.AutoCAD.GraphicsInterface; + +public delegate void WorldDrawEvent(WorldDraw draw); +public class JigEx : DrawJig +{ + #region 成员 + /// + /// 事件:亮显/暗显会被刷新冲刷掉,所以这个事件用于补充非刷新的工作 + /// + event WorldDrawEvent? WorldDrawEvent; + /// + /// 最后的鼠标点,用来确认长度 + /// + public Point3d MousePointWcsLast; + /// + /// 最后的图元,用来生成 + /// + public Entity[] Entitys => _drawEntitys.ToArray(); + + + readonly Action>? _mouseAction; + readonly Tolerance _tolerance;//容差 + + readonly Queue _drawEntitys;//重复生成的图元,放在这里刷新 + JigPromptPointOptions? _options;//jig鼠标配置 + bool _worldDrawFlag = false; // 20220503 + + bool _systemVariables_Orthomode = false; + bool SystemVariables_Orthomode // 正交修改还原 + { + get => _systemVariables_Orthomode; + set + { + //1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html + if (Env.OrthoMode != value) + Env.OrthoMode = _systemVariables_Orthomode = value; + } + } + #endregion + + #region 构造 + /// + /// 在界面绘制图元 + /// + private JigEx() + { + _drawEntitys = new(); + } + + /// + /// 在界面绘制图元 + /// + /// + /// 用来频繁执行的回调: + /// 鼠标点; + /// 加入新建的图元,鼠标采样期间会Dispose图元的;所以已经在数据库图元利用事件加入,不要在此加入; + /// + /// 鼠标移动的容差 + public JigEx(Action>? action = null, double tolerance = 1e-6) : this() + { + _mouseAction = action; + _tolerance = new(tolerance, tolerance); + } + #endregion + + #region 方法 + /// + /// 鼠标配置:基点 + /// + /// 基点 + /// 光标绑定 + /// 提示信息 + /// 正交开关 + public JigPromptPointOptions SetOptions(Point3d basePoint, + CursorType cursorType = CursorType.RubberBand, + string msg = "点选第二点", + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + _options.Cursor = cursorType; //光标绑定 + _options.UseBasePoint = true; //基点打开 + _options.BasePoint = basePoint; //基点设定 + return _options; + } + + /// + /// 鼠标配置:提示信息,关键字 + /// + /// 信息 + /// 关键字 + /// 正交开关 + /// + public JigPromptPointOptions SetOptions(string msg, + Dictionary? keywords = null, + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + + //加入关键字,加入时候将空格内容放到最后 + string spaceValue = string.Empty; + const string spaceKey = " "; + + if (keywords != null) + foreach (var item in keywords) + if (item.Key == spaceKey) + spaceValue = item.Value; + else + _options.Keywords.Add(item.Key, item.Key, item.Value); + + ///因为默认配置函数导致此处空格触发是无效的, + ///但是用户如果想触发,就需要在外部减去默认UserInputControls配置 + ///要放最后,才能优先触发其他关键字 + if (spaceValue != string.Empty) + _options.Keywords.Add(spaceKey, spaceKey, spaceValue); + else + _options.Keywords.Add(spaceKey, spaceKey, "<空格退出>"); + + // 外部设置减去配置 + // _options.UserInputControls = + // _options.UserInputControls + // ^ UserInputControls.NullResponseAccepted //输入了鼠标右键,结束jig + // ^ UserInputControls.AnyBlankTerminatesInput; //空格或回车,结束jig; + return _options; + } + + /// + /// 鼠标配置:自定义 + /// + /// + /// 正交开关 + public void SetOptions(Action action, bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = new JigPromptPointOptions(); + action.Invoke(_options); + } + + /// + /// 执行 + /// + /// + public PromptResult Drag() + { + //jig功能必然是当前前台文档,所以封装内部更好调用 + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var dr = ed.Drag(this); + + if (SystemVariables_Orthomode) + SystemVariables_Orthomode = !SystemVariables_Orthomode; + return dr; + } + + /// + /// 最后一次的图元加入数据库 + /// + /// 加入此空间 + /// 不生成的图元用于排除,例如刷新时候的提示文字 + /// 加入数据库的id集合 + public IEnumerable? AddEntityToMsPs(BlockTableRecord btrOfAddEntitySpace, + IEnumerable? removeEntity = null) + { + //内部用 _drawEntitys 外部用 Entitys,减少一层转换 + if (_drawEntitys.Count == 0) + return null; + + IEnumerable es = _drawEntitys; + if (removeEntity != null) + es = es.Except(removeEntity);//差集 + + return btrOfAddEntitySpace.AddEntity(es); + } + #endregion + + #region 重写 + /// + /// 鼠标采样器 + /// + /// + /// 返回状态:令频繁刷新结束 + protected override SamplerStatus Sampler(JigPrompts prompts) + { + if (_worldDrawFlag) + return SamplerStatus.NoChange;//OK的时候拖动鼠标与否都不出现图元 + + if (_options is null) + throw new NullReferenceException(nameof(_options)); + + var pro = prompts.AcquirePoint(_options); + if (pro.Status == PromptStatus.Keyword) + return SamplerStatus.OK; + else if (pro.Status != PromptStatus.OK) + return SamplerStatus.Cancel; + + //上次鼠标点不同(一定要这句,不然图元刷新太快会看到奇怪的边线) + var mousePointWcs = pro.Value; + + //== 是比较类字段,但是最好转为哈希比较. + //IsEqualTo 是方形判断(仅加法),但是cad是距离. + //Distance 是圆形判断(会求平方根,使用了牛顿迭代), + //大量数据(十万以上/频繁刷新)面前会显得非常慢. + if (mousePointWcs.IsEqualTo(MousePointWcsLast, _tolerance)) + return SamplerStatus.NoChange; + + //上次循环的缓冲区图元清理,否则将会在vs输出遗忘 Dispose + while (_drawEntitys.Count > 0) + _drawEntitys.Dequeue().Dispose(); + + //委托把容器扔出去接收新创建的图元,然后给重绘更新 + _mouseAction?.Invoke(mousePointWcs, _drawEntitys); + MousePointWcsLast = mousePointWcs; + + return SamplerStatus.OK; + } + + /// + /// 重绘已在数据库的图元 + /// 0x01 此处不加入newEntity的,它们在构造函数的参数回调处加入,它们会进行频繁new和Dispose从而避免遗忘释放 + /// 0x02 此处用于重绘已经在数据的图元 + /// 0x03 此处用于图元亮显暗显,因为会被重绘冲刷掉所以独立出来不重绘,它们也往往已经存在数据库的 + /// + /// + /// newEntity只会存在一个图元队列中,而数据库图元可以分多个集合 + /// 例如: 集合A亮显时 集合B暗显/集合B亮显时 集合A暗显,所以我没有设计多个"数据库图元集合"存放,而是由用户在构造函数外自行创建 + /// + /// + public void DatabaseEntityDraw(WorldDrawEvent action) + { + WorldDrawEvent = action; + } + + /* WorldDraw 封装外的操作说明: + * 0x01 + * 我有一个业务是一次性生成四个方向的箭头,因为cad08缺少瞬时图元, + * 那么可以先提交一次事务,再开一个事务,把Entity传给jig,最后选择删除部分. + * 虽然这个是可行的方案,但是Entity穿越事务本身来说是非必要不使用的. + * 0x02 + * 四个箭头最近鼠标的亮显,其余淡显, + * 在jig使用淡显ent.Unhighlight和亮显ent.Highlight() + * 需要绕过重绘,否则重绘将导致图元频闪,令这两个操作失效, + * 此时需要自定义一个集合 EntityList (不使用本函数的_drawEntitys) + * 再将 EntityList 传给 WorldDrawEvent 事件,事件内实现亮显和淡显. + * 0x03 + * draw.Geometry.Draw(_drawEntitys[i]); + * 此函数有问题,acad08克隆一份数组也可以用来刷新, + * 而arx上面的jig只能一次改一个,所以可以用此函数. + * 起因是此函数属于异步刷新, + * 同步上下文的刷新是 RawGeometry + * 0x04 + * cad22测试出现,08不会, + * draw.RawGeometry.Draw(ent);会跳到 Sampler(),所以设置 _worldDrawFlag + * 但是禁止重绘重入的话(令图元不频繁重绘),那么鼠标停着的时候就看不见图元, + * 所以只能重绘结束的时候才允许鼠标采集,采集过程的时候不会触发重绘, + * 这样才可以保证容器在重绘中不被更改. + */ + /// + /// 重绘图形 + /// + protected override bool WorldDraw(WorldDraw draw) + { + _worldDrawFlag = true; + WorldDrawEvent?.Invoke(draw); + _drawEntitys.ForEach(ent => { + draw.RawGeometry.Draw(ent); + }); + _worldDrawFlag = false; + return true; + } + #endregion + + /// + /// 用户输入控制默认配置 + /// 令jig.Drag().Status == + /// + /// + static JigPromptPointOptions JigPointOptions() + { + return new JigPromptPointOptions() + { + UserInputControls = + UserInputControls.GovernedByUCSDetect //由UCS探测用 + | UserInputControls.Accept3dCoordinates //接受三维坐标 + | UserInputControls.NullResponseAccepted //输入了鼠标右键,结束jig + | UserInputControls.AnyBlankTerminatesInput //空格或回车,结束jig; + }; + } + + /// + /// 空格默认是, + /// 将它设置为 + /// + public void SetSpaceIsKeyword() + { + var opt = _options; + if (opt == null) + throw new ArgumentNullException(nameof(_options)); + + if ((opt.UserInputControls & UserInputControls.NullResponseAccepted) == UserInputControls.NullResponseAccepted) + opt.UserInputControls ^= UserInputControls.NullResponseAccepted; //输入了鼠标右键,结束jig + if ((opt.UserInputControls & UserInputControls.AnyBlankTerminatesInput) == UserInputControls.AnyBlankTerminatesInput) + opt.UserInputControls ^= UserInputControls.AnyBlankTerminatesInput; //空格或回车,结束jig + } +} + +#if false +| UserInputControls.DoNotEchoCancelForCtrlC //不要取消CtrlC的回音 +| UserInputControls.DoNotUpdateLastPoint //不要更新最后一点 +| UserInputControls.NoDwgLimitsChecking //没有Dwg限制检查 +| UserInputControls.NoZeroResponseAccepted //接受非零响应 +| UserInputControls.NoNegativeResponseAccepted //不否定回复已被接受 +| UserInputControls.Accept3dCoordinates //返回点的三维坐标,是转换坐标系了? +| UserInputControls.AcceptMouseUpAsPoint //接受释放按键时的点而不是按下时 + +| UserInputControls.InitialBlankTerminatesInput //初始 空格或回车,结束jig +| UserInputControls.AcceptOtherInputString //接受其他输入字符串 +| UserInputControls.NoZDirectionOrtho //无方向正射,直接输入数字时以基点到当前点作为方向 +| UserInputControls.UseBasePointElevation //使用基点高程,基点的Z高度探测 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs new file mode 100644 index 0000000..c831959 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs @@ -0,0 +1,21 @@ +namespace IFoxCAD.Cad; + +public static class ObjEx +{ + /// + /// cad的打印 + /// + /// + public static void Print(this object obj) + { + Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"{obj}\n"); + } + /// + /// 系统的打印 + /// + /// + public static void PrintLine(this object obj) + { + Console.WriteLine(obj.ToString()); + } +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs index 2a89bd5..52c9261 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs @@ -1,70 +1,89 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Runtime; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 对象id扩展类 +/// +public static class ObjectIdEx { + #region GetObject + /// - /// 对象id扩展类 + /// 获取指定类型对象 /// - public static class ObjectIdEx + /// 指定的泛型 + /// 对象id + /// 事务 + /// 打开模式 + /// 打开删除对象 + /// 指定类型对象 + public static T? GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction? tr = default) where T : DBObject { - #region GetObject - - /// - /// 获取指定类型对象 - /// - /// 指定的泛型 - /// 对象id - /// 事务 - /// 打开模式 - /// 打开删除对象 - /// 指定类型对象 - public static T GetObject(this ObjectId id, - OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject - { - tr ??= DBTrans.Top.Transaction; - return tr.GetObject(id, mode, openErased) as T; - } - - /// - /// 获取指定类型对象集合 - /// - /// 指定的泛型 - /// 对象id集合 - /// 事务 - /// 打开模式 - /// 打开删除对象 - /// 指定类型对象集合 - public static IEnumerable GetObject(this IEnumerable ids, - OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject - { - return ids.Select(id => id.GetObject(mode, openErased, tr)); - } + tr ??= DBTrans.Top.Transaction; + //tr = Env.GetTrans(tr); + return tr.GetObject(id, mode, openErased) as T; + } - /// - /// 返回符合类型的对象id - /// - /// 对象类型 - /// 对象id集合 - /// 对象id集合 - public static IEnumerable OfType(this IEnumerable ids) where T : DBObject - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return ids.Where(id => id.ObjectClass().DxfName == dxfName); - } + /// + /// 获取指定类型对象集合 + /// + /// 指定的泛型 + /// 对象id集合 + /// 事务 + /// 打开模式 + /// 打开删除对象 + /// 指定类型对象集合 + public static IEnumerable GetObject(this IEnumerable ids, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction? tr = default) where T : DBObject + { + return ids.Select(id => id.GetObject(mode, openErased, tr)); + } - //Acad08缺少 id.ObjectClass 如何补偿? - public static RXClass ObjectClass(this ObjectId id) - { + /// + /// 返回符合类型的对象id + /// + /// 对象类型 + /// 对象id集合 + /// 对象id集合 + public static IEnumerable OfType(this IEnumerable ids) where T : DBObject + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return ids.Where(id => id.ObjectClass().DxfName == dxfName); + } + #endregion GetObject + + //Acad08缺少 id.ObjectClass 如何补偿? + public static RXClass ObjectClass(this ObjectId id) + { #if NET35 - return RXClass.GetClass(id.GetType()); + return RXClass.GetClass(id.GetType()); #else - return id.ObjectClass; + return id.ObjectClass; #endif - } - #endregion GetObject + } + + /// + /// id是否有效,未被删除 + /// + /// 对象id + /// id有效返回 ,反之返回 + public static bool IsOk(this ObjectId id) + { + return !id.IsNull && id.IsValid && !id.IsErased && !id.IsEffectivelyErased && id.IsResident; + } + /// + /// 删除id代表的对象 + /// + /// 对象id + public static void Erase(this ObjectId id) + { + if (id.IsOk()) + { + var ent = id.GetObject()!; + using (ent.ForWrite()) + { + ent.Erase(); + }// 第一种读写权限自动转换写法 + //Env.Editor.Regen(); + } } } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs new file mode 100644 index 0000000..b1bae82 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs @@ -0,0 +1,128 @@ +namespace IFoxCAD.Cad; + +public static class PointEx +{ + + /// + /// 获取点的hash字符串,同时可以作为pt的字符串表示 + /// + /// 点 + /// 指示计算几维坐标的标志,1为计算x,2为计算x,y,其他为计算x,y,z + /// 保留的小数位数 + /// hash字符串 + public static string GetHashString(this Point3d pt, int xyz = 3, int decimalRetain = 6) + { + var de = $"f{decimalRetain}"; + return xyz switch + { + 1 => $"({pt.X.ToString(de)})", + 2 => $"({pt.X.ToString(de)},{pt.Y.ToString(de)})", + _ => $"({pt.X.ToString(de)},{pt.Y.ToString(de)},{pt.Z.ToString(de)})" + }; + } + + //为了频繁触发所以弄个全局变量 + static Plane? _Plane; + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 方向 + /// 弧度值 + public static double GetAngle(this Point3d startPoint, Point3d endPoint, Vector3d? direction = null) + { + if (direction != null) + _Plane = new Plane(Point3d.Origin, direction.Value); + if (_Plane == null) + _Plane = new Plane(Point3d.Origin, Vector3d.ZAxis); + return startPoint.GetVectorTo(endPoint).AngleOnPlane(_Plane); + } + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 弧度值 + public static double GetAngle(this Point2d startPoint, Point2d endPoint) + { + return startPoint.GetVectorTo(endPoint).Angle; + } + + /// + /// 获取中点 + /// + /// + /// + /// + public static Point2d GetCenter(this Point2d a, Point2d b) + { + // (p1 + p2) / 2; //溢出风险 + return new Point2d(a.X * 0.5 + b.X * 0.5, + a.Y * 0.5 + b.Y * 0.5); + } + + /// http://www.lee-mac.com/bulgeconversion.html + /// + /// 求凸度,判断三点是否一条直线上 + /// + /// 圆弧起点 + /// 圆弧腰点 + /// 圆弧尾点 + /// 逆时针为正,顺时针为负 + public static double GetArcBulge(this Point2d arc1, Point2d arc2, Point2d arc3, double tol = 1e-10) + { + double dStartAngle = arc2.GetAngle(arc1); + double dEndAngle = arc2.GetAngle(arc3); + //求的P1P2与P1P3夹角 + var talAngle = (Math.PI - dStartAngle + dEndAngle) / 2; + //凸度==拱高/半弦长==拱高比值/半弦长比值 + //有了比值就不需要拿到拱高值和半弦长值了,因为接下来是相除得凸度 + double bulge = Math.Sin(talAngle) / Math.Cos(talAngle); + + //处理精度 + if (bulge > 0.9999 && bulge < 1.0001) + bulge = 1; + else if (bulge < -0.9999 && bulge > -1.0001) + bulge = -1; + else if (Math.Abs(bulge) < tol) + bulge = 0; + return bulge; + } + + + #region 首尾相连 + /// + /// 首尾相连 + /// + public static Point2dCollection End2End(this Point2dCollection ptcol!!) + { + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 + return ptcol; + + //首尾不同,去加一个到最后 + var lst = new Point2d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + return new(lst); + } + /// + /// 首尾相连 + /// + public static Point3dCollection End2End(this Point3dCollection ptcol!!) + { + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 + return ptcol; + + //首尾不同,去加一个到最后 + var lst = new Point3d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + return new(lst); + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs index 9ede888..dbde0ac 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs @@ -1,106 +1,101 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 选择集扩展类 +/// +public static class SelectionSetEx { - + #region 获取对象id /// - /// 选择集扩展类 + /// 获取已选择的对象 /// - public static class SelectionSetEx + /// 选择集 + /// 已选择的对象集合 + public static IEnumerable GetSelectedObjects(this SelectionSet ss) { - #region 获取对象id - /// - /// 获取已选择的对象 - /// - /// 选择集 - /// 已选择的对象集合 - public static IEnumerable GetSelectedObjects(this SelectionSet ss) - { - return ss.Cast(); - } + return ss.Cast(); + } - /// - /// 获取已选择的对象 - /// - /// 已选择的对象泛型 - /// 选择集 - /// 已选择的对象集合 - public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : SelectedObject - { - return ss.Cast().OfType(); - } + /// + /// 获取已选择的对象 + /// + /// 已选择的对象泛型 + /// 选择集 + /// 已选择的对象集合 + public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : SelectedObject + { + return ss.Cast().OfType(); + } - /// - /// 从选择集中获取对象id - /// - /// 图元类型 - /// 选择集 - /// 已选择的对象id集合 - public static IEnumerable GetObjectIds(this SelectionSet ss) where T : Entity - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return - ss - .GetObjectIds() - .Where(id => id.ObjectClass().DxfName == dxfName); - } + /// + /// 从选择集中获取对象id + /// + /// 图元类型 + /// 选择集 + /// 已选择的对象id集合 + public static IEnumerable GetObjectIds(this SelectionSet ss) where T : Entity + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return + ss + .GetObjectIds() + .Where(id => id.ObjectClass().DxfName == dxfName); + } - /// - /// 将选择集的对象按类型分组 - /// - /// 选择集 - /// 分组后的类型/对象id集合 - public static IEnumerable> GetObjectIdGroup(this SelectionSet ss) - { - return - ss - .GetObjectIds() - .GroupBy(id => id.ObjectClass().DxfName); - } - #endregion + /// + /// 将选择集的对象按类型分组 + /// + /// 选择集 + /// 分组后的类型/对象id集合 + public static IEnumerable> GetObjectIdGroup(this SelectionSet ss) + { + return + ss + .GetObjectIds() + .GroupBy(id => id.ObjectClass().DxfName); + } + #endregion - #region 获取实体对象 + #region 获取实体对象 - /// - /// 获取指定类型图元 - /// - /// 指定类型 - /// 选择集 - /// 事务 - /// 打开模式 - /// 图元集合 - public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity - { - return - ss - .GetObjectIds() - .Select(id => tr.GetObject(id, openMode) as T); - } + /// + /// 获取指定类型图元 + /// + /// 指定类型 + /// 选择集 + /// 事务 + /// 打开模式 + /// 图元集合 + public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, DBTrans? tr = default) where T : Entity + { + if (ss is null) + throw new ArgumentNullException(nameof(ss)); - #endregion + tr ??= DBTrans.Top; + return + ss + .GetObjectIds() + .Select(id => tr.GetObject(id, openMode)) + .Where(ent => ent != null); + } - #region ForEach + #endregion - /// - /// 遍历选择集 - /// - /// 指定图元类型 - /// 选择集 - /// 事务 - /// 打开模式 - /// 处理函数 - public static void ForEach(this SelectionSet ss, Action action, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity + #region ForEach + /// + /// 遍历选择集 + /// + /// 指定图元类型 + /// 选择集 + /// 事务 + /// 打开模式 + /// 处理函数 + public static void ForEach(this SelectionSet ss, Action action, OpenMode openMode = OpenMode.ForRead, DBTrans? tr = default) where T : Entity + { + foreach (T? ent in ss.GetEntities(openMode, tr)) { - foreach (T ent in ss.GetEntities(openMode, tr)) - { - action?.Invoke(ent); - } + action?.Invoke(ent); } - #endregion } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs index 327f8ba..3c0ace3 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs @@ -1,81 +1,73 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; - -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; - -namespace IFoxCAD.Cad +/// +/// 符号表类扩展函数 +/// +public static class SymbolTableEx { + #region 图层表 + /// + /// 添加图层 + /// + /// 图层符号表 + /// 图层名 + /// 图层颜色 + /// 图层id + public static ObjectId Add(this SymbolTable table, string name, Color color) + { + return table.Add(name, lt => lt.Color = color); + } /// - /// 符号表类扩展函数 + /// 添加图层 /// - public static class SymbolTableEx + /// 图层符号表 + /// 图层名 + /// 图层颜色索引值 + /// 图层id + public static ObjectId Add(this SymbolTable table, string name, int colorIndex) { - #region 图层表 - /// - /// 添加图层 - /// - /// 图层符号表 - /// 图层名 - /// 图层颜色 - /// 图层id - public static ObjectId Add(this SymbolTable table, string name, Color color) + colorIndex %= 256;//防止输入的颜色超出256 + colorIndex = Math.Abs(colorIndex);//防止负数 + return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); + } + /// + /// 更改图层名 + /// + /// 图层符号表 + /// 旧图层名 + /// 新图层名 + public static ObjectId Rename(this SymbolTable table, string Oldname, string NewName) + { + if (table.Has(Oldname)) { - return table.Add(name, lt => lt.Color = color); + table.Change(Oldname, ly => + { + ly.Name = NewName; + } + ); + return table[NewName]; } - /// - /// 添加图层 - /// - /// 图层符号表 - /// 图层名 - /// 图层颜色索引值 - /// 图层id - public static ObjectId Add(this SymbolTable table, string name, int colorIndex) + else { - colorIndex %= 256; //防止输入的颜色超出256 - colorIndex = Math.Abs(colorIndex);//防止负数 - return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); + return ObjectId.Null; } - /// - /// 更改图层名 - /// - /// 图层符号表 - /// 旧图层名 - /// 新图层名 - public static ObjectId Rename(this SymbolTable table, string Oldname, string NewName) + } + /// + /// 删除图层 + /// + /// 层表 + /// 图层名 + /// 成功返回 ,失败返回 + public static bool Delete(this SymbolTable table, string name) + { + if (name == "0" || name == "Defpoints" || !table.Has(name) || table[name] == DBTrans.Top.Database.Clayer) { - if (table.Has(Oldname)) - { - table.Change(Oldname, ly => { - ly.Name = NewName; - } - ); - return table[NewName]; - } - else - { - return ObjectId.Null; - } + return false; } - /// - /// 删除图层 - /// - /// 层表 - /// 图层名 - /// 成功返回 ,失败返回 - public static bool Delete(this SymbolTable table, string name) + table.CurrentSymbolTable.GenerateUsageData(); + var ltr = table.GetRecord(name); + if (ltr is not null) { - if (name == "0" || name == "Defpoints" || !table.Has(name) || table[name] == DBTrans.Top.Database.Clayer) - { - return false; - } - table.CurrentSymbolTable.GenerateUsageData(); - var ltr = table.GetRecord(name); if (ltr.IsUsed) { return false; @@ -86,191 +78,303 @@ public static bool Delete(this SymbolTable table, } return true; } - #endregion - - #region 块表 - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 对所添加块表的委托n - /// 添加图元的委托 - /// 添加属性定义的委托 - /// 块定义id - /// TODO: 需要测试匿名块等特殊的块是否能定义 - public static ObjectId Add(this SymbolTable table, string name, Action action = null, Func> ents = null, Func> attdef = null) - { - return table.Add(name, btr => { - action?.Invoke(btr); - var entsres = ents?.Invoke(); - if (entsres is not null) - { - btr.AddEntity(entsres); - } - var adddefres = attdef?.Invoke(); - if (adddefres is not null) - { - btr.AddEntity(adddefres); - } - //if (ents is not null) - //{ - // btr.AddEntity(ents?.Invoke()); - //} - //if (attdef is not null) - //{ - // btr.AddEntity(attdef?.Invoke()); - //} - }); - } - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 图元 - /// 属性定义 - /// - public static ObjectId Add(this SymbolTable table, string name, IEnumerable ents = null, IEnumerable attdef = null) - { - return table.Add(name, null, () => { return ents; }, () => { return attdef; }); - } + return false; + } + #endregion - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 图元(包括属性) - /// - public static ObjectId Add(this SymbolTable table, string name, params Entity[] ents) + #region 块表 + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 对所添加块表的委托n + /// 添加图元的委托 + /// 添加属性定义的委托 + /// 块定义id + /// TODO: 需要测试匿名块等特殊的块是否能定义 + public static ObjectId Add(this SymbolTable table, string name, Action? action = null, Func>? ents = null, Func>? attdef = null) + { + return table.Add(name, btr => { - return table.Add(name, null, () => { return ents; }); - } + action?.Invoke(btr); + var entsres = ents?.Invoke(); + if (entsres is not null) + { + btr.AddEntity(entsres); + } + var adddefres = attdef?.Invoke(); + if (adddefres is not null) + { + btr.AddEntity(adddefres); + } - /// - /// 从文件中获取块定义 - /// - /// 块表 - /// 文件名 - /// 是否覆盖 - /// 块定义Id - public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, bool over) + }); + } + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 图元 + /// 属性定义 + /// + public static ObjectId Add(this SymbolTable table, string name, IEnumerable? ents = null, IEnumerable? attdef = null) + { + return table.Add(name, btr => { - //FileInfo fi = new(fileName); - //string blkdefname = fi.Name; - //if (blkdefname.Contains(".")) - //{ - // blkdefname = blkdefname.Substring(0, blkdefname.LastIndexOf('.')); - //} + if (ents is not null) + { + btr.AddEntity(ents); + } + if (attdef is not null) + { + btr.AddEntity(attdef); + } + }); + } - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 图元(包括属性) + /// + public static ObjectId Add(this SymbolTable table, string name, params Entity[] ents) + { + return table.Add(name, null, () => { return ents; }); + } - ObjectId id = table[blkdefname]; - bool has = id != ObjectId.Null; - if ((has && over) || !has) + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义id + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, ObjectId id, List atts) + { + table.Change(id, btr => + { + var attTags = new List(); + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + foreach (AttributeDefinition att in atts) { - Database db = new(); - db.ReadDwgFile(fileName, FileShare.Read, true, null); - id = table.Database.Insert(BlockTableRecord.ModelSpace, blkdefname, db, false); + if (!attTags.Contains(att.Tag.ToUpper())) + { + btr.AddEntity(att); + } } + }); + } + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义名字 + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, string name, List atts) + { + table.Change(name, btr => + { + var attTags = new List(); + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + foreach (AttributeDefinition att in atts) + { + if (!attTags.Contains(att.Tag.ToUpper())) + { + btr.AddEntity(att); + } + } + }); + } - return id; - } - + /// + /// 从文件中获取块定义 + /// + /// 块表 + /// 文件名 + /// 是否覆盖 + /// 块定义Id + public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, bool over) + { + //FileInfo fi = new(fileName); + //string blkdefname = fi.Name; + //if (blkdefname.Contains(".")) + //{ + // blkdefname = blkdefname.Substring(0, blkdefname.LastIndexOf('.')); + //} + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); - /// - /// 从文件中获取块定义 - /// - /// 块表 - /// 文件名 - /// 块定义名 - /// 是否覆盖 - /// 块定义Id - public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, string blockName, bool over) + ObjectId id = table[blkdefname]; + bool has = id != ObjectId.Null; + if ((has && over) || !has) { - return - table.GetRecordFrom( - t => t.BlockTable, - fileName, - blockName, - over); + Database db = new(false, true); + db.ReadDwgFile(fileName, FileShare.Read, true, null); + db.CloseInput(true); + id = table.Database.Insert(BlockTableRecord.ModelSpace, blkdefname, db, false); } - #endregion + + return id; + } - #region 线型表 - /// - /// 添加线型 - /// - /// 线型表 - /// 线型名 - /// 线型说明 - /// 线型长度 - /// 笔画长度数组 - /// 线型id - public static ObjectId Add(this SymbolTable table, string name, string description, double length, double[] dash) - { - return table.Add( - name, - ltt => { - ltt.AsciiDescription = description; - ltt.PatternLength = length; //线型的总长度 + + /// + /// 从文件中获取块定义 + /// + /// 块表 + /// 文件名 + /// 块定义名 + /// 是否覆盖 + /// 块定义Id + public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, string blockName, bool over) + { + return + table.GetRecordFrom( + t => t.BlockTable, + fileName, + blockName, + over); + } + #endregion + + + #region 线型表 + /// + /// 添加线型 + /// + /// 线型表 + /// 线型名 + /// 线型说明 + /// 线型长度 + /// 笔画长度数组 + /// 线型id + public static ObjectId Add(this SymbolTable table, string name, string description, double length, double[] dash) + { + return table.Add( + name, + ltt => + { + ltt.AsciiDescription = description; + ltt.PatternLength = length; //线型的总长度 ltt.NumDashes = dash.Length; //组成线型的笔画数目 for (int i = 0; i < dash.Length; i++) - { - ltt.SetDashLengthAt(i, dash[i]); - } + { + ltt.SetDashLengthAt(i, dash[i]); + } //ltt.SetDashLengthAt(0, 0.5); //0.5个单位的划线 //ltt.SetDashLengthAt(1, -0.25); //0.25个单位的空格 //ltt.SetDashLengthAt(2, 0); // 一个点 //ltt.SetDashLengthAt(3, -0.25); //0.25个单位的空格 } - ); - } - #endregion + ); + } + #endregion - #region 文字样式表 - /// - /// 添加文字样式记录 - /// - /// 文字样式表 - /// 文字样式名 - /// 字体名 - /// 宽度比例 - /// 文字样式Id - public static ObjectId Add(this SymbolTable table, string textStyleName, string font, double xscale) + #region 文字样式表 + /// + /// 添加文字样式记录 + /// + /// 文字样式表 + /// 文字样式名 + /// 字体名 + /// 宽度比例 + /// 文字样式Id + public static ObjectId Add(this SymbolTable table, + string textStyleName, + string font, + double xscale = 1.0) + { + return + table.Add( + textStyleName, + tstr => + { + tstr.Name = textStyleName; + tstr.FileName = font; + tstr.XScale = xscale; + }); + } + /// + /// 添加文字样式记录 + /// + /// 文字样式表 + /// 文字样式名 + /// 字体名枚举 + /// 宽度比例 + /// 文字样式Id + public static ObjectId Add(this SymbolTable table, string textStyleName, FontTTF fontTTF, double xscale = 1.0) + { + return table.Add(textStyleName, fontTTF.GetDesc(), xscale); + } + + /// + ///

添加文字样式记录,如果存在就默认强制替换

+ /// 此函数为了 而设 + ///
+ /// 文字样式表 + /// 文字样式名 + /// 字体名 + /// 大字体名 + /// 宽度比例 + /// 高度 + /// 是否强制替换 + /// 文字样式Id + public static ObjectId AddWithChange(this SymbolTable table, + string textStyleName, + string smallFont, + string bigFont = "", + double xScale = 1, + double height = 0, + bool forceChange = true) + { + if (forceChange && table.Has(textStyleName)) { - return - table.Add( - textStyleName, - tstr => { - tstr.Name = textStyleName; - tstr.FileName = font; - tstr.XScale = xscale; - }); + table.Change(textStyleName, ttr => + { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + if (bigFont != "") + { + ttr.BigFontFileName = bigFont; + } + }); + return table[textStyleName]; } - #endregion + return table.Add(textStyleName, ttr => + { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + }); + } - #region 注册应用程序表 - #endregion + #endregion - #region 标注样式表 + #region 注册应用程序表 - #endregion + #endregion - #region 用户坐标系表 + #region 标注样式表 - #endregion + #endregion - #region 视图表 + #region 用户坐标系表 - #endregion + #endregion - #region 视口表 + #region 视图表 - #endregion - } + #endregion + + #region 视口表 + + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs index c66b2e2..bceeac0 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs @@ -1,353 +1,460 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; - -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; - -namespace IFoxCAD.Cad +/// +/// 符号表记录扩展类 +/// +public static class SymbolTableRecordEx { + #region 块表记录 + + #region 克隆实体id /// - /// 符号表记录扩展类 + /// 深度克隆id到块表记录 + /// 0x01 此方法不允许是未添加数据库的图元,因此它是id + /// 0x02 若为未添加数据库图元,则利用entity.Clone();同时不需要考虑动态块属性,可以使用entity.GetTransformedCopy /// - public static class SymbolTableRecordEx + /// + /// 克隆到当前块表记录,相当于原地克隆 + /// 克隆到目标块表记录内,相当于制作新块 + /// + /// 图元id集合,注意所有成员都要在同一个空间中 + /// 克隆后的id词典 + public static IdMapping DeepClone(this BlockTableRecord btr, ObjectIdCollection objIds) { - - - #region 块表记录 - - #region 添加实体 - /// - /// 添加实体对象 - /// - /// 块表记录 - /// 实体 - /// 事务管理器 - /// 对象 id - public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Transaction tr = null) + if (objIds is null || objIds.Count == 0) + throw new ArgumentNullException(nameof(objIds)); + + var db = objIds[0].Database; + IdMapping mapping = new(); + using (btr.ForWrite()) { - if (entity is null) - throw new ArgumentNullException(nameof(entity), "对象为 null"); + try + { + db.DeepCloneObjects(objIds, btr.ObjectId, mapping, false); - ObjectId id; - tr ??= DBTrans.Top.Transaction; - using (btr.ForWrite()) + // 不在此提取,为了此函数被高频调用 + // 获取克隆键值对(旧块名,新块名) + // foreach (ObjectId item in blockIds) + // result.Add(mapping[item].Value); + } + catch (System.Exception e) { - id = btr.AppendEntity(entity); - tr.AddNewlyCreatedDBObject(entity, true); + LogHelper.FlagOutVsOutput = true; + e.WriteLog("深度克隆出错了"); } - return id; } + return mapping; + } - /// - /// 添加实体集合 - /// - /// 实体类型 - /// 块表记录 - /// 事务 - /// 实体集合 - /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, Transaction tr = null) where T : Entity - { - if (ents.Any(ent => ent is null)) - throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); + /// + /// 克隆图元实体(这个函数有问题,会出现偶尔成功,偶尔失败,拖动过变成匿名块) + /// 若为块则进行设置属性,因此控制动态块属性丢失; + /// + /// 图元 + /// 矩阵 + //public static void EntityTransformedCopy(this Entity ent, Matrix3d matrix) + //{ + // var entNew = ent.GetTransformedCopy(matrix); + // if (ent is BlockReference blockReference) + // entNew.SetPropertiesFrom(blockReference); + //} - tr ??= DBTrans.Top.Transaction; - using (btr.ForWrite()) - { - return ents - .Select( - ent => { - ObjectId id = btr.AppendEntity(ent); - tr.AddNewlyCreatedDBObject(ent, true); - return id; - }) - .ToList(); - } - } - /// - /// 添加多个实体 - /// - /// 块表记录 - /// 实体集合 - /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, params Entity[] ents) - { - return btr.AddEntity(ents, null); - } - #endregion + #endregion - #region 添加图元 - /// - /// 在指定绘图空间添加图元 - /// - /// 图元类型 - /// 绘图空间 - /// 图元对象 - /// 图元属性设置委托 - /// 事务管理器 - /// 图元id - private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action action, Transaction trans) where T : Entity - { - trans ??= DBTrans.Top.Transaction; - action?.Invoke(ent); - return btr.AddEntity(ent, trans); - } + #region 添加实体 + /// + /// 添加实体对象 + /// + /// 块表记录 + /// 实体 + /// 事务管理器 + /// 对象 id + public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, + Transaction? trans = null) + { + //if (entity is null) + // throw new ArgumentNullException(nameof(entity), "对象为 null"); - /// - /// 在指定绘图空间添加直线 - /// - /// 事务管理器 - /// 起点 - /// 终点 - /// 绘图空间 - /// 直线属性设置委托 - /// 直线的id - public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, Action action = default, Transaction trans = default) + ObjectId id; + trans ??= DBTrans.Top.Transaction; + using (btr.ForWrite()) { - var line = new Line(start, end); - return btr.AddEnt(line, action, trans); + id = btr.AppendEntity(entity); + trans.AddNewlyCreatedDBObject(entity, true); } - /// - /// 在指定绘图空间X-Y平面添加圆 - /// - /// 绘图空间 - /// 圆心 - /// 半径 - /// 圆属性设置委托 - /// 事务管理器 - /// 圆的id - public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, Action action = default, Transaction trans = default) + return id; + } + + /// + /// 添加实体集合 + /// + /// 实体类型 + /// 块表记录 + /// 实体集合 + /// 事务 + /// 对象 id 列表 + public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, + Transaction? trans = null) where T : Entity + { + //if (ents.Any(ent => ent is null)) + // throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); + + trans ??= DBTrans.Top.Transaction; + using (btr.ForWrite()) { - var circle = new Circle(center, Vector3d.ZAxis, radius); - return btr.AddEnt(circle, action, trans); + return ents + .Select( + ent => { + ObjectId id = btr.AppendEntity(ent); + trans.AddNewlyCreatedDBObject(ent, true); + return id; + }) + .ToList(); } + } - /// - /// 在指定绘图空间X-Y平面3点画外接圆 - /// - /// 绘图空间 - /// 第一点 - /// 第二点 - /// 第三点 - /// 圆属性设置委托 - /// 事务管理器 - /// 三点有外接圆则返回圆的id,否则返回ObjectId.Null - public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d p1, Point3d p2, - Action action = default, Transaction trans = default) + /// + /// 添加多个实体 + /// + /// 块表记录 + /// 实体集合 + /// 对象 id 列表 + public static IEnumerable AddEntity(this BlockTableRecord btr, params Entity[] ents) + { + return btr.AddEntity(ents, null); + } + #endregion + + #region 添加图元 + /// + /// 在指定绘图空间添加图元 + /// + /// 图元类型 + /// 绘图空间 + /// 图元对象 + /// 图元属性设置委托 + /// 事务管理器 + /// 图元id + private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action? action, Transaction? trans) where T : Entity + { + //trans ??= DBTrans.Top.Transaction; + action?.Invoke(ent); + return btr.AddEntity(ent, trans); + } + /// + /// 委托式的添加图元 + /// + /// 块表 + /// 返回图元的委托 + /// 事务 + /// 图元id,如果委托返回 null,则为 ObjectId.Null + public static ObjectId AddEnt(this BlockTableRecord btr, Func action, Transaction? transaction) + { + //transaction ??= DBTrans.Top.Transaction; + var ent = action.Invoke(); + if (ent is null) + return ObjectId.Null; + + return btr.AddEntity(ent, transaction); + } + + /// + /// 在指定绘图空间添加直线 + /// + /// 事务管理器 + /// 起点 + /// 终点 + /// 绘图空间 + /// 直线属性设置委托 + /// 直线的id + public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, + Action? action = default, Transaction? trans = default) + { + var line = new Line(start, end); + return btr.AddEnt(line, action, trans); + } + /// + /// 在指定绘图空间X-Y平面添加圆 + /// + /// 绘图空间 + /// 圆心 + /// 半径 + /// 圆属性设置委托 + /// 事务管理器 + /// 圆的id + public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, + Action? action = default, Transaction? trans = default) + { + var circle = new Circle(center, Vector3d.ZAxis, radius); + return btr.AddEnt(circle, action, trans); + } + + /// + /// 在指定绘图空间X-Y平面3点画外接圆 + /// + /// 绘图空间 + /// 第一点 + /// 第二点 + /// 第三点 + /// 圆属性设置委托 + /// 事务管理器 + /// 三点有外接圆则返回圆的id,否则返回ObjectId.Null + public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d p1, Point3d p2, + Action? action = default, Transaction? trans = default) + { + var circle = EntityEx.CreateCircle(p0, p1, p2); + //return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + if (circle is null) + throw new ArgumentNullException(nameof(circle), "对象为 null"); + + return btr.AddEnt(circle, action, trans); + } + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 多段线信息 + /// 线宽 + /// 是否闭合 + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List bvws, + double? constantWidth = null, + bool isClosed = true, + Action? action = default, + Transaction? trans = default) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + if (constantWidth is not null) { - Circle circle = EntityEx.CreateCircle(p0, p1, p2); - return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, constantWidth.Value, constantWidth.Value); } - /// - /// 在指定的绘图空间添加轻多段线 - /// - /// 绘图空间 - /// 端点表 - /// 凸度表 - /// 端点的起始宽度 - /// 端点的终止宽度 - /// 轻多段线属性设置委托 - /// 事务管理器 - /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List pts, List bulges = default, List startWidths = default, List endWidths = default, Action action = default, Transaction trans = default) + else { - bulges ??= new List(new double[pts.Count]); - startWidths ??= new List(new double[pts.Count]); - endWidths ??= new List(new double[pts.Count]); - Polyline pl = new(); - for (int i = 0; i < pts.Count; i++) - { - pl.AddVertexAt(i, pts[i].Point2d(), bulges[i], startWidths[i], endWidths[i]); - } - return btr.AddEnt(pl, action, trans); + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, bvws[i].StartWidth, bvws[i].EndWidth); } + pl.Closed = isClosed;//闭合 + return btr.AddEnt(pl, action, trans); + } + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 端点表 + /// 凸度表 + /// 端点的起始宽度 + /// 端点的终止宽度 + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List pts, + List? bulges = default, + List? startWidths = default, + List? endWidths = default, + Action? action = default, + Transaction? trans = default) + { + bulges ??= new(new double[pts.Count]); + startWidths ??= new(new double[pts.Count]); + endWidths ??= new(new double[pts.Count]); -#if ac2013 - /// - /// 在指定的绘图空间添加轻多段线 - /// - /// 绘图空间 - /// 端点表,利用元组(Point3d pt, double bulge, double startWidth, double endWidth) - /// 轻多段线属性设置委托 - /// 事务管理器 - /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, Action action = default, Transaction trans = default) - { + Polyline pl = new(); + pl.SetDatabaseDefaults(); - Polyline pl = new(); - pts.ForEach((i, vertex) => - { - pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); - }); + for (int i = 0; i < pts.Count; i++) + pl.AddVertexAt(i, pts[i].Point2d(), bulges[i], startWidths[i], endWidths[i]); + return btr.AddEnt(pl, action, trans); + } - return btr.AddEnt(pl, action, trans); - } -#endif + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 端点表,利用元组(Point3d pt, double bulge, double startWidth, double endWidth) + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, + Action? action = default, + Transaction? trans = default) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((i, vertex) => { + pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); + }); - /// - /// 在指定绘图空间X-Y平面3点画圆弧 - /// - /// 绘图空间 - /// 圆弧起点 - /// 圆弧上的点 - /// 圆弧终点 - /// 圆弧属性设置委托 - /// 事务管理器 - /// 圆弧id - public static ObjectId AddArc(this BlockTableRecord btr, Point3d startPoint, Point3d pointOnArc, Point3d endPoint, Action action = default, Transaction trans = default) - { - var arc = EntityEx.CreateArc(startPoint, pointOnArc, endPoint); - return btr.AddEnt(arc, action, trans); - } - #endregion + return btr.AddEnt(pl, action, trans); + } - #region 获取实体/实体id - /// - /// 获取块表记录内的指定类型的实体 - /// - /// 实体类型 - /// 块表记录 - /// 事务 - /// 打开模式 - /// 实体集合 - public static IEnumerable GetEntities(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, Transaction trans = null) where T : Entity - { - trans ??= DBTrans.Top.Transaction; - return - btr - .Cast() - .Select(id => trans.GetObject(id, mode)) - .OfType(); - } + /// + /// 在指定绘图空间X-Y平面3点画圆弧 + /// + /// 绘图空间 + /// 圆弧起点 + /// 圆弧上的点 + /// 圆弧终点 + /// 圆弧属性设置委托 + /// 事务管理器 + /// 圆弧id + public static ObjectId AddArc(this BlockTableRecord btr, + Point3d startPoint, Point3d pointOnArc, Point3d endPoint, + Action? action = default, Transaction? trans = default) + { + var arc = EntityEx.CreateArc(startPoint, pointOnArc, endPoint); + return btr.AddEnt(arc, action, trans); + } - /// - /// 按类型获取实体Id,AutoCad2010以上版本支持 - /// - /// 实体类型 - /// 块表记录 - /// 实体Id集合 - public static IEnumerable GetObjectIds(this BlockTableRecord btr) where T : Entity - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return btr.Cast() - .Where(id => id.ObjectClass().DxfName == dxfName); - } + // todo: 所有涉及默认无参构造的实体类型,都需要调用SetDatabaseDefaults(); + #endregion - /// - /// 按类型获取实体Id的分组 - /// - /// 块表记录 - /// 实体Id分组 - public static IEnumerable> GetObjectIds(this BlockTableRecord btr) - { - return - btr - .Cast() - .GroupBy(id => id.ObjectClass().DxfName); - } + #region 获取实体/实体id + /// + /// 获取块表记录内的指定类型的实体 + /// + /// 实体类型 + /// 块表记录 + /// 打开模式 + /// 事务 + /// 实体集合 + public static IEnumerable GetEntities(this BlockTableRecord btr, + OpenMode mode = OpenMode.ForRead, + Transaction? trans = default) where T : Entity + { + trans ??= DBTrans.Top.Transaction; + return + btr + .Cast() + .Select(id => trans.GetObject(id, mode)) + .OfType(); + } - /// - /// 获取绘制顺序表 - /// - /// 块表 - /// 事务 - /// 绘制顺序表 - public static DrawOrderTable GetDrawOrderTable(this BlockTableRecord btr, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - return tr.GetObject(btr.DrawOrderTableId, OpenMode.ForRead) as DrawOrderTable; - } + /// + /// 按类型获取实体Id,AutoCad2010以上版本支持 + /// + /// 实体类型 + /// 块表记录 + /// 实体Id集合 + public static IEnumerable GetObjectIds(this BlockTableRecord btr) where T : Entity + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return btr.Cast() + .Where(id => id.ObjectClass().DxfName == dxfName); + } - #endregion + /// + /// 按类型获取实体Id的分组 + /// + /// 块表记录 + /// 实体Id分组 + public static IEnumerable> GetObjectIds(this BlockTableRecord btr) + { + return + btr + .Cast() + .GroupBy(id => id.ObjectClass().DxfName); + } - #region 插入块参照 + /// + /// 获取绘制顺序表 + /// + /// 块表 + /// 事务 + /// 绘制顺序表 + public static DrawOrderTable? GetDrawOrderTable(this BlockTableRecord btr, + Transaction? trans = default) + { + trans ??= DBTrans.Top.Transaction; + return trans.GetObject(btr.DrawOrderTableId, OpenMode.ForRead) as DrawOrderTable; + } + #endregion - /// - /// 插入块参照 - /// - /// 插入点 - /// 块名 - /// 块插入比例,默认为1 - /// 块插入旋转角(弧度),默认为0 - /// 属性字典{Tag,Value},默认为null - /// 块参照对象id - public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - string blockName, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction trans = null) + #region 插入块参照 + /// + /// 插入块参照 + /// + /// 块表记录 + /// 插入点 + /// 块名 + /// 块插入比例,默认为1 + /// 块插入旋转角(弧度),默认为0 + /// 属性字典{Tag,Value},默认为null + /// 事务 + /// 块参照对象id + public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, + string blockName, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = default, Transaction? trans = null) + { + trans ??= DBTrans.Top.Transaction; + if (!DBTrans.Top.BlockTable.Has(blockName)) { - trans ??= DBTrans.Top.Transaction; - if (!DBTrans.Top.BlockTable.Has(blockName)) - { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{blockName}的块定义。"); - return ObjectId.Null; - } - return blockTableRecord.InsertBlock(position, DBTrans.Top.BlockTable[blockName], scale, rotation, atts, trans); + DBTrans.Top.Editor?.WriteMessage($"\n不存在名字为{blockName}的块定义。"); + return ObjectId.Null; } - /// - /// 插入块参照 - /// - /// 插入点 - /// 块定义id - /// 块插入比例,默认为1 - /// 块插入旋转角(弧度),默认为0 - /// 属性字典{Tag,Value},默认为null - /// 块参照对象id - public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - ObjectId blockId, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction tr = null) + return blockTableRecord.InsertBlock(position, DBTrans.Top.BlockTable[blockName], scale, rotation, atts, trans); + } + /// + /// 插入块参照 + /// + /// 插入点 + /// 块定义id + /// 块插入比例,默认为1 + /// 块插入旋转角(弧度),默认为0 + /// 属性字典{Tag,Value},默认为null + /// 块参照对象id + public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, + ObjectId blockId, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = default, Transaction? trans = null) + { + trans ??= DBTrans.Top.Transaction; + if (!DBTrans.Top.BlockTable.Has(blockId)) { - tr ??= DBTrans.Top.Transaction; - if (!DBTrans.Top.BlockTable.Has(blockId)) - { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{DBTrans.Top.GetObject(blockId).Name}的块定义。"); - return ObjectId.Null; - } - using var blockref = new BlockReference(position, blockId) - { - ScaleFactors = scale, - Rotation = rotation - }; - var objid = blockTableRecord.AddEntity(blockref); - if (atts != default) + DBTrans.Top.Editor?.WriteMessage($"\n不存在块定义。"); + return ObjectId.Null; + } + using var blockref = new BlockReference(position, blockId) + { + ScaleFactors = scale, + Rotation = rotation + }; + var objid = blockTableRecord.AddEntity(blockref); + if (atts != default) + { + var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord)!; + if (btr.HasAttributeDefinitions) { - var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord); - if (btr.HasAttributeDefinitions) + var attdefs = btr.GetEntities(); + foreach (var attdef in attdefs) { - var attdefs = btr - .GetEntities() - .Where(attdef => !(attdef.Constant || attdef.Invisible)); - foreach (var attdef in attdefs) - { - using AttributeReference attref = new(); - attref.SetAttributeFromBlock(attdef, blockref.BlockTransform); - attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); - attref.AdjustAlignment(DBTrans.Top.Database); - if (atts.ContainsKey(attdef.Tag)) - attref.TextString = atts[attdef.Tag]; + using AttributeReference attref = new(); + attref.SetDatabaseDefaults(); + attref.SetAttributeFromBlock(attdef, blockref.BlockTransform); + attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); + attref.AdjustAlignment(DBTrans.Top.Database); + + if (atts.ContainsKey(attdef.Tag)) + attref.TextString = atts[attdef.Tag]; - blockref.AttributeCollection.AppendAttribute(attref); - tr.AddNewlyCreatedDBObject(attref, true); - } + blockref.AttributeCollection.AppendAttribute(attref); + trans.AddNewlyCreatedDBObject(attref, true); } } - return objid; } - - #endregion - #endregion - - - + return objid; } + #endregion + + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs b/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs new file mode 100644 index 0000000..0e875c5 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs @@ -0,0 +1,188 @@ +namespace IFoxCAD.Cad; + +public static class Tools +{ + public static void TestTimes2(int count, string message, Action action) + { + System.Diagnostics.Stopwatch watch = new(); + watch.Start(); //开始监视代码运行时间 + for (int i = 0; i < count; i++) + action.Invoke();//需要测试的代码 + watch.Stop(); //停止监视 + TimeSpan timespan = watch.Elapsed; //获取当前实例测量得出的总时间 + double time = timespan.TotalMilliseconds; + string name = "毫秒"; + if (timespan.TotalMilliseconds > 1000) + { + time = timespan.TotalSeconds; + name = "秒"; + } + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({name})"); //总毫秒数 + } + + /// + /// 纳秒计时器 + /// + public static void TestTimes(int count, string message, Action action, + Timer.TimeEnum timeEnum = Timer.TimeEnum.Millisecond) + { + double time = Timer.RunTime(() => { + for (int i = 0; i < count; i++) + action(); + }, timeEnum); + + string timeNameZn = ""; + switch (timeEnum) + { + case Timer.TimeEnum.Second: + timeNameZn = " 秒"; + break; + case Timer.TimeEnum.Millisecond: + timeNameZn = " 毫秒"; + break; + case Timer.TimeEnum.Microsecond: + timeNameZn = " 微秒"; + break; + case Timer.TimeEnum.Nanosecond: + timeNameZn = " 纳秒"; + break; + } + + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({timeNameZn})"); + } +} + +/* +//测试例子,同时验证两个计时器 +var stopwatch = new Stopwatch(); +Timer.RunTime(() => { + + stopwatch.Start(); + for (int i = 0; i < 10000000; i++) + i++; + stopwatch.Stop(); + +}, Timer.TimeEnum.Millisecond, "运行:"); +Console.WriteLine("运行毫秒:" + stopwatch.ElapsedMilliseconds); + */ + +public class Timer +{ + [Flags] + public enum TimeEnum + { + /// + /// 秒 + /// + Second, + /// + /// 毫秒 + /// + Millisecond, + /// + /// 微秒 + /// + Microsecond, + /// + /// 纳秒 + /// + Nanosecond, + } + + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + + /// + /// 这个函数会检索性能计数器的频率. + /// 性能计数器的频率在系统启动时是固定的,并且在所有处理器上都是一致的 + /// 因此,只需在应用初始化时查询频率,即可缓存结果 + /// 在运行 Windows XP 或更高版本的系统上,该函数将始终成功,因此永远不会返回零 + /// + /// + /// + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceFrequency(out long lpFrequency); + + long _startTime, _stopTime; + long _freq; + + public Timer() + { + _startTime = 0; + _stopTime = 0; + + if (!QueryPerformanceFrequency(out _freq)) + throw new Win32Exception("不支持高性能计数器"); + } + + /// + /// 开始计时器 + /// + public void Start() + { + System.Threading.Thread.Sleep(0); + QueryPerformanceCounter(out _startTime); + } + + /// + /// 停止计时器 + /// + public void Stop() + { + QueryPerformanceCounter(out _stopTime); + _Second = (double)(_stopTime - _startTime) / _freq; + } + double _Second = 0; + + // 返回计时器经过时间 + public double Second => _Second; + public double Millisecond => _Second * 1000.0; + public double Microsecond => _Second * 1000000.0; + public double Nanosecond => _Second * 1000000000.0; + + public static double RunTime(Action action, TimeEnum timeEnum = TimeEnum.Millisecond, string? msg = null) + { + var nanoSecond = new Timer(); + nanoSecond.Start(); + action(); + nanoSecond.Stop(); + + double time = 0; + switch (timeEnum) + { + case TimeEnum.Second: + time = nanoSecond.Second; + break; + case TimeEnum.Millisecond: + time = nanoSecond.Millisecond; + break; + case TimeEnum.Microsecond: + time = nanoSecond.Microsecond; + break; + case TimeEnum.Nanosecond: + time = nanoSecond.Nanosecond; + break; + } + if (msg != null) + { + string timeNameZn = ""; + switch (timeEnum) + { + case TimeEnum.Second: + timeNameZn = " 秒"; + break; + case TimeEnum.Millisecond: + timeNameZn = " 毫秒"; + break; + case TimeEnum.Microsecond: + timeNameZn = " 微秒"; + break; + case TimeEnum.Nanosecond: + timeNameZn = " 纳秒"; + break; + } + Env.Print(msg + " " + time + timeNameZn); + } + return time; + } +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" new file mode 100644 index 0000000..7868bee --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" @@ -0,0 +1,358 @@ +namespace IFoxCAD.Cad; +using PointV = Point2d; + +/// +/// 填充边界转换器 +/// +public class HatchConverter +{ + #region 辅助类 + /// + /// 生成圆形数据 + /// + class CircleData + { + public PointV Center; + public double Radius; + + /// + /// 生成圆形数据 + /// + /// 对称点1 + /// 对称点2 + public CircleData(PointV symmetryAxisPoint1, PointV symmetryAxisPoint2) + { + Center = symmetryAxisPoint1.GetCenter(symmetryAxisPoint2); + Radius = symmetryAxisPoint1.GetDistanceTo(symmetryAxisPoint2) * 0.5; + } + } + + /// + /// 填充转换器的数据 + /// + class HatchConverterData + { + public List PolyLineData; + public List CircleData; + public List SplineData; + + /// + /// 填充转换器的数据 + /// + public HatchConverterData() + { + PolyLineData = new(); + CircleData = new(); + SplineData = new(); + } + } + #endregion + + #region 成员 + /// + /// 外部只能调用id,否则跨事务造成错误 + /// + public ObjectId OldHatchId + { + get + { + if (_oldHatch is null) + return ObjectId.Null; + return _oldHatch.ObjectId; + } + } + readonly Hatch? _oldHatch; + + readonly List _hcDatas; + /// + /// 生成的填充边界id + /// + public List BoundaryIds; + #endregion + + #region 构造 + /// + /// 填充边界转换器 + /// + HatchConverter() + { + _hcDatas = new(); + BoundaryIds = new(); + } + + /// + /// 填充边界转换器 + /// + /// 需要转化的Hatch对象 + public HatchConverter(Hatch hatch) : this() + { + _oldHatch = hatch; + + //不能在提取信息的时候进行新建cad图元, + //否则cad将会提示遗忘释放 + hatch.ForEach(loop => { + var hcData = new HatchConverterData(); + + bool isCurve2d = true; + if (loop.IsPolyline) + { + //边界是多段线 + HatchLoopIsPolyline(loop, hcData); + isCurve2d = false; + } + else + { + if (loop.Curves.Count == 2)//1是不可能的,大于2的是曲线 + { + //边界是曲线,过滤可能是圆形的情况 + var cir = TwoArcFormOneCircle(loop); + if (cir is not null) + { + hcData.CircleData.Add(cir); + isCurve2d = false; + } + } + } + + //边界是曲线 + if (isCurve2d) + HatchLoopIsCurve2d(loop, hcData); + + _hcDatas.Add(hcData); + }); + } + #endregion + + #region 方法 + /// + /// 多段线处理 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsPolyline(HatchLoop loop, HatchConverterData hcData) + { + if (loop is null) + throw new ArgumentNullException(nameof(loop)); + + if (hcData is null) + throw new ArgumentNullException(nameof(hcData)); + + //判断为圆形: + //上下两个圆弧,然后填充,就会生成此种填充 + //顶点数是3,凸度是半圆,两个半圆就是一个圆形 + if (loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == 1 && loop.Polyline[1].Bulge == 1 || + loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == -1 && loop.Polyline[1].Bulge == -1) + { + hcData.CircleData.Add(new CircleData(loop.Polyline[0].Vertex, loop.Polyline[1].Vertex)); + } + else + { + //遍历多段线信息 + var bvc = loop.Polyline; + for (int i = 0; i < bvc.Count; i++) + hcData.PolyLineData.Add(new BulgeVertexWidth(bvc[i])); + } + } + + /// + /// 两个圆弧组成圆形 + /// + /// + /// + static CircleData? TwoArcFormOneCircle(HatchLoop loop) + { + if (loop is null) + throw new ArgumentNullException(nameof(loop)); + + if (loop.Curves.Count != 2) + throw new ArgumentException( + "边界非多段线,而且点数!=2,点数为:" + nameof(loop.Curves.Count) + ";两个矩形交集的时候会出现此情况."); + + CircleData? circular = null; + + //判断为圆形: + //用一条(不是两条)多段线画出两条圆弧为正圆,就会生成此种填充 + //边界为曲线,数量为2,可能是两个半圆曲线,如果是,就加入圆形数据中 + + //第一段 + var getCurves1Pts = loop.Curves[0].GetSamplePoints(3); //曲线取样点分两份(3点) + var mid1Pt = getCurves1Pts[1]; //腰点 + double bulge1 = loop.Curves[0].StartPoint.GetArcBulge(mid1Pt, loop.Curves[0].EndPoint); + + //第二段 + var getCurves2Pts = loop.Curves[1].GetSamplePoints(3); + var mid2Pt = getCurves2Pts[1]; + double bulge2 = loop.Curves[1].StartPoint.GetArcBulge(mid2Pt, loop.Curves[1].EndPoint); + + //第一段上弧&&第二段反弧 || 第一段反弧&&第二段上弧 + if (bulge1 == -1 && bulge2 == -1 || bulge1 == 1 && bulge2 == 1) + circular = new CircleData(loop.Curves[0].StartPoint, loop.Curves[1].StartPoint); //两个起点就是对称点 + + return circular; + } + + /// + /// 处理边界曲线 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsCurve2d(HatchLoop loop, HatchConverterData hcData) + { + //取每一段曲线,曲线可能是直线来的,但是圆弧会按照顶点来分段 + int curveIsClosed = 0; + + //遍历边界的多个子段 + foreach (Curve2d curve in loop.Curves) + { + //计数用于实现闭合 + curveIsClosed++; + if (curve is NurbCurve2d spl) + { + //判断为样条曲线: + hcData.SplineData.Add(spl); + continue; + } + + var pts = curve.GetSamplePoints(3); + var midPt = pts[1]; + if (curve.StartPoint.IsEqualTo(curve.EndPoint, new Tolerance(1e-6, 1e-6)))//首尾相同,就是圆形 + { + //判断为圆形: + //获取起点,然后采样三点,中间就是对称点(直径点) + hcData.CircleData.Add(new CircleData(curve.StartPoint, midPt)); + continue; + } + + //判断为多段线,圆弧: + double bulge = curve.StartPoint.GetArcBulge(midPt, curve.EndPoint); + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.StartPoint, bulge)); + + //末尾点,不闭合的情况下就要获取这个 + if (curveIsClosed == loop.Curves.Count) + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.EndPoint, 0)); + } + } + + /// + /// 创建边界图元 + /// + /// 返回图元 + public void CreateBoundaryEntitys(List outEnts) + { + for (int i = 0; i < _hcDatas.Count; i++) + { + var data = _hcDatas[i]; + + //生成边界:多段线 + if (data.PolyLineData.Count > 0) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + for (int j = 0; j < data.PolyLineData.Count; j++) + { + pl.AddVertexAt(j, + data.PolyLineData[j].Vertex, + data.PolyLineData[j].Bulge, + data.PolyLineData[j].StartWidth, + data.PolyLineData[j].EndWidth); + } + outEnts.Add(pl); + } + + //生成边界:圆 + data.CircleData.ForEach(item => { + outEnts.Add(new Circle(item.Center.Point3d(), Vector3d.ZAxis, item.Radius)); + }); + + //生成边界:样条曲线 + data.SplineData.ForEach(item => { + outEnts.Add(item.ToCurve()); + }); + } + + if (_oldHatch is not null) + { + outEnts.ForEach(ent => { + ent.Color = _oldHatch.Color; + ent.Layer = _oldHatch.Layer; + }); + } + } + + + /// + /// 创建边界图元和新填充到当前空间 + /// + /// 事务 + /// 数据库 + /// 边界关联 + /// 是否创建填充,false则只创建边界 + /// 新填充id,边界在获取 + public ObjectId CreateBoundarysAndHatchToMsPs(BlockTableRecord btrOfAddEntitySpace, + bool boundaryAssociative = true, + bool createHatchFlag = true, + Transaction? trans = null) + { + //重设边界之前肯定是有边界才可以 + if (BoundaryIds.Count == 0) + { + List boundaryEntitys = new(); + CreateBoundaryEntitys(boundaryEntitys); + boundaryEntitys.ForEach(ent => { + BoundaryIds.Add(btrOfAddEntitySpace.AddEntity(ent)); + }); + } + + if (!createHatchFlag) + return ObjectId.Null; + /* + * 此处为什么要克隆填充,而不是新建填充? + * 因为填充如果是新建的,那么将会丢失基点,概念如下: + * 两个一样的填充,平移其中一个,那么再提取他们的基点会是一样的! + * 所以生成时候就不等同于画面相同. + * 也因为我不知道什么新建方式可以新建一模一样的填充,因此使用了克隆 + * 那么它的平移后的基点在哪里呢? + */ + + var newHatchId = btrOfAddEntitySpace.DeepClone(new ObjectIdCollection(new ObjectId[] { OldHatchId })).GetValues()[0]; + trans ??= DBTrans.Top.Transaction; + var hatchEnt = trans.GetObject(newHatchId, OpenMode.ForWrite) as Hatch; + if (hatchEnt != null) + { + ResetBoundary(hatchEnt, boundaryAssociative); + hatchEnt.DowngradeOpen(); + } + return newHatchId; + } + + + /// + /// 重设边界 + /// + /// + /// 边界关联 + void ResetBoundary(Hatch hatch, + bool boundaryAssociative = true) + { + //删除原有边界 + while (hatch.NumberOfLoops != 0) + hatch.RemoveLoopAt(0); + + hatch.Associative = boundaryAssociative; + + var obIds = new ObjectIdCollection(); + for (int i = 0; i < BoundaryIds.Count; i++) + { + obIds.Clear(); + obIds.Add(BoundaryIds[i]); + //要先添加最外面的边界 + if (i == 0) + hatch.AppendLoop(HatchLoopTypes.Outermost, obIds); + else + hatch.AppendLoop(HatchLoopTypes.Default, obIds); + } + //计算填充并显示 + hatch.EvaluateHatch(true); + } + #endregion +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" new file mode 100644 index 0000000..8bfd043 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" @@ -0,0 +1,15 @@ +namespace IFoxCAD.Cad; + +public static class HatchEx +{ + /// + /// 遍历填充每条边 + /// + /// + /// + public static void ForEach(this Hatch hatch, Action action) + { + for (int i = 0; i < hatch.NumberOfLoops; i++) + action.Invoke(hatch.GetLoopAt(i)); + } +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" new file mode 100644 index 0000000..1d66417 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" @@ -0,0 +1,351 @@ +namespace IFoxCAD.Cad; + +/* + * ӵĵһ߽߽,ڶͼı߽硣 + * Ҫⲿ߽,ʹӻΪ HatchLoopTypes.Outermost AppendLoop , + * һ߽类,ͿԼı߽硣 + * ڲ߽ʹô HatchLoopTypes.Default AppendLoop + * + * ߽ʱ,ӵ(߽,߽,߽,߽ͨ....) + * ߽ʱ,ӵ(߽,߽ͨ.....߽,߽ͨ....) + */ + +/// +/// ͼ +/// +public class HatchInfo +{ + #region Ա + /// + /// ߽id(ŵһ) + /// + readonly List _boundaryIds; + /// + /// ͼԪ + /// + readonly Hatch _hatch; + /// + /// ߽(˴ֱ=>Ա,Ϊ뷴Ӧ) + /// + readonly bool _boundaryAssociative; + /// + /// :û(̶)//ݶļ + /// + string? _hatchName; + /// + /// ģʽ(Ԥ/û/Զ) + /// + HatchPatternType _patternTypeHatch; + /// + /// ģʽ + /// + GradientPatternType _patternTypeGradient; + /// + /// / + /// + double Scale => _hatch.PatternScale; + /// + /// Ƕ + /// + double Angle => _hatch.PatternAngle; + #endregion + + #region + HatchInfo() + { + _hatch = new Hatch(); + _hatch.SetDatabaseDefaults(); + _boundaryIds = new(); + } + + /// + /// ͼ + /// + /// ߽ + /// ԭ + /// + /// Ƕ + public HatchInfo(bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) : this() + { + if (hatchScale <= 0) + throw new ArgumentException("Сڵ0"); + + _hatch.PatternScale = hatchScale;// + _hatch.PatternAngle = hatchAngle;//Ƕ + _boundaryAssociative = boundaryAssociative; + + hatchOrigin ??= Point2d.Origin; + _hatch.Origin = hatchOrigin.Value; //ԭ + } + + /// + /// ͼ + /// + /// ߽ + /// ߽ + /// ԭ + /// + /// Ƕ + public HatchInfo(IEnumerable boundaryIds, + bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) + : this(boundaryAssociative, hatchOrigin, hatchScale, hatchAngle) + { + _boundaryIds.AddRange(boundaryIds); + } + + #endregion + + #region + /// + /// ģʽ1:Ԥ + /// + public HatchInfo Mode1PreDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.PreDefined; + return this; + } + + /// + /// ģʽ2:û + /// + /// Ƿ˫ + public HatchInfo Mode2UserDefined(bool patternDouble = true) + { + _hatchName = "_USER"; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.UserDefined; + + _hatch.PatternDouble = patternDouble; //Ƿ˫򣨱д SetHatchPattern ֮ǰ + _hatch.PatternSpace = Scale; //ࣨд SetHatchPattern ֮ǰ + return this; + } + + /// + /// ģʽ3:Զ + /// + /// + public HatchInfo Mode3UserDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.CustomDefined; + return this; + } + + /// + /// ģʽ4: + /// + /// + /// ɫʼɫ + /// ɫɫ + /// ƶ + /// ɫֵ + /// ɫ˫ɫ + public HatchInfo Mode4Gradient(GradientName name, Color colorStart, Color colorEnd, + float gradientShift = 0, + float shadeTintValue = 0, + bool gradientOneColorMode = false) + { + //entgetֱȻ"SOLID",Ϊ"","" + _hatchName = name.ToString(); + _hatch.HatchObjectType = HatchObjectType.GradientObject; //(/) + _patternTypeGradient = GradientPatternType.PreDefinedGradient;//ģʽ4: + //_patternTypeGradient = GradientPatternType.UserDefinedGradient;//ģʽ5:..ģʽɶ + + //ýɫʼͽɫ + var gColor1 = new GradientColor(colorStart, 0); + var gColor2 = new GradientColor(colorEnd, 1); + _hatch.SetGradientColors(new GradientColor[] { gColor1, gColor2 }); + + _hatch.GradientShift = gradientShift; //ݶλ + _hatch.ShadeTintValue = shadeTintValue; //Ӱɫֵ + _hatch.GradientOneColorMode = gradientOneColorMode;//䵥ɫ/˫ɫ + _hatch.GradientAngle = Angle; //Ƕ + + return this; + } + + /// + /// + /// + /// ˿ռ + public ObjectId Build(BlockTableRecord btrOfAddEntitySpace) + { + //ݿ + var hatchId = btrOfAddEntitySpace.AddEntity(_hatch); + + //ģʽ:/ + if (_hatch.HatchObjectType == HatchObjectType.GradientObject) + _hatch.SetGradient(_patternTypeGradient, _hatchName); + else + _hatch.SetHatchPattern(_patternTypeHatch, _hatchName); + + //߽,ݿռھͻ + //Ϊ true 뷴Ӧ,˱Ƚ(ά뽫ʮɺ),. + _hatch.Associative = _boundaryAssociative; + + // AppendLoop ؼ,Ͳ + if (_boundaryIds.Count > 0) + AppendLoop(_boundaryIds, HatchLoopTypes.Default); + + //䲢ʾ(߽,쳣) + _hatch.EvaluateHatch(true); + + return hatchId; + } + + /// + /// ִͼԪ޸ + /// + /// ӳʵ + public HatchInfo Action(Action action) + { + action(_hatch); + return this; + } + + /// + /// ձ߽缯 + /// + public HatchInfo ClearBoundary() + { + _boundaryIds.Clear(); + return this; + } + + /// + /// ɾ߽ͼԪ + /// + public HatchInfo EraseBoundary() + { + for (int i = 0; i < _boundaryIds.Count; i++) + _boundaryIds[i].Erase(); + return this; + } + + /// + /// ߽ + /// + /// ߽id + /// 뷽ʽ + void AppendLoop(IEnumerable boundaryIds, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + var obIds = new ObjectIdCollection(); + //߽DZպϵ,Ѿݿ + //պϻ. + foreach (var border in boundaryIds) + { + obIds.Clear(); + obIds.Add(border); + _hatch.AppendLoop(hatchLoopTypes, obIds); + } + obIds.Dispose(); + } + + /// + /// ߽(¸߰汾亯) + /// + /// 㼯 + /// ͹ȼ + /// ˿ռ + /// 뷽ʽ + /// + public HatchInfo AppendLoop(Point2dCollection pts!!, + DoubleCollection bluges, + BlockTableRecord btrOfAddEntitySpace, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + var ptsEnd2End = pts.End2End(); +#if NET35 + _boundaryIds.Add(CreateAddBoundary(ptsEnd2End, bluges, btrOfAddEntitySpace)); +#else + //2011API,ԲͼԪ¼߽, + //ͨĻ,߽ _boundaryIds ǿյ,ô Build() ʱҪ˿յ + _hatch.AppendLoop(hatchLoopTypes, ptsEnd2End, bluges); +#endif + return this; + } + +#if NET35 + /// + /// ͨ㼯͹ɱ߽Ķ + /// + /// 㼯 + /// ͹ȼ + /// ˿ռ + /// id + static ObjectId CreateAddBoundary(Point2dCollection? pts, + DoubleCollection? bluges, + BlockTableRecord btrOfAddEntitySpace) + { + if (pts is null) + throw new ArgumentException(null, nameof(pts)); + if (bluges is null) + throw new ArgumentException(null, nameof(bluges)); + + var bvws = new List(); + + var itor1 = pts.GetEnumerator(); + var itor2 = bluges.GetEnumerator(); + while (itor1.MoveNext() && itor2.MoveNext()) + bvws.Add(new BulgeVertexWidth(itor1.Current, itor2.Current)); + + return btrOfAddEntitySpace.AddPline(bvws); + } +#endif + #endregion + + #region ö + /// + /// ɫͼ + /// + public enum GradientName + { + /// + /// ״ + /// + Linear, + /// + /// Բ״ + /// + Cylinder, + /// + /// Բ״ + /// + Invcylinder, + /// + /// ״ + /// + Spherical, + /// + /// ״ + /// + Invspherical, + /// + /// ״ + /// + Hemisperical, + /// + /// ״ + /// + InvHemisperical, + /// + /// ״ + /// + Curved, + /// + /// ״ + /// + Incurved + } + #endregion +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" new file mode 100644 index 0000000..989721c --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" @@ -0,0 +1,95 @@ +namespace IFoxCAD.Cad; + +public static class AttachmentPointHelper +{ + static readonly Dictionary _alignment = new() + { + { "左上", AttachmentPoint.TopLeft }, + { "中上", AttachmentPoint.TopCenter },//单行的对齐 + { "右上", AttachmentPoint.TopRight }, + + { "左中", AttachmentPoint.MiddleLeft }, + { "正中", AttachmentPoint.MiddleCenter },//多行的正中 + { "右中", AttachmentPoint.MiddleRight }, + + { "左对齐", AttachmentPoint.BaseLeft },//※优先(放在前面优先获取) + { "左", AttachmentPoint.BaseLeft }, + + { "中间", AttachmentPoint.BaseMid }, + + { "右对齐", AttachmentPoint.BaseRight },//※优先(放在前面优先获取) + { "右", AttachmentPoint.BaseRight }, + + { "左下", AttachmentPoint.BottomLeft }, + { "中下", AttachmentPoint.BottomCenter }, + { "右下", AttachmentPoint.BottomRight }, + + { "对齐", AttachmentPoint.BaseAlign },//※优先(放在前面优先获取) + { "调整", AttachmentPoint.BaseAlign }, + + { "居中", AttachmentPoint.BaseCenter },//单行的中 + { "铺满", AttachmentPoint.BaseFit }, + }; + + /// + /// 输入文字获得对齐方式 + /// + /// + /// + public static AttachmentPoint Get(string key) + { + return _alignment[key]; + } + + /// + /// 输入对齐方式获得文字 + /// + /// + /// + public static string Get(AttachmentPoint value) + { + return _alignment.FirstOrDefault(q => q.Value == value).Key; + } +} + +#if false +//反射描述 +//这些东西cad没有用到啊...所以不纳入了 +public enum AttachmentPoint2 +{ + [Description("下对齐")] + BottomAlign = 14, + [Description("中对齐")] + MiddleAlign = 15,//0xF + [Description("上对齐")] + TopAlign = 16,//0x10 + [Description("下铺满")] + BottomFit = 18, + [Description("中铺满")] + MiddleFit = 19, + [Description("上铺满")] + TopFit = 20, + [Description("下居中")] + BottomMid = 22, + [Description("中居中")] + MiddleMid = 23, + [Description("下居中")] + TopMid = 24, +} + +public static Dictionary GetEnumDic(Type enumType) +{ + Dictionary dic = new(); + var fieldinfos = enumType.GetFields(); + for (int i = 0; i < fieldinfos.Length; i++) + { + var field = fieldinfos[i]; + if (field.FieldType.IsEnum) + { + var objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false); + dic.Add(field.Name, ((DescriptionAttribute)objs[0]).Description); + } + } + return dic; +} +#endif diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" new file mode 100644 index 0000000..5b37a31 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" @@ -0,0 +1,62 @@ +namespace IFoxCAD.Cad; + +public static partial class EntityAdd +{ + /// + /// 创建单行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// 对齐点,因样式 可能无效 + /// + public static Entity AddDBTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft, + Point3d? justifyPoint = null) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + justifyPoint, + textStyleId, + textHigh, + db); + return TextInfo.AddDBTextToEntity(); + } + + /// + /// 新建多行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// + public static Entity AddMTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + null, + textStyleId, + textHigh, + db); + return TextInfo.AddMTextToEntity(); + } +} diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" new file mode 100644 index 0000000..573963b --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" @@ -0,0 +1,178 @@ +namespace IFoxCAD.Cad; + +/// +/// 文字信息类 +/// +public class TextInfo +{ + readonly Database? Database; + readonly string? Contents; + readonly Point3d Position; + + public string TextJustifyCn => AttachmentPointHelper.Get(TextJustify); + readonly AttachmentPoint TextJustify; + readonly Point3d? AlignmentPoint; + + readonly double TextHeight; + readonly ObjectId? TextStyleId; + + /// + /// 文字信息类 + /// + /// 内容 + /// 基点 + /// 对齐方式 + /// 对齐点(对齐方式是左,此参数无效,为null不为左就报错) + /// 文字样式id + /// 文字高度 + /// 数据库 + public TextInfo(string? contents, + Point3d position, + AttachmentPoint justify, + Point3d? justifyPoint = null, + ObjectId? textStyleId = null, + double textHeight = 2.5, + Database? database = null) + { + Contents = contents; + Position = position; + TextJustify = justify; + + if (justifyPoint is null && TextJustify != AttachmentPoint.BaseLeft) + throw new ArgumentNullException(nameof(justifyPoint)); + + AlignmentPoint = justifyPoint; + TextHeight = textHeight; + TextStyleId = textStyleId; + Database = database; + } + + /// + /// 创建单行文字 + /// + public DBText AddDBTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var acText = new DBText(); + acText.SetDatabaseDefaults(); + + if (Database is not null) + acText.SetDatabaseDefaults(Database);//我的默认值是填满的,所以可以不需要 + + if (TextStyleId is not null) + acText.SetTextStyleId(TextStyleId.Value); + + acText.Height = TextHeight; //高度 + acText.TextString = Contents; //内容 + acText.Position = Position; //插入点(一定要先设置) + acText.Justify = TextJustify; //使他们对齐 + //acText.HorizontalMode + + if (AlignmentPoint is not null) + acText.AlignmentPoint = AlignmentPoint.Value; + else if (acText.Justify != AttachmentPoint.BaseLeft) + acText.AlignmentPoint = Position; + + if (Database is not null) + acText.AdjustAlignment(Database); + return acText; + } + + /// + /// 创建多行文字 + /// + /// + public MText AddMTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var mText = new MText(); + mText.SetDatabaseDefaults(); + + if (Database is not null) + mText.SetDatabaseDefaults(Database); + + if (TextStyleId is not null) + mText.SetTextStyleId(TextStyleId.Value); + + mText.TextHeight = TextHeight; //高度 + mText.Contents = Contents; //内容 + mText.Location = Position; //插入点(一定要先设置) + + //mText.SetAttachmentMovingLocation(TextJustify); + mText.Attachment = TextJustify;//使他们对齐 + + return mText; + } +} + +//反射设定对象的文字样式id +public static partial class TextInfoHelper +{ + /// + /// 设置文字样式id + /// + /// 单行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this DBText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + /// + /// 设置文字样式id + /// + /// 多行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this MText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + static void SetEntityTxtStyleId(Entity acText, ObjectId ltrObjectId) + { + GetTextStyleIdType(acText)?.SetValue(acText, ltrObjectId, null); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this DBText acText) + { + return GetEntityTxtStyleId(acText); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this MText acText) + { + return GetEntityTxtStyleId(acText); + } + + static ObjectId GetEntityTxtStyleId(Entity acText) + { + var result = ObjectId.Null; + var id = GetTextStyleIdType(acText)?.GetValue(acText, null); + if (id != null) + result = (ObjectId)id; + return result; + } + + static PropertyInfo? _textStyleId = null; + static PropertyInfo GetTextStyleIdType(Entity acText) + { + if (_textStyleId == null) + { + var entType = acText.GetType(); + var prs = entType.GetProperties(); + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyle");//反射获取属性 + if (_textStyleId == null) + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyleId");//反射获取属性 + } + return _textStyleId; + } +} diff --git a/src/IFoxCAD.Cad/GlobalUsings.cs b/src/IFoxCAD.Cad/GlobalUsings.cs new file mode 100644 index 0000000..7051b36 --- /dev/null +++ b/src/IFoxCAD.Cad/GlobalUsings.cs @@ -0,0 +1,31 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; + +/// autocad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; + +global using Autodesk.AutoCAD.DatabaseServices.Filters; + + +global using System.Collections.Specialized; +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// ifoxcad.basal 引用 +global using IFoxCAD.Basal; \ No newline at end of file diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index d742bef..d72026f 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -4,7 +4,7 @@ preview enable - net35;net40 + net35;net40;net45 true 0.1.3 InspireFunction @@ -23,28 +23,37 @@ true - - - - - - runtime - - - - - DEBUG - - - $(Configuration);ac2009 - - - $(Configuration);ac2013 - + + + + + + + + - + + + + + + + DEBUG + + + $(Configuration);ac2008;ac2009 + + + $(Configuration);ac2013 + + + $(Configuration);ac2015 + + + + True @@ -56,7 +65,7 @@ - + diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data deleted file mode 100644 index 5f28270..0000000 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs index 0aa2f69..009d13e 100644 --- a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs +++ b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs @@ -1,72 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Runtime; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// lisp点对表的数据封装类 +/// +public class LispDottedPair : LispList { + #region 构造函数 /// - /// lisp 点对表的数据封装类 + /// 默认无参构造函数 /// - public class LispDottedPair : LispList + public LispDottedPair() { - #region 构造函数 - /// - /// 默认无参构造函数 - /// - public LispDottedPair() - { - } + } - /// - /// 构造函数 - /// - /// TypedValue 迭代器 - public LispDottedPair(IEnumerable values) : base(values) - { - } - /// - /// 构造函数 - /// - /// 点对表左数 - /// 点对表右数 - public LispDottedPair(TypedValue left, TypedValue right) - { - Add(left); - Add(right); - } - #endregion + /// + /// 构造函数 + /// + /// TypedValue 迭代器 + public LispDottedPair(IEnumerable values) : base(values) + { + } + /// + /// 构造函数 + /// + /// 点对表左数 + /// 点对表右数 + public LispDottedPair(TypedValue left, TypedValue right) + { + Add(left); + Add(right); + } + #endregion - /// - /// 点对表的值 - /// - public override List Value + #region 重写 + /// + /// 点对表的值 + /// + public override List Value + { + get { - get - { - var value = new List + var value = new List { new TypedValue((int)LispDataType.ListBegin,-1), new TypedValue((int)LispDataType.DottedPair,-1) }; - value.InsertRange(1, this); - return value; - } + value.InsertRange(1, this); + return value; } + } + #endregion - #region 转换器 + #region 转换器 - /// - /// LispDottedPair 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](LispDottedPair values) => values.Value.ToArray(); - /// - /// LispDottedPair 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(LispDottedPair values) => new(values.Value.ToArray()); + /// + /// LispDottedPair 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](LispDottedPair values) => values.Value.ToArray(); + /// + /// LispDottedPair 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(LispDottedPair values) => new(values.Value.ToArray()); - #endregion - } + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/LispList.cs b/src/IFoxCAD.Cad/ResultData/LispList.cs index 4742d38..8c72669 100644 --- a/src/IFoxCAD.Cad/ResultData/LispList.cs +++ b/src/IFoxCAD.Cad/ResultData/LispList.cs @@ -1,202 +1,195 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// lisp数据封装类 +/// +public class LispList : TypedValueList { + #region 构造函数 /// - /// lisp数据封装类 + /// 默认构造函数 /// - public class LispList : TypedValueList - { - #region 构造函数 - /// - /// 默认构造函数 - /// - public LispList() { } + public LispList() { } + + /// + /// 构造函数 + /// + /// TypedValue 迭代器 + public LispList(IEnumerable values) : base(values) { } + #endregion - /// - /// 构造函数 - /// - /// TypedValue 迭代器 - public LispList(IEnumerable values) : base(values) { } - #endregion - /// - /// lisp 列表的值 - /// - public virtual List Value + #region 重写 + /// + /// lisp 列表的值 + /// + public virtual List Value + { + get { - get - { - var value = new List + var value = new List { new TypedValue((int)LispDataType.ListBegin,-1), new TypedValue((int)LispDataType.ListEnd,-1) }; - value.InsertRange(1, this); - return value; - } + value.InsertRange(1, this); + return value; } + } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object? obj) + { + if (code < 5000) { - if (code < 5000) - { - throw new System.Exception("传入的组码值不是 lisp数据 有效范围!"); - } - Add(new TypedValue(code, obj)); + throw new System.Exception("传入的组码值不是 lisp数据 有效范围!"); } + Add(new TypedValue(code, obj)); + } - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(LispDataType code, object obj) - { - Add((int)code, obj); - } - /// - /// 添加数据,参数为true时添加 lisp 中的 T,false时添加 lisp 中的 nil - /// - /// bool 型的数据 - public void Add(bool value) - { - if (value) - { - Add(LispDataType.T_atom, true); - } - else - { - Add(LispDataType.Nil, null); - } - } - /// - /// 添加字符串 - /// - /// 字符串 - public void Add(string value) - { - Add(LispDataType.Text, value); - } - /// - /// 添加短整型数 - /// - /// 短整型数 - public void Add(short value) - { - Add(LispDataType.Int16, value); - } - /// - /// 添加整型数 - /// - /// 整型数 - public void Add(int value) - { - Add(LispDataType.Int32, value); - } - /// - /// 添加浮点数 - /// - /// 浮点数 - public void Add(double value) - { - Add(LispDataType.Double, value); - } - /// - /// 添加对象id - /// - /// 对象id - public void Add(ObjectId value) - { - Add(LispDataType.ObjectId, value); - } - /// - /// 添加选择集 - /// - /// 选择集 - public void Add(SelectionSet value) - { - Add(LispDataType.SelectionSet, value); - } - /// - /// 添加二维点 - /// - /// 二维点 - public void Add(Point2d value) - { - Add(LispDataType.Point2d, value); - } - /// - /// 添加三维点 - /// - /// 三维点 - public void Add(Point3d value) - { - Add(LispDataType.Point3d, value); - } - /// - /// 添加二维点 - /// - /// X - /// Y - public void Add(double x, double y) - { - Add(LispDataType.Point2d, new Point2d(x, y)); - } - /// - /// 添加三维点 - /// - /// X - /// Y - /// Z - public void Add(double x, double y, double z) + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(LispDataType code, object? obj) + { + Add((int)code, obj); + } + /// + /// 添加数据,参数为true时添加 lisp 中的 T,false时添加 lisp 中的 nil + /// + /// bool 型的数据 + public void Add(bool value) + { + if (value) { - Add(LispDataType.Point3d, new Point3d(x, y, z)); + Add(LispDataType.T_atom, true); } - /// - /// 添加列表 - /// - /// lisp 列表 - public void Add(LispList value) + else { - this.AddRange(value.Value); + Add(LispDataType.Nil, null); } + } + /// + /// 添加字符串 + /// + /// 字符串 + public void Add(string value) + { + Add(LispDataType.Text, value); + } + /// + /// 添加短整型数 + /// + /// 短整型数 + public void Add(short value) + { + Add(LispDataType.Int16, value); + } + /// + /// 添加整型数 + /// + /// 整型数 + public void Add(int value) + { + Add(LispDataType.Int32, value); + } + /// + /// 添加浮点数 + /// + /// 浮点数 + public void Add(double value) + { + Add(LispDataType.Double, value); + } + /// + /// 添加对象id + /// + /// 对象id + public void Add(ObjectId value) + { + Add(LispDataType.ObjectId, value); + } + /// + /// 添加选择集 + /// + /// 选择集 + public void Add(SelectionSet value) + { + Add(LispDataType.SelectionSet, value); + } + /// + /// 添加二维点 + /// + /// 二维点 + public void Add(Point2d value) + { + Add(LispDataType.Point2d, value); + } + /// + /// 添加三维点 + /// + /// 三维点 + public void Add(Point3d value) + { + Add(LispDataType.Point3d, value); + } + /// + /// 添加二维点 + /// + /// X + /// Y + public void Add(double x, double y) + { + Add(LispDataType.Point2d, new Point2d(x, y)); + } + /// + /// 添加三维点 + /// + /// X + /// Y + /// Z + public void Add(double x, double y, double z) + { + Add(LispDataType.Point3d, new Point3d(x, y, z)); + } + /// + /// 添加列表 + /// + /// lisp 列表 + public void Add(LispList value) + { + this.AddRange(value.Value); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 LispList - /// - /// ResultBuffer 实例 - public static implicit operator LispList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// LispList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](LispList values) => values.Value.ToArray(); - /// - /// LispList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(LispList values) => new(values.Value.ToArray()); - /// - /// TypedValue 数组隐式转换到 LispList - /// - /// TypedValue 数组 - public static implicit operator LispList(TypedValue[] values) => new(values); - #endregion - } + #region 转换器 + /// + /// ResultBuffer 隐式转换到 LispList + /// + /// ResultBuffer 实例 + public static implicit operator LispList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// LispList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](LispList values) => values.Value.ToArray(); + /// + /// LispList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(LispList values) => new(values.Value.ToArray()); + /// + /// TypedValue 数组隐式转换到 LispList + /// + /// TypedValue 数组 + public static implicit operator LispList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs index 2fccce5..896a226 100644 --- a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs +++ b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs @@ -1,69 +1,63 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// 用于集中管理扩展数据/扩展字典/resultbuffer的类 +/// +public class TypedValueList : List { + #region 构造函数 /// - /// 用于集中管理扩展数据/扩展字典/resultbuffer的类 + /// 默认无参构造函数 /// - public class TypedValueList : List - { - #region 构造函数 - /// - /// 默认无参构造函数 - /// - public TypedValueList() { } - /// - /// 采用 TypedValue 迭代器构造 TypedValueList - /// - /// - public TypedValueList(IEnumerable values) : base(values) { } - - #endregion + public TypedValueList() { } + /// + /// 采用 TypedValue 迭代器构造 TypedValueList + /// + /// + public TypedValueList(IEnumerable values) : base(values) { } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public virtual void Add(int code, object obj) - { - Add(new TypedValue(code, obj)); - } + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public virtual void Add(int code, object obj) + { + Add(new TypedValue(code, obj)); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 TypedValueList - /// - /// ResultBuffer 实例 - public static implicit operator TypedValueList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// TypedValueList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](TypedValueList values) => values.ToArray(); - /// - /// TypedValueList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(TypedValueList values) => new(values); - /// - /// TypedValue 数组隐式转换到 TypedValueList - /// - /// TypedValue 数组 - public static implicit operator TypedValueList(TypedValue[] values) => new(values); - /// - /// 转换为字符串 - /// - /// ResultBuffer 字符串 - public override string ToString() - { - return new ResultBuffer(this).ToString(); - } - #endregion + #region 转换器 + /// + /// ResultBuffer 隐式转换到 TypedValueList + /// + /// ResultBuffer 实例 + public static implicit operator TypedValueList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// TypedValueList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](TypedValueList values) => values.ToArray(); + /// + /// TypedValueList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(TypedValueList values) => new(values); + /// + /// TypedValue 数组隐式转换到 TypedValueList + /// + /// TypedValue 数组 + public static implicit operator TypedValueList(TypedValue[] values) => new(values); + /// + /// 转换为字符串 + /// + /// ResultBuffer 字符串 + public override string ToString() + { + return new ResultBuffer(this).ToString(); } + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs index 0364f9a..1fb19d8 100644 --- a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs @@ -1,65 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// 扩展字典数据封装类 +/// +public class XRecordDataList : TypedValueList { + #region 构造函数 /// /// 扩展字典数据封装类 /// - public class XRecordDataList : TypedValueList - { - public XRecordDataList() - { - } - public XRecordDataList(IEnumerable values) : base(values) { } - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) - { - if (code >= 1000) - { - throw new System.Exception("传入的组码值不是 XRecordData 有效范围!"); - } - Add(new TypedValue(code, obj)); - } + public XRecordDataList() { } - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(DxfCode code, object obj) + /// + /// 扩展字典数据封装类 + /// + public XRecordDataList(IEnumerable values) : base(values) { } + #endregion + + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object obj) + { + if (code >= 1000) { - Add((int)code, obj); + throw new System.Exception("传入的组码值不是 XRecordData 有效范围!"); } - #endregion + Add(new TypedValue(code, obj)); + } - #region 转换器 - /// - /// ResultBuffer 隐式转换到 XRecordDataList - /// - /// ResultBuffer 实例 - public static implicit operator XRecordDataList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// XRecordDataList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](XRecordDataList values) => values.ToArray(); - /// - /// XRecordDataList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例s - public static implicit operator ResultBuffer(XRecordDataList values) => new(values); - /// - /// TypedValue 数组隐式转换到 XRecordDataList - /// - /// TypedValue 数组 - public static implicit operator XRecordDataList(TypedValue[] values) => new(values); - #endregion + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(DxfCode code, object obj) + { + Add((int)code, obj); } + #endregion + + #region 转换器 + /// + /// ResultBuffer 隐式转换到 XRecordDataList + /// + /// ResultBuffer 实例 + public static implicit operator XRecordDataList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// XRecordDataList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](XRecordDataList values) => values.ToArray(); + /// + /// XRecordDataList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例s + public static implicit operator ResultBuffer(XRecordDataList values) => new(values); + /// + /// TypedValue 数组隐式转换到 XRecordDataList + /// + /// TypedValue 数组 + public static implicit operator XRecordDataList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/XdataList.cs b/src/IFoxCAD.Cad/ResultData/XdataList.cs index e22ffe2..a666100 100644 --- a/src/IFoxCAD.Cad/ResultData/XdataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XdataList.cs @@ -1,71 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +/// +/// 扩展数据封装类 +/// +public class XDataList : TypedValueList { - + #region 构造函数 /// /// 扩展数据封装类 /// - public class XDataList : TypedValueList - { - public XDataList() - { - } + public XDataList() { } - public XDataList(IEnumerable values) : base(values) { } + /// + /// 扩展数据封装类 + /// + public XDataList(IEnumerable values) : base(values) { } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) - { - if (code < 1000 || code > 1071) - { - throw new System.Exception("传入的组码值不是XData有效范围!"); - } - Add(new TypedValue(code, obj)); - } + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object obj) + { + if (code < 1000 || code > 1071) + throw new System.Exception("传入的组码值不是XData有效范围!"); - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(DxfCode code, object obj) - { + Add(new TypedValue(code, obj)); + } - Add((int)code, obj); - } + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(DxfCode code, object obj) + { + Add((int)code, obj); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 XDataList - /// - /// ResultBuffer 实例 - public static implicit operator XDataList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// XDataList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](XDataList values) => values.ToArray(); - /// - /// XDataList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(XDataList values) => new(values); - /// - /// TypedValue 数组隐式转换到 XDataList - /// - /// TypedValue 数组 - public static implicit operator XDataList(TypedValue[] values) => new(values); - #endregion - } + #region 转换器 + /// + /// ResultBuffer 隐式转换到 XDataList + /// + /// ResultBuffer 实例 + public static implicit operator XDataList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// XDataList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](XDataList values) => values.ToArray(); + /// + /// XDataList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(XDataList values) => new(values); + /// + /// TypedValue 数组隐式转换到 XDataList + /// + /// TypedValue 数组 + public static implicit operator XDataList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/AOP.cs b/src/IFoxCAD.Cad/Runtime/AOP.cs new file mode 100644 index 0000000..ac8270b --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/AOP.cs @@ -0,0 +1,99 @@ +//namespace IFoxCAD.Cad; +//using HarmonyLib; + +//public class IFoxRefuseInjectionTransaction : Attribute +//{ +// /// +// /// 拒绝注入事务 +// /// +// public IFoxRefuseInjectionTransaction() +// { +// } +//} + +//public class AOP +//{ +// /// +// /// 在此命名空间下的命令末尾注入清空事务栈函数 +// /// +// public static void Run(string nameSpace) +// { +// Dictionary cmdDic = new(); +// AutoClass.AppDomainGetTypes(type => { +// if (type.Namespace != nameSpace) +// return; +// //类上面特性 +// if (type.IsClass) +// { +// var attr = type.GetCustomAttributes(true); +// if (RefuseInjectionTransaction(attr)) +// return; +// } + +// //函数上面特性 +// var mets = type.GetMethods();//获得它的成员函数 +// for (int ii = 0; ii < mets.Length; ii++) +// { +// var method = mets[ii]; +// //找到特性,特性下面的方法要是Public,否则就被编译器优化掉了. +// var attr = method.GetCustomAttributes(true); +// for (int jj = 0; jj < attr.Length; jj++) +// if (attr[jj] is CommandMethodAttribute cmdAtt) +// { +// if (!RefuseInjectionTransaction(attr)) +// cmdDic.Add(cmdAtt.GlobalName, (cmdAtt, type, method)); +// } +// } +// }); + +// //运行的命令写在了Test.dll,当然不是ifox.cad类库内了.... +// if (cmdDic.Count == 0) +// return; + +// var harmony = new Harmony(nameSpace); +// var mPrefix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddFirst());//进入函数前 +// var mPostfix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddLast());//进入函数后 +// var mp1 = new HarmonyMethod(mPrefix); +// var mp2 = new HarmonyMethod(mPostfix); + +// foreach (var item in cmdDic) +// { +// //原函数执行(空间type,函数名) +// var mOriginal = AccessTools.Method(item.Value.MetType, item.Value.MetInfo.Name); +// //mOriginal.Invoke(); +// //新函数执行:创造两个函数加入里面 +// var newMet = harmony.Patch(mOriginal, mp1, mp2); +// //newMet.Invoke(); +// } +// } + +// /// +// /// 拒绝注入事务 +// /// +// /// 属性 +// /// +// private static bool RefuseInjectionTransaction(object[] attr) +// { +// bool refuseInjectionTransaction = false; +// for (int kk = 0; kk < attr.Length; kk++) +// { +// if (attr[kk] is IFoxRefuseInjectionTransaction) +// { +// refuseInjectionTransaction = true; +// break; +// } +// } +// return refuseInjectionTransaction; +// } + +// public static void IFoxCmdAddFirst() +// { +// //此生命周期会在静态事务栈上面,被无限延长 +// var _ = DBTrans.Top; +// } + +// public static void IFoxCmdAddLast() +// { +// DBTrans.FinishDatabase(); +// } +//} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs index 7be301f..94c7947 100644 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs @@ -1,130 +1,74 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text.RegularExpressions; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// cad版本号类 +/// +public static class AcadVersion { + private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; + /// - /// cad版本号类 + /// 所有安装的cad的版本号 /// - public class AcadVersion + public static List Versions { - /// - /// 主版本 - /// - public int Major - { private set; get; } - - /// - /// 次版本 - /// - public int Minor - { private set; get; } - - /// - /// 版本号 - /// - public double ProgId => double.Parse($"{Major}.{Minor}"); - - /// - /// 注册表名称 - /// - public string ProductName - { private set; get; } - - /// - /// 注册表位置 - /// - public string ProductRootKey - { private set; get; } - - private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; - - private static List _versions; - - /// - /// 所有安装的cad的版本号 - /// - public static List Versions + get { - get + + string[] copys = + Registry.LocalMachine + .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") + .GetValueNames(); + var _versions = new List(); + foreach (var rootkey in copys) { - if (_versions is null) + if (Regex.IsMatch(rootkey, _pattern)) { - string[] copys = - Registry.LocalMachine - .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") - .GetValueNames(); - _versions = new List(); - foreach (var rootkey in copys) - { - if (Regex.IsMatch(rootkey, _pattern)) + var gs = Regex.Match(rootkey, _pattern).Groups; + var ver = + new CadVersion { - var gs = Regex.Match(rootkey, _pattern).Groups; - var ver = - new AcadVersion - { - ProductRootKey = rootkey, - ProductName = - Registry.LocalMachine - .OpenSubKey("SOFTWARE") - .OpenSubKey(rootkey) - .GetValue("ProductName") - .ToString(), + ProductRootKey = rootkey, + ProductName = + Registry.LocalMachine + .OpenSubKey("SOFTWARE") + .OpenSubKey(rootkey) + .GetValue("ProductName") + .ToString(), - Major = int.Parse(gs[1].Value), - Minor = int.Parse(gs[2].Value), - }; + Major = int.Parse(gs[1].Value), + Minor = int.Parse(gs[2].Value), + }; - _versions.Add(ver); - } - } + _versions.Add(ver); } - return _versions; } + return _versions; } + } - /// 已打开的cad的版本号 - /// 已打开cad的application对象 - /// cad版本号对象 - public static AcadVersion FromApp(object app) - { - if (app is null) - { - throw new ArgumentNullException(nameof(app)); - } - - string acver = - app.GetType() - .InvokeMember( - "Version", - BindingFlags.GetProperty, - null, - app, - new object[0]).ToString(); - - var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; - int major = int.Parse(gs[1].Value); - int minor = int.Parse(gs[2].Value); - foreach (var ver in Versions) - { - if (ver.Major == major && ver.Minor == minor) - return ver; - } - - return null; - } + /// 已打开的cad的版本号 + /// 已打开cad的application对象 + /// cad版本号对象 + public static CadVersion? FromApp(object app!!) + { + string acver = + app.GetType() + .InvokeMember( + "Version", + BindingFlags.GetProperty, + null, + app, + new object[0]).ToString(); - /// - /// 转换为字符串 - /// - /// 表示版本号的字符串 - public override string ToString() + var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; + int major = int.Parse(gs[1].Value); + int minor = int.Parse(gs[2].Value); + foreach (var ver in Versions) { - return - $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + if (ver.Major == major && ver.Minor == minor) + return ver; } + return null; } } diff --git a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs index 62b70f8..c13cc26 100644 --- a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs +++ b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs @@ -1,36 +1,77 @@ -using System; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 程序集信息 +/// +[Serializable] +public struct AssemInfo { /// - /// 程序集信息 - /// - [Serializable] - public struct AssemInfo - { - /// - /// 注册名 - /// - public string Name { get; set; } - - /// - /// 程序集全名 - /// - public string Fullname { get; set; } - - /// - /// 程序集路径 - /// - public string Loader { get; set; } - - /// - /// 加载方式 - /// - public AssemLoadType LoadType { get; set; } - - /// - /// 程序集说明 - /// - public string Description { get; set; } - } + /// 注册名 + ///
+ public string Name; + + /// + /// 程序集全名 + /// + public string Fullname; + + /// + /// 程序集路径 + /// + public string Loader; + + /// + /// 加载方式 + /// + public AssemLoadType LoadType; + + /// + /// 程序集说明 + /// + public string Description; +} + + +/// +/// 程序集加载类型 +/// +public enum AssemLoadType +{ + /// + /// 启动 + /// + Startting = 2, + + /// + /// 随命令 + /// + ByCommand = 12, + + /// + /// 无效 + /// + Disabled = 20 +} + + +/// +/// 注册中心配置信息 +/// +public enum AutoRegConfig +{ + /// + /// 注册表 + /// + Regedit = 1, + /// + /// 反射特性 + /// + ReflectionAttribute = 2, + /// + /// 反射接口 + /// + ReflectionInterface = 4, + + All = Regedit | ReflectionAttribute | ReflectionInterface, } diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs index 111be63..540762c 100644 --- a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs +++ b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs @@ -1,131 +1,169 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Microsoft.Win32; -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using CadRuntime = Autodesk.AutoCAD.Runtime; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + + +/// +/// 注册中心 +/// 初始化程序集信息写入注册表并反射特性和接口 +/// 启动cad后的执行顺序为: +/// 1:程序集配置中心构造函数 +/// 2:特性..(多个) +/// 3:接口..(多个) +/// +public abstract class AutoRegAssem : IExtensionApplication { + #region 字段 + readonly AutoReflection _autoRef; + readonly AssemInfo _info; + #endregion + + #region 静态方法 + /// + /// 程序集的路径 + /// + public static FileInfo Location => new(Assembly.GetCallingAssembly().Location); + + /// + /// 程序集的目录 + /// + public static DirectoryInfo CurrDirectory => Location.Directory; + /// - /// 程序集加载类型 + /// 获取程序集的目录 /// - public enum AssemLoadType + /// 程序集 + /// 路径对象 + public static DirectoryInfo GetDirectory(Assembly? assem) { - /// - /// 启动 - /// - Startting = 2, - - /// - /// 随命令 - /// - ByCommand = 12, - - /// - /// 无效 - /// - Disabled = 20 + if (assem is null) + throw new(nameof(assem)); + + return new FileInfo(assem.Location).Directory; } + #endregion + #region 构造函数 /// - /// 自动加载程序集的抽象类,继承自 IExtensionApplication 接口 + /// 注册中心 /// - public abstract class AutoRegAssem : CadRuntime.IExtensionApplication + /// 配置项目 + public AutoRegAssem(AutoRegConfig autoRegConfig) { - private AssemInfo _info = new(); - - /// - /// 程序集的路径 - /// - public static FileInfo Location => new(Assembly.GetCallingAssembly().Location); - - /// - /// 程序集的目录 - /// - public static DirectoryInfo CurrDirectory => Location.Directory; - - /// - /// 获取程序集的目录 - /// - /// 程序集 - /// 路径对象 - public static DirectoryInfo GetDirectory(Assembly assem) + var assem = Assembly.GetCallingAssembly(); + _info = new() { - if (assem is null) - { - throw new(nameof(assem)); - } - return new FileInfo(assem.Location).Directory; - } + Loader = assem.Location, + Fullname = assem.FullName, + Name = assem.GetName().Name, + LoadType = AssemLoadType.Startting + }; - /// - /// 初始化程序集信息 - /// - public AutoRegAssem() + if ((autoRegConfig & AutoRegConfig.Regedit) == AutoRegConfig.Regedit) { - Assembly assem = Assembly.GetCallingAssembly(); - _info.Loader = assem.Location; - _info.Fullname = assem.FullName; - _info.Name = assem.GetName().Name; - _info.LoadType = AssemLoadType.Startting; - if (!SearchForReg()) - { RegApp(); - } } - #region RegApp + //实例化了 AutoClass 之后会自动执行 IFoxAutoGo 接口下面的类, + //以及自动执行特性 [IFoxInitialize] + //类库用户不在此处进行其他代码,而是实现特性 + _autoRef = new AutoReflection(_info.Name, autoRegConfig); + _autoRef.Initialize(); + } + #endregion - private static RegistryKey GetAcAppKey() - { -#if ac2009 - string key = HostApplicationServices.Current.RegistryProductRootKey; -#elif ac2013 - string key = HostApplicationServices.Current.MachineRegistryProductRootKey; + #region RegApp + + /// + /// 获取当前cad注册表位置 + /// + /// 打开权限 + /// + public static RegistryKey GetAcAppKey(bool writable = true) + { + RegistryKey? ackey = null; + +#if NET35 + string key = HostApplicationServices.Current.RegistryProductRootKey; //这里浩辰读出来是"" +#elif !HC2020 + string key = HostApplicationServices.Current.UserRegistryProductRootKey; #endif - RegistryKey ackey = - Registry.CurrentUser.OpenSubKey(key, true); - return ackey.CreateSubKey("Applications"); - } - private bool SearchForReg() - { - RegistryKey appkey = GetAcAppKey(); - var regApps = appkey.GetSubKeyNames(); - return regApps.Contains(_info.Name); - } +#if !HC2020 + ackey = Registry.CurrentUser.OpenSubKey(key, writable); +#else + //浩辰 + var s = GrxCAD.DatabaseServices.HostApplicationServices.Current.RegistryProductRootKey;//浩辰奇怪的空值 + string str = CadSystem.Getvar("ACADVER"); + str = Regex.Replace(str, @"[^\d.\d]", ""); + double.TryParse(str, out double a); + // string regedit = @"Software\Gstarsoft\GstarCAD\R" + a.ToString() + @"\zh-CN"; //2019 + // string regedit = @"Software\Gstarsoft\GstarCAD\B" + a.ToString() + @"\zh-CN";//2020 这里是 + string regedit = @"Software\Gstarsoft\GstarCAD\B20\zh-CN";//2020 这里是 + + ackey = Registry.CurrentUser.OpenSubKey(regedit, writable); +#endif + return ackey.CreateSubKey("Applications"); + } + + /// + /// 卸载注册表信息 + /// + public bool UnRegApp() + { + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; - /// - /// 在注册表写入自动加载的程序集信息 - /// - public void RegApp() + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) { - RegistryKey appkey = GetAcAppKey(); - RegistryKey rk = appkey.CreateSubKey(_info.Name); - rk.SetValue("DESCRIPTION", _info.Fullname, RegistryValueKind.String); - rk.SetValue("LOADCTRLS", _info.LoadType, RegistryValueKind.DWord); - rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); - rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); - appkey.Close(); + appkey.DeleteSubKey(_info.Name, false); + return true; } + return false; + } - #endregion RegApp + /// + /// 是否已经存在注册表 + /// + /// + bool SearchForReg() + { + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; - #region IExtensionApplication 成员 + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) + { + //20220409 bug:文件名相同,路径不同,需要判断路径 + var info = appkey.OpenSubKey(_info.Name); + return info.GetValue("LOADER")?.ToString().ToLower() == _info.Loader.ToLower(); + } + return false; + } - /// - /// 初始化函数 - /// - public abstract void Initialize(); + /// + /// 在注册表写入自动加载的程序集信息 + /// + public void RegApp() + { + var appkey = GetAcAppKey(); + var rk = appkey.CreateSubKey(_info.Name); + rk.SetValue("DESCRIPTION", _info.Fullname, RegistryValueKind.String); + rk.SetValue("LOADCTRLS", _info.LoadType, RegistryValueKind.DWord); + rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); + rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); + appkey.Close(); + } - /// - /// 结束函数 - /// - public abstract void Terminate(); + //这里的是不会自动执行的 + public void Initialize() { } + public void Terminate() { } - #endregion IExtensionApplication 成员 + ~AutoRegAssem() + { + _autoRef.Terminate(); } + #endregion RegApp } diff --git a/src/IFoxCAD.Cad/Runtime/CadVersion.cs b/src/IFoxCAD.Cad/Runtime/CadVersion.cs new file mode 100644 index 0000000..2e32c74 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/CadVersion.cs @@ -0,0 +1,92 @@ +namespace IFoxCAD.Cad; + +#if ac2009 + +public class CadVersion +{ + /// + /// 主版本 + /// + public int Major { get; set; } + + /// + /// 次版本 + /// + public int Minor { get; set; } + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName { get; set; } + + /// + /// 注册表位置 + /// + public string? ProductRootKey { get; set; } + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } + // public override bool Equals(object obj) + // { + // return base.Equals(obj); + // } + + // public override int GetHashCode() + // { + // return base.GetHashCode(); + // } + + // //public override string ToString() + // //{ + // // return base.ToString(); + // //} +} +#else +public record CadVersion +{ + /// + /// 主版本 + /// + public int Major; + + /// + /// 次版本 + /// + public int Minor; + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName; + + /// + /// 注册表位置 + /// + public string? ProductRootKey; + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/DBTrans.cs b/src/IFoxCAD.Cad/Runtime/DBTrans.cs index 9507f17..d207edf 100644 --- a/src/IFoxCAD.Cad/Runtime/DBTrans.cs +++ b/src/IFoxCAD.Cad/Runtime/DBTrans.cs @@ -1,319 +1,440 @@ -using System; -using System.Collections.Generic; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.ApplicationServices; -using System.IO; - -namespace IFoxCAD.Cad -{ - public class DBTrans : IDisposable - { - #region 私有字段 - /// - /// 文档锁 - /// - private DocumentLock documentLock = default; - /// - /// 是否释放资源 - /// - private bool disposedValue; - /// - /// 是否提交事务 - /// - private bool _commit; - /// - /// 事务栈 - /// - private static readonly Stack dBTrans = new(); - #endregion - - #region 公开属性 - /// - /// 返回当前事务 - /// - public static DBTrans Top => dBTrans.Peek(); - /// - /// 数据库 - /// - public Database Database { get; private set; } - /// - /// 文档 - /// - public Document Document { get; private set; } - /// - /// 命令行 - /// - public Editor Editor { get; private set; } - /// - /// 事务管理器 - /// - public Transaction Transaction { get; private set; } - - #endregion - - #region 构造函数 - /// - /// 默认构造函数,默认为打开当前文档,默认提交事务 - /// - /// 要打开的文档 - /// 事务是否提交 - public DBTrans(Document doc = null, bool commit = true, bool doclock = false) - { - doc ??= Application.DocumentManager.MdiActiveDocument; - Document = doc; - Database = Document.Database; - Editor = Document.Editor; - Init(commit, doclock); - } +namespace IFoxCAD.Cad; - /// - /// 构造函数,打开数据库,默认提交事务 - /// - /// 要打开的数据库 - /// 事务是否提交 - public DBTrans(Database database, bool commit = true) - { - Database = database; - Init(commit, false); - } - /// - /// 构造函数,打开文件,默认提交事务 - /// - /// 要打开的文件名 - /// 事务是否提交 - public DBTrans(string fileName, bool commit = true) - { - Database = new Database(false, true); - Database.ReadDwgFile(fileName, FileShare.Read, true, null); - Database.CloseInput(true); - Init(commit, false); - } - /// - /// 初始化事务及事务队列、提交模式 - /// - /// 提交模式 - private void Init(bool commit, bool doclock) - { - if (doclock) - { - documentLock = Document.LockDocument(); - } - Transaction = Database.TransactionManager.StartTransaction(); - _commit = commit; - dBTrans.Push(this); - } +/// +/// 事务栈,隐匿事务在数据库其中担任的角色 +/// +public class DBTrans : IDisposable +{ + #region 私有字段 + /// + /// 文档锁 + /// + private readonly DocumentLock? documentLock; + /// + /// 是否释放资源 + /// + private bool disposedValue; + /// + /// 是否提交事务 + /// + private readonly bool _commit; + /// + /// 事务栈 + /// + private static readonly Stack dBTrans = new(); + #endregion - #endregion - - #region 符号表 - - /// - /// 块表 - /// - public SymbolTable BlockTable => new(this, Database.BlockTableId); - /// - /// 当前绘图空间 - /// - public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId); - /// - /// 模型空间 - /// - public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace]); - /// - /// 图纸空间 - /// - public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace]); - /// - /// 层表 - /// - public SymbolTable LayerTable => new(this, Database.LayerTableId); - /// - /// 文字样式表 - /// - public SymbolTable TextStyleTable => new(this, Database.TextStyleTableId); - - /// - /// 注册应用程序表 - /// - public SymbolTable RegAppTable => new(this, Database.RegAppTableId); - - /// - /// 标注样式表 - /// - public SymbolTable DimStyleTable => new(this, Database.DimStyleTableId); - - /// - /// 线型表 - /// - public SymbolTable LinetypeTable => new(this, Database.LinetypeTableId); - - /// - /// 用户坐标系表 - /// - public SymbolTable UcsTable => new(this, Database.UcsTableId); - - /// - /// 视图表 - /// - public SymbolTable ViewTable => new(this, Database.ViewTableId); - - /// - /// 视口表 - /// - public SymbolTable ViewportTable => new(this, Database.ViewportTableId); - #endregion - - #region 字典 - //TODO: 补充关于扩展字典,命名对象字典,组字典,多线样式字典等对象字典的属性 - /// - /// 命名对象字典 - /// - public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId); - /// - /// 组字典 - /// - public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId); - /// - /// 多重引线样式字典 - /// - public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId); - /// - /// 多线样式字典 - /// - public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId); - /// - /// 材质字典 - /// - public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId); - /// - /// 表格样式字典 - /// - public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId); - /// - /// 视觉样式字典 - /// - public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId); - /// - /// 颜色字典 - /// - public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId); - /// - /// 打印设置字典 - /// - public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId); - /// - /// 打印样式表名字典 - /// - public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId); - /// - /// 布局字典 - /// - public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId); - /// - /// 数据链接字典 - /// - public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId); -#if ac2013 - /// - /// 详细视图样式字典 - /// - public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId); - /// - /// 剖面视图样式字典 - /// - public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId); -#endif - #endregion - - #region 获取对象 - /// - /// 根据对象id获取图元对象 - /// - /// 要获取的图元对象的类型 - /// 对象id - /// 打开模式,默认为只读 - /// 是否打开已删除对象,默认为不打开 - /// 是否打开锁定图层对象,默认为不打开 - /// 图元对象,类型不匹配时返回 - public T GetObject(ObjectId id, - OpenMode mode = OpenMode.ForRead, - bool openErased = false, - bool forceOpenOnLockedLayer = false) where T : DBObject + #region 公开属性 + /// + /// 返回当前事务 + /// + public static DBTrans Top + { + get { - return Transaction.GetObject(id, mode, openErased, forceOpenOnLockedLayer) as T; - } + /* + * 0x01 + * 事务栈上面有事务,这个事务属于当前文档, + * 那么直接提交原本事务然后再开一个(一直把栈前面的同数据库提交清空) + * 那不就发生跨事务读取图元了吗?....否决 + * + * 0x02 + * 跨文档事务出错 Autodesk.AutoCAD.Runtime.Exception:“eNotFromThisDocument” + * Curves.GetEntities()会从Top获取事务(Top会new一个),此时会是当前文档; + * 然后命令文中发生了 using var tr = new DBTrans(); + * 当退出命令此事务释放,但是从来不释放Top, + * 然后我新建了一个文档,再进行命令=>又进入Top,Top返回了前一个文档的事务 + * 因此所以无法清理栈,所以Dispose不触发,导致无法刷新图元和Ctrl+Z出错 + * 所以用AOP方式修复 + * + * 0x03 + * 经过艰苦卓绝的测试,aop模式由于不能断点调试,所以暂时放弃。 + */ - /// - /// 根据对象句柄字符串获取对象Id - /// - /// 句柄字符串 - /// 对象id - public ObjectId GetObjectId(string handleString) - { - var hanle = new Handle(Convert.ToInt64(handleString, 16)); - return Database.GetObjectId(false, hanle, 0); + // 由于大量的函数依赖本属性,强迫用户先开启事务 + if (dBTrans.Count == 0) + throw new ArgumentNullException("事务栈没有任何事务,请在调用前创建:" + nameof(DBTrans)); + var trans = dBTrans.Peek(); + return trans; } + } - #endregion + /// + /// 文档 + /// + public Document? Document { get; private set; } + /// + /// 命令行 + /// + public Editor? Editor { get; private set; } + /// + /// 事务管理器 + /// + public Transaction Transaction { get; private set; } + /// + /// 数据库 + /// + public Database Database { get; private set; } + #endregion + #region 构造函数 + /// + /// 事务栈 + /// 默认构造函数,默认为打开当前文档,默认提交事务 + /// + /// 要打开的文档 + /// 事务是否提交 + /// 是否锁文档 + public DBTrans(Document? doc = null, bool commit = true, bool doclock = false) + { + Document = doc ?? Application.DocumentManager.MdiActiveDocument; + Database = Document.Database; + Editor = Document.Editor; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + if (doclock) + documentLock = Document.LockDocument(); + dBTrans.Push(this); + } - #region idispose接口相关函数 + /// + /// 事务栈 + /// 打开数据库,默认提交事务 + /// + /// 要打开的数据库 + /// 事务是否提交 + public DBTrans(Database database, bool commit = true) + { + Database = database; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + dBTrans.Push(this); + } - public void Abort() - { - Transaction.Abort(); - } + /// + /// 事务栈 + /// 打开文件,默认提交事务 + /// + /// 要打开的文件名 + /// 事务是否提交 + /// 开图模式 + /// 密码 + public DBTrans(string fileName, + bool commit = true, +#pragma warning disable CS0436 // 类型与导入类型冲突 + FileOpenMode openMode = FileOpenMode.OpenForReadAndWriteNoShare, +#pragma warning restore CS0436 // 类型与导入类型冲突 + string? password = null) + { + if (string.IsNullOrEmpty(fileName?.Trim())) + throw new ArgumentNullException(nameof(fileName)); - public void Commit() + if (!File.Exists(fileName)) + Database = new Database(true, false); + else { - if (_commit) + var doc = Acap.DocumentManager + .Cast() + .FirstOrDefault(doc => doc.Name == fileName); + if (doc is not null) { - Transaction.Commit(); + Database = doc.Database; + Document = doc; + Editor = doc.Editor; } else { - Abort(); + Database = new Database(false, true); + if (Path.GetExtension(fileName).ToLower().Contains("dxf")) + Database.DxfIn(fileName, null); + else + { +#if ac2008 + //此处没有一一对应的关系 +#pragma warning disable CS0436 // 类型与导入类型冲突 + var sf = openMode switch + { + FileOpenMode.OpenTryForReadShare => FileShare.Read, + FileOpenMode.OpenForReadAndAllShare => FileShare.ReadWrite, + FileOpenMode.OpenForReadAndWriteNoShare => FileShare.None, + FileOpenMode.OpenForReadAndReadShare => FileShare.ReadWrite, + _ => FileShare.ReadWrite, + }; +#pragma warning restore CS0436 // 类型与导入类型冲突 + Database.ReadDwgFile(fileName, sf, true/*控制读入一个与系统编码不相同的文件时的转换操作*/, password); +#else + Database.ReadDwgFile(fileName, openMode, true/*控制读入一个与系统编码不相同的文件时的转换操作*/, password); +#endif + } + Database.CloseInput(true); } - } - protected virtual void Dispose(bool disposing) + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + dBTrans.Push(this); + } + #endregion + + #region 类型转换 + /// + /// 隐式转换为Transaction + /// + /// 事务管理器 + /// 事务管理器 + public static implicit operator Transaction(DBTrans tr) + { + return tr.Transaction; + } + #endregion + + #region 符号表 + + /// + /// 块表 + /// + public SymbolTable BlockTable => new(this, Database.BlockTableId); + /// + /// 当前绘图空间 + /// + public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId)!; + /// + /// 模型空间 + /// + public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace])!; + /// + /// 图纸空间 + /// + public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace])!; + /// + /// 层表 + /// + public SymbolTable LayerTable => new(this, Database.LayerTableId); + /// + /// 文字样式表 + /// + public SymbolTable TextStyleTable => new(this, Database.TextStyleTableId); + + /// + /// 注册应用程序表 + /// + public SymbolTable RegAppTable => new(this, Database.RegAppTableId); + + /// + /// 标注样式表 + /// + public SymbolTable DimStyleTable => new(this, Database.DimStyleTableId); + + /// + /// 线型表 + /// + public SymbolTable LinetypeTable => new(this, Database.LinetypeTableId); + + /// + /// 用户坐标系表 + /// + public SymbolTable UcsTable => new(this, Database.UcsTableId); + + /// + /// 视图表 + /// + public SymbolTable ViewTable => new(this, Database.ViewTableId); + + /// + /// 视口表 + /// + public SymbolTable ViewportTable => new(this, Database.ViewportTableId); + #endregion + + #region 字典 + /// + /// 命名对象字典 + /// + public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId)!; + /// + /// 组字典 + /// + public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId)!; + /// + /// 多重引线样式字典 + /// + public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId)!; + /// + /// 多线样式字典 + /// + public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId)!; + /// + /// 材质字典 + /// + public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId)!; + /// + /// 表格样式字典 + /// + public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId)!; + /// + /// 视觉样式字典 + /// + public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId)!; + /// + /// 颜色字典 + /// + public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId)!; + /// + /// 打印设置字典 + /// + public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId)!; + /// + /// 打印样式表名字典 + /// + public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId)!; + /// + /// 布局字典 + /// + public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId)!; + /// + /// 数据链接字典 + /// + public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId)!; +#if !ac2009 + /// + /// 详细视图样式字典 + /// + public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId)!; + /// + /// 剖面视图样式字典 + /// + public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId)!; +#endif + #endregion + + #region 获取对象 + /// + /// 根据对象id获取图元对象 + /// + /// 要获取的图元对象的类型 + /// 对象id + /// 打开模式,默认为只读 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + /// 图元对象,类型不匹配时返回 + public T? GetObject(ObjectId id, + OpenMode mode = OpenMode.ForRead, + bool openErased = false, + bool forceOpenOnLockedLayer = false) where T : DBObject + { + return Transaction.GetObject(id, mode, openErased, forceOpenOnLockedLayer) as T; + } + + /// + /// 根据对象句柄字符串获取对象Id + /// + /// 句柄字符串 + /// 对象id + public ObjectId GetObjectId(string handleString) + { + var hanle = new Handle(Convert.ToInt64(handleString, 16)); + //return Database.GetObjectId(false, hanle, 0); + return Helper.TryGetObjectId(Database, hanle); + } + + + + #endregion + + #region 保存文件 + /// + /// 保存当前数据库的dwg文件,如果前台打开则按dwg默认版本保存,否则按version参数的版本保存 + /// + /// dwg版本,默认为2004 + public void SaveDwgFile(DwgVersion version = DwgVersion.AC1800) + { + bool flag = true; + foreach (Document doc in Application.DocumentManager) { - if (!disposedValue) + // 前台开图,使用命令保存 + if (doc.Database.Filename == this.Database.Filename) { - if (disposing) - { - // 释放托管状态(托管对象) - Commit(); - dBTrans.Pop(); - if (!Transaction.IsDisposed) - { - Transaction.Dispose(); - } - documentLock?.Dispose(); - } - - // 释放未托管的资源(未托管的对象)并替代终结器 - // 将大型字段设置为 null - disposedValue = true; + doc.SendStringToExecute("_qsave\n", false, true, true); //不需要切换文档 + flag = false; + break; } } - - // 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 - ~DBTrans() + if (flag) { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: false); + // 后台开图,用数据库保存 + Database.SaveAs(Database.Filename, version); } + } + #endregion - public void Dispose() + #region idispose接口相关函数 + /// + /// 取消事务 + /// + public void Abort() + { + Dispose(false); + } + /// + /// 提交事务 + /// + public void Commit() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + /* 事务dispose流程: + * 1. 根据传入的参数确定是否提交,true为提交,false为不提交 + * 2. 根据disposedValue的值确定是否重复dispose,false为首次dispose + * 3. 如果锁文档就将文档锁dispose + * 4. 不管是否提交,既然进入dispose,就要将事务栈的当前事务弹出 + * 注意这里的事务栈不是cad的事务管理器,而是dbtrans的事务 + * 5. 清理非托管的字段 + */ + + if (disposedValue) + return; + + // 释放未托管的资源(未托管的对象)并替代终结器 + // 将大型字段设置为 null + disposedValue = true; + + if (disposing) + { + // 调用cad的事务进行提交,释放托管状态(托管对象) + Transaction.Commit(); + } + else { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: true); - GC.SuppressFinalize(this); + // 否则取消所有的修改 + Transaction.Abort(); } - #endregion + // 调用 cad事务的dispose进行销毁 + if (!Transaction.IsDisposed) + Transaction.Dispose(); + + // 调用文档锁dispose + documentLock?.Dispose(); + + // 将事务栈的当前dbtrans弹栈 + dBTrans.Pop(); + } + + // 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 + ~DBTrans() + { + // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 + Dispose(disposing: false); + } + + public void Dispose() + { + // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 + Dispose(disposing: true); + GC.SuppressFinalize(this); } + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/IFoxCAD.Cad/Runtime/Env.cs index 0d490e1..c7c6e92 100644 --- a/src/IFoxCAD.Cad/Runtime/Env.cs +++ b/src/IFoxCAD.Cad/Runtime/Env.cs @@ -1,453 +1,493 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; +namespace IFoxCAD.Cad; + +//此命名空间容易引起Polyline等等重义,因此不放入全局空间 using Autodesk.AutoCAD.GraphicsSystem; -using System; -namespace IFoxCAD.Cad +/// +/// 系统管理类 +/// 封装了一些系统 osmode、cmdecho、dimblk 系统变量 +/// 封装了常用的 文档 编辑器 数据库等对象为静态变量 +/// 封装了配置页面的注册表信息获取函数 +/// +public static class Env { + #region Goal + /// - /// 系统管理类 - /// 封装了一些系统 osmode、cmdecho、dimblk 系统变量 - /// 封装了常用的 文档 编辑器 数据库等对象为静态变量 - /// 封装了配置页面的注册表信息获取函数 + /// 当前的数据库 /// - public static class Env + public static Database Database => HostApplicationServices.WorkingDatabase; + + /// + /// 当前文档 + /// + public static Document Document => Application.DocumentManager.MdiActiveDocument; + + /// + /// 编辑器对象 + /// + public static Editor Editor => Document.Editor; + + /// + /// 图形管理器 + /// + public static Manager GsManager => Document.GraphicsManager; + + #endregion Goal + + #region Preferences + + /// + /// 获取当前配置的数据 + /// + /// 小节名 + /// 数据名 + /// 对象 + public static object GetCurrentProfileProperty(string subSectionName, string propertyName) + { + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection cpf = ucm.OpenCurrentProfile(); + IConfigurationSection ss = cpf.OpenSubsection(subSectionName); + return ss.ReadProperty(propertyName, ""); + } + + /// + /// 获取对话框配置的数据 + /// + /// 对话框对象 + /// 配置项 + public static IConfigurationSection GetDialogSection(object dialog) { - #region Goal + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection ds = ucm.OpenDialogSection(dialog); + return ds; + } + /// + /// 获取公共配置的数据 + /// + /// 数据名 + /// 配置项 + public static IConfigurationSection GetGlobalSection(string propertyName) + { + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection gs = ucm.OpenGlobalSection(); + IConfigurationSection ss = gs.OpenSubsection(propertyName); + return ss; + } + + #endregion Preferences + + #region Enum + + /// + /// 控制在AutoLISP的command函数运行时AutoCAD是否回显提示和输入, 为显示, 为不显示 + /// + public static bool CmdEcho + { + get => Convert.ToInt16(Application.GetSystemVariable("cmdecho")) == 1; + set => Application.SetSystemVariable("cmdecho", Convert.ToInt16(value)); + } + + /// + /// 控制在光标是否为正交模式, 为打开正交, 为关闭正交 + /// + public static bool OrthoMode + { + get => Convert.ToInt16(Application.GetSystemVariable("ORTHOMODE")) == 1; + set => Application.SetSystemVariable("ORTHOMODE", Convert.ToInt16(value)); + } + + #region Dimblk + + /// + /// 标注箭头类型 + /// + public enum DimblkType + { /// - /// 当前的数据库 + /// 实心闭合 /// - public static Database CurrentDatabase - { - get - { - return HostApplicationServices.WorkingDatabase; - } - } + Defult, /// - /// 当前文档 + /// 点 /// - public static Document ActiveDocument - { - get - { - return Application.DocumentManager.MdiActiveDocument; - } - } + Dot, /// - /// 编辑器对象 + /// 小点 /// - public static Editor Editor - { - get - { - return ActiveDocument.Editor; - } - } + DotSmall, /// - /// 图形管理器 + /// 空心点 /// - public static Manager GsManager - { - get - { - return ActiveDocument.GraphicsManager; - } - } + DotBlank, - #endregion Goal + /// + /// 原点标记 + /// + Origin, - #region Preferences + /// + /// 原点标记2 + /// + Origin2, /// - /// 获取当前配置的数据 + /// 打开 /// - /// 小节名 - /// 数据名 - /// 对象 - public static object GetCurrentProfileProperty(string subSectionName, string propertyName) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection cpf = ucm.OpenCurrentProfile(); - IConfigurationSection ss = cpf.OpenSubsection(subSectionName); - return ss.ReadProperty(propertyName, ""); - } + Open, /// - /// 获取对话框配置的数据 + /// 直角 /// - /// 对话框对象 - /// 配置项 - public static IConfigurationSection GetDialogSection(object dialog) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection ds = ucm.OpenDialogSection(dialog); - return ds; - } + Open90, /// - /// 获取公共配置的数据 + /// 30度角 /// - /// 数据名 - /// 配置项 - public static IConfigurationSection GetGlobalSection(string propertyName) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection gs = ucm.OpenGlobalSection(); - IConfigurationSection ss = gs.OpenSubsection(propertyName); - return ss; - } + Open30, - #endregion Preferences + /// + /// 闭合 + /// + Closed, - #region Enum + /// + /// 空心小点 + /// + Small, /// - /// 控制在AutoLISP的command函数运行时AutoCAD是否回显提示和输入, 为显示, 为不显示 + /// 无 /// - public static bool CmdEcho - { - get => Convert.ToInt16(Application.GetSystemVariable("cmdecho")) == 1; - set => Application.SetSystemVariable("cmdecho", Convert.ToInt16(value)); - } + None, - #region Dimblk + /// + /// 倾斜 + /// + Oblique, /// - /// 标注箭头类型 + /// 实心框 /// - public enum DimblkType - { - /// - /// 实心闭合 - /// - Defult, - - /// - /// 点 - /// - Dot, - - /// - /// 小点 - /// - DotSmall, - - /// - /// 空心点 - /// - DotBlank, - - /// - /// 原点标记 - /// - Origin, - - /// - /// 原点标记2 - /// - Origin2, - - /// - /// 打开 - /// - Open, - - /// - /// 直角 - /// - Open90, - - /// - /// 30度角 - /// - Open30, - - /// - /// 闭合 - /// - Closed, - - /// - /// 空心小点 - /// - Small, - - /// - /// 无 - /// - None, - - /// - /// 倾斜 - /// - Oblique, - - /// - /// 实心框 - /// - BoxFilled, - - /// - /// 方框 - /// - BoxBlank, - - /// - /// 空心闭合 - /// - ClosedBlank, - - /// - /// 实心基准三角形 - /// - DatumFilled, - - /// - /// 基准三角形 - /// - DatumBlank, - - /// - /// 完整标记 - /// - Integral, - - /// - /// 建筑标记 - /// - ArchTick, - } + BoxFilled, /// - /// 标注箭头属性 + /// 方框 /// - public static DimblkType Dimblk - { - get - { - string s = (string)Application.GetSystemVariable("dimblk"); - if (string.IsNullOrEmpty(s)) - { - return DimblkType.Defult; - } - else - { - return s.ToEnum(); - } - } - set - { - string s = GetDimblkName(value); - Application.SetSystemVariable("dimblk", s); - } - } + BoxBlank, /// - /// 获取标注箭头名 + /// 空心闭合 /// - /// 标注箭头类型 - /// 箭头名 - public static string GetDimblkName(DimblkType dimblk) - { - return - dimblk == DimblkType.Defult - ? - "." - : - "_" + dimblk.GetName(); - } + ClosedBlank, /// - /// 获取标注箭头ID + /// 实心基准三角形 /// - /// 标注箭头类型 - /// 箭头ID - public static ObjectId GetDimblkId(DimblkType dimblk) - { - DimblkType oldDimblk = Dimblk; - Dimblk = dimblk; - ObjectId id = HostApplicationServices.WorkingDatabase.Dimblk; - Dimblk = oldDimblk; - return id; - } + DatumFilled, - #endregion Dimblk + /// + /// 基准三角形 + /// + DatumBlank, - #region OsMode + /// + /// 完整标记 + /// + Integral, /// - /// 捕捉模式系统变量类型 + /// 建筑标记 /// - public enum OSModeType + ArchTick + } + + private static readonly Dictionary dimdescdict = new() + { + { "实心闭合", DimblkType.Defult }, + { "点", DimblkType.Dot }, + { "小点", DimblkType.DotSmall }, + { "空心点", DimblkType.DotBlank }, + { "原点标记", DimblkType.Origin }, + { "原点标记 2", DimblkType.Origin2 }, + { "打开", DimblkType.Open }, + { "直角", DimblkType.Open90 }, + { "30 度角", DimblkType.Open30 }, + { "闭合", DimblkType.Closed }, + { "空心小点", DimblkType.Small }, + { "无", DimblkType.None }, + { "倾斜", DimblkType.Oblique }, + { "实心框", DimblkType.BoxFilled }, + { "方框", DimblkType.BoxBlank }, + { "空心闭合", DimblkType.ClosedBlank }, + { "实心基准三角形", DimblkType.DatumFilled }, + { "基准三角形", DimblkType.DatumBlank }, + { "完整标记", DimblkType.Integral }, + { "建筑标记", DimblkType.ArchTick }, + + { "", DimblkType.Defult }, + { "_DOT", DimblkType.Dot }, + { "_DOTSMALL", DimblkType.DotSmall }, + { "_DOTBLANK", DimblkType.DotBlank }, + { "_ORIGIN", DimblkType.Origin }, + { "_ORIGIN2", DimblkType.Origin2 }, + { "_OPEN", DimblkType.Open }, + { "_OPEN90", DimblkType.Open90 }, + { "_OPEN30", DimblkType.Open30 }, + { "_CLOSED", DimblkType.Closed }, + { "_SMALL", DimblkType.Small }, + { "_NONE", DimblkType.None }, + { "_OBLIQUE", DimblkType.Oblique }, + { "_BOXFILLED", DimblkType.BoxFilled }, + { "_BOXBLANK", DimblkType.BoxBlank }, + { "_CLOSEDBLANK", DimblkType.ClosedBlank }, + { "_DATUMFILLED", DimblkType.DatumFilled }, + { "_DATUMBLANK", DimblkType.DatumBlank }, + { "_INTEGRAL", DimblkType.Integral }, + { "_ARCHTICK", DimblkType.ArchTick }, + }; + + + + /// + /// 标注箭头属性 + /// + public static DimblkType Dimblk + { + get + { + string s = ((string)Application.GetSystemVariable("dimblk")).ToUpper(); + //if (string.IsNullOrEmpty(s)) + //{ + // return DimblkType.Defult; + //} + //else + //{ + // if (dimdescdict.TryGetValue(s, out DimblkType value)) + // { + // return value; + // } + // return s.ToEnum(); + // //return s.FromDescName(); + //} + return dimdescdict[s]; + } + set { - /// - /// 无 - /// - None = 0, - - /// - /// 端点 - /// - End = 1, - - /// - /// 中点 - /// - Middle = 2, - - /// - /// 圆心 - /// - Center = 4, - - /// - /// 节点 - /// - Node = 8, - - /// - /// 象限点 - /// - Quadrant = 16, - - /// - /// 交点 - /// - Intersection = 32, - - /// - /// 插入点 - /// - Insert = 64, - - /// - /// 垂足 - /// - Pedal = 128, - - /// - /// 切点 - /// - Tangent = 256, - - /// - /// 最近点 - /// - Nearest = 512, - - /// - /// 几何中心 - /// - Quick = 1024, - - /// - /// 外观交点 - /// - Appearance = 2048, - - /// - /// 延伸 - /// - Extension = 4096, - - /// - /// 平行 - /// - Parallel = 8192 + string s = GetDimblkName(value); + Application.SetSystemVariable("dimblk", s); } + } + + /// + /// 获取标注箭头名 + /// + /// 标注箭头类型 + /// 箭头名 + public static string GetDimblkName(DimblkType dimblk) + { + return + dimblk == DimblkType.Defult + ? + "." + : + "_" + dimblk.GetName(); + } + /// + /// 获取标注箭头ID + /// + /// 标注箭头类型 + /// 箭头ID + public static ObjectId GetDimblkId(DimblkType dimblk) + { + DimblkType oldDimblk = Dimblk; + Dimblk = dimblk; + ObjectId id = HostApplicationServices.WorkingDatabase.Dimblk; + Dimblk = oldDimblk; + return id; + } + + #endregion Dimblk + + #region OsMode + + /// + /// 捕捉模式系统变量类型 + /// + public enum OSModeType + { /// - /// 捕捉模式系统变量 + /// 无 /// - public static OSModeType OSMode - { - get - { - return (OSModeType)Convert.ToInt16(Application.GetSystemVariable("osmode")); - } - set - { - Application.SetSystemVariable("osmode", (int)value); - } - } + None = 0, + /// - /// 捕捉模式osm1是否包含osm2 + /// 端点 /// - /// 原模式 - /// 要比较的模式 - /// 包含时返回 ,不包含时返回 - public static bool Include(this OSModeType osm1, OSModeType osm2) - { - return (osm1 & osm2) == osm2; - } - #endregion OsMode + End = 1, - private static T ToEnum(this string value) - { - return (T)Enum.Parse(typeof(T), value, true); - } + /// + /// 中点 + /// + Middle = 2, - private static string GetName(this T value) - { - return Enum.GetName(typeof(T), value); - } + /// + /// 圆心 + /// + Center = 4, - #endregion Enum + /// + /// 节点 + /// + Node = 8, - #region 环境变量 /// - /// 获取cad变量 + /// 象限点 /// - /// 变量名 - /// 变量值 - public static object GetVar(string varName) - { - return Application.GetSystemVariable(varName); - } + Quadrant = 16, + /// - /// 设置cad变量 + /// 交点 /// - /// 变量名 - /// 变量值 - public static void SetVar(string varName, object value) - { - Application.SetSystemVariable(varName, value); - } -#nullable enable + Intersection = 32, + /// - /// 获取系统环境变量 + /// 插入点 /// - /// 变量名 - /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null - public static string? GetEnv(string var) - { - //从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 - return Environment.GetEnvironmentVariable(var); - } + Insert = 64, + /// - /// 设置系统环境变量 + /// 垂足 /// - /// 变量名 - /// 变量值 - public static void SetEnv(string var, string? value) - { - //创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 - Environment.SetEnvironmentVariable(var, value); - } -#nullable disable - #endregion + Pedal = 128, + /// + /// 切点 + /// + Tangent = 256, + + /// + /// 最近点 + /// + Nearest = 512, + + /// + /// 几何中心 + /// + Quick = 1024, + + /// + /// 外观交点 + /// + Appearance = 2048, /// - /// 命令行打印,会自动调用对象的toString函数 + /// 延伸 /// - /// 要打印的对象 - public static void Print(object message) => Editor.WriteMessage($"{message}\n"); + Extension = 4096, + + /// + /// 平行 + /// + Parallel = 8192 + } + + /// + /// 捕捉模式系统变量 + /// + public static OSModeType OSMode + { + get + { + return (OSModeType)Convert.ToInt16(Application.GetSystemVariable("osmode")); + } + set + { + Application.SetSystemVariable("osmode", (int)value); + } + } + /// + /// 捕捉模式osm1是否包含osm2 + /// + /// 原模式 + /// 要比较的模式 + /// 包含时返回 ,不包含时返回 + public static bool Include(this OSModeType osm1, OSModeType osm2) + { + return (osm1 & osm2) == osm2; + } + #endregion OsMode + + + private static string GetName(this T value) + { + return Enum.GetName(typeof(T), value); + } + + #endregion Enum + + #region 环境变量 + /// + /// 获取cad变量 + /// + /// 变量名 + /// 变量值 + public static object GetVar(string varName) + { + return Application.GetSystemVariable(varName); + } + /// + /// 设置cad变量 + /// + /// 变量名 + /// 变量值 + public static void SetVar(string varName, object value) + { + try + { + Application.SetSystemVariable(varName, value); + } + catch (System.Exception) + { + Env.Print($"{varName} 是不存在的变量!"); + } + } + /// + /// 获取系统环境变量 + /// + /// 变量名 + /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null + public static string? GetEnv(string var) + { + //从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 + return Environment.GetEnvironmentVariable(var); + } + /// + /// 设置系统环境变量 + /// + /// 变量名 + /// 变量值 + public static void SetEnv(string var, string? value) + { + //创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 + Environment.SetEnvironmentVariable(var, value); } + #endregion + + + /// + /// 命令行打印,会自动调用对象的toString函数 + /// + /// 要打印的对象 + public static void Print(object message) => Editor.WriteMessage($"{message}\n"); + /// + /// 判断当前是否在UCS坐标下 + /// + /// Bool + public static bool IsUcs() => (short)GetVar("WORLDUCS") == 0; } diff --git a/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs b/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs new file mode 100644 index 0000000..5108dd5 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs @@ -0,0 +1,13 @@ +#if ac2008 //NET35 +namespace Autodesk.AutoCAD.DatabaseServices +{ + [Wrapper("AcDbDatabase::OpenMode")] + public enum FileOpenMode + { + OpenTryForReadShare = 4, + OpenForReadAndAllShare = 3, + OpenForReadAndWriteNoShare = 2, + OpenForReadAndReadShare = 1 + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/IAutoGo.cs b/src/IFoxCAD.Cad/Runtime/IAutoGo.cs new file mode 100644 index 0000000..27ce550 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/IAutoGo.cs @@ -0,0 +1,307 @@ +namespace IFoxCAD.Cad; + +using System.Diagnostics; + +/// +/// 加载时优先级 +/// +[Flags] +public enum Sequence : byte +{ + First,// 最先 + Last, // 最后 +} + +/// +/// 加载时自动执行接口 +/// +public interface IFoxAutoGo +{ + // 控制加载顺序 + Sequence SequenceId(); + // 关闭cad的时候会自动执行 + void Terminate(); + // 打开cad的时候会自动执行 + void Initialize(); +} + +/// +/// 加载时自动执行特性 +/// +[AttributeUsage(AttributeTargets.Method)] +public class IFoxInitialize : Attribute +{ + /// + /// 优先级 + /// + internal Sequence SequenceId; + /// + /// 用于初始化;用于结束回收 + /// + internal bool IsInitialize; + /// + /// 用于初始化和结束回收 + /// + /// 优先级 + /// 用于初始化;用于结束回收 + public IFoxInitialize(Sequence sequence = Sequence.Last, bool isInitialize = true) + { + SequenceId = sequence; + IsInitialize = isInitialize; + } +} + +//为了解决IExtensionApplication在一个dll内无法多次实现接口的关系 +//所以在这里反射加载所有的 IAutoGo ,以达到能分开写"启动运行"函数的目的 +class RunClass +{ + public Sequence Sequence { get; } + readonly MethodInfo _methodInfo; + + public RunClass(MethodInfo method, Sequence sequence) + { + _methodInfo = method; + Sequence = sequence; + } + + /// + /// 运行方法 + /// + public void Run() + { + _methodInfo.Invoke(); + } +} + +/// +/// 此类作为加载后cad自动运行接口的一部分,用于反射特性和接口 +/// 启动cad后的执行顺序为: +/// 1:特性..(多个) +/// 2:接口..(多个) +/// +public class AutoReflection +{ + static List _InitializeList = new(); //储存方法用于初始化 + static List _TerminateList = new(); //储存方法用于结束释放 + + readonly string _dllName; + readonly AutoRegConfig _autoRegConfig; + + /// + /// 反射执行 + /// 1.特性: + /// 2.接口: + /// + /// 约束在此dll进行加速 + public AutoReflection(string dllName, AutoRegConfig configInfo) + { + _dllName = dllName; + _autoRegConfig = configInfo; + } + + //启动cad的时候会自动执行 + public void Initialize() + { + try + { + //收集特性,包括启动时和关闭时 + if ((_autoRegConfig & AutoRegConfig.ReflectionAttribute) == AutoRegConfig.ReflectionAttribute) + GetAttributeFunctions(_InitializeList, _TerminateList); + + if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + GetInterfaceFunctions(_InitializeList, nameof(Initialize)); + + if (_InitializeList.Count > 0) + { + //按照 SequenceId 排序_升序 + _InitializeList = _InitializeList.OrderBy(runClass => runClass.Sequence).ToList(); + RunFunctions(_InitializeList); + } + } + catch (System.Exception) + { + Debugger.Break(); + } + } + + //关闭cad的时候会自动执行 + public void Terminate() + { + try + { + if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + GetInterfaceFunctions(_TerminateList, nameof(Terminate)); + + if (_TerminateList.Count > 0) + { + //按照 SequenceId 排序_降序 + _TerminateList = _TerminateList.OrderByDescending(runClass => runClass.Sequence).ToList(); + RunFunctions(_TerminateList); + } + } + catch (System.Exception) + { + Debugger.Break(); + } + } + + /// + /// 遍历程序域下所有类型 + /// + /// 输出每个成员执行 + /// 过滤此dll,不含后缀 + public static void AppDomainGetTypes(Action action, string? dllNameWithoutExtension = null) + { +#if DEBUG + int error = 0; +#endif + try + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); +#if !NET35 + //cad2021出现如下报错 + //System.NotSupportedException:动态程序集中不支持已调用的成员 + //assemblies = assemblies.Where(p => !p.IsDynamic).ToArray();//这个要容器类型转换 + assemblies = Array.FindAll(assemblies, p => !p.IsDynamic); +#endif + //主程序域 + for (int ii = 0; ii < assemblies.Length; ii++) + { + var assembly = assemblies[ii]; + + //获取类型集合,反射时候还依赖其他的dll就会这个错误 + //此通讯库要跳过,否则会报错. + var location = Path.GetFileNameWithoutExtension(assembly.Location); + if (dllNameWithoutExtension != null && location != dllNameWithoutExtension) + continue; + if (location == "AcInfoCenterConn")//通讯库 + continue; + + Type[]? types = null; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) { continue; } + + if (types is null) + continue; + + for (int jj = 0; jj < types.Length; jj++) + { + var type = types[jj]; + if (type is not null) + { +#if DEBUG + ++error; +#endif + action(type); + } + } + } + } + catch (System.Exception e) + { +#if DEBUG + Debug.WriteLine($"出错:{nameof(AppDomainGetTypes)};计数{error};错误信息:{e.Message}"); + Debugger.Break(); +#endif + } + } + + /// + /// 收集接口下的函数 + /// + /// 储存要运行的方法 + /// 查找方法名 + /// + void GetInterfaceFunctions(List runClassList, string methodName) + { + const string sqid = nameof(Sequence) + "Id"; + + AppDomainGetTypes(type => { + if (type.IsAbstract) + return; + + var ints = type.GetInterfaces(); + for (int sss = 0; sss < ints.Length; sss++) + { + var inters = ints[sss]; + if (inters.Name != nameof(IFoxAutoGo)) + continue; + + Sequence? sequence = null; + MethodInfo? initialize = null; + + var mets = type.GetMethods(); + for (int jj = 0; jj < mets.Length; jj++) + { + var method = mets[jj]; + if (method.IsAbstract) + continue; + + if (method.Name == sqid) + { + var obj = method.Invoke(); + if (obj is not null) + sequence = (Sequence)obj; + continue; + } + else if (method.Name == methodName) + initialize = method; + + if (initialize is not null && sequence is not null) + break; + } + + if (initialize is null) + continue; + + var seq = sequence is null ? Sequence.Last : sequence.Value; + runClassList.Add(new RunClass(initialize, seq)); + break; + } + }, _dllName); + + } + + /// + /// 收集特性下的函数 + /// + void GetAttributeFunctions(List initialize, List terminate) + { + AppDomainGetTypes(type => { + if (type.IsAbstract) + return; + + var mets = type.GetMethods(); + for (int ii = 0; ii < mets.Length; ii++) + { + var method = mets[ii]; + var attr = method.GetCustomAttributes(true); + for (int jj = 0; jj < attr.Length; jj++) + { + if (attr[jj] is IFoxInitialize jjAtt) + { + var runc = new RunClass(method, jjAtt.SequenceId); + if (jjAtt.IsInitialize) + initialize.Add(runc); + else + terminate.Add(runc); + break; + } + } + } + }, _dllName); + } + + /// + /// 执行收集到的函数 + /// + static void RunFunctions(List runClassList) + { + for (int i = 0; i < runClassList.Count; i++) + runClassList[i].Run(); + runClassList.Clear(); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/Log.cs b/src/IFoxCAD.Cad/Runtime/Log.cs new file mode 100644 index 0000000..02293e2 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/Log.cs @@ -0,0 +1,412 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Diagnostics; +using System.Threading; + +#region 写入日志到不同的环境中 +//https://zhuanlan.zhihu.com/p/338492989 +public abstract class LogBase +{ + public abstract void ReadLog(string message); + public abstract void WriteLog(string message); + public abstract void DeleteLog(string message); +} + +/// +/// 日志输出环境 +/// +public enum LogTarget +{ + /// + /// 文件 + /// + File = 1, + /// + /// 数据库 + /// + Database = 2, + /// + /// windows日志 + /// + EventLog = 4, +} + +/// +/// 写入到文件中 +/// +public class FileLogger : LogBase +{ + public override void DeleteLog(string message) + { + throw new NotImplementedException(); + } + + public override void ReadLog(string message) + { + throw new NotImplementedException(); + } + + public override void WriteLog(string? message) + { + //把异常信息输出到文件 + var sw = new StreamWriter(LogHelper.LogAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(message); + sw.Flush(); + sw.Close(); + sw.Dispose(); + } +} + +/// +/// 写入到数据库(暂时不支持) +/// +public class DBLogger : LogBase +{ + public override void DeleteLog(string message) + { + throw new NotImplementedException(); + } + + public override void ReadLog(string message) + { + throw new NotImplementedException(); + } + + public override void WriteLog(string? message) + { + throw new NotImplementedException(); + } +} + +/// +/// 写入到win日志 +/// +public class EventLogger : LogBase +{ + // https://docs.microsoft.com/en-us/answers/questions/526018/windows-event-log-with-net-5.html + // net50要加 + // 需要win权限 + + public string LogName = "IFoxCadLog"; + public override void DeleteLog(string message) + { +#if !NET5_0 && !NET6_0 + if (EventLog.Exists(LogName)) + EventLog.Delete(LogName); +#endif + } + + public override void ReadLog(string message) + { +#if !NET5_0 && !NET6_0 + EventLog eventLog = new(); + eventLog.Log = LogName; + foreach (EventLogEntry entry in eventLog.Entries) + { + //Write your custom code here + } +#endif + } + + public override void WriteLog(string? message) + { +#if !NET5_0 && !NET6_0 + try + { + EventLog eventLog = new() + { + Source = LogName + }; + eventLog.WriteEntry(message, EventLogEntryType.Information); + } + catch (System.Security.SecurityException e) + { + throw new Exception("您没有权限写入win日志中" + e.Message); + } +#endif + } +} + +#endregion + +#region 静态方法 +public static class LogHelper +{ +#pragma warning disable CA2211 // 非常量字段应当不可见 + /// + /// 日志文件完整路径 + /// + public static string? LogAddress; + /// + /// 输出错误信息到日志文件的开关 + /// + public static bool FlagOutFile = false; + /// + /// 输出错误信息到vs输出窗口的开关 + /// + public static bool FlagOutVsOutput = true; + +#pragma warning restore CA2211 // 非常量字段应当不可见 + + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 提供给外部设置log文件保存路径 + /// + /// 空的话就为运行的dll旁边的一个文件夹上 + public static void OptionFile(string? newlogAddress = null) + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + try + { + LogAddress = newlogAddress; + if (string.IsNullOrEmpty(LogAddress)) + { + //微软回复:静态构造函数只会被调用一次, + //并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + //https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + //新建文件夹 + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; //设置文件夹属性为普通 + } + + sb.Append('\\'); + sb.Append(DateTime.Now.ToString("yy-MM-dd")); + sb.Append(".log"); + LogAddress = sb.ToString(); + } + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } + + public static string WriteLog(this string? message, + LogTarget target = LogTarget.File) + { + if (message == null) + return string.Empty; + return LogAction(null, message, target); + } + + public static string WriteLog(this Exception? exception, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, null, target); + } + + public static string WriteLog(this Exception? exception, string? message, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, message, target); + } + + + + static string LogAction(Exception? ex, string? message, LogTarget target) + { + if (ex == null && message == null) + return string.Empty; + + if (target == LogTarget.File && LogAddress == null) + OptionFile(); + + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, message); + //var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt?.ToString(); + if (logtxtJson == null) + return string.Empty; + + if (FlagOutFile) + { + LogBase? logger; + switch (target) + { + case LogTarget.File: + logger = new FileLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.Database: + logger = new DBLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.EventLog: + logger = new EventLogger(); + logger.WriteLog(logtxtJson); + break; + } + } + + if (FlagOutVsOutput) + { + Debug.WriteLine("错误日志: " + LogAddress); + Debug.Write(logtxtJson); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endregion + +#region 序列化 +[Serializable] +public class LogTxt +{ + public string 当前时间; + public string? 备注信息; + public string? 异常信息; + public string? 异常对象; + public string? 触发方法; + public string? 调用堆栈; + + LogTxt() + { + // 以不同语言显示日期 + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("es-ES")) + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("zh-cn")) + // 为了最小信息熵,所以用这样的格式,并且我喜欢补0 + 当前时间 = DateTime.Now.ToString("yy-MM-dd hh:mm:ss"); + } + + public LogTxt(Exception? ex, string? message) : this() + { + if (ex == null && message == null) + throw new ArgumentNullException(nameof(ex)); + + if (ex != null) + { + 异常信息 = ex.Message; + 异常对象 = ex.Source; + 触发方法 = ex.TargetSite == null ? string.Empty : ex.TargetSite.ToString(); + 调用堆栈 = ex.StackTrace == null ? string.Empty : ex.StackTrace.Trim(); + } + if (message != null) + 备注信息 = message; + } + + /// 为了不引入json的dll,所以这里自己构造 + public override string? ToString() + { + var sb = new StringBuilder(); + sb.Append('{'); + sb.Append(Environment.NewLine); + sb.AppendLine($" \"{nameof(当前时间)}\": \"{当前时间}\""); + sb.AppendLine($" \"{nameof(备注信息)}\": \"{备注信息}\""); + sb.AppendLine($" \"{nameof(异常信息)}\": \"{异常信息}\""); + sb.AppendLine($" \"{nameof(异常对象)}\": \"{异常对象}\""); + sb.AppendLine($" \"{nameof(触发方法)}\": \"{触发方法}\""); + sb.AppendLine($" \"{nameof(调用堆栈)}\": \"{调用堆栈}\""); + sb.Append('}'); + return sb.ToString(); + } +} +#endregion + + +#if false //最简单的实现 +public static class Log +{ + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 日志文件完整路径 + /// + static readonly string _logAddress; + + static Log() + { + //微软回复:静态构造函数只会被调用一次, + //并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + //https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + //新建文件夹 + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; //设置文件夹属性为普通 + } + + sb.Append('\\'); + sb.Append(DateTime.Now.ToString("yy-MM-dd")); + sb.Append(".log"); + _logAddress = sb.ToString(); + } + + + /// + /// 将异常打印到日志文件 + /// + /// 异常 + /// 备注 + /// DEBUG模式打印到vs输出窗口 + public static string? WriteLog(this Exception? ex, + string? remarks = null, + bool printDebugWindow = true) + { + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, remarks); + //var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt.ToString(); + + if (logtxtJson == null) + return string.Empty; + + //把异常信息输出到文件 + var sw = new StreamWriter(_logAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(logtxtJson); + sw.Flush(); + sw.Close(); + sw.Dispose(); + + if (printDebugWindow) + { + Debug.WriteLine("错误日志: " + _logAddress); + Debug.Write(logtxtJson); + //Debugger.Break(); + //Debug.Assert(false, "终止进程"); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs b/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs new file mode 100644 index 0000000..c64a5f8 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs @@ -0,0 +1,53 @@ +namespace IFoxCAD.Cad; + +internal static class MethodInfoHelper +{ + private static readonly Dictionary methodDic = new(); + + /// + /// 执行函数 + /// + /// 函数 + /// 已经外部创建的对象,为空则此处创建 + public static object? Invoke(this MethodInfo methodInfo, object? instance = null) + { + if (methodInfo == null) + throw new ArgumentNullException(nameof(methodInfo)); + + object? result = null; + if (methodInfo.IsStatic) + { + //新函数指针进入此处 + //参数数量一定要匹配,为null则参数个数不同导致报错, + //参数为stirng[],则可以传入object[]代替,其他参数是否还可以实现默认构造? + var paramInfos = methodInfo.GetParameters(); + var args = new List { }; + for (int i = 0; i < paramInfos.Length; i++) + args.Add(null!); + result = methodInfo.Invoke(null, args.ToArray());//静态调用 + } + else + { + //原命令的函数指针进入此处 + //object instance; + if (methodDic.ContainsKey(methodInfo)) + instance = methodDic[methodInfo]; + + if (instance == null) + { + var reftype = methodInfo.ReflectedType; + if (reftype == null) return null; + var fullName = reftype.FullName; //命名空间+类 + if (fullName == null) return null; + var type = reftype.Assembly.GetType(fullName);//通过程序集反射创建类+ + if (type == null) return null; + instance = Activator.CreateInstance(type); + if (!type.IsAbstract)//无法创建抽象类成员 + methodDic.Add(methodInfo, instance); + } + if (instance != null) + result = methodInfo.Invoke(instance, null); //非静态,调用实例化方法 + } + return result; + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs index c319b5e..a31e0ac 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs @@ -1,317 +1,327 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Autodesk.AutoCAD.DatabaseServices; -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +public class SymbolTable : IEnumerable + where TTable : SymbolTable + where TRecord : SymbolTableRecord, new() { - public class SymbolTable : IEnumerable - where TTable : SymbolTable - where TRecord : SymbolTableRecord, new() - { - #region 程序集内部属性 - /// - /// 事务管理器 - /// - internal DBTrans DTrans { get; private set; } - /// - /// 数据库 - /// - internal Database Database { get; private set; } + #region 程序集内部属性 + /// + /// 事务管理器 + /// + internal DBTrans DTrans { get; private set; } + /// + /// 数据库 + /// + internal Database Database { get; private set; } - #endregion + #endregion - #region 公开属性 - /// - /// 当前符号表 - /// - public TTable CurrentSymbolTable { get; private set; } - #endregion + #region 公开属性 + /// + /// 当前符号表 + /// + public TTable CurrentSymbolTable { get; private set; } + #endregion - #region 构造函数 - /// - /// 构造函数,初始化Trans和CurrentSymbolTable属性 - /// - /// 事务管理器 - /// 符号表id - internal SymbolTable(DBTrans tr, ObjectId tableId) - { - DTrans = tr; - CurrentSymbolTable = DTrans.GetObject(tableId); - } + #region 构造函数 + /// + /// 构造函数,初始化Trans和CurrentSymbolTable属性 + /// + /// 事务管理器 + /// 符号表id + internal SymbolTable(DBTrans tr, ObjectId tableId) + { + DTrans = tr; + Database = tr.Database; + CurrentSymbolTable = DTrans.GetObject(tableId)!; + } - #endregion + #endregion - #region 索引器 - /// - /// 索引器 - /// - /// 对象名称 - /// 对象的id - public ObjectId this[string key] + #region 索引器 + /// + /// 索引器 + /// + /// 对象名称 + /// 对象的id + public ObjectId this[string key] + { + get { - get - { - if (Has(key)) - { - return CurrentSymbolTable[key]; - } - return ObjectId.Null; - } + if (Has(key)) + return CurrentSymbolTable[key]; + return ObjectId.Null; } - #endregion + } + #endregion - #region Has - /// - /// 判断是否存在符号表记录 - /// - /// 记录名 - /// 存在返回 , 不存在返回 - public bool Has(string key) - { - return CurrentSymbolTable.Has(key); - } - /// - /// 判断是否存在符号表记录 - /// - /// 记录id - /// 存在返回 , 不存在返回 - public bool Has(ObjectId objectId) - { - return CurrentSymbolTable.Has(objectId); - } - #endregion + #region Has + /// + /// 判断是否存在符号表记录 + /// + /// 记录名 + /// 存在返回 , 不存在返回 + public bool Has(string key) + { + return CurrentSymbolTable.Has(key); + } + /// + /// 判断是否存在符号表记录 + /// + /// 记录id + /// 存在返回 , 不存在返回 + public bool Has(ObjectId objectId) + { + return CurrentSymbolTable.Has(objectId); + } + #endregion - #region 添加符号表记录 - /// - /// 添加符号表记录 - /// - /// 符号表记录 - /// 对象id - private ObjectId Add(TRecord record) + #region 添加符号表记录 + /// + /// 添加符号表记录 + /// + /// 符号表记录 + /// 对象id + private ObjectId Add(TRecord record) + { + ObjectId id; + using (CurrentSymbolTable.ForWrite()) { - ObjectId id; - using (CurrentSymbolTable.ForWrite()) - { - id = CurrentSymbolTable.Add(record); - DTrans.Transaction.AddNewlyCreatedDBObject(record, true); - } - return id; + id = CurrentSymbolTable.Add(record); + DTrans.Transaction.AddNewlyCreatedDBObject(record, true); } - /// - /// 添加符号表记录 - /// - /// 符号表记录名 - /// 符号表记录处理函数的无返回值委托 - /// 对象id - public ObjectId Add(string name, Action action = null) - { - ObjectId id = this[name]; - if (id.IsNull) - { - TRecord record = new() - { - Name = name - }; - id = Add(record); - using (record.ForWrite()) - { - action?.Invoke(record); - } - } + return id; + } + /// + /// 添加符号表记录 + /// + /// 符号表记录名 + /// 符号表记录处理函数的无返回值委托 + /// 对象id + public ObjectId Add(string name, Action? action = null) + { + ObjectId id = this[name]; + if (id.IsNull) return id; - } - #endregion - #region 删除符号表记录 - /// - /// 删除符号表记录 - /// - /// 符号表记录对象 - private static void Remove(TRecord record) + var record = new TRecord() { - using (record.ForWrite()) - { - record.Erase(); - } - } - /// - /// 删除符号表记录 - /// - /// 符号表记录名 - public void Remove(string name) - { - TRecord record = GetRecord(name); - if (record is not null) - { - Remove(record); - } + Name = name + }; + id = Add(record); + using (record.ForWrite()) + action?.Invoke(record); + return id; + } + #endregion - } - /// - /// 删除符号表记录 - /// - /// 符号表记录对象id - public void Remove(ObjectId id) - { - TRecord record = GetRecord(id); - if (record is not null) - { - Remove(record); - } - } + #region 删除符号表记录 + /// + /// 删除符号表记录 + /// + /// 符号表记录对象 + private static void Remove(TRecord record) + { + using (record.ForWrite()) + record.Erase(); + } + /// + /// 删除符号表记录 + /// + /// 符号表记录名 + public void Remove(string name) + { + var record = GetRecord(name); + if (record is not null) + Remove(record); + } - #endregion + /// + /// 删除符号表记录 + /// + /// 符号表记录对象id + public void Remove(ObjectId id) + { + var record = GetRecord(id); + if (record is not null) + Remove(record); + } + #endregion - #region 修改符号表记录 - /// - /// 修改符号表 - /// - /// 符号表记录 - /// 修改委托 - private static void Change(TRecord record, Action action) + #region 修改符号表记录 + /// + /// 修改符号表 + /// + /// 符号表记录 + /// 修改委托 + private static void Change(TRecord record, Action action) + { + using (record.ForWrite()) { - using (record.ForWrite()) - { - action?.Invoke(record); - } + action.Invoke(record); } - /// - /// 修改符号表 - /// - /// 符号表记录名 - /// 修改委托 - public void Change(string name, Action action) + // 调用regen()函数可能会导致卡顿 + //Env.Editor.Regen(); + } + /// + /// 修改符号表 + /// + /// 符号表记录名 + /// 修改委托 + public void Change(string name, Action action) + { + var record = GetRecord(name); + if (record is not null) { - var record = GetRecord(name); - if (record is not null) - { - Change(record, action); - } + Change(record, action); } - /// - /// 修改符号表 - /// - /// 符号表记录id - /// 修改委托 - public void Change(ObjectId id, Action action) + } + /// + /// 修改符号表 + /// + /// 符号表记录id + /// 修改委托 + public void Change(ObjectId id, Action action) + { + var record = GetRecord(id); + if (record is not null) { - var record = GetRecord(id); - if (record is not null) - { - Change(record, action); - } + Change(record, action); } - #endregion + } + #endregion - #region 获取符号表记录 - /// - /// 获取符号表记录 - /// - /// 符号表记录的id - /// 打开模式,默认为只读 - /// 符号表记录 - public TRecord GetRecord(ObjectId id, OpenMode openMode = OpenMode.ForRead) => id.IsNull ? null : DTrans.GetObject(id, openMode); + #region 获取符号表记录 + /// + /// 获取符号表记录 + /// + /// 符号表记录的id + /// 打开模式,默认为只读 + /// 符号表记录 + public TRecord? GetRecord(ObjectId id, OpenMode openMode = OpenMode.ForRead) => /*id.IsNull ? null : */DTrans.GetObject(id, openMode); - /// - /// 获取符号表记录 - /// - /// 符号表记录名 - /// 打开模式,默认为只读 - /// 符号表记录 - public TRecord GetRecord(string name, OpenMode openMode = OpenMode.ForRead) => GetRecord(this[name], openMode); + /// + /// 获取符号表记录 + /// + /// 符号表记录名 + /// 打开模式,默认为只读 + /// 符号表记录 + public TRecord? GetRecord(string name, OpenMode openMode = OpenMode.ForRead) => GetRecord(this[name], openMode); - /// - /// 获取符号表记录 - /// - /// 符号表记录集合 - public IEnumerable GetRecords() - { - return this.Select(id => GetRecord(id)); - } - - /// - /// 获取符号表记录的名字集合 - /// - /// 记录的名字集合 - public IEnumerable GetRecordNames() => this.Select(id => GetRecord(id).Name); - /// - /// 获取符合过滤条件的符号表记录名字集合 - /// - /// 过滤器委托 - /// 记录的名字集合 - public IEnumerable GetRecordNames(Func filter) + /// + /// 获取符号表记录 + /// + /// 符号表记录集合 + public IEnumerable GetRecords() + { + foreach (var item in this) { - foreach (var id in this) + var record = GetRecord(item); + if (record is not null) { - var record = GetRecord(id); - if (filter.Invoke(record)) - { - yield return record.Name; - } + yield return record; } } + } - /// - /// 从源数据库拷贝符号表记录 - /// - /// 符号表 - /// 符号表记录名 - /// 是否覆盖, 为覆盖, 为不覆盖 - /// 对象id - public ObjectId GetRecordFrom(SymbolTable table, string name, bool over) + /// + /// 获取符号表记录的名字集合 + /// + /// 记录的名字集合 + public IEnumerable GetRecordNames() => GetRecords().Select(record => record.Name); + /// + /// 获取符合过滤条件的符号表记录名字集合 + /// + /// 过滤器委托 + /// 记录的名字集合 + public IEnumerable GetRecordNames(Func filter) + { + foreach (var item in this) { - if (table is null) - { - throw new ArgumentNullException(nameof(table)); - } - - ObjectId rid = this[name]; - bool has = rid != ObjectId.Null; - if ((has && over) || !has) + var record = GetRecord(item); + if (record is not null && filter.Invoke(record)) { - ObjectId id = table[name]; - using IdMapping idm = new(); - using (ObjectIdCollection ids = new() { id }) - { - table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); - } - rid = idm[id].Value; + yield return record.Name; } - return rid; } + } - /// - /// 从文件拷贝符号表记录 - /// - /// 符号表过滤器 - /// 文件名 - /// 符号表记录名 - /// 是否覆盖, 为覆盖, 为不覆盖 - /// 对象id - internal ObjectId GetRecordFrom(Func> tableSelector, string fileName, string name, bool over) + /// + /// 从源数据库拷贝符号表记录 + /// + /// 符号表 + /// 符号表记录名 + /// 是否覆盖, 为覆盖, 为不覆盖 + /// 对象id + public ObjectId GetRecordFrom(SymbolTable table, string name, bool over) + { + if (table is null) { - using var tr = new DBTrans(fileName); - return GetRecordFrom(tableSelector(tr), name, over); + throw new ArgumentNullException(nameof(table), "对象为null"); } - #endregion - #region IEnumerable 成员 - - public IEnumerator GetEnumerator() + ObjectId rid = this[name]; + bool has = rid != ObjectId.Null; + if ((has && over) || !has) { - - foreach (var id in CurrentSymbolTable) + ObjectId id = table[name]; + using IdMapping idm = new(); + using (ObjectIdCollection ids = new() { id }) { - yield return id; + table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); } + rid = idm[id].Value; } + return rid; + } + + /// + /// 从文件拷贝符号表记录 + /// + /// 符号表过滤器 + /// 文件名 + /// 符号表记录名 + /// 是否覆盖, 为覆盖, 为不覆盖 + /// 对象id + internal ObjectId GetRecordFrom(Func> tableSelector, string fileName, string name, bool over) + { + using var tr = new DBTrans(fileName); + return GetRecordFrom(tableSelector(tr), name, over); + } + #endregion + + #region 遍历 + /// + /// 遍历集合的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public void ForEach(Action action, OpenMode openMode = OpenMode.ForRead) + { + //GetRecords().ForEach(re => action(re)); - IEnumerator IEnumerable.GetEnumerator() + foreach (var item in this) { - return GetEnumerator(); + var record = GetRecord(item, openMode); + if (record is not null) + action(record); } - #endregion } + #endregion + + #region IEnumerable 成员 + + public IEnumerator GetEnumerator() + { + foreach (var id in CurrentSymbolTable) + yield return id; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/Utils.cs b/src/IFoxCAD.Cad/Runtime/Utils.cs new file mode 100644 index 0000000..eb372ab --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/Utils.cs @@ -0,0 +1,98 @@ +using System; + +namespace IFoxCAD.Cad; + +public class Helper +{ + /* + * id = db.GetObjectId(false, handle, 0); + * 参数意义: db.GetObjectId(如果没有找到就创建,句柄号,标记..将来备用) + * 在vs的输出会一直抛出: + * 引发的异常:“Autodesk.AutoCAD.Runtime.Exception”(位于 AcdbMgd.dll 中) + * "eUnknownHandle" + * 这就是为什么慢的原因,所以直接运行就好了!而Debug还是需要用arx的API替代. + */ + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + /// + /// 句柄转id,NET35(08~12)专用的 + /// + /// 数据库 + /// 句柄 + /// 返回的id + /// 不存在则创建 + /// 保留,用于未来 + /// 成功0,其他值都是错误.可以强转ErrorStatus + static int GetAcDbObjectId(IntPtr db, Handle handle, out ObjectId id, bool createIfNotFound = false, uint reserved = 0) + { + id = ObjectId.Null; + switch (Application.Version.Major) + { + case 17: + { + if (IntPtr.Size == 4) + return getAcDbObjectId17x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId17x64(db, out id, createIfNotFound, ref handle, reserved); + } + case 18: + { + if (IntPtr.Size == 4) + return getAcDbObjectId18x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId18x64(db, out id, createIfNotFound, ref handle, reserved); + } + } + return -1; + } + + /// + /// 句柄转id + /// + /// 数据库 + /// 句柄 + /// id + public static ObjectId TryGetObjectId(Database db, Handle handle) + { +#if !NET35 + //高版本直接利用 + var es = db.TryGetObjectId(handle, out ObjectId id); + //if (!es) +#else + var es = GetAcDbObjectId(db.UnmanagedObject, handle, out ObjectId id); + //if (ErrorStatus.OK != (ErrorStatus)es) +#endif + return id; + } + + //public static int GetCadFileVersion(string filename) + //{ + // var bytes = File.ReadAllBytes(filename); + // var headstr = Encoding.Default.GetString(bytes)[0..6]; + // if (!headstr.StartsWith("AC")) return 0; + // var vernum = int.Parse(headstr.Replace("AC", "")); + // var a = Enum.Parse(typeof(DwgVersion), "AC1800"); + // Enum.TryParse() + // return vernum + 986; + + //} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs index 567be46..9727b72 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs @@ -1,83 +1,79 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 比较运算符类 +/// +public class OpComp : OpEqual { /// - /// 比较运算符类 + /// 比较运算符,如: + /// "<=" + /// 以及合并比较运算符: + /// "<=,<=,=" /// - public class OpComp : OpEqual - { - /// - /// 比较运算符,如: - /// "<=" - /// 以及合并比较运算符: - /// "<=,<=,=" - /// - public string Content { get; } + public string Content { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Comp"; } - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Comp"; } + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 数据 - public OpComp(string content, TypedValue value) - : base(value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 数据 + public OpComp(string content, TypedValue value) + : base(value) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - public OpComp(string content, int code) - : base(code) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + public OpComp(string content, int code) + : base(code) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - /// 组码值 - public OpComp(string content, int code, object value) - : base(code, value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + /// 组码值 + public OpComp(string content, int code, object value) + : base(code, value) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - /// 组码值 - public OpComp(string content, DxfCode code, object value) - : base(code, value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + /// 组码值 + public OpComp(string content, DxfCode code, object value) + : base(code, value) + { + Content = content; + } - /// - /// 获取过滤器数据迭代器 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return new TypedValue(-4, Content); - yield return Value; - } + /// + /// 获取过滤器数据迭代器 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return new TypedValue(-4, Content); + yield return Value; } } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs index 76dc738..370a0c1 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs @@ -1,90 +1,86 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 相等运算符类 +/// +public class OpEqual : OpFilter { /// - /// 相等运算符类 + /// 组码与匹配值的TypedValue类型值 /// - public class OpEqual : OpFilter - { - /// - /// 组码与匹配值的TypedValue类型值 - /// - public TypedValue Value { get; private set; } + public TypedValue Value { get; private set; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Equal"; } - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Equal"; } + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - public OpEqual(int code) - { - Value = new TypedValue(code); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + public OpEqual(int code) + { + Value = new TypedValue(code); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - /// 组码值 - public OpEqual(int code, object value) - { - Value = new TypedValue(code, value); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + /// 组码值 + public OpEqual(int code, object value) + { + Value = new TypedValue(code, value); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - /// 组码值 - public OpEqual(DxfCode code, object value) - { - Value = new TypedValue((int)code, value); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + /// 组码值 + public OpEqual(DxfCode code, object value) + { + Value = new TypedValue((int)code, value); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码与组码值的TypedValue类型值 - internal OpEqual(TypedValue value) - { - Value = value; - } + /// + /// 相等运算符类构造函数 + /// + /// 组码与组码值的TypedValue类型值 + internal OpEqual(TypedValue value) + { + Value = value; + } - /// - /// 过滤器数据迭代器 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return Value; - } + /// + /// 过滤器数据迭代器 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return Value; + } - /// - /// 设置数据 - /// - /// 组码值 - public void SetValue(object value) - { - Value = new TypedValue(Value.TypeCode, value); - } + /// + /// 设置数据 + /// + /// 组码值 + public void SetValue(object value) + { + Value = new TypedValue(Value.TypeCode, value); + } - /// - /// 设置数据 - /// - /// 组码 - /// 组码值 - public void SetValue(int code, object value) - { - Value = new TypedValue(code, value); - } + /// + /// 设置数据 + /// + /// 组码 + /// 组码值 + public void SetValue(int code, object value) + { + Value = new TypedValue(code, value); } } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs index cd73129..a698aeb 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs @@ -1,342 +1,346 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 选择集过滤器抽象类 +/// +public abstract class OpFilter { /// - /// 选择集过滤器抽象类 + /// 过滤器的名字 + /// + public abstract string Name { get; } + + /// + /// 获取TypedValue类型的值的迭代器的抽象方法,子类必须重写 + /// + /// TypedValue迭代器 + public abstract IEnumerable GetValues(); + + /// + /// 非操作符,返回的是OpFilter类型变量的 属性 + /// + /// OpFilter类型变量 + /// OpFilter对象 + public static OpFilter operator !(OpFilter item) + { + return item.Not; + } + + /// + /// 只读属性,表示这个过滤器取反 + /// + public OpFilter Not + { + get { return new OpNot(this); } + } + + /// + /// 过滤器值转换为 TypedValue 类型数组 + /// + /// TypedValue数组 + public TypedValue[] ToArray() + { + return GetValues().ToArray(); + } + + /// + /// 隐式类型转换,将自定义的过滤器转换为 Autocad 认识的选择集过滤器 + /// + /// 过滤器对象 + /// + /// 选择集过滤器. + /// + public static implicit operator SelectionFilter(OpFilter item) + { + return new SelectionFilter(item.ToArray()); + } + + /// + /// 转换为字符串 + /// + /// 字符串 + public override string ToString() + { + string s = ""; + foreach (var value in GetValues()) + s += value.ToString(); + return s; + } + + /// + /// 构建过滤器 + /// + /// + /// 举两个利用构建函数创建选择集过滤的例子 + /// + /// !(e.Dxf(0) == "line" & e.Dxf(8) == "0") + /// | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); + /// + /// 例子2: + /// var f2 = OpFilter.Bulid( + /// e => e.Or( + /// !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), + /// e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", + /// e.Dxf(10) <= new Point3d(10, 10, 0)))); + /// ]]> + /// + /// 构建过滤器的函数委托 + /// 过滤器 + public static OpFilter Bulid(Func func) + { + return func(new Op()).Filter!; + } + + #region Operator + + /// + /// 过滤器操作符类 /// - public abstract class OpFilter + public class Op { /// - /// 过滤器的名字 + /// 过滤器属性 /// - public abstract string Name { get; } + internal OpFilter? Filter { get; private set; } + + internal Op() + { + } + + private Op(OpFilter filter) + { + Filter = filter; + } /// - /// 获取TypedValue类型的值的迭代器的抽象方法,子类必须重写 + /// AND 操作符 /// - /// TypedValue迭代器 - public abstract IEnumerable GetValues(); + /// 操作符类型的可变参数 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op And(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static + { + var filter = new OpAnd(); + foreach (var op in args) + filter.Add(op.Filter!); + return new Op(filter); + } /// - /// 非操作符,返回的是OpFilter类型变量的 属性 + /// or 操作符 /// - /// OpFilter类型变量 - /// OpFilter对象 - public static OpFilter operator !(OpFilter item) + /// 操作符类型的可变参数 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Or(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static { - return item.Not; + var filter = new OpOr(); + foreach (var op in args) + filter.Add(op.Filter!); + return new Op(filter); } /// - /// 只读属性,表示这个过滤器取反 + /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 /// - public OpFilter Not + /// 组码 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code) +#pragma warning restore CA1822 // 将成员标记为 static { - get { return new OpNot(this); } + return new Op(new OpEqual(code)); } /// - /// 过滤器值转换为 TypedValue 类型数组 + /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 /// - /// TypedValue数组 - public TypedValue[] ToArray() + /// 组码 + /// 关系运算符的值,比如">,>,=" + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code, string content) +#pragma warning restore CA1822 // 将成员标记为 static { - return GetValues().ToArray(); + return new Op(new OpComp(content, code)); } /// - /// 隐式类型转换,将自定义的过滤器转换为 Autocad 认识的选择集过滤器 + /// 非操作符 /// - /// 过滤器对象 - /// - /// 选择集过滤器. - /// - public static implicit operator SelectionFilter(OpFilter item) + /// 过滤器操作符对象 + /// Op对象 + public static Op operator !(Op right) { - return new SelectionFilter(item.ToArray()); + right.Filter = !right.Filter!; + return right; } /// - /// 转换为字符串 + /// 相等操作符 /// - /// 字符串 - public override string ToString() + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator ==(Op left, object right) { - string s = ""; - foreach (var value in GetValues()) - s += value.ToString(); - return s; + var eq = (OpEqual)left.Filter!; + eq.SetValue(right); + return left; } /// - /// 构建过滤器 + /// 不等操作符 /// - /// - /// 举两个利用构建函数创建选择集过滤的例子 - /// - /// !(e.Dxf(0) == "line" & e.Dxf(8) == "0") - /// | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); - /// - /// 例子2: - /// var f2 = OpFilter.Bulid( - /// e => e.Or( - /// !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), - /// e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", - /// e.Dxf(10) <= new Point3d(10, 10, 0)))); - /// ]]> - /// - /// 构建过滤器的函数委托 - /// 过滤器 - public static OpFilter Bulid(Func func) + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator !=(Op left, object right) { - return func(new OpFilter.Op()).Filter; + var eq = (OpEqual)left.Filter!; + eq.SetValue(right); + left.Filter = eq.Not; + return left; } - #region Operator + private static Op GetCompOp(string content, Op left, object right) + { + var eq = (OpEqual)left.Filter!; + var comp = new OpComp(content, eq.Value.TypeCode, right); + return new Op(comp); + } /// - /// 过滤器操作符类 + /// 大于操作符 /// - public class Op + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator >(Op left, object right) { - /// - /// 过滤器属性 - /// - internal OpFilter Filter { get; private set; } + return GetCompOp(">", left, right); + } - internal Op() - { - } + /// + /// 小于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator <(Op left, object right) + { + return GetCompOp("<", left, right); + } - private Op(OpFilter filter) - { - Filter = filter; - } - - /// - /// AND 操作符 - /// - /// 操作符类型的可变参数 - /// Op对象 - public Op And(params Op[] args) - { - var filter = new OpAnd(); - foreach (var op in args) - filter.Add(op.Filter); - return new Op(filter); - } - - /// - /// or 操作符 - /// - /// 操作符类型的可变参数 - /// Op对象 - public Op Or(params Op[] args) - { - var filter = new OpOr(); - foreach (var op in args) - filter.Add(op.Filter); - return new Op(filter); - } - - /// - /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 - /// - /// 组码 - /// Op对象 - public Op Dxf(int code) - { - return new Op(new OpEqual(code)); - } - - /// - /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 - /// - /// 组码 - /// 关系运算符的值,比如">,>,=" - /// Op对象 - public Op Dxf(int code, string content) - { - return new Op(new OpComp(content, code)); - } - - /// - /// 非操作符 - /// - /// 过滤器操作符对象 - /// Op对象 - public static Op operator !(Op right) - { - right.Filter = !right.Filter; - return right; - } - - /// - /// 相等操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator ==(Op left, object right) - { - var eq = (OpEqual)left.Filter; - eq.SetValue(right); - return left; - } - - /// - /// 不等操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator !=(Op left, object right) - { - var eq = (OpEqual)left.Filter; - eq.SetValue(right); - left.Filter = eq.Not; - return left; - } + /// + /// 大于等于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator >=(Op left, object right) + { + return GetCompOp(">=", left, right); + } - private static Op GetCompOp(string content, Op left, object right) - { - var eq = (OpEqual)left.Filter; - var comp = new OpComp(content, eq.Value.TypeCode, right); - return new Op(comp); - } - - /// - /// 大于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator >(Op left, object right) - { - return GetCompOp(">", left, right); - } - - /// - /// 小于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator <(Op left, object right) - { - return GetCompOp("<", left, right); - } - - /// - /// 大于等于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator >=(Op left, object right) - { - return GetCompOp(">=", left, right); - } - - /// - /// 小于等于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator <=(Op left, object right) - { - return GetCompOp("<=", left, right); - } - - /// - /// 大于等于操作符 - /// - /// 过滤器操作符对象 - /// 点 - /// Op对象 - public static Op operator >=(Op left, Point3d right) - { - return GetCompOp(">,>,*", left, right); - } - - /// - /// 小于等于操作符 - /// - /// 过滤器操作符对象 - /// 点 - /// Op对象 - public static Op operator <=(Op left, Point3d right) - { - return GetCompOp("<,<,*", left, right); - } - - /// - /// 并操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator &(Op left, Op right) - { - var filter = new OpAnd(); - filter.Add(left.Filter); - filter.Add(right.Filter); - return new Op(filter); - } - - /// - /// 或操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator |(Op left, Op right) + /// + /// 小于等于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator <=(Op left, object right) + { + return GetCompOp("<=", left, right); + } + + /// + /// 大于等于操作符 + /// + /// 过滤器操作符对象 + /// 点 + /// Op对象 + public static Op operator >=(Op left, Point3d right) + { + return GetCompOp(">,>,*", left, right); + } + + /// + /// 小于等于操作符 + /// + /// 过滤器操作符对象 + /// 点 + /// Op对象 + public static Op operator <=(Op left, Point3d right) + { + return GetCompOp("<,<,*", left, right); + } + + /// + /// 并操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator &(Op left, Op right) + { + var filter = new OpAnd { - var filter = new OpOr(); - filter.Add(left.Filter); - filter.Add(right.Filter); - return new Op(filter); - } - - /// - /// 异或操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator ^(Op left, Op right) + left.Filter!, + right.Filter! + }; + return new Op(filter); + } + + /// + /// 或操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator |(Op left, Op right) + { + var filter = new OpOr { - var filter = new OpXor(left.Filter, right.Filter); - return new Op(filter); - } - - /// - /// 比较函数 - /// - /// 对象 - /// - /// 是否相等 - /// - public override bool Equals(object obj) => base.Equals(obj); - - /// - /// 获取HashCode - /// - /// HashCode - public override int GetHashCode() => base.GetHashCode(); + left.Filter!, + right.Filter! + }; + return new Op(filter); } - #endregion Operator + /// + /// 异或操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator ^(Op left, Op right) + { + var filter = new OpXor(left.Filter!, right.Filter!); + return new Op(filter); + } + + /// + /// 比较函数 + /// + /// 对象 + /// + /// 是否相等 + /// + public override bool Equals(object obj) => base.Equals(obj); + + /// + /// 获取HashCode + /// + /// HashCode + public override int GetHashCode() => base.GetHashCode(); } + + #endregion Operator } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs index 06a5408..59d1d1f 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs @@ -1,170 +1,166 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 逻辑操作符的列表抽象类 +/// +public abstract class OpList : OpLogi { /// - /// 逻辑操作符的列表抽象类 + /// 过滤器列表 /// - public abstract class OpList : OpLogi + protected List _lst = new(); + + /// + /// 添加过滤器条件的虚函数,子类可以重写 + /// + /// 举个利用这个类及其子类创建选择集过滤的例子 + /// + /// ,>,*" } + /// }, + /// }; + /// ]]> + /// + /// 过滤器对象 + public virtual void Add(OpFilter value) { - /// - /// 过滤器列表 - /// - protected List _lst = new(); + _lst.Add(value); + } - /// - /// 添加过滤器条件的虚函数,子类可以重写 - /// - /// 举个利用这个类及其子类创建选择集过滤的例子 - /// - /// ,>,*" } - /// }, - /// }; - /// ]]> - /// - /// 过滤器对象 - public virtual void Add(OpFilter value) - { - _lst.Add(value); - } + /// + /// 添加过滤条件 + /// + /// 逻辑非~ + /// 组码 + /// 组码值 + public void Add(string speccode, int code, object value) + { + if (speccode == "~") + _lst.Add(new OpEqual(code, value).Not); + } - /// - /// 添加过滤条件 - /// - /// 逻辑非~ - /// 组码 - /// 组码值 - public void Add(string speccode, int code, object value) - { - if (speccode == "~") - _lst.Add(new OpEqual(code, value).Not); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + public void Add(int code, object value) + { + _lst.Add(new OpEqual(code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - public void Add(int code, object value) - { - _lst.Add(new OpEqual(code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + public void Add(DxfCode code, object value) + { + _lst.Add(new OpEqual(code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - public void Add(DxfCode code, object value) - { - _lst.Add(new OpEqual(code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + /// 比较运算符 + public void Add(int code, object value, string comp) + { + _lst.Add(new OpComp(comp, code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - /// 比较运算符 - public void Add(int code, object value, string comp) - { - _lst.Add(new OpComp(comp, code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + /// 比较运算符 + public void Add(DxfCode code, object value, string comp) + { + _lst.Add(new OpComp(comp, code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - /// 比较运算符 - public void Add(DxfCode code, object value, string comp) - { - _lst.Add(new OpComp(comp, code, value)); - } + /// + /// 过滤器迭代器 + /// + /// OpFilter迭代器 + public override IEnumerator GetEnumerator() + { + foreach (var value in _lst) + yield return value; + } +} - /// - /// 过滤器迭代器 - /// - /// OpFilter迭代器 - public override IEnumerator GetEnumerator() - { - foreach (var value in _lst) - yield return value; - } +/// +/// 逻辑与类 +/// +public class OpAnd : OpList +{ + /// + /// 符号名 + /// + public override string Name + { + get { return "And"; } } /// - /// 逻辑与类 + /// 添加过滤条件 /// - public class OpAnd : OpList + /// 过滤器对象 + public override void Add(OpFilter value) { - /// - /// 符号名 - /// - public override string Name + if (value is OpAnd opand) { - get { return "And"; } + foreach (var item in opand) + _lst.Add(item); } - - /// - /// 添加过滤条件 - /// - /// 过滤器对象 - public override void Add(OpFilter value) + else { - if (value is OpAnd opand) - { - foreach (var item in opand) - _lst.Add(item); - } - else - { - _lst.Add(value); - } + _lst.Add(value); } } +} + +/// +/// 逻辑或类 +/// +public class OpOr : OpList +{ + /// + /// 符号名 + /// + public override string Name + { + get { return "Or"; } + } /// - /// 逻辑或类 + /// 添加过滤条件 /// - public class OpOr : OpList + /// 过滤器对象 + public override void Add(OpFilter value) { - /// - /// 符号名 - /// - public override string Name + if (value is OpOr opor) { - get { return "Or"; } + foreach (var item in opor) + _lst.Add(item); } - - /// - /// 添加过滤条件 - /// - /// 过滤器对象 - public override void Add(OpFilter value) + else { - if (value is OpOr opor) - { - foreach (var item in opor) - _lst.Add(item); - } - else - { - _lst.Add(value); - } + _lst.Add(value); } } } \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs index 3fa0785..b61583d 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs @@ -1,133 +1,128 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 过滤器逻辑运算符抽象类 +/// +public abstract class OpLogi : OpFilter, IEnumerable { /// - /// 过滤器逻辑运算符抽象类 + /// 返回-4组码的开始内容 /// - public abstract class OpLogi : OpFilter, IEnumerable + public TypedValue First { - /// - /// 返回-4组码的开始内容 - /// - public TypedValue First - { - get { return new TypedValue(-4, $"<{Name}"); } - } - - /// - /// 返回-4组码的结束内容 - /// - public TypedValue Last - { - get { return new TypedValue(-4, $"{Name}>"); } - } - - /// - /// 获取过滤条件 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return First; - foreach (var item in this) - { - foreach (var value in item.GetValues()) - yield return value; - } - yield return Last; - } + get { return new TypedValue(-4, $"<{Name}"); } + } - /// - /// 获取迭代器 - /// - /// OpFilter迭代器 - public abstract IEnumerator GetEnumerator(); + /// + /// 返回-4组码的结束内容 + /// + public TypedValue Last + { + get { return new TypedValue(-4, $"{Name}>"); } + } - IEnumerator IEnumerable.GetEnumerator() + /// + /// 获取过滤条件 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return First; + foreach (var item in this) { - return GetEnumerator(); + foreach (var value in item.GetValues()) + yield return value; } + yield return Last; } /// - /// 逻辑非类 + /// 获取迭代器 /// - public class OpNot : OpLogi + /// OpFilter迭代器 + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() { - private OpFilter Value { get; } + return GetEnumerator(); + } +} - /// - /// 逻辑非类构造函数 - /// - /// OpFilter数据 - public OpNot(OpFilter value) - { - Value = value; - } +/// +/// 逻辑非类 +/// +public class OpNot : OpLogi +{ + private OpFilter Value { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Not"; } - } + /// + /// 逻辑非类构造函数 + /// + /// OpFilter数据 + public OpNot(OpFilter value) + { + Value = value; + } - /// - /// 获取迭代器 - /// - /// OpFilter迭代器 - public override IEnumerator GetEnumerator() - { - yield return Value; - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Not"; } } /// - /// 逻辑异或类 + /// 获取迭代器 /// - public class OpXor : OpLogi + /// OpFilter迭代器 + public override IEnumerator GetEnumerator() { - /// - /// 左操作数 - /// - public OpFilter Left { get; } + yield return Value; + } +} - /// - /// 右操作数 - /// - public OpFilter Right { get; } +/// +/// 逻辑异或类 +/// +public class OpXor : OpLogi +{ + /// + /// 左操作数 + /// + public OpFilter Left { get; } - /// - /// 逻辑异或类构造函数 - /// - /// 左操作数 - /// 右操作数 - public OpXor(OpFilter left, OpFilter right) - { - Left = left; - Right = right; - } + /// + /// 右操作数 + /// + public OpFilter Right { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Xor"; } - } + /// + /// 逻辑异或类构造函数 + /// + /// 左操作数 + /// 右操作数 + public OpXor(OpFilter left, OpFilter right) + { + Left = left; + Right = right; + } - /// - /// 获取迭代器 - /// - /// 选择集过滤器类型迭代器 - public override IEnumerator GetEnumerator() - { - yield return Left; - yield return Right; - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Xor"; } + } + + /// + /// 获取迭代器 + /// + /// 选择集过滤器类型迭代器 + public override IEnumerator GetEnumerator() + { + yield return Left; + yield return Right; } } \ No newline at end of file diff --git a/src/IFoxCAD.WPF/Converter.cs b/src/IFoxCAD.WPF/Converter.cs index 7682212..828ece9 100644 --- a/src/IFoxCAD.WPF/Converter.cs +++ b/src/IFoxCAD.WPF/Converter.cs @@ -1,108 +1,98 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; +namespace IFoxCAD.WPF; -using System.Windows.Data; - - -namespace IFoxCAD.WPF +/// +/// 字符串到整数的转换器 +/// +public class StringToIntConverter : IValueConverter +{ + /// + /// 字符串转换到整数 + /// + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + string? a = value as string; + _ = int.TryParse(a, out int b); + return b; + } + /// + /// 整数转换到字符串 + /// + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } +} +/// +/// 字符串到实数的转换器 +/// +public class StringToDoubleConverter : IValueConverter { /// - /// 字符串到整数的转换器 + /// 字符串转换到实数 /// - public class StringToIntConverter : IValueConverter + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 字符串转换到整数 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = int.TryParse(a, out int b); - return b; - } - /// - /// 整数转换到字符串 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } + string? a = value as string; + _ = double.TryParse(a, out double b); + return b; } /// - /// 字符串到实数的转换器 + /// 实数转换到字符串 + /// + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } +} +/// +/// 整数到字符串的转换器 +/// +public class IntToStringConverter : IValueConverter +{ + /// + /// 整数转换到字符串 /// - public class StringToDoubleConverter : IValueConverter + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 字符串转换到实数 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = double.TryParse(a, out double b); - return b; - } - /// - /// 实数转换到字符串 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } + return value.ToString(); } /// - /// 整数到字符串的转换器 + /// 字符串转换到整数 /// - public class IntToStringConverter : IValueConverter + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 整数转换到字符串 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } - /// - /// 字符串转换到整数 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = int.TryParse(a, out int b); - return b; - } + string? a = value as string; + _ = int.TryParse(a, out int b); + return b; } } diff --git a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs index 39cd184..596961d 100644 --- a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs +++ b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs @@ -1,40 +1,34 @@ -using System.Windows; -using System.Windows.Media; +namespace IFoxCAD.WPF; -namespace IFoxCAD.WPF +/// +/// 依赖属性扩展类 +/// +public static class DependencyObjectExtensions { /// - /// 依赖属性扩展类 + /// 获取父对象依赖属性 /// - public static class DependencyObjectExtensions + /// 子对象 + /// 依赖属性 + public static DependencyObject? GetParentObject(this DependencyObject child) { - /// - /// 获取父对象依赖属性 - /// - /// 子对象 - /// 依赖属性 - public static DependencyObject? GetParentObject(this DependencyObject child) - { - if (child is null) - return null; - - if (child is ContentElement contentElement) - { - var parent = ContentOperations.GetParent(contentElement); - if (parent is not null) return parent; + if (child is null) return null; - var fce = contentElement as FrameworkContentElement; - return fce?.Parent; - } + if (child is ContentElement contentElement) + { + DependencyObject parent = ContentOperations.GetParent(contentElement); + if (parent is not null) return parent; - if (child is FrameworkElement frameworkElement) - { - var parent = frameworkElement.Parent; - if (parent is not null) - return parent; - } + FrameworkContentElement? fce = contentElement as FrameworkContentElement; + return fce?.Parent; + } - return VisualTreeHelper.GetParent(child); + if (child is FrameworkElement frameworkElement) + { + DependencyObject parent = frameworkElement.Parent; + if (parent is not null) return parent; } + + return VisualTreeHelper.GetParent(child); } } diff --git a/src/IFoxCAD.WPF/EnumSelection.cs b/src/IFoxCAD.WPF/EnumSelection.cs new file mode 100644 index 0000000..d964351 --- /dev/null +++ b/src/IFoxCAD.WPF/EnumSelection.cs @@ -0,0 +1,72 @@ +namespace IFoxCAD.WPF; +public class EnumSelection : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible +{ + private T value; // stored value of the Enum + private readonly bool isFlagged; // Enum uses flags? + private readonly bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can) + private readonly T blankValue; // what is considered the "blank" value if it can be deselected? + + public EnumSelection(T value) : this(value, false, default) { } + public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default) { } + public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { } + public EnumSelection(T value, bool canDeselect, T blankValue) + { + if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums... + isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false); + + this.value = value; + this.canDeselect = canDeselect; + this.blankValue = blankValue; + } + + public T Value + { + get { return value; } + set + { + if (this.value.Equals(value)) return; + this.value = value; + OnPropertyChanged(); + OnPropertyChanged("Item[]"); // Notify that the indexer property has changed + } + } + + [IndexerName("Item")] + public bool this[T key] + { + get + { + int iKey = (int)(object)key; + return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key); + } + set + { + if (isFlagged) + { + int iValue = (int)(object)this.value; + int iKey = (int)(object)key; + + if (((iValue & iKey) == iKey) == value) return; + + if (value) + Value = (T)(object)(iValue | iKey); + else + Value = (T)(object)(iValue & ~iKey); + } + else + { + if (this.value.Equals(key) == value) return; + if (!value && !canDeselect) return; + + Value = value ? key : blankValue; + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/EventBindingExtension.cs b/src/IFoxCAD.WPF/EventBindingExtension.cs index 9298c07..eb58c75 100644 --- a/src/IFoxCAD.WPF/EventBindingExtension.cs +++ b/src/IFoxCAD.WPF/EventBindingExtension.cs @@ -1,221 +1,210 @@ -using System; -using System.ComponentModel; -using System.Reflection.Emit; -using System.Windows.Input; -using System.Windows.Markup; -using System.Windows; -using System.Reflection; +namespace IFoxCAD.WPF; - -namespace IFoxCAD.WPF +/// +/// 事件绑定标签类 +/// +/// +public class EventBindingExtension : MarkupExtension { /// - /// 事件绑定标签类 + /// 命令属性 + /// + public string? Command { get; set; } + /// + /// 命令参数属性 + /// + public string? CommandParameter { get; set; } + /// + /// 当在派生类中实现时,返回用作此标记扩展的目标属性值的对象。 /// - /// - public class EventBindingExtension : MarkupExtension + /// 可为标记扩展提供服务的服务提供程序帮助程序。 + /// + /// 要在应用了扩展的属性上设置的对象值。 + /// + /// + /// + public override object? ProvideValue(IServiceProvider serviceProvider) { - /// - /// 命令属性 - /// - public string Command { get; set; } - /// - /// 命令参数属性 - /// - public string CommandParameter { get; set; } - /// - /// 当在派生类中实现时,返回用作此标记扩展的目标属性值的对象。 - /// - /// 可为标记扩展提供服务的服务提供程序帮助程序。 - /// - /// 要在应用了扩展的属性上设置的对象值。 - /// - /// - /// - public override object ProvideValue(IServiceProvider serviceProvider) + if (serviceProvider is null) { - if (serviceProvider is null) - { - throw new ArgumentNullException(nameof(serviceProvider)); - } - if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider)) - { - throw new InvalidOperationException(); - } - - if (!(targetProvider.TargetObject is FrameworkElement targetObject)) - { - throw new InvalidOperationException(); - } - - var memberInfo = targetProvider.TargetProperty as MemberInfo; - if (memberInfo is null) - { - throw new InvalidOperationException(); - } + throw new ArgumentNullException(nameof(serviceProvider)); + } + if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideValueTarget targetProvider) + { + throw new InvalidOperationException(); + } - if (string.IsNullOrWhiteSpace(Command)) - { - Command = memberInfo.Name.Replace("Add", ""); - if (Command.Contains("Handler")) - { - Command = Command.Replace("Handler", "Command"); - } - else - { - Command += "Command"; - } - } + if (targetProvider.TargetObject is not FrameworkElement targetObject) + { + throw new InvalidOperationException(); + } - return CreateHandler(memberInfo, Command, targetObject.GetType()); + if (targetProvider.TargetProperty is not MemberInfo memberInfo) + { + throw new InvalidOperationException(); } - private Type GetEventHandlerType(MemberInfo memberInfo) + if (string.IsNullOrWhiteSpace(Command)) { - Type eventHandlerType = null; - if (memberInfo is EventInfo) + Command = memberInfo.Name.Replace("Add", ""); + if (Command.Contains("Handler")) { - var info = memberInfo as EventInfo; - var eventInfo = info; - eventHandlerType = eventInfo.EventHandlerType; + Command = Command.Replace("Handler", "Command"); } - else if (memberInfo is MethodInfo) + else { - var info = memberInfo as MethodInfo; - var methodInfo = info; - ParameterInfo[] pars = methodInfo.GetParameters(); - eventHandlerType = pars[1].ParameterType; + Command += "Command"; } - - return eventHandlerType; } - private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) + return CreateHandler(memberInfo, Command!, targetObject.GetType()); + } + + private Type? GetEventHandlerType(MemberInfo memberInfo) + { + Type? eventHandlerType = null; + if (memberInfo is EventInfo eventInfo) + { + //var info = memberInfo as EventInfo; + //var eventInfo = info; + eventHandlerType = eventInfo.EventHandlerType; + } + else if (memberInfo is MethodInfo methodInfo) { - Type eventHandlerType = GetEventHandlerType(memberInfo); + //var info = memberInfo as MethodInfo; + //var methodInfo = info; + ParameterInfo[] pars = methodInfo.GetParameters(); + eventHandlerType = pars[1].ParameterType; + } - if (eventHandlerType is null) return null; + return eventHandlerType; + } - var handlerInfo = eventHandlerType.GetMethod("Invoke"); - var method = new DynamicMethod("", handlerInfo.ReturnType, - new Type[] - { - handlerInfo.GetParameters()[0].ParameterType, - handlerInfo.GetParameters()[1].ParameterType, - }); +#pragma warning disable IDE0060 // 删除未使用的参数 + private object? CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) +#pragma warning restore IDE0060 // 删除未使用的参数 + { + Type? eventHandlerType = GetEventHandlerType(memberInfo); - var gen = method.GetILGenerator(); - gen.Emit(OpCodes.Ldarg, 0); - gen.Emit(OpCodes.Ldarg, 1); - gen.Emit(OpCodes.Ldstr, cmdName); - if (CommandParameter is null) - { - gen.Emit(OpCodes.Ldnull); - } - else + if (eventHandlerType is null) return null; + + var handlerInfo = eventHandlerType.GetMethod("Invoke"); + var method = new DynamicMethod("", handlerInfo.ReturnType, + new Type[] { - gen.Emit(OpCodes.Ldstr, CommandParameter); - } - gen.Emit(OpCodes.Call, getMethod); - gen.Emit(OpCodes.Ret); + handlerInfo.GetParameters()[0].ParameterType, + handlerInfo.GetParameters()[1].ParameterType, + }); - return method.CreateDelegate(eventHandlerType); + var gen = method.GetILGenerator(); + gen.Emit(OpCodes.Ldarg, 0); + gen.Emit(OpCodes.Ldarg, 1); + gen.Emit(OpCodes.Ldstr, cmdName); + if (CommandParameter is null) + { + gen.Emit(OpCodes.Ldnull); } - - static readonly MethodInfo getMethod = typeof(EventBindingExtension).GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); - - static void Handler(object sender, object args) + else { - HandlerIntern(sender, args, "cmd", null); + gen.Emit(OpCodes.Ldstr, CommandParameter); } - /// - /// Handlers the intern. - /// - /// The sender. - /// The arguments. - /// Name of the command. - /// The command parameter. - public static void HandlerIntern(object sender, object args, string cmdName, string commandParameter) + gen.Emit(OpCodes.Call, getMethod); + gen.Emit(OpCodes.Ret); + + return method.CreateDelegate(eventHandlerType); + } + + static readonly MethodInfo getMethod = typeof(EventBindingExtension).GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); + +#pragma warning disable IDE0051 // 删除未使用的私有成员 + static void Handler(object sender, object args) +#pragma warning restore IDE0051 // 删除未使用的私有成员 + { + HandlerIntern(sender, args, "cmd", null); + } + /// + /// Handlers the intern. + /// + /// The sender. + /// The arguments. + /// Name of the command. + /// The command parameter. + public static void HandlerIntern(object sender, object args, string cmdName, string? commandParameter) + { + if (sender is FrameworkElement fe) { - var fe = sender as FrameworkElement; - if (fe is not null) + var cmd = GetCommand(fe, cmdName); + object? commandParam = null; + if (!string.IsNullOrWhiteSpace(commandParameter)) + { + commandParam = GetCommandParameter(fe, args, commandParameter!); + } + if ((cmd is not null) && cmd.CanExecute(commandParam)) { - ICommand cmd = GetCommand(fe, cmdName); - object commandParam = null; - if (!string.IsNullOrWhiteSpace(commandParameter)) - { - commandParam = GetCommandParameter(fe, args, commandParameter); - } - if ((cmd is not null) && cmd.CanExecute(commandParam)) - { - cmd.Execute(commandParam); - } + cmd.Execute(commandParam); } } + } - internal static ICommand GetCommand(FrameworkElement target, string cmdName) - { - var vm = FindViewModel(target); - if (vm is null) return null; + internal static ICommand? GetCommand(FrameworkElement target, string cmdName) + { + var vm = FindViewModel(target); + if (vm is null) return null; - var vmType = vm.GetType(); - var cmdProp = vmType.GetProperty(cmdName); - if (cmdProp is not null) - return cmdProp.GetValue(vm) as ICommand; + var vmType = vm.GetType(); + var cmdProp = vmType.GetProperty(cmdName); + if (cmdProp is not null) + { + return cmdProp.GetValue(vm) as ICommand; + } #if DEBUG - throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); + throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); #endif - return null; - } - internal static object GetCommandParameter(FrameworkElement target, object args, string commandParameter) - { - var classify = commandParameter.Split('.'); - object ret; - switch (classify[0]) - { - case "$e": - ret = args; - break; - case "$this": - ret = classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target; - break; - default: - ret = commandParameter; - break; - } - return ret; - } +#pragma warning disable CS0162 // 检测到无法访问的代码 + return null; +#pragma warning restore CS0162 // 检测到无法访问的代码 + } - internal static ViewModelBase? FindViewModel(FrameworkElement? target) + internal static object GetCommandParameter(FrameworkElement target, object args, string commandParameter) + { + var classify = commandParameter.Split('.'); + object ret = classify[0] switch { - if (target is null) - return null; + "$e" => args, + "$this" => classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target, + _ => commandParameter, + }; + return ret; + } - if (target.DataContext is ViewModelBase vm) - return vm; + internal static ViewModelBase? FindViewModel(FrameworkElement? target) + { + if (target is null) return null; - var parent = target.GetParentObject() as FrameworkElement; - return FindViewModel(parent); - } + if (target.DataContext is ViewModelBase vm) return vm; - internal static object FollowPropertyPath(object target, string path, Type valueType = null) - { - if (target is null) throw new ArgumentNullException(nameof(target)); - if (path is null) throw new ArgumentNullException(nameof(path)); + var parent = target.GetParentObject() as FrameworkElement; - Type currentType = valueType ?? target.GetType(); + return FindViewModel(parent); + } - foreach (string propertyName in path.Split('.')) - { - PropertyInfo property = currentType.GetProperty(propertyName); - if (property is null) throw new NullReferenceException("property"); + internal static object FollowPropertyPath(object target, string path, Type? valueType = null) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (path is null) throw new ArgumentNullException(nameof(path)); - target = property.GetValue(target); - currentType = property.PropertyType; - } - return target; + Type currentType = valueType ?? target.GetType(); + + foreach (string propertyName in path.Split('.')) + { + PropertyInfo property = currentType.GetProperty(propertyName); + if (property is null) throw new NullReferenceException("property"); + + target = property.GetValue(target); + currentType = property.PropertyType; } + return target; } } diff --git a/src/IFoxCAD.WPF/GlobalUsings.cs b/src/IFoxCAD.WPF/GlobalUsings.cs new file mode 100644 index 0000000..8ab21a7 --- /dev/null +++ b/src/IFoxCAD.WPF/GlobalUsings.cs @@ -0,0 +1,18 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.ComponentModel; +global using System.Runtime.CompilerServices; +global using System.Diagnostics; +global using System.Windows; +global using System.Windows.Input; +global using System.Reflection.Emit; +global using System.Windows.Markup; +global using System.Reflection; +global using System.Windows.Media; +global using System.Globalization; +global using System.Windows.Data; + diff --git a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj index 297d6ce..9fa74ff 100644 --- a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj +++ b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj @@ -1,43 +1,40 @@  - - - preview - enable + + preview + enable - - NET45;NET46;NET47;NET48; + net45 + true + true + true + true + 0.3.0 + xsfhlzh;vicwjb + InspireFunction + WPF的简单MVVM模式开发类库 + InspireFunction + LICENSE + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + IFoxCAD;C#;NET;WPF;MVVM + git + 开启可空类型. + - true - true - true - true - 0.1.0 - xsfhlzh;vicwjb - InspireFunction - WPF的简单MVVM模式开发类库 - InspireFunction - LICENSE - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - IFoxCAD;C#;NET;WPF;MVVM - git - 基于NFOX类库的重构版本 - + + DEBUG;TRACE + - - DEBUG;TRACE - + + + - - - - - - - True - - - + + + True + + + diff --git a/src/IFoxCAD.WPF/RelayCommand.cs b/src/IFoxCAD.WPF/RelayCommand.cs index cb4f118..72fabca 100644 --- a/src/IFoxCAD.WPF/RelayCommand.cs +++ b/src/IFoxCAD.WPF/RelayCommand.cs @@ -1,205 +1,199 @@ using Microsoft.Xaml.Behaviors; -using System; -using System.Diagnostics; -using System.Windows; -using System.Windows.Input; -namespace IFoxCAD.WPF +namespace IFoxCAD.WPF; + +/// +/// 命令基类 +/// +/// +public class RelayCommand : ICommand { + readonly Func? _canExecute; + readonly Action _execute; /// - /// 命令基类 + /// 初始化 类. /// - /// - public class RelayCommand : ICommand + /// 执行函数 + public RelayCommand(Action execute) : this(execute, null) { - readonly Func _canExecute; - readonly Action _execute; - /// - /// 初始化 类. - /// - /// 执行函数 - public RelayCommand(Action execute):this(execute,null) - { - } - /// - /// 初始化 类. - /// - /// 执行函数委托 - /// 是否可执行函数委托 - /// execute - public RelayCommand(Action execute,Func canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } + } + /// + /// 初始化 类. + /// + /// 执行函数委托 + /// 是否可执行函数委托 + /// execute + public RelayCommand(Action execute, Func? canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } - /// - /// 当出现影响是否应执行该命令的更改时发生。 - /// - public event EventHandler CanExecuteChanged + /// + /// 当出现影响是否应执行该命令的更改时发生。 + /// + public event EventHandler CanExecuteChanged + { + add { - add - { - if (_canExecute is not null) - { - CommandManager.RequerySuggested += value; - } - } - remove + if (_canExecute is not null) { - if (_canExecute is not null) - { - CommandManager.RequerySuggested -= value; - } + CommandManager.RequerySuggested += value; } } - /// - /// 定义确定此命令是否可在其当前状态下执行的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - /// - /// 如果可执行此命令,则为 ;否则为 。 - /// - [DebuggerStepThrough] - public bool CanExecute(object parameter) + remove { - return _canExecute is null ? true : _canExecute(parameter); - } - /// - /// 定义在调用此命令时要调用的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - public void Execute(object parameter) - { - _execute(parameter); + if (_canExecute is not null) + { + CommandManager.RequerySuggested -= value; + } } } + /// + /// 定义确定此命令是否可在其当前状态下执行的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + /// + /// 如果可执行此命令,则为 ;否则为 。 + /// + [DebuggerStepThrough] + public bool CanExecute(object parameter) + { + return _canExecute is null || _canExecute(parameter); + } + /// + /// 定义在调用此命令时要调用的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + public void Execute(object parameter) + { + _execute(parameter); + } +} +/// +/// 命令泛型基类 +/// +/// 事件类型 +/// +public class RelayCommand : ICommand +{ + readonly Func _canExecute; + readonly Action _execute; /// - /// 命令泛型基类 + /// 初始化 类。 /// - /// 事件类型 - /// - public class RelayCommand : ICommand + /// 执行函数 + public RelayCommand(Action execute) : this(execute, (o) => true) { - readonly Func _canExecute; - readonly Action _execute; - /// - /// 初始化 类。 - /// - /// 执行函数 - public RelayCommand(Action execute) : this(execute, (o)=>true) - { - } + } - /// - /// 初始化 类。 - /// - /// 执行函数委托 - /// 是否可执行函数委托 - /// execute - public RelayCommand(Action execute, Func canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } - /// - /// 当出现影响是否应执行该命令的更改时发生。 - /// - public event EventHandler CanExecuteChanged + /// + /// 初始化 类。 + /// + /// 执行函数委托 + /// 是否可执行函数委托 + /// execute + public RelayCommand(Action execute, Func canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + /// + /// 当出现影响是否应执行该命令的更改时发生。 + /// + public event EventHandler CanExecuteChanged + { + add { - add - { - if (_canExecute is not null) - { - CommandManager.RequerySuggested += value; - } - } - remove + if (_canExecute is not null) { - if (_canExecute is not null) - { - CommandManager.RequerySuggested -= value; - } + CommandManager.RequerySuggested += value; } } - /// - /// 定义确定此命令是否可在其当前状态下执行的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - /// - /// 如果可执行此命令,则为 ;否则为 。 - /// - public bool CanExecute(object parameter) + remove { - if (_canExecute is null) + if (_canExecute is not null) { - return true; + CommandManager.RequerySuggested -= value; } - return _canExecute((T)parameter); } - /// - /// 定义在调用此命令时要调用的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - public void Execute(object parameter) + } + /// + /// 定义确定此命令是否可在其当前状态下执行的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + /// + /// 如果可执行此命令,则为 ;否则为 。 + /// + public bool CanExecute(object parameter) + { + if (_canExecute is null) { - if (_execute is not null && CanExecute(parameter)) - { - _execute((T)parameter); - } + return true; } + return _canExecute((T)parameter); } - /// - /// 事件命令 + /// 定义在调用此命令时要调用的方法。 /// - public class EventCommand : TriggerAction + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + public void Execute(object parameter) { - /// - /// 执行动作 - /// - /// 要执行的动作参数, 如果动作为提供参数,就设置为null - protected override void Invoke(object parameter) + if (_execute is not null && CanExecute(parameter)) { - if (CommandParameter is not null) - { - parameter = CommandParameter; - } - if (Command is not null) - { - Command.Execute(parameter); - } + _execute((T)parameter); } - /// - /// 事件 - /// - public ICommand Command + } +} + +/// +/// 事件命令 +/// +public class EventCommand : TriggerAction +{ + /// + /// 执行动作 + /// + /// 要执行的动作参数, 如果动作为提供参数,就设置为null + protected override void Invoke(object parameter) + { + if (CommandParameter is not null) { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } + parameter = CommandParameter; } - /// - /// 事件属性 - /// - public static readonly DependencyProperty CommandProperty = - DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null)); - - /// - /// 事件参数,如果为空,将自动传入事件的真实参数 - /// - public object CommandParameter + if (Command is not null) { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } + Command.Execute(parameter); } - /// - /// 事件参数属性 - /// - public static readonly DependencyProperty CommandParameterProperty = - DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null)); } + /// + /// 事件 + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + /// + /// 事件属性 + /// + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null)); + /// + /// 事件参数,如果为空,将自动传入事件的真实参数 + /// + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + /// + /// 事件参数属性 + /// + public static readonly DependencyProperty CommandParameterProperty = + DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null)); } diff --git a/src/IFoxCAD.WPF/ViewModelBase.cs b/src/IFoxCAD.WPF/ViewModelBase.cs index 0d0a2dc..c992e81 100644 --- a/src/IFoxCAD.WPF/ViewModelBase.cs +++ b/src/IFoxCAD.WPF/ViewModelBase.cs @@ -1,63 +1,58 @@ -using System; -using System.ComponentModel; -using System.Runtime.CompilerServices; +namespace IFoxCAD.WPF; -namespace IFoxCAD.WPF +/// +/// ViewModel基类 +/// +/// +public class ViewModelBase : INotifyPropertyChanged { /// - /// ViewModel基类 + /// 属性值更改事件。 /// - /// - public class ViewModelBase : INotifyPropertyChanged + public event PropertyChangedEventHandler? PropertyChanged; + /// + /// 属性改变时调用 + /// + /// 属性名 + public void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + /// + /// 设置属性函数,自动通知属性改变事件 + /// + /// 属性类型 + /// 属性 + /// 属性值 + /// 属性名 + /// 成功返回 ,反之 + protected virtual bool Set(ref T storage, T value, [CallerMemberName] string propertyName = "") { - /// - /// 属性值更改事件。 - /// - public event PropertyChangedEventHandler PropertyChanged; - /// - /// 属性改变时调用 - /// - /// 属性名 - public void OnPropertyChanged([CallerMemberName]string propertyName = "") - { - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - /// - /// 设置属性函数,自动通知属性改变事件 - /// - /// 属性类型 - /// 属性 - /// 属性值 - /// 属性名 - /// 成功返回 ,反之 - protected virtual bool Set(ref T storage, T value, [CallerMemberName]string propertyName = null) - { - if (object.Equals(storage, value)) return false; + if (object.Equals(storage, value)) return false; - storage = value; - this.OnPropertyChanged(propertyName); + storage = value; + this.OnPropertyChanged(propertyName); - return true; - } - /// - /// 创建命令 - /// - /// 要调用的命令函数委托 - /// WPF命令 - protected RelayCommand CreateCommand(Action executeMethod) - { - return CreateCommand(executeMethod, (o) => true); - } - /// - /// 创建命令 - /// - /// 要调用的命令函数委托 - /// 命令是否可以执行的委托 - /// WPF命令 - protected RelayCommand CreateCommand(Action executeMethod, Func canExecuteMethod) - { - return new RelayCommand(executeMethod, canExecuteMethod); - } + return true; + } + /// + /// 创建命令 + /// + /// 要调用的命令函数委托 + /// WPF命令 + protected RelayCommand CreateCommand(Action executeMethod) + { + return CreateCommand(executeMethod, (o) => true); + } + /// + /// 创建命令 + /// + /// 要调用的命令函数委托 + /// 命令是否可以执行的委托 + /// WPF命令 + protected RelayCommand CreateCommand(Action executeMethod, Func canExecuteMethod) + { + return new RelayCommand(executeMethod, canExecuteMethod); } } diff --git a/tests/Test/CmdINI.cs b/tests/Test/CmdINI.cs new file mode 100644 index 0000000..2e37c1d --- /dev/null +++ b/tests/Test/CmdINI.cs @@ -0,0 +1,111 @@ +/// +/// 注册中心(自动执行接口): +/// 用于启动cad后写入启动注册表及反射调用以下特性和接口 +/// netload的工程必须继承虚函数后才能使用特性和接口 +/// 启动cad后的执行顺序为: +/// 1:构造函数 +/// 2:特性..多个 +/// 3:接口..多个 +/// 4:本类的构造函数 +/// +public class CmdINI : AutoRegAssem +{ + public CmdINI() : base(AutoRegConfig.All) + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n {nameof(CmdINI)}构造函数,开始自动执行\r\n"); + } + + ///如果netload之后用 删除注册表, + ///由于不是也不能卸载dll,再netload是无法执行自动接口的, + ///所以此时会产生无法再注册的问题...因此需要暴露此注册函数(硬来) + [CommandMethod("IFoxAddReg")] + public void IFoxAddReg() + { + base.RegApp(); + + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n 加入注册表"); + } + + /// + /// 卸载注册表信息 + /// + [CommandMethod("IFoxRemoveReg")] + public void IFoxRemoveReg() + { + //执行命令的时候会再次执行构造函数(导致初始化两次),但是再次执行就不会了 + base.UnRegApp(); + + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n 卸载注册表"); + } +} + + +/* + * 自动执行特性例子: + */ +public class Cmd_IFoxInitialize +{ + [IFoxInitialize] + public void NameCasual() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 可以分开多个类和多个函数 \r\n"); + } + + [IFoxInitialize] + public void NameCasualtest() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 又一次测试 \r\n"); + } + + [IFoxInitialize] + public void Initialize() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 Initialize \r\n"); + } + + [IFoxInitialize(isInitialize: false)] + public void Terminate() + { + //try + //{ + // var dm = Application.DocumentManager; + // var doc = dm.MdiActiveDocument; + // var ed = doc.Editor; //注意此时编辑器已经回收,所以此句没用,并引发错误 + // ed.WriteMessage("\n 结束自动执行 Terminate \r\n"); + //} + //catch (System.Exception) + //{ + //} + } + + //[IFoxInitialize] + //public void Initialize() + //{ + // //文档管理器将比此接口前创建,因此此句会执行 + // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); + //} + //[IFoxInitialize(Sequence.First, false)] + //public void Terminate() + //{ + // //文档管理器将比此接口前死亡,因此此句不会执行 + // Application.DocumentManager.MdiActiveDocument?.Editor.WriteMessage("\nunload...."); + //} +} \ No newline at end of file diff --git a/tests/Test/GlobalUsings.cs b/tests/Test/GlobalUsings.cs new file mode 100644 index 0000000..2896cbf --- /dev/null +++ b/tests/Test/GlobalUsings.cs @@ -0,0 +1,30 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.CompilerServices; + +/// autocad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acgi = Autodesk.AutoCAD.GraphicsInterface; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// ifoxcad +global using IFoxCAD.Cad; +global using IFoxCAD.WPF; +global using IFoxCAD.Basal; \ No newline at end of file diff --git a/tests/Test/Properties/launchSettings.json b/tests/Test/Properties/launchSettings.json index 1ecc024..4b464c3 100644 --- a/tests/Test/Properties/launchSettings.json +++ b/tests/Test/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "DBTrans.test": { + "Test": { "commandName": "Executable", "executablePath": "C:\\Program Files\\Autodesk\\AutoCAD 2021\\acad.exe", "commandLineArgs": "/nologo", diff --git a/tests/Test/Test.cs b/tests/Test/Test.cs index 78746f4..bc45757 100644 --- a/tests/Test/Test.cs +++ b/tests/Test/Test.cs @@ -1,193 +1,179 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; +#pragma warning disable CS0219 // 变量已被赋值,但从未使用过它的值 +namespace Test; -using IFoxCAD.Cad; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -using test.wpf; - -namespace test +public class Test { - public class Test + [CommandMethod("dbtest")] + public void Dbtest() { - [CommandMethod("dbtest")] - public void Dbtest() + using var tr = new DBTrans(); + tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); + tr.Editor.WriteMessage("\n----------开始测试--------------"); + tr.Editor.WriteMessage("\n测试document属性是否工作"); + if (tr.Document == Getdoc()) { - using var tr = new DBTrans(); - tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); - tr.Editor.WriteMessage("\n----------开始测试--------------"); - tr.Editor.WriteMessage("\n测试document属性是否工作"); - if (tr.Document == Getdoc()) - { - tr.Editor.WriteMessage("\ndocument 正常"); - } - tr.Editor.WriteMessage("\n测试database属性是否工作"); - if (tr.Database == Getdb()) - { - tr.Editor.WriteMessage("\ndatabase 正常"); - } - - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); - //var lienid = tr.AddEntity(line); - //var cirid = tr.AddEntity(circle); - //var linent = tr.GetObject(lienid); - //var lineent = tr.GetObject(cirid); - //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null - //var dd = tr.GetObject(lienid); - //List ds = new() { linee, dd }; + tr.Editor.WriteMessage("\ndocument 正常"); } - - //add entity test - [CommandMethod("addent")] - public void Addent() + tr.Editor.WriteMessage("\n测试database属性是否工作"); + if (tr.Database == Getdb()) { - using var tr = new DBTrans(); - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line); - Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); - tr.ModelSpace.AddEntity(line1); - Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); - tr.PaperSpace.AddEntity(line2); + tr.Editor.WriteMessage("\ndatabase 正常"); } - [CommandMethod("drawarc")] - public void drawarc() - { - using var tr = new DBTrans(); - Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 - Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 - Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 - tr.CurrentSpace.AddEntity(arc1, arc2, arc3); - tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 - } + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); + //var lienid = tr.AddEntity(line); + //var cirid = tr.AddEntity(circle); + //var linent = tr.GetObject(lienid); + //var lineent = tr.GetObject(cirid); + //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null + //var dd = tr.GetObject(lienid); + //List ds = new() { linee, dd }; + //tr.CurrentSpace.AddEntity(line,tr); + } - [CommandMethod("drawcircle")] - public void draCircle() + //add entity test + [CommandMethod("addent")] + public void Addent() + { + using var tr = new DBTrans(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); + tr.ModelSpace.AddEntity(line1); + Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); + tr.PaperSpace.AddEntity(line2); + } + + [CommandMethod("drawarc")] + public void Drawarc() + { + using var tr = new DBTrans(); + Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 + Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 + Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 + tr.CurrentSpace.AddEntity(arc1, arc2, arc3); + tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 + } + + [CommandMethod("drawcircle")] + public void DraCircle() + { + using var tr = new DBTrans(); + Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 + Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 + Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 + tr.CurrentSpace.AddEntity(circle1, circle2); + if (circle3 is not null) { - using var tr = new DBTrans(); - Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 - Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 - Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 - tr.CurrentSpace.AddEntity(circle1, circle2); - if (circle3 is not null) - tr.CurrentSpace.AddEntity(circle3); - else - { - tr.Editor.WriteMessage("三点画圆失败"); - return; - } tr.CurrentSpace.AddEntity(circle3); - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) } - - [CommandMethod("layertest")] - public void Layertest() + else { - using var tr = new DBTrans(); - tr.LayerTable.Add("1"); - tr.LayerTable.Add("2", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); - lt.LineWeight = LineWeight.LineWeight030; - - }); - tr.LayerTable.Remove("3"); - tr.LayerTable.Delete("0"); - tr.LayerTable.Change("4", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); - }); + tr.Editor.WriteMessage("三点画圆失败"); } + tr.CurrentSpace.AddEntity(circle3); + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) + } + [CommandMethod("layertest")] + public void Layertest() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("1"); + tr.LayerTable.Add("2", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + lt.LineWeight = LineWeight.LineWeight030; + + }); + tr.LayerTable.Remove("3"); + tr.LayerTable.Delete("0"); + tr.LayerTable.Change("4", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); + }); + } - //添加图层 - [CommandMethod("layerAdd1")] - public void Layertest1() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); - } - //添加图层 - [CommandMethod("layerAdd2")] - public void Layertest2() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test2", 2); - //tr.LayerTable["3"] = new LayerTableRecord(); - } - //删除图层 - [CommandMethod("layerdel")] - public void LayerDel() - { - using var tr = new DBTrans(); - Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 - Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints - Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 - Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 - Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 + //添加图层 + [CommandMethod("layerAdd1")] + public void Layertest1() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); + } - tr.LayerTable.Remove("2"); //测试是否能强制删除 - } + //添加图层 + [CommandMethod("layerAdd2")] + public void Layertest2() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("test2", 2); + //tr.LayerTable["3"] = new LayerTableRecord(); + } + //删除图层 + [CommandMethod("layerdel")] + public void LayerDel() + { + using var tr = new DBTrans(); + Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 + Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints + Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 + Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 + Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 + + tr.LayerTable.Remove("2"); //测试是否能强制删除 + } - //添加直线 - [CommandMethod("linedemo1")] - public void AddLine1() - { - using var tr = new DBTrans(); - // tr.ModelSpace.AddEnt(line); - // tr.ModelSpace.AddEnts(line,circle); - - // tr.PaperSpace.AddEnt(line); - // tr.PaperSpace.AddEnts(line,circle); - - // tr.addent(btr,line); - // tr.addents(btr,line,circle); - - - // tr.BlockTable.Add(new BlockTableRecord(), line => - // { - // line. - // }); - Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); - Circle circle = new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); - tr.CurrentSpace.AddEntity(line1); - tr.CurrentSpace.AddEntity(line2, line3, circle); - } + //添加直线 + [CommandMethod("linedemo1")] + public void AddLine1() + { + using var tr = new DBTrans(); + // tr.ModelSpace.AddEnt(line); + // tr.ModelSpace.AddEnts(line,circle); + + // tr.PaperSpace.AddEnt(line); + // tr.PaperSpace.AddEnts(line,circle); + + // tr.addent(btr,line); + // tr.addents(btr,line,circle); + + + // tr.BlockTable.Add(new BlockTableRecord(), line => + // { + // line. + // }); + Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); + tr.CurrentSpace.AddEntity(line1); + tr.CurrentSpace.AddEntity(line2, line3, circle); + } - //增加多段线1 - [CommandMethod("Pldemo1")] - public void AddPolyline1() - { - using var tr = new DBTrans(); - Polyline pl = new Polyline(); - pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); - pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); - pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); - pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); - pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); - pl.Closed = true; - pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); - tr.CurrentSpace.AddEntity(pl); - } + //增加多段线1 + [CommandMethod("Pldemo1")] + public void AddPolyline1() + { + using var tr = new DBTrans(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); + pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); + pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); + pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); + pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); + pl.Closed = true; + pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); + tr.CurrentSpace.AddEntity(pl); + } - //增加多段线2 - [CommandMethod("pldemo2")] - public void Addpl2() - { - var pts = new List<(Point3d, double, double, double)> + //增加多段线2 + [CommandMethod("pldemo2")] + public void Addpl2() + { + var pts = new List<(Point3d, double, double, double)> { (new Point3d(0,0,0),0,0,0), (new Point3d(10,0,0),0,0,0), @@ -195,154 +181,28 @@ public void Addpl2() (new Point3d(0,10,0),0,0,0), (new Point3d(5,5,0),0,0,0) }; - using var tr = new DBTrans(); - tr.CurrentSpace.AddPline(pts); - } - - //块定义 - [CommandMethod("blockdef")] - public void BlockDef() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Add("test", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - }, - () => //图元 - { - return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; - }, - () => //属性定义 - { - var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; - var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; - return new List { id1, id2 }; - } - ); - //ObjectId objectId = tr.BlockTable.Add("a");//新建块 - //objectId.GetObject().AddEntity();//测试添加空实体 - } - //修改块定义 - [CommandMethod("blockdefchange")] - public void BlockDefChange() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Change("test", btr => - { - btr.Origin = new Point3d(5, 5, 0); - btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); - btr.GetEntities() - .ToList() - .ForEach(e => e.Flush()); //刷新块显示 - - }); - tr.Editor.Regen(); - } - - [CommandMethod("insertblockdef")] - public void InsertBlockDef() - { - using var tr = new DBTrans(); - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; - var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; - tr.BlockTable.Add("test1", line1, line2, att1, att2); - - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - - - var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); - var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); - var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; - var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; - tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 - //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 - - var def1 = new Dictionary - { - { "tagTest1", "1" }, - { "tagTest2", "2" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); - var def2 = new Dictionary - { - { "tagTest3", "1" }, - { "tagTest4", "" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); - tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); - } - - [CommandMethod("testblocknullbug")] - public void TestBlockNullBug() - { - using var tr = new DBTrans(); + using var tr = new DBTrans(); + tr.CurrentSpace.AddPline(pts); + } - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); - } - [CommandMethod("testclip")] - public void TestClipBlock() - { - using var tr = new DBTrans(); - tr.BlockTable.Add("test1", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), - new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) - ); - } - ); - //tr.BlockTable.Add("hah"); - var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); - var bref = tr.GetObject(id); - var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; - bref.ClipBlockRef(pts); - var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); - var bref1 = tr.GetObject(id); - - bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); - } + // 测试扩展数据 + [CommandMethod("addxdata")] + public void AddXdata() + { + using var tr = new DBTrans(); + var appname = "myapp"; + tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 + tr.RegAppTable.Add("myapp2"); - // 测试扩展数据 - [CommandMethod("addxdata")] - public void AddXdata() + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) { - using var tr = new DBTrans(); - var appname = "myapp"; - - tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 - tr.RegAppTable.Add("myapp2"); - - var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) - { - XData = new XDataList() + XData = new XDataList() { { DxfCode.ExtendedDataRegAppName, appname }, //可以用dxfcode和int表示组码 { DxfCode.ExtendedDataAsciiString, "hahhahah" }, @@ -351,278 +211,150 @@ public void AddXdata() { DxfCode.ExtendedDataAsciiString, "hahhahah" }, {1070, 12 } } - }; - - tr.CurrentSpace.AddEntity(line); - } - - [CommandMethod("getxdata")] - public void GetXdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId).XData; - ed.WriteMessage(data.ToString()); - } - } - - [CommandMethod("changexdata")] - public void Changexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); + }; - ed.WriteMessage(data.XData.ToString()); - } - } - [CommandMethod("removexdata")] - public void Removexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); + tr.CurrentSpace.AddEntity(line); + } - ed.WriteMessage(data.XData.ToString()); - } - } + [CommandMethod("getxdata")] + public void GetXdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + using var tr = new DBTrans(); + tr.RegAppTable.ForEach(id => + id.GetObject().Name.Print()); + tr.RegAppTable.GetRecords().ForEach(rec => rec.Name.Print()); + tr.RegAppTable.GetRecordNames().ForEach(name => name.Print()); + tr.RegAppTable.ForEach(re => re.Name.Print()); + + //var res = ed.GetEntity("\n select the entity:"); + //if (res.Status == PromptStatus.OK) + //{ + // using var tr = new DBTrans(); + // tr.RegAppTable.ForEach(id => id.GetObject().Print()); + // var data = tr.GetObject(res.ObjectId).XData; + // ed.WriteMessage(data.ToString()); + //} + } - [CommandMethod("PrintLayerName")] - public void PrintLayerName() + [CommandMethod("changexdata")] + public void Changexdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) { using var tr = new DBTrans(); - foreach (var layerRecord in tr.LayerTable.GetRecords()) - { - tr.Editor.WriteMessage(layerRecord.Name); - } - - } - + var data = tr.GetObject(res.ObjectId); + data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); - [CommandMethod("testwpf")] - public void TestWPf() - { - - var test = new TestView(); - Application.ShowModalWindow(test); + ed.WriteMessage(data.XData.ToString()); } - - [CommandMethod("testpt")] - public void TestPt() + } + [CommandMethod("removexdata")] + public void Removexdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) { - //var pt = Env.Editor.GetPoint("pick pt:").Value; - //var pl = Env.Editor.GetEntity("pick pl").ObjectId; - var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - using var tr2 = new DBTrans(); - var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr2.Transaction == tr3); - Env.Print(tr3 == tr6); - using var tr4 = new DBTrans(); - var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr4.Transaction == tr5); - Env.Print(tr5 == tr7); - var trm = HostApplicationServices.WorkingDatabase.TransactionManager; - //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); - //var pt1 = new Point3d(0, 0.00000000000001, 0); - //var pt2 = new Point3d(0, 0.00001, 0); - //Env.Print(Tolerance.Global.EqualPoint); - //Env.Print(pt1.IsEqualTo(pt2).ToString()); - //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); - //Env.Print((pt1 == pt2).ToString()); - //Env.Print((pt1 != pt2).ToString()); - - + using var tr = new DBTrans(); + var data = tr.GetObject(res.ObjectId); + data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); + ed.WriteMessage(data.XData.ToString()); } + } - - public Database Getdb() - { - var db = Application.DocumentManager.MdiActiveDocument.Database; - return db; - } - - - public Document Getdoc() + [CommandMethod("PrintLayerName")] + public void PrintLayerName() + { + using var tr = new DBTrans(); + foreach (var layerRecord in tr.LayerTable.GetRecords()) { - var doc = Application.DocumentManager.MdiActiveDocument; - return doc; + tr.Editor.WriteMessage(layerRecord.Name); } - //public override void Initialize() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); - //} - - //public override void Terminate() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nunload...."); - //} } - public class BlockImportClass + [CommandMethod("testrec")] + public void TestRec() { + Point2d p1 = new(10000.2, 100000.5); + Point2d p2 = new(15000.9, 100000.5); + Point2d p3 = new(15000.9, 105000.7); + Point2d p4 = new(10000.2, 105000.7); - [CommandMethod("CBLL")] - public void cbll() - { - string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; - using var tr = new DBTrans(); - using var tr1 = new DBTrans(filename); - //tr.BlockTable.GetBlockFrom(filename, true); - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); - tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 + var p12 = p2 - p1; + var p23 = p3 - p2; + var p34 = p4 - p3; + var p41 = p1 - p4; + var p13 = p3 - p1; + var p24 = p4 - p2; - } + const double pi90 = Math.PI / 2; - [CommandMethod("CBL")] - public void CombineBlocksIntoLibrary() - { - Document doc = - Application.DocumentManager.MdiActiveDocument; - Editor ed = doc.Editor; - Database destDb = doc.Database; + Tools.TestTimes(1000000, "对角线", () => { + var result = false; + if (Math.Abs(p13.Length - p24.Length) <= 1e8) + { + result = p41.IsParallelTo(p12); + } - // Get name of folder from which to load and import blocks + }); - PromptResult pr = - ed.GetString("\nEnter the folder of source drawings: "); + Tools.TestTimes(1000000, "三次点乘", () => { + var result = false; - if (pr.Status != PromptStatus.OK) - return; - string pathName = pr.StringResult; + if (Math.Abs(p12.DotProduct(p23)) < 1e8 && + Math.Abs(p23.DotProduct(p34)) < 1e8 && + Math.Abs(p34.DotProduct(p41)) < 1e8) + { + result = true; + } - // Check the folder exists + }); - if (!Directory.Exists(pathName)) + Tools.TestTimes(1000000, "三次垂直", () => { + var result = false; + if (p12.IsParallelTo(p23) && + p23.IsParallelTo(p34) && + p34.IsParallelTo(p41)) { - ed.WriteMessage( - "\nDirectory does not exist: {0}", pathName - ); - return; + result = true; } - // Get the names of our DWG files in that folder + }); - string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); - // A counter for the files we've imported + } - int imported = 0, failed = 0; - // For each file in our list - foreach (string fileName in fileNames) - { - // Double-check we have a DWG file (probably unnecessary) - if (fileName.EndsWith( - ".dwg", - StringComparison.InvariantCultureIgnoreCase - ) - ) - { - // Catch exceptions at the file level to allow skipping - - try - { - // Suggestion from Thorsten Meinecke... - - string destName = - SymbolUtilityServices.GetSymbolNameFromPathName( - fileName, "dwg" - ); - - // And from Dan Glassman... - - destName = - SymbolUtilityServices.RepairSymbolName( - destName, false - ); - - // Create a source database to load the DWG into - - using (Database db = new Database(false, true)) - { - // Read the DWG into our side database - - db.ReadDwgFile(fileName, FileShare.Read, true, ""); - bool isAnno = db.AnnotativeDwg; - - // Insert it into the destination database as - // a named block definition - - ObjectId btrId = destDb.Insert( - destName, - db, - false - ); - - if (isAnno) - { - // If an annotative block, open the resultant BTR - // and set its annotative definition status - - Transaction tr = - destDb.TransactionManager.StartTransaction(); - using (tr) - { - BlockTableRecord btr = - (BlockTableRecord)tr.GetObject( - btrId, - OpenMode.ForWrite - ); - btr.Annotative = AnnotativeStates.True; - tr.Commit(); - } - } - - // Print message and increment imported block counter - - ed.WriteMessage("\nImported from \"{0}\".", fileName); - imported++; - } - } - catch (System.Exception ex) - { - ed.WriteMessage( - "\nProblem importing \"{0}\": {1} - file skipped.", - fileName, ex.Message - ); - failed++; - } - } - } - ed.WriteMessage( - "\nImported block definitions from {0} files{1} in " + - "\"{2}\" into the current drawing.", - imported, - failed > 0 ? " (" + failed + " failed)" : "", - pathName - ); - } + + public Database Getdb() + { + var db = Application.DocumentManager.MdiActiveDocument.Database; + return db; } + + public Document Getdoc() + { + var doc = Application.DocumentManager.MdiActiveDocument; + return doc; + } } + + + +#pragma warning restore CS0219 // 变量已被赋值,但从未使用过它的值 diff --git a/tests/Test/Test.csproj b/tests/Test/Test.csproj index fa8ed34..8e3469a 100644 --- a/tests/Test/Test.csproj +++ b/tests/Test/Test.csproj @@ -1,32 +1,22 @@  - preview - enable + - - 1.0.0.* - 1.0.0.0 - False - git - - - NET45; + net45 true true + x64 - - - - - - + + 1701;1702;CS1685 + - - - + + 1701;1702;CS1685 + @@ -34,4 +24,5 @@ + diff --git a/tests/Test/TestAOP.cs b/tests/Test/TestAOP.cs new file mode 100644 index 0000000..b5b5c92 --- /dev/null +++ b/tests/Test/TestAOP.cs @@ -0,0 +1,66 @@ +//被注入的函数将不能使用断点, +//因此用户要充分了解才能使用 +#if false +/* + * 类库用户想侵入的命名空间是用户的, + * 所以需要用户手动进行AOP.Run(), + * 默认情况不侵入用户的命令,必须用户手动启用此功能; + * 启动执行策略之后,侵入命名空间下的命令, + * 此时有拒绝特性的策略保证括免,因为用户肯定是想少写一个事务注入的特性; + */ +public class AutoAOP +{ + [IFoxInitialize] + public void Initialize() + { + AOP.Run(nameof(Test)); + } +} + +namespace Test +{ + /* + * 天秀的事务注入,让你告别事务处理 + * https://www.cnblogs.com/JJBox/p/16157578.html + */ + public class AopTestClass + { + //类不拒绝,这里拒绝 + [IFoxRefuseInjectionTransaction] + [CommandMethod("IFoxRefuseInjectionTransaction")] + public void IFoxRefuseInjectionTransaction() + { + } + + //不拒绝 + [CommandMethod("InjectionTransaction")] + public void InjectionTransaction() + { + //怎么用事务呢? + //直接用 DBTrans.Top + var dBTrans = new DBTrans(); + dBTrans.Commit(); + } + } + + //拒绝注入事务,写类上,则方法全都拒绝 + [IFoxRefuseInjectionTransaction] + public class AopTestClassRefuseInjection + { + //此时这个也是拒绝的..这里加特性只是无所谓 + [IFoxRefuseInjectionTransaction] + [CommandMethod("IFoxRefuseInjectionTransaction2")] + public void IFoxRefuseInjectionTransaction2() + { + //拒绝注入就要自己开事务,通常用在循环提交事务上面. + //另见 报错0x02 https://www.cnblogs.com/JJBox/p/10798940.html + using var tr = new DBTrans(); + } + + [CommandMethod("InjectionTransaction2")] + public void InjectionTransaction2() + { + } + } +} +#endif \ No newline at end of file diff --git a/tests/Test/TestCurve.cs b/tests/Test/TestCurve.cs new file mode 100644 index 0000000..4d5cd96 --- /dev/null +++ b/tests/Test/TestCurve.cs @@ -0,0 +1,158 @@ +using System.Security.Policy; + +namespace Test +{ + public class TestGraph + { + [CommandMethod("testpointindict")] + public void TestPointInDict() + { + var pt1 = new Point3d(0.0255, 0.452, 0); + var pt2 = new Point3d(0.0255001, 0.452003, 0); + var pt3 = new Point3d(0.0255002, 0.4520001, 0); + var pt4 = new Point3d(0.0255450, 0.45287893, 0); + var pt5 = new Point3d(0.02554935, 0.452092375, 0); + var dict = new Dictionary + { + { pt1, 1 }, + { pt2, 2 }, + { pt3, 3 }, + { pt4, 4 }, + { pt5, 5 } + }; + Env.Print(dict[pt1]); + } + + [CommandMethod("testgraph")] + public void TestGraph1() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + Tools.TestTimes2(1, "new", () => { + + + var res = ents.GetAllCycle(); + + //res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + Env.Print(res.Count()); + tr.CurrentSpace.AddEntity(res); + }); + + } + + [CommandMethod("testgraphspeed")] + public void TestGraphspeed() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + + + var graph = new IFoxCAD.Cad.Graph(); // 为了调试先把图的访问改为internal + + foreach (var curve in ents) + { + + graph.AddEdge(curve.GetGeCurve()); + + + } + //新建 dfs + var dfs = new DepthFirst(); +#if true + Tools.TestTimes2(100, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#else + Tools.TestTimes2(100, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#endif + //res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + + //tr.CurrentSpace.AddEntity(res); + + } + } + + + + + public class TestCurve + { + [CommandMethod("testbreakcurve")] + public void TestBreakCurve() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet().Value.GetEntities(); + var tt = CurveEx.BreakCurve(ents.ToList()); + tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + tr.CurrentSpace.AddEntity(tt); + } + + [CommandMethod("testCurveCurveIntersector3d")] + public void TestCurveCurveIntersector3d() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet().Value.GetEntities() + .Select(e => e.ToCompositeCurve3d()).ToList(); + + var cci3d = new CurveCurveIntersector3d(); + + + for (int i = 0; i < ents.Count; i++) + { + var gc1 = ents[i]; + var int1 = gc1.GetInterval(); + //var pars1 = paramss[i]; + for (int j = i; j < ents.Count; j++) + { + var gc2 = ents[j]; + //var pars2 = paramss[j]; + var int2 = gc2.GetInterval(); + cci3d.Set(gc1, gc2, int1, int2, Vector3d.ZAxis); + var d = cci3d.OverlapCount(); + var a = cci3d.GetIntersectionRanges(); + Env.Print($"{a[0].LowerBound}-{a[0].UpperBound} and {a[1].LowerBound}-{a[1].UpperBound}"); + for (int m = 0; m < d; m++) + { + var b = cci3d.GetOverlapRanges(m); + Env.Print($"{b[0].LowerBound}-{b[0].UpperBound} and {b[1].LowerBound}-{b[1].UpperBound}"); + } + + for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) + { + //var a = cci3d.GetOverlapRanges(k); + //var b = cci3d.IsTangential(k); + //var c = cci3d.IsTransversal(k); + //var d = cci3d.OverlapCount(); + //var e = cci3d.OverlapDirection(); + var pt = cci3d.GetIntersectionParameters(k); + var pts = cci3d.GetIntersectionPoint(k); + Env.Print(pts); + } + + + + } + + } + // var tt = CurveEx.Topo(ents.ToList()); + //tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + //tr.CurrentSpace.AddEntity(tt); + } + } +} \ No newline at end of file diff --git a/tests/Test/TestDBTrans.cs b/tests/Test/TestDBTrans.cs new file mode 100644 index 0000000..230882e --- /dev/null +++ b/tests/Test/TestDBTrans.cs @@ -0,0 +1,78 @@ +namespace Test; + +public class TestTrans +{ + [CommandMethod("testtr")] + public void Testtr() + { + string filename = @"C:\Users\vic\Desktop\test.dwg"; + using var tr = new DBTrans(filename); + tr.ModelSpace.AddCircle(new Point3d(10, 10, 0), 20); + //tr.Database.SaveAs(filename,DwgVersion.Current); + tr.SaveDwgFile(); + } + [CommandMethod("testifoxcommit")] + public void Testifoxcommit() + { + + using var tr = new DBTrans(); + tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + tr.Abort(); + //tr.Commit(); + } + + // AOP 应用 预计示例: + // 1. 无参数 + //[AOP] + //[CommandMethod("TESTAOP")] + //public void testaop() + //{ + // // 不用 using var tr = new DBTrans(); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + //} + + // 2. 有参数 + //[AOP("file")] + //[CommandMethod("TESTAOP")] + //public void testaop() + //{ + // // 不用 using var tr = new DBTrans(file); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + //} + + + [CommandMethod("testpt")] + public void TestPt() + { + //var pt = Env.Editor.GetPoint("pick pt:").Value; + //var pl = Env.Editor.GetEntity("pick pl").ObjectId; + + var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + using var tr2 = new DBTrans(); + var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr2.Transaction == tr3); + Env.Print(tr3 == tr6); + using var tr4 = new DBTrans(); + var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr4.Transaction == tr5); + Env.Print(tr5 == tr7); + var trm = HostApplicationServices.WorkingDatabase.TransactionManager; + + //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); + //var pt1 = new Point3d(0, 0.00000000000001, 0); + //var pt2 = new Point3d(0, 0.00001, 0); + //Env.Print(Tolerance.Global.EqualPoint); + //Env.Print(pt1.IsEqualTo(pt2).ToString()); + //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); + //Env.Print((pt1 == pt2).ToString()); + //Env.Print((pt1 != pt2).ToString()); + + + + } + +} diff --git a/tests/Test/TestEnt.cs b/tests/Test/TestEnt.cs new file mode 100644 index 0000000..e3cccf7 --- /dev/null +++ b/tests/Test/TestEnt.cs @@ -0,0 +1,40 @@ +namespace Test; + +public class TestEnt +{ + [CommandMethod("TestEntRoration")] + public void TestEntRoration() + { + var line = new Line(new(0,0,0),new(100,0,0)); + + using var tr = new DBTrans(); + tr.CurrentSpace.AddEntity(line); + var line2 = line.Clone() as Line; + tr.CurrentSpace.AddEntity(line2); + line2.Rotation(new(100, 0, 0), Math.PI / 2); + + + } + + + [CommandMethod("Testtypespeed")] + public void TestTypeSpeed() + { + var line = new Line(); + var line1 = line as Entity; + Tools.TestTimes(100000, "is 匹配:", () => + { + var t = line1 is Line; + }); + Tools.TestTimes(100000, "name 匹配:", () => + { + //var t = line.GetType().Name; + var tt = line1.GetType().Name == nameof(Line); + }); + Tools.TestTimes(100000, "dxfname 匹配:", () => + { + //var t = line.GetType().Name; + var tt = line1.GetRXClass().DxfName == nameof(Line); + }); + } +} diff --git a/tests/Test/TestFileDatabase.cs b/tests/Test/TestFileDatabase.cs new file mode 100644 index 0000000..7493f30 --- /dev/null +++ b/tests/Test/TestFileDatabase.cs @@ -0,0 +1,29 @@ +/************************************************************** +*作者:Leon +*创建时间:2022/2/11 9:55:32 +**************************************************************/ +namespace Test +{ + public class TestFileDatabase + { + [CommandMethod("Test_FileDatabaseInit")] + public void TestDatabase() + { + try + { + var fileName = @"C:\Users\Administrator\Desktop\合并详图测试BUG.dwg"; + using DBTrans trans = new(fileName); + trans.ModelSpace.AddEntity(new Line(new(0, 0, 0), new(1000, 1000, 0))); + if (trans.Document is not null && trans.Document.IsActive) + trans.Document.SendStringToExecute("_qsave\n", false, true, true); + else + trans.Database.SaveAs(fileName, DwgVersion.AC1021); + } + catch (System.Exception e) + { + System.Windows.MessageBox.Show(e.Message); + } + + } + } +} \ No newline at end of file diff --git a/tests/Test/TestJig.cs b/tests/Test/TestJig.cs new file mode 100644 index 0000000..605958c --- /dev/null +++ b/tests/Test/TestJig.cs @@ -0,0 +1,335 @@ +namespace Test; +using System.Windows.Forms; + +public class Commands_Jig +{ + //已在数据库的图元如何进入jig + [CommandMethod("TestCmd_jig33")] + public static void TestCmd_jig33() + { + Circle cir; + using var tr = new DBTrans(); + var per = tr.Editor.GetEntity("\n点选圆形:"); + if (per.Status != PromptStatus.OK) + return; + cir = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + + if (cir == null) + return; + var oldSp = cir.StartPoint; + JigEx moveJig = null; + moveJig = new JigEx((mousePoint, drawEntitys) => { + moveJig.SetOptions(oldSp);//回调过程中也可以修改基点 + //cir.UpgradeOpen();//已经提权了,所以这里不需要提权 + cir.Move(cir.StartPoint, mousePoint); + //cir.DowngradeOpen(); + + //此处会Dispose图元, + //所以此处不加入已经在数据库的图元,而是加入new Entity的. + //drawEntitys.Enqueue(cir); + }); + moveJig.SetOptions(cir.GeometricExtents.MinPoint, orthomode: true); + + //此处详见方法注释 + moveJig.DatabaseEntityDraw(draw => { + draw.RawGeometry.Draw(cir); + }); + + while (true) + { + var prDrag = moveJig.Drag(); + if (prDrag.Status == PromptStatus.OK) + break; + } + } + + + //不在数据库的图元如何进入jig + [CommandMethod("TestCmd_Jig44")] + public void TestCmd_Jig44() + { + using var tr = new DBTrans(); + var per = Env.Editor.GetEntity("\n请选择一条多段线:"); + if (per.Status != PromptStatus.OK) + return; + var ent = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + if (ent is not Polyline pl) + return; + + /* + * 鼠标采样器执行时修改鼠标基点 + * 原因: 多段线与鼠标垂直点作为 BasePoint,jig鼠标点为确定点 + * 所以需要先声明再传入指针,但是我发现null也可以. + */ + JigEx jig = null; + JigPromptPointOptions options = null; + jig = new JigEx((mousePoint, drawEntitys) => { + var closestPt = pl.GetClosestPointTo(mousePoint, false); + + //回调过程中SetOptions会覆盖配置,所以如果想增加关键字或者修改基点, + //不要这样做: jig.SetOptions(closestPt) 而是使用底层暴露 + options.BasePoint = closestPt; + + //需要避免重复加入同一个关键字 + if (!options.Keywords.Contains("A")) + options.Keywords.Add("A"); + + //生成文字 + var dictString = (pl.GetDistAtPoint(closestPt) * 0.001).ToString("0.00"); + var acText = new TextInfo(dictString, closestPt, AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + //加入刷新队列 + drawEntitys.Enqueue(acText); + }); + + options = jig.SetOptions(per.PickedPoint); + + // 如果没有这个,那么空格只会是 PromptStatus.None 而不是 PromptStatus.Keyword + // options.Keywords.Add(" ", " ", "空格结束啊"); + // jig.SetSpaceIsKeyword(); + + bool flag = true; + while (flag) + { + var pr = jig.Drag(); + if (pr.Status == PromptStatus.Keyword) + { + switch (pr.StringResult) + { + case "A": + tr.Editor.WriteMessage($"\n 您触发了关键字{pr.StringResult}"); + flag = false; + break; + case " ": + tr.Editor.WriteMessage("\n 触发关键字空格"); + flag = false; + break; + } + } + else if (pr.Status != PromptStatus.OK)//PromptStatus.None == 右键,空格,回车,都在这里结束 + { + tr.Editor.WriteMessage(Environment.NewLine + pr.Status.ToString()); + return; + } + else + flag = false; + } + tr.CurrentSpace.AddEntity(jig.Entitys); + } + + [CommandMethod("TestCmd_loop")] + public void TestCmd_loop() + { + DocumentCollection dm = + Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager; + Editor ed = dm.MdiActiveDocument.Editor; + // Create and add our message filter + MyMessageFilter filter = new(); + System.Windows.Forms.Application.AddMessageFilter(filter); + // Start the loop + while (true) + { + // Check for user input events + System.Windows.Forms.Application.DoEvents(); + // Check whether the filter has set the flag + if (filter.bCanceled == true) + { + ed.WriteMessage("\nLoop cancelled."); + break; + } + ed.WriteMessage($"\nInside while loop...and {filter.Key}"); + } + // We're done - remove the message filter + System.Windows.Forms.Application.RemoveMessageFilter(filter); + } + // Our message filter class + public class MyMessageFilter : IMessageFilter + { + public const int WM_KEYDOWN = 0x0100; + public bool bCanceled = false; + public Keys Key { get; private set; } + public bool PreFilterMessage(ref Message m) + { + if (m.Msg == WM_KEYDOWN) + { + // Check for the Escape keypress + Keys kc = (Keys)(int)m.WParam & Keys.KeyCode; + if (m.Msg == WM_KEYDOWN && kc == Keys.Escape) + { + bCanceled = true; + } + Key = kc; + // Return true to filter all keypresses + return true; + } + // Return false to let other messages through + return false; + } + } + + + [CommandMethod("TestCmd_QuickText")] + static public void TestCmd_QuickText() + { + var dm = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + + PromptStringOptions pso = new("\nEnter text string") + { + AllowSpaces = true + }; + var pr = ed.GetString(pso); + if (pr.Status != PromptStatus.OK) + return; + + var tr = doc.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + db.CurrentSpaceId, OpenMode.ForWrite + ); + // Create the text object, set its normal and contents + + var acText = new TextInfo(pr.StringResult, + Point3d.Origin, + AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + acText.Normal = ed.CurrentUserCoordinateSystem.CoordinateSystem3d.Zaxis; + btr.AppendEntity(acText); + tr.AddNewlyCreatedDBObject(acText, true); + + // Create our jig + var pj = new TextPlacementJig(tr, db, acText); + // Loop as we run our jig, as we may have keywords + PromptStatus stat = PromptStatus.Keyword; + while (stat == PromptStatus.Keyword) + { + PromptResult res = ed.Drag(pj); + stat = res.Status; + if ( + stat != PromptStatus.OK && + stat != PromptStatus.Keyword + ) + return; + } + tr.Commit(); + } + } + class TextPlacementJig : EntityJig + { + // Declare some internal state + readonly Database _db; + readonly Transaction _tr; + Point3d _position; + double _angle, _txtSize; + // Constructor + public TextPlacementJig( + Transaction tr, Database db, Entity ent + ) : base(ent) + { + _db = db; + _tr = tr; + _angle = 0; + _txtSize = 1; + } + protected override SamplerStatus Sampler( + JigPrompts jp + ) + { + // We acquire a point but with keywords + JigPromptPointOptions po = + new( + "\nPosition of text" + ); + po.UserInputControls = + (UserInputControls.Accept3dCoordinates | + UserInputControls.NullResponseAccepted | + UserInputControls.NoNegativeResponseAccepted | + UserInputControls.GovernedByOrthoMode); + po.SetMessageAndKeywords( + "\nSpecify position of text or " + + "[Bold/Italic/LArger/Smaller/" + + "ROtate90/LEft/Middle/RIght]: ", + "Bold Italic LArger Smaller " + + "ROtate90 LEft Middle RIght" + ); + PromptPointResult ppr = jp.AcquirePoint(po); + if (ppr.Status == PromptStatus.Keyword) + { + switch (ppr.StringResult) + { + case "Bold": + { + break; + } + case "Italic": + { + break; + } + case "LArger": + { + // Multiple the text size by two + _txtSize *= 2; + break; + } + case "Smaller": + { + // Divide the text size by two + _txtSize /= 2; + break; + } + case "ROtate90": + { + // To rotate clockwise we subtract 90 degrees and + // then normalise the angle between 0 and 360 + _angle -= Math.PI / 2; + while (_angle < Math.PI * 2) + { + _angle += Math.PI * 2; + } + break; + } + case "LEft": + { + break; + } + case "RIght": + { + break; + } + case "Middle": + { + break; + } + } + return SamplerStatus.OK; + } + else if (ppr.Status == PromptStatus.OK) + { + // Check if it has changed or not (reduces flicker) + if ( + _position.DistanceTo(ppr.Value) < + Tolerance.Global.EqualPoint + ) + return SamplerStatus.NoChange; + _position = ppr.Value; + return SamplerStatus.OK; + } + return SamplerStatus.Cancel; + } + protected override bool Update() + { + // Set properties on our text object + DBText txt = (DBText)Entity; + txt.Position = _position; + txt.Height = _txtSize; + txt.Rotation = _angle; + return true; + } + } +} diff --git a/tests/Test/TestLisp.cs b/tests/Test/TestLisp.cs new file mode 100644 index 0000000..c6e1f47 --- /dev/null +++ b/tests/Test/TestLisp.cs @@ -0,0 +1,122 @@ +namespace Test +{ + public class TestLisp + { + //定义lisp函数 + [LispFunction("LispTest_RunLisp")] + public static object LispTest_RunLisp(ResultBuffer rb) + { + CmdTest_RunLisp(); + return null!; + } + + //模态命令,只有当CAD发出命令提示或当前没有其他的命令或程序活动的时候才可以被触发 + [CommandMethod("CmdTest_RunLisp1", CommandFlags.Modal)] + //透明命令,可以在一个命令提示输入的时候触发例如正交切换,zoom等 + [CommandMethod("CmdTest_RunLisp2", CommandFlags.Transparent)] + //选择图元之后执行命令将可以从 获取图元 + [CommandMethod("CmdTest_RunLisp3", CommandFlags.UsePickSet)] + //命令执行前已选中部分实体.在命令执行过程中这些标记不会被清除 + [CommandMethod("CmdTest_RunLisp4", CommandFlags.Redraw)] + //命令不能在透视图中使用 + [CommandMethod("CmdTest_RunLisp5", CommandFlags.NoPerspective)] + //命令不能通过 MULTIPLE命令 重复触发 + [CommandMethod("CmdTest_RunLisp6", CommandFlags.NoMultiple)] + //不允许在模型空间使用命令 + [CommandMethod("CmdTest_RunLisp7", CommandFlags.NoTileMode)] + //不允许在布局空间使用命令 + [CommandMethod("CmdTest_RunLisp8", CommandFlags.NoPaperSpace)] + //命令不能在OEM产品中使用 + [CommandMethod("CmdTest_RunLisp9", CommandFlags.NoOem)] + //不能直接使用命令名调用,必须使用 组名.全局名 调用 + [CommandMethod("CmdTest_RunLisp10", CommandFlags.Undefined)] + //定义lisp方法.已废弃 请使用lispfunction + [CommandMethod("CmdTest_RunLisp11", CommandFlags.Defun)] + //命令不会被存储在新的命令堆上 + [CommandMethod("CmdTest_RunLisp12", CommandFlags.NoNewStack)] + //命令不能被内部锁定(命令锁) + [CommandMethod("CmdTest_RunLisp13", CommandFlags.NoInternalLock)] + //调用命令的文档将会被锁定为只读 + [CommandMethod("CmdTest_RunLisp14", CommandFlags.DocReadLock)] + //调用命令的文档将会被锁定,类似document.lockdocument + [CommandMethod("CmdTest_RunLisp15", CommandFlags.DocExclusiveLock)] + //命令在CAD运行期间都能使用,而不只是在当前文档 + [CommandMethod("CmdTest_RunLisp16", CommandFlags.Session)] + //获取用户输入时,可以与属性面板之类的交互 + [CommandMethod("CmdTest_RunLisp17", CommandFlags.Interruptible)] + //命令不会被记录在命令历史记录 + [CommandMethod("CmdTest_RunLisp18", CommandFlags.NoHistory)] + //命令不会被 UNDO取消 + [CommandMethod("CmdTest_RunLisp19", CommandFlags.NoUndoMarker)] + //不能在参照块中使用命令 + [CommandMethod("CmdTest_RunLisp20", CommandFlags.NoBlockEditor)] +#if ac2009 + //acad09增,不会被动作录制器 捕捉到 + [CommandMethod("CmdTest_RunLisp21", CommandFlags.NoActionRecording)] + //acad09增,会被动作录制器捕捉 + [CommandMethod("CmdTest_RunLisp22", CommandFlags.ActionMacro)] +#endif +#if !NET35 + //推断约束时不能使用命令 + [CommandMethod("CmdTest_RunLisp23", CommandFlags.NoInferConstraint)] + //命令允许在选择图元时临时显示动态尺寸 + [CommandMethod("CmdTest_RunLisp24", CommandFlags.TempShowDynDimension)] +#endif + public static void CmdTest_RunLisp() + { + // 测试方法1: (command "CmdTest_RunLisp1") + // 测试方式2: (LispTest_RunLisp) + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + + var sb = new StringBuilder(); + foreach (var item in Enum.GetValues(typeof(EditorEx.RunLispFlag))) + { + sb.Append((byte)item); + sb.Append(','); + } + sb.Remove(sb.Length - 1, 1); + var option = new PromptIntegerOptions($"\n输入RunLispFlag枚举值:[{sb}]"); + var ppr = ed.GetInteger(option); + + if (ppr.Status != PromptStatus.OK) + return; + var flag = (EditorEx.RunLispFlag)ppr.Value; + + if (flag == EditorEx.RunLispFlag.AdsQueueexpr) + { + // 同步 + Env.Editor.RunLisp("(setq a 10)(princ)", + EditorEx.RunLispFlag.AdsQueueexpr); + Env.Editor.RunLisp("(princ a)", + EditorEx.RunLispFlag.AdsQueueexpr);//成功输出 + } + else if (flag == EditorEx.RunLispFlag.AcedEvaluateLisp) + { + // 使用(command "CmdTest_RunLisp1")发送,然后 !b 查看变量,acad08是有值的,高版本是null + var strlisp0 = "(setq b 20)"; + var res0 = Env.Editor.RunLisp(strlisp0, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + + var strlisp1 = "(defun f1( / )(princ \"aa\"))"; + var res1 = Env.Editor.RunLisp(strlisp1, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + + var strlisp2 = "(defun f2( / )(command \"line\"))"; + var res2 = Env.Editor.RunLisp(strlisp2, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + } + else if (flag == EditorEx.RunLispFlag.SendStringToExecute) + { + //测试异步 + //(command "CmdTest_RunLisp1")和(LispTest_RunLisp)4都是异步 + var str = "(setq c 40)(princ)"; + Env.Editor.RunLisp(str, + EditorEx.RunLispFlag.SendStringToExecute); //异步,后发送 + Env.Editor.RunLisp("(princ c)", + EditorEx.RunLispFlag.AdsQueueexpr); //同步,先发送了,输出是null + } + } + } +} diff --git a/tests/Test/TestLoop.cs b/tests/Test/TestLoop.cs new file mode 100644 index 0000000..8992df9 --- /dev/null +++ b/tests/Test/TestLoop.cs @@ -0,0 +1,36 @@ +using IFoxCAD.Basal; + +using System.Diagnostics.CodeAnalysis; +namespace Test +{ + public class TestLoop + { + [CommandMethod("testloop")] + public void Testloop() + { + var loop = new LoopList + { + 0, + 1, + 2, + 3, + 4, + 5 + }; + + + + + Env.Print(loop); + + loop.SetFirst(loop.Last); + Env.Print(loop); + Env.Print(loop.Min()); + loop.SetFirst(new LoopListNode (loop.Min() ,loop)); + Env.Print(loop); + + + + } + } +} diff --git a/tests/Test/TestMirrorFile.cs b/tests/Test/TestMirrorFile.cs new file mode 100644 index 0000000..b5f212f --- /dev/null +++ b/tests/Test/TestMirrorFile.cs @@ -0,0 +1,73 @@ +public class MirrorFile +{ + const string file = "D:/JX.dwg"; + const string fileSave = "D:/JX222.dwg"; + + /// + /// 测试:后台打开图纸,镜像文字是否存在文字偏移 + /// 答案:不存在 + /// + [CommandMethod("CmdTest_MirrorFile")] + public static void CmdTest_MirrorFile() + { + using var tr = new DBTrans(file, openMode: FileOpenMode.OpenForReadAndReadShare); + + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + foreach (ObjectId entId in modelSpace) + { + var dbText = tr.GetObject(entId, OpenMode.ForRead)!; + if (dbText is null) + continue; + + dbText.UpgradeOpen(); + var pos = dbText.Position; + //text.Move(pos, Point3d.Origin); + //Y轴 + dbText.Mirror(Point3d.Origin, new Point3d(0, 1, 0)); + //text.Move(Point3d.Origin, pos); + dbText.DowngradeOpen(); + } + }); + tr.Database.SaveAs(fileSave, DwgVersion.AC1021/*AC1021 AutoCAD 2007/2008/2009.*/); + } + + /// + /// 测试:后台设置 dbText.IsMirroredInX 属性会令文字偏移 + /// 答案:存在,并提出解决方案 + /// + [CommandMethod("CmdTest_MirrorFile2")] + public static void CmdTest_MirrorFile2() + { + using var tr = new DBTrans(file, openMode: FileOpenMode.OpenForReadAndReadShare); + + tr.Database.DBTextDeviation(() => { + + var yaxis = new Point3d(0, 1, 0); + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + foreach (ObjectId entId in modelSpace) + { + var entity = tr.GetObject(entId, OpenMode.ForWrite)!; + if (entity is DBText dbText) + { + dbText.Mirror(Point3d.Origin, yaxis); + dbText.IsMirroredInX = true; //这句将导致文字偏移 + + //指定文字的垂直对齐方式 + if (dbText.VerticalMode == TextVerticalMode.TextBase) + dbText.VerticalMode = TextVerticalMode.TextBottom; + + //指定文字的水平对齐方式 + dbText.HorizontalMode = dbText.HorizontalMode switch + { + TextHorizontalMode.TextLeft => TextHorizontalMode.TextRight, + TextHorizontalMode.TextRight => TextHorizontalMode.TextLeft, + _ => dbText.HorizontalMode + }; + dbText.AdjustAlignment(tr.Database); + } + } + }); + }); + tr.Database.SaveAs(fileSave, DwgVersion.AC1021/*AC1021 AutoCAD 2007/2008/2009.*/); + } +} \ No newline at end of file diff --git a/tests/Test/TestPoint.cs b/tests/Test/TestPoint.cs new file mode 100644 index 0000000..46efe2e --- /dev/null +++ b/tests/Test/TestPoint.cs @@ -0,0 +1,160 @@ +using System.Diagnostics; + +namespace Test +{ + public class TestPoint + { + /// + /// 红黑树排序点集 + /// + [CommandMethod("TestptSortedSet")] + public void TestptSortedSet() + { + var ss1 = new SortedSet(); + ss1.Add(new Point2d(1, 1)); + ss1.Add(new Point2d(4.6, 2)); + ss1.Add(new Point2d(8, 3)); + ss1.Add(new Point2d(4, 3)); + ss1.Add(new Point2d(5, 40)); + ss1.Add(new Point2d(6, 5)); + ss1.Add(new Point2d(1, 6)); + ss1.Add(new Point2d(7, 6)); + ss1.Add(new Point2d(9, 6)); + + /*判断区间,超过就中断*/ + foreach (var item in ss1) + { + if (item.X > 3 && item.X < 7) + { + Debug.WriteLine(item); + } + else if (item.X >= 7) + { + break; + } + } + } + + + + [CommandMethod("TestptGethash")] + public void TestptGethash() + { + // test + var pt = Env.Editor.GetPoint("pick pt").Value; + //Tools.TestTimes2(1_000_000, "新语法", () => { + // pt.GetHashString2(); + //}); + Tools.TestTimes2(1_000_000, "旧语法", () => { + pt.GetHashString(); + }); + } + + [CommandMethod("Testpoint3d")] + public void TestPoint3d() + { + Env.Print($"4位小数的hash:{new Point3d(0.0_001, 0.0_002, 0.0).GetHashCode()}"); + Env.Print($"5位小数的hash:{new Point3d(0.00_001, 0.00_002, 0.0).GetHashCode()}"); + Env.Print($"6位小数的hash:{new Point3d(0.000_001, 0.000_002, 0.0).GetHashCode()}"); + Env.Print($"7位小数的hash:{new Point3d(0.000_0_001, 0.000_0_002, 0.0).GetHashCode()}"); + Env.Print($"8位小数的hash:{new Point3d(0.000_00_001, 0.000_00_002, 0.0).GetHashCode()}"); + Env.Print($"9位小数的hash:{new Point3d(0.000_000_001, 0.000_000_002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_01, 0.0).GetHashCode()}"); + } + + [CommandMethod("Testlistequalspeed")] + public void Testlistequalspeed() + { + var lst1 = new List { 1, 2, 3, 4 }; + var lst2 = new List { 1, 2, 3, 4}; + lst1.EqualsAll(null); + Tools.TestTimes2(1000000, "eqaulspeed:", () => { + lst1.EqualsAll(lst2); + }); + + + } + + [CommandMethod("Testcontains")] + public void Testcontains() + { + // test list and dict contains speed + var lst = new List { 1, 2, 3, 4 , 5,6,7,8,9,10, 11,12,13,14,15,16,17,18,19,20}; + var hashset = new HashSet { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var dict = new Dictionary + { + { 1, 0 }, + { 2, 1 }, + { 3, 2 }, + { 4, 3 }, + { 5, 4 }, + { 6, 5 }, + { 7, 6 }, + { 8, 7 }, + { 9, 8 }, + { 10, 9 }, + { 11, 11 }, + { 12, 12 }, + { 13, 13 }, + { 14, 14 }, + { 15, 15 }, + { 16, 16 }, + { 17, 17 }, + { 18, 18 }, + { 19, 19 }, + { 20, 20 }, + }; + + Tools.TestTimes2(100_0000, "list:", () => { + lst.Contains(20); + }); + + Tools.TestTimes2(100_0000, "hashset:", () => { + hashset.Contains(20); + }); + + Tools.TestTimes2(100_0000, "dict:", () => { + dict.ContainsKey(20); + }); + + } + + + + + + } + + +} \ No newline at end of file diff --git a/tests/Test/TestQuadTree.cs b/tests/Test/TestQuadTree.cs new file mode 100644 index 0000000..7670eae --- /dev/null +++ b/tests/Test/TestQuadTree.cs @@ -0,0 +1,461 @@ +namespace Test; + +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 +/* + * 这里属于用户调用例子, + * 调用时候必须要继承它,再提供给四叉树 + * 主要是用户可以扩展属性 + */ +public class CadEntity : QuadEntity +{ + public ObjectId ObjectId; + //这里加入其他字段 + public List? Link;//碰撞链 + public System.Drawing.Color Color; + public double Angle; + public CadEntity(ObjectId objectId, Rect box) : base(box) + { + ObjectId = objectId; + } + public int CompareTo(CadEntity? other) + { + if (other == null) + return -1; + return GetHashCode() ^ other.GetHashCode(); + } + public override int GetHashCode() + { + return (base.GetHashCode(), ObjectId.GetHashCode()).GetHashCode(); + } +} +#pragma warning restore CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + + + + + +public partial class TestQuadTree +{ + QuadTree _quadTreeRoot; + #region 四叉树创建并加入 + [CommandMethod("Test_QuadTree")] + public void Test_QuadTree() + { + using var tr = new DBTrans(); + + Rect dbExt; + //使用数据库边界来进行 + var dbExtent = tr.Database.GetValidExtents3d(); + if (dbExtent == null) + { + //throw new ArgumentException("画一个矩形"); + + //这个初始值的矩形是很有意义, + //主要是四叉树分裂过程中产生多个Rect,Rect内有很多重复的double值,是否可以内存复用,以此减少内存大小? + //接着想了一下,Rect可以是int,long,这样可以利用位运算它扩展和缩小, + //最小就是1,并且可以控制四叉树深度,不至于无限递归. + //而且指针长度跟值是一样的,所以就不需要复用了,毕竟跳转一个函数地址挺麻烦的. + //但是因为啊惊懒的原因,并没有单独制作这样的矩形, + //而且非常糟糕的是,c#不支持模板约束运算符,使得值类型之间需要通过一层接口来委婉处理,拉低了效率..引用类型倒是无所谓.. + //要么忍着,要么换c++去搞四叉树吧 + dbExt = new Rect(0, 0, 1 << 10, 1 << 10); + } + else + { + var a = new Point2d(dbExtent.Value.MinPoint.X, dbExtent.Value.MinPoint.Y); + var b = new Point2d(dbExtent.Value.MaxPoint.X, dbExtent.Value.MaxPoint.Y); + dbExt = new Rect(a, b); + } + + //创建四叉树 + _quadTreeRoot = new QuadTree(dbExt); + + //数据库边界 + var pl = dbExt.ToPoints(); + var databaseBoundary = new List<(Point3d, double, double, double)> + { + (new Point3d(pl[0].X,pl[0].Y,0),0,0,0), + (new Point3d(pl[1].X,pl[1].Y,0),0,0,0), + (new Point3d(pl[2].X,pl[2].Y,0),0,0,0), + (new Point3d(pl[3].X,pl[3].Y,0),0,0,0), + }; + tr.CurrentSpace.AddPline(databaseBoundary); + + //生成多少个图元,导致cad会令undo出错(八叉树深度过大 treemax) + //int maximumItems = 30_0000; + int maximumItems = 1000; + + //随机图元生成 + List ces = new(); //用于随机获取图元 + Timer.RunTime(() => { + //生成外边界和随机圆形 + var grc = GenerateRandomCircle(maximumItems, dbExt); + foreach (var ent in grc) + { + //初始化图元颜色 + ent.ColorIndex = 1; //Color.FromRgb(0, 0, 0);//黑色 + var edge = ent.GeometricExtents; + //四叉树数据 + var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); + var entId = tr.CurrentSpace.AddEntity(ent); + var ce = new CadEntity(entId, entRect) + { + Color = Utility.RandomColor + }; + ces.Add(ce); + /*加入随机点*/ + var p = edge.MinPoint + new Vector3d(10, 10, 0); + entRect = new Rect(p.Point2d(), p.Point2d()); + entId = tr.CurrentSpace.AddEntity(new DBPoint(p)); + var dbPointCe = new CadEntity(entId, entRect); + ces.Add(dbPointCe); + } + }, Timer.TimeEnum.Millisecond, "画圆消耗时间:");//30万图元±3秒.cad2021 + + //测试只加入四叉树的时间 + Timer.RunTime(() => { + for (int i = 0; i < ces.Count; i++) + { + _quadTreeRoot.Insert(ces[i]); + } + }, Timer.TimeEnum.Millisecond, "插入四叉树时间:");//30万图元±0.7秒.cad2021 + + tr.Editor.WriteMessage($"\n加入图元数量:{maximumItems}"); + } + + /// + /// 创建随机圆形 + /// + /// 创建数量 + /// 数据库边界 + static IEnumerable GenerateRandomCircle(int createNumber, Rect dbExt) + { + var x1 = (int)dbExt.X; + var x2 = (int)(dbExt.X + dbExt.Width); + var y1 = (int)dbExt.Y; + var y2 = (int)(dbExt.Y + dbExt.Height); + + var rand = Utility.GetRandom(); + for (int i = 0; i < createNumber; i++) + { + var x = rand.Next(x1, x2) + rand.NextDouble(); + var y = rand.Next(y1, y2) + rand.NextDouble(); + yield return EntityEx.CreateCircle(new Point3d(x, y, 0), rand.Next(1, 100)); //起点,终点 + } + } + + /*TODO 啊惊: 有点懒不想改了*/ +#if true2 + + //选择加入到四叉树 + [CommandMethod("CmdTest_QuadTree21")] + public void CmdTest_QuadTree21() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n选择单个图元加入已有的四叉树"); + + var ss = ed.Ssget(); + if (ss.Count == 0) + return; + + AddQuadTreeRoot(db, ed, ss); + } + + //自动加入全图到四叉树 + [CommandMethod("CmdTest_QuadTree20")] + public void CmdTest_QuadTree20() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n自动加入全图到四叉树"); + + var ss = new List(); + int entnum = 0; + var time1 = Timer.RunTime(() => { + db.Action(tr => { + db.TraverseBlockTable(tr, btRec => { + if (!btRec.IsLayout)//布局跳过 + return false; + + foreach (var item in btRec) + { + //var ent = item.ToEntity(tr); + ss.Add(item); + ++entnum;//图元数量:100000, 遍历全图时间:0.216秒 CmdTest_QuadTree2 + } + return false; + }); + }); + }); + ed.WriteMessage($"\n图元数量:{entnum}, 遍历全图时间:{time1 / 1000.0}秒"); + + //清空原有的 + _quadTreeRoot = null; + AddQuadTreeRoot(db, ed, ss); + } + + void AddQuadTreeRoot(Database db, Editor ed, List ss) + { + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的,重新初始化"); + + Rect dbExt; + //使用数据库边界来进行 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + { + //throw new ArgumentException("画一个矩形"); + + //测试时候画个矩形,在矩形内画随机坐标的圆形 + dbExt = new Rect(0, 0, 32525, 32525); + } + else + { + dbExt = new Rect(dbExtent.Value.MinPoint.Point2d(), dbExtent.Value.MaxPoint.Point2d()); + } + _quadTreeRoot = new(dbExt); + } + + /* 测试: + * 为了测试删除内容释放了分支,再重复加入是否报错 + * 先创建 CmdTest_QuadTree1 + * 再减去 CmdTest_QuadTree0 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + * 然后加入 CmdTest_QuadTree2 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + */ + + List ces = new(); + db.Action(tr => { + ss.ForEach(entId => { + var ent = entId.ToEntity(tr); + if (ent is null) + return; + var edge = new EdgeEntity(ent); + //四叉树数据 + var ce = new CadEntity(entId, edge.Edge) + { + Color = Utility.RandomColor + }; + ces.Add(ce); + + edge.Dispose(); + }); + }); + + var time2 = Timer.RunTime(() => { + _quadTreeRoot.Insert(ces); + }); + ed.WriteMessage($"\n图元数量:{ces.Count}, 加入四叉树时间:{time2 / 1000.0}秒"); + } +#endif + + #endregion + + /*TODO 啊惊: 有点懒不想改了*/ +#if true2 + + #region 节点边界显示 + //四叉树减去节点 + [CommandMethod("CmdTest_QuadTree0")] + public void CmdTest_QuadTree0() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + //var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n四叉树减区"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + var rect = GetCorner(ed); + if (rect is null) + return; + _quadTreeRoot.Remove(rect); + } + + //创建节点边界 + [CommandMethod("CmdTest_QuadTree00")] + public void CmdTest_CreateNodesRect() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n创建边界"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + + //此处发现了一个事务处理的bug,提交数量过多的时候,会导致 ctrl+z 无法回滚, + //需要把事务放在循环体内部 + //报错: 0x6B00500A (msvcr80.dll)处(位于 acad.exe 中)引发的异常: 0xC0000005: 写入位置 0xFFE00000 时发生访问冲突。 + //画出所有的四叉树节点边界,因为事务放在外面引起 + var nodeRects = new List(); + _quadTreeRoot.ForEach(node => { + nodeRects.Add(node); + return false; + }); + var rectIds = new List(); + foreach (var item in nodeRects)//Count = 97341 当数量接近这个量级 + { + db.Action(tr => { + var pts = item.ToPoints(); + var rec = EntityAdd.AddPolyLineToEntity(pts.ToPoint2d()); + rec.ColorIndex = 250; + rectIds.Add(tr.AddEntityToMsPs(db, rec)); + }); + } + db.Action(tr => { + db.CoverGroup(tr, rectIds); + }); + + //获取四叉树深度 + int dep = 0; + _quadTreeRoot.ForEach(node => { + dep = dep > node.Depth ? dep : node.Depth; + return false; + }); + ed.WriteMessage($"\n四叉树深度是: {dep}"); + } + #endregion + +#endif + + #region 四叉树查询节点 + //选择范围改图元颜色 + [CommandMethod("CmdTest_QuadTree3")] + public void CmdTest_QuadTree3() + { + Ssget(QuadTreeSelectMode.IntersectsWith); + } + + [CommandMethod("CmdTest_QuadTree4")] + public void CmdTest_QuadTree4() + { + Ssget(QuadTreeSelectMode.Contains); + } + + /// + /// 改颜色 + /// + /// + void Ssget(QuadTreeSelectMode mode) + { + using var tr = new DBTrans(); + + if (_quadTreeRoot is null) + return; + var rect = GetCorner(tr.Editor); + if (rect is null) + return; + + tr.Editor.WriteMessage("选择模式:" + mode); + + //仿选择集 + var ces = _quadTreeRoot.Query(rect, mode); + ces.ForEach(item => { + var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); + ent.Color = Color.FromColor(item.Color); + ent.DowngradeOpen(); + ent.Dispose(); + }); + } + + /// + /// 交互获取 + /// + /// + /// +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + public static Rect? GetCorner(Editor ed) + { + var optionsA = new PromptPointOptions($"{Environment.NewLine}起点位置:"); + var pprA = ed.GetPoint(optionsA); + if (pprA.Status != PromptStatus.OK) + return null; + var optionsB = new PromptCornerOptions(Environment.NewLine + "输入矩形角点2:", pprA.Value) + { + UseDashedLine = true,//使用虚线 + AllowNone = true,//回车 + }; + var pprB = ed.GetCorner(optionsB); + if (pprB.Status != PromptStatus.OK) + return null!; + + return new Rect(new Point2d(pprA.Value.X, pprA.Value.Y), + new Point2d(pprB.Value.X, pprB.Value.Y), + true); + } +#pragma warning restore CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + + #endregion +} + +//public partial class TestQuadTree +//{ +// public void Cmd_tt6() +// { +// using var tr = new DBTrans(); +// var ed = tr.Editor; +// //创建四叉树,默认参数无所谓 +// var TreeRoot = new QuadTree(new Rect(0, 0, 32525, 32525)); + +// var fil = OpFilter.Bulid(e => e.Dxf(0) == "LINE"); +// var psr = ed.SSGet("\n 选择需要连接的直线", fil); +// if (psr.Status != PromptStatus.OK) return; +// var LineEnts = new List(psr.Value.GetEntities(OpenMode.ForWrite)!); +// //将实体插入到四岔树 +// foreach (var line in LineEnts) +// { +// var edge = line.GeometricExtents; +// var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); +// var ce = new CadEntity(line.Id, entRect) +// { +// //四叉树数据 +// Angle = line.Angle +// }; +// TreeRoot.Insert(ce); +// } + +// var ppo = new PromptPointOptions(Environment.NewLine + "\n指定标注点:<空格退出>") +// { +// AllowArbitraryInput = true,//任意输入 +// AllowNone = true //允许回车 +// }; +// var ppr = ed.GetPoint(ppo);//用户点选 +// if (ppr.Status != PromptStatus.OK) +// return; +// var rect = new Rect(ppr.Value.Point2d(), 100, 100); +// tr.CurrentSpace.AddEntity(rect.ToPolyLine());//显示选择靶标范围 + +// var nent = TreeRoot.FindNearEntity(rect);//查询最近实体,按逆时针 +// var ent = tr.GetObject(nent.ObjectId, OpenMode.ForWrite);//打开实体 +// ent.ColorIndex = Utility.GetRandom().Next(1, 256);//1~256随机色 +// ent.DowngradeOpen();//实体降级 +// ent.Dispose(); + +// var res = TreeRoot.Query(rect, QuadTreeSelectMode.IntersectsWith);//查询选择靶标范围相碰的ID +// res.ForEach(item => { +// if (item.Angle == 0 || item.Angle == Math.PI) //过滤直线角度为0或180的直线 +// { +// var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); +// ent.ColorIndex = Utility.GetRandom().Next(1, 7); +// ent.DowngradeOpen(); +// ent.Dispose(); +// } +// }); +// } +//} \ No newline at end of file diff --git a/tests/Test/Testid.cs b/tests/Test/Testid.cs new file mode 100644 index 0000000..d93b823 --- /dev/null +++ b/tests/Test/Testid.cs @@ -0,0 +1,74 @@ +namespace Test +{ + public class Testid + { + [CommandMethod("testid")] + public void TestId() + { + using var tr = new DBTrans(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + tr.Dispose(); + + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == PromptStatus.OK) + { + res.ObjectId.Erase(); + } + //using (var tr = new DBTrans()) + //{ + // var res = Env.Editor.GetEntity("\npick ent:"); + // if(res.Status == PromptStatus.OK) + // { + // res.ObjectId.Erase(); + // } + + //} + } + + [CommandMethod("testmycommand")] + public void TestMyCommand() + { + using var dbtrans = new DBTrans(Env.Document, true, false); + using var trans = Env.Database.TransactionManager.StartTransaction(); + + var l1 = new Line(new Point3d(0, 0, 0), new Point3d(100, 100, 0)); + var blkred = trans.GetObject(Env.Database.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + blkred.AppendEntity(l1); + trans.AddNewlyCreatedDBObject(l1, true); + trans.Commit(); + //dbtrans.Dispose(); + } + [CommandMethod("testtextstyle")] + public void TestTextStyle() + { + using var tr = new DBTrans(); + tr.TextStyleTable.Add("宋体", "宋体.ttf", 0.8); + + tr.TextStyleTable.Add("宋体1", FontTTF.宋体, 0.8); + tr.TextStyleTable.Add("仿宋体", FontTTF.仿宋, 0.8); + tr.TextStyleTable.Add("fsgb2312", FontTTF.仿宋GB2312, 0.8); + tr.TextStyleTable.Add("arial", FontTTF.Arial, 0.8); + tr.TextStyleTable.Add("romas", FontTTF.Romans, 0.8); + + + + tr.TextStyleTable.Add("daziti", ttr => + { + ttr.FileName = "ascii.shx"; + ttr.BigFontFileName = "gbcbig.shx"; + }); + } + + [CommandMethod("testtextstylechange")] + public void TestTextStyleChange() + { + using var tr = new DBTrans(); + + + tr.TextStyleTable.AddWithChange("宋体1", "simfang.ttf", height: 5); + tr.TextStyleTable.AddWithChange("仿宋体", "宋体.ttf"); + tr.TextStyleTable.AddWithChange("fsgb2312", "Romans", "gbcbig"); + } + } +} diff --git a/tests/Test/testConvexHull.cs b/tests/Test/testConvexHull.cs index ee792c6..19c3da7 100644 --- a/tests/Test/testConvexHull.cs +++ b/tests/Test/testConvexHull.cs @@ -10,12 +10,12 @@ using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Colors; -namespace test +namespace Test { - public class testConvexHull + public class TestConvexHull { [CommandMethod("testch")] - public void testch() + public void Testch() { //using var tr = new DBTrans(); //var pts = new List(); diff --git a/tests/Test/testblock.cs b/tests/Test/testblock.cs new file mode 100644 index 0000000..ef615be --- /dev/null +++ b/tests/Test/testblock.cs @@ -0,0 +1,631 @@ +namespace Test; +public class TestBlock +{ + //块定义 + [CommandMethod("blockdef")] + public void BlockDef() + { + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Add("test", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => //图元 + { + return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; + }, + () => //属性定义 + { + var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; + var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; + return new List { id1, id2 }; + } + ); + //ObjectId objectId = tr.BlockTable.Add("a");//新建块 + //objectId.GetObject().AddEntity();//测试添加空实体 + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + + }, + () => { + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var acText = new TextInfo("123", Point3d.Origin, AttachmentPoint.BaseLeft) + .AddDBTextToEntity(); + + return new List { line, acText }; + }); + } + //修改块定义 + [CommandMethod("blockdefchange")] + public void BlockDefChange() + { + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + //tr.BlockTable.Change("test", btr => + //{ + // btr.Origin = new Point3d(5, 5, 0); + // btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); + // btr.GetEntities() + // .ToList() + // .ForEach(e => e.Flush()); //刷新块显示 + + //}); + + + + + tr.BlockTable.Change("test", btr => { + foreach (var id in btr) + { + var ent = tr.GetObject(id); + using (ent.ForWrite()) + { + if (ent is Dimension dBText) + { + dBText.DimensionText = "234"; + dBText.RecomputeDimensionBlock(true); + } + + if (ent is Hatch hatch) + { + hatch.ColorIndex = 0; + + } + + + } + + } + }); + tr.Editor.Regen(); + } + + [CommandMethod("insertblockdef")] + public void InsertBlockDef() + { + using var tr = new DBTrans(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + tr.BlockTable.Add("test1", line1, line2, att1, att2); + + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + + + var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); + var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); + var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; + var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; + tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 + //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 + + var def1 = new Dictionary + { + { "tagTest1", "1" }, + { "tagTest2", "2" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); + var def2 = new Dictionary + { + { "tagTest3", "1" }, + { "tagTest4", "" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); + tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); + } + + [CommandMethod("addattsdef")] + public void AddAttsDef() + { + using var tr = new DBTrans(); + var blockid = Env.Editor.GetEntity("pick block:").ObjectId; + var blockref = tr.GetObject(blockid).BlockTableRecord; + + var att1 = new AttributeDefinition() { Position = new Point3d(20, 20, 0), Tag = "addtagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + + tr.BlockTable.AddAttsToBlocks(blockref, new List { att1, att2 }); + } + + [CommandMethod("testblocknullbug")] + public void TestBlockNullBug() + { + using var tr = new DBTrans(); + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); + } + + [CommandMethod("test_block_file")] + public void TestBlockFile() + { + var tr = new DBTrans(); + var id = tr.BlockTable.GetBlockFrom(@"C:\Users\vic\Desktop\test.dwg", false); + tr.CurrentSpace.InsertBlock(Point3d.Origin, id); + } + + + [CommandMethod("testclip")] + public void TestClipBlock() + { + using var tr = new DBTrans(); + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), + new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) + ); + } + ); + //tr.BlockTable.Add("hah"); + var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); + var bref = tr.GetObject(id); + var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; + bref.ClipBlockRef(pts); + + var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); + var bref1 = tr.GetObject(id); + + bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); + } + + /// + /// 给用户的测试程序,不知道对错 + /// + [CommandMethod("test_block_ej")] + public void EJ() + { + using (var tr = new DBTrans()) + { + + //Point3d.Origin.AddBellowToModelSpace(100, 100, 5, 3, 30);//画波纹管 + + //Database db2 = new Database(false, true); + //string fullFileName = @".\MyBlockDwgFile\001.dwg"; + //db2.ReadDwgFile(fullFileName, System.IO.FileShare.Read, true, null); + //db2.CloseInput(true); + //string blockName = "test"; + //if (!tr.BlockTable.Has(blockName)) + //{ + // //tr.Database.Insert(blockName, db2, false);//插入块 + // db.Insert(blockName, db2, false); + + //} + + string fullFileName = @"C:\Users\vic\Desktop\001.dwg"; + var blockdef = tr.BlockTable.GetBlockFrom(fullFileName, false); + + tr.Database.Clayer = tr.LayerTable["0"];//当前图层切换为0图层 + tr.LayerTable.Change(tr.Database.Clayer, ltr => { + ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2); //ColorMethod.ByAci可以让我们使用AutoCAD ACI颜色索引……这里为2(表示黄色) + }); + + ObjectId id = tr.ModelSpace.InsertBlock(Point3d.Origin, blockdef);//插入块参照 + + + + + + var entTest = tr.GetObject(id); + entTest.Draw(); + + } + + using var tr2 = new DBTrans(); + PromptEntityOptions PEO = new("\n请选择一个块"); + PEO.SetRejectMessage("\n对象必须是块"); + PEO.AddAllowedClass(typeof(BlockReference), true); + + PromptEntityResult PER = Env.Editor.GetEntity(PEO); + if (PER.Status != PromptStatus.OK) + { + return; + } + + var Bref = tr2.GetObject(PER.ObjectId); + //var BTR = tr.GetObject(Bref.BlockTableRecord, OpenMode.ForWrite); + ////如果知道块名字BTRName + //BlockTableRecord BTR = tr.GetObject(tr.BlockTable[blockName], OpenMode.ForWrite); + + var btr = tr2.BlockTable[Bref.Name]; + + tr2.BlockTable.Change(btr, ltr => { + + foreach (ObjectId OID in ltr) + { + var Ent = tr2.GetObject(OID); + using (Ent.ForWrite()) + { + if (Ent is MText mText) + { + switch (mText.Text) + { + case "$$A": + mText.Contents = "hahaha"; + break; + case "$$B": + ; + break; + default: + ; + break; + } + + }; + if (Ent is DBText dBText) { dBText.TextString = "haha"; }; + if (Ent is Dimension dimension) + { + switch (dimension.DimensionText) + { + case "$$pipeLen": + dimension.DimensionText = "350"; + dimension.RecomputeDimensionBlock(true); + break; + default: + break; + } + }; + } + + + } + + }); + + + tr2.Editor.Regen(); + + + } + + [CommandMethod("W_KSZK")] + public void QuickBlockDef() + { + //Database db = HostApplicationServices.WorkingDatabase; + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + //var rss = ed.GetSelection(promptOpt); + var rss = Env.Editor.GetSelection(promptOpt); + using var tr = new DBTrans(); + if (rss.Status == PromptStatus.OK) + { + //SelectionSet ss = rss.Value; + //ObjectId[] ids = ss.GetObjectIds(); + //var ents = new List>(); + //var extents = new Extents3d(); + //foreach (var id in ids) + //{ + // Entity ent = tr.GetObject(id); + // if (ent is null) + // continue; + // try + // { + // extents.AddExtents(ent.GeometricExtents); + // var order = id.Handle.Value; + // var newEnt = ent.Clone() as Entity; + // ents.Add(new KeyValuePair(newEnt, order)); + // ent.UpgradeOpen(); + // ent.Erase(); + // ent.DowngradeOpen(); + // } + // catch (System.Exception exc) + // { + // ed.WriteMessage(exc.Message); + // } + //} + //ents = ents.OrderBy(x => x.Value).ToList(); + var ents = rss.Value.GetEntities(); + //ents.ForEach(ent => extents.AddExtents(ent.GeometricExtents)); + var extents = ents.GetExtents(); + + Point3d pt = extents.MinPoint; + Matrix3d matrix = Matrix3d.Displacement(Point3d.Origin - pt); + //var newEnts = new List(); + //foreach (var ent in ents) + //{ + // var newEnt = ent.Key; + // newEnt.TransformBy(matrix); + // newEnts.Add(newEnt); + //} + //if (tr.BlockTable.Has(blockName)) + //{ + // Application.ShowAlertDialog(Environment.NewLine + "块名重复,程序退出!"); + // return; + //} + ents.ForEach(ent => + ent.ForWrite(e => e.TransformBy(matrix))); + //var newents = ents.Select(ent => + //{ + // var maping = new IdMapping(); + // return ent.DeepClone(ent, maping, true) as Entity; + //}); + var newents = ents.Select(ent => ent.Clone() as Entity); + + //ents.ForEach(ent => ent.ForWrite(e => e.Erase(true))); // 删除实体就会卡死,比较奇怪,估计是Clone()函数的问题 + // 经过测试不是删除的问题 + var btrId = tr.BlockTable.Add(blockName, newents); + ents.ForEach(ent => ent.ForWrite(e => e.Erase(true))); + var bId = tr.CurrentSpace.InsertBlock(pt, blockName); + //tr.GetObject(bId, OpenMode.ForWrite).Move(Point3d.Origin, Point3d.Origin); + //var ed = Application.DocumentManager.MdiActiveDocument.Editor; + //ed.Regen(); + //tr.Editor.Regen(); + // 调用regen() 卡死 + } + //tr.Editor.Regen(); + //ed.Regen(); + //using (var tr = new DBTrans()) + //{ + // tr.CurrentSpace.InsertBlock(Point3d.Origin, blockName); + // tr.Editor.Regen(); + //} + } + + [CommandMethod("testquickblockdef")] + public void TestQuickBlockDef() + { + Database db = HostApplicationServices.WorkingDatabase; + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + var rss = ed.GetSelection(promptOpt); + //var rss = Env.Editor.GetSelection(promptOpt); + if (rss.Status != PromptStatus.OK) + { + return; + } + + using var tr = db.TransactionManager.StartTransaction(); + var ids = rss.Value.GetObjectIds(); + var bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr = new BlockTableRecord + { + Name = blockName + }; + foreach (var item in ids) + { + var ent = tr.GetObject(item, OpenMode.ForRead) as Entity; + + btr.AppendEntity(ent.Clone() as Entity); + ent.ForWrite(e => e.Erase(true)); + } + bt.UpgradeOpen(); + bt.Add(btr); + tr.AddNewlyCreatedDBObject(btr, true); + bt.DowngradeOpen(); + // tr.Commit(); + //} + + //using (var tr1 = db.TransactionManager.StartTransaction()) + //{ + //var bt = tr1.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr1 = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + var br = new BlockReference(Point3d.Origin, bt[blockName]) + { + ScaleFactors = default + }; + btr1.AppendEntity(br); + tr.AddNewlyCreatedDBObject(br, true); + btr1.DowngradeOpen(); + ed.Regen(); + tr.Commit(); + //ed.Regen(); + + } + + public void TestWblock() + { + var curdb = HostApplicationServices.WorkingDatabase; + PromptSelectionOptions opts = new(); + opts.MessageForAdding = "选择对象"; + var ss = Env.Editor.GetSelection(opts).Value; + var ids = new ObjectIdCollection(ss.GetObjectIds()); + var db = curdb.Wblock(ids, Point3d.Origin); + db.SaveAs(@"c:\test.dwg", DwgVersion.Current); + } + + public void TestChangeDynameicBlock() + { + var pro = new Dictionary + { + { "haha", 1 } + }; + var blockid = Env.Editor.GetEntity("选择个块").ObjectId; + using var tr = new DBTrans(); + var blockref = tr.GetObject(blockid); + blockref.ChangeBlockProperty(pro); + // 这是第一个函数的用法 + } + + [CommandMethod("TestBack")] + public void TestBack() + { + string dir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + string dwg = dir + "\\test.dwg"; + if (!File.Exists(dwg)) + { + System.Windows.Forms.MessageBox.Show(dwg, "你还没有创建此文件"); + return; + } + + using var tr = new DBTrans(dwg); + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 3); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 4); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + + } + +} + +public class BlockImportClass +{ + + [CommandMethod("CBLL")] + public void Cbll() + { + string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; + using var tr = new DBTrans(); + using var tr1 = new DBTrans(filename); + //tr.BlockTable.GetBlockFrom(filename, true); + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); + tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 + } + + + [CommandMethod("CBL")] + public void CombineBlocksIntoLibrary() + { + Document doc = + Application.DocumentManager.MdiActiveDocument; + Editor ed = doc.Editor; + Database destDb = doc.Database; + + // Get name of folder from which to load and import blocks + + PromptResult pr = + ed.GetString("\nEnter the folder of source drawings: "); + + if (pr.Status != PromptStatus.OK) + return; + string pathName = pr.StringResult; + + // Check the folder exists + + if (!Directory.Exists(pathName)) + { + ed.WriteMessage( + "\nDirectory does not exist: {0}", pathName + ); + return; + } + + // Get the names of our DWG files in that folder + + string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); + + // A counter for the files we've imported + + int imported = 0, failed = 0; + + // For each file in our list + + foreach (string fileName in fileNames) + { + // Double-check we have a DWG file (probably unnecessary) + + if (fileName.EndsWith( + ".dwg", + StringComparison.InvariantCultureIgnoreCase + ) + ) + { + // Catch exceptions at the file level to allow skipping + + try + { + // Suggestion from Thorsten Meinecke... + + string destName = + SymbolUtilityServices.GetSymbolNameFromPathName( + fileName, "dwg" + ); + + // And from Dan Glassman... + + destName = + SymbolUtilityServices.RepairSymbolName( + destName, false + ); + + // Create a source database to load the DWG into + + using Database db = new(false, true); + // Read the DWG into our side database + + db.ReadDwgFile(fileName, FileShare.Read, true, ""); + bool isAnno = db.AnnotativeDwg; + + // Insert it into the destination database as + // a named block definition + + ObjectId btrId = destDb.Insert( + destName, + db, + false + ); + + if (isAnno) + { + // If an annotative block, open the resultant BTR + // and set its annotative definition status + + Transaction tr = + destDb.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + btrId, + OpenMode.ForWrite + ); + btr.Annotative = AnnotativeStates.True; + tr.Commit(); + } + } + + // Print message and increment imported block counter + + ed.WriteMessage("\nImported from \"{0}\".", fileName); + imported++; + } + catch (System.Exception ex) + { + ed.WriteMessage( + "\nProblem importing \"{0}\": {1} - file skipped.", + fileName, ex.Message + ); + failed++; + } + } + } + + ed.WriteMessage( + "\nImported block definitions from {0} files{1} in " + + "\"{2}\" into the current drawing.", + imported, + failed > 0 ? " (" + failed + " failed)" : "", + pathName + ); + } +} \ No newline at end of file diff --git a/tests/Test/testeditor.cs b/tests/Test/testeditor.cs index 397d5c4..5c5ac13 100644 --- a/tests/Test/testeditor.cs +++ b/tests/Test/testeditor.cs @@ -1,38 +1,69 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using IFoxCAD.Cad; -namespace test +namespace Test; + +public class Testeditor { - public class testeditor + [CommandMethod("tested")] + public void Tested() + { + var pts = new List + { + new Point2d(0,0), + new Point2d(0,1), + new Point2d(1,1), + new Point2d(1,0) + }; + var res = EditorEx.GetLines(pts, false); + var res1 = EditorEx.GetLines(pts, true); + var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); + + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); + var d = ed.GetDouble("qudoule"); + var i = ed.GetInteger("quint"); + var s = ed.GetString("qustr"); + Env.Editor.WriteMessage(""); + } + [CommandMethod("testzoom")] + public void Testzoom() { - [CommandMethod("tested")] - public void tested() + using var tr = new DBTrans(); + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) { - var pts = new List - { - new Point2d(0,0), - new Point2d(0,1), - new Point2d(1,1), - new Point2d(1,0) - }; - var res = EditorEx.GetLines(pts, false); - var res1 = EditorEx.GetLines(pts, true); - var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); - - Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; - var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); - var d = ed.GetDouble("qudoule"); - var i = ed.GetInteger("quint"); - var s = ed.GetString("qustr"); - Env.Editor.WriteMessage(""); + Env.Editor.ZoomObject(res.ObjectId.GetObject()); } + + + } + [CommandMethod("testzoomextent")] + public void Testzoomextent() + { + //using var tr = new DBTrans(); + //var res = Env.Editor.GetEntity("\npick ent:"); + //if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) + //{ + // Env.Editor.ZoomObject(res.ObjectId.GetObject()); + //} + + Env.Editor.ZoomExtents(); + } + + [CommandMethod("testssget")] + public void Testssget() + { + var action_a = () => { Env.Print("this is a"); }; + var action_b = () => { Env.Print("this is b"); }; + + var keyword = new Dictionary + { + { "A", action_a }, + { "B", action_b } + }; + + var ss = Env.Editor.SSGet( ":S", + messages: new string[2] { "get", "del" }, + keywords: keyword); + + Env.Print(ss); } } diff --git a/tests/Test/testenv.cs b/tests/Test/testenv.cs index 6cd7c9b..05ffc5c 100644 --- a/tests/Test/testenv.cs +++ b/tests/Test/testenv.cs @@ -1,71 +1,89 @@ -using IFoxCAD.Cad; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; +namespace Test; -namespace test +public class Testenv { - public class testenv + [CommandMethod("testenum")] + public void Testenum() { - [CommandMethod("testenum")] - public void testenum() - { - Env.CmdEcho = true; - } - [CommandMethod("testenum1")] - public void testenum1() - { - Env.CmdEcho = false; - } + + Env.CmdEcho = true; + + } + [CommandMethod("testenum1")] + public void Testenum1() + { + + Env.CmdEcho = false; + + } - [CommandMethod("testdimblk")] - public void testdimblk() - { + [CommandMethod("testdimblk")] + public void Testdimblk() + { - Env.Dimblk = Env.DimblkType.Dot; - Env.Dimblk = Env.DimblkType.Defult; + Env.Dimblk = Env.DimblkType.Dot; + Env.Dimblk = Env.DimblkType.Defult; + Env.Dimblk = Env.DimblkType.Oblique; - } - [CommandMethod("testdimblk1")] - public void testdimblk1() - { - var dim = Env.Dimblk; - Env.Editor.WriteMessage(dim.ToString()); + } + [CommandMethod("testdimblk1")] + public void Testdimblk1() + { + var dim = Env.Dimblk; + Env.Editor.WriteMessage(dim.ToString()); - } + } - [CommandMethod("testosmode")] - public void testosmode() - { - // 设置osmode变量,多个值用逻辑或 - Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; - // 也可以直接写数值,进行强转 - Env.OSMode = (Env.OSModeType)5179; - // 追加模式 - Env.OSMode |= Env.OSModeType.Center; - //检查是否有某个模式 - var os = Env.OSMode.Include(Env.OSModeType.Center); - // 取消某个模式 - Env.OSMode ^= Env.OSModeType.Center; - Env.Editor.WriteMessage(Env.OSMode.ToString()); - } - [CommandMethod("testosmode1")] - public void testosmode1() - { - var dim = Env.OSMode; - Env.Editor.WriteMessage(dim.ToString()); + [CommandMethod("testosmode")] + public void Testosmode() + { + // 设置osmode变量,多个值用逻辑或 + Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; + // 也可以直接写数值,进行强转 + Env.OSMode = (Env.OSModeType)5179; + // 追加模式 + Env.OSMode |= Env.OSModeType.Center; + //检查是否有某个模式 + var os = Env.OSMode.Include(Env.OSModeType.Center); + // 取消某个模式 + Env.OSMode ^= Env.OSModeType.Center; + Env.Editor.WriteMessage(Env.OSMode.ToString()); + } + [CommandMethod("testosmode1")] + public void Testosmode1() + { + var dim = Env.OSMode; + Env.Editor.WriteMessage(dim.ToString()); - } + } + + [CommandMethod("testcadver")] + public void Testcadver() + { + //Env.Print(AcadVersion.Versions); + AcadVersion.Versions.ForEach(v => Env.Print(v)); + AcadVersion.FromApp(Application.AcadApplication).Print(); + 1.Print(); + "1".Print(); + + } - [CommandMethod("testzoom")] - public void testzoom() - { - using var tr = new DBTrans(); - var res = Env.Editor.GetEntity("\npick ent:"); - if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) - { - Env.Editor.ZoomObject(res.ObjectId.GetObject()); - } - } + [CommandMethod("TestGetVar")] + public void TestGetVar() + { + // test getvar + var a = Env.GetVar("dbmod"); + a.Print(); + Env.SetVar("dbmod1", 1); + } + + [CommandMethod("TestDwgVersion")] + public void TestDwgVersion() + { + // + //string filename = @"C:\Users\vic\Desktop\test.dwg"; + //var a = Helper.GetCadFileVersion(filename); + //a.Print(); + //((DwgVersion)a).Print(); } } diff --git a/tests/Test/testselectfilter.cs b/tests/Test/testselectfilter.cs index 9b658ee..3c403a9 100644 --- a/tests/Test/testselectfilter.cs +++ b/tests/Test/testselectfilter.cs @@ -10,20 +10,20 @@ using IFoxCAD.Cad; using Autodesk.AutoCAD.EditorInput; -namespace test +namespace Test { - public class testselectfilter + public class Testselectfilter { [CommandMethod("testfilter")] - public void testfilter() + public void Testfilter() { - + var p = new Point3d(10, 10, 0); var f = OpFilter.Bulid( - e => !(e.Dxf(0) == "line" & e.Dxf(8) == "0") + e =>!(e.Dxf(0) == "line" & e.Dxf(8) == "0") | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); - - + + var f2 = OpFilter.Bulid( e => e.Or( !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), @@ -37,7 +37,7 @@ public void testfilter() } [CommandMethod("testselectanpoint")] - public void testselectanpoint() + public void Testselectanpoint() { var sel2 = Env.Editor.SelectAtPoint(new Point3d(0, 0, 0)); Env.Editor.WriteMessage(""); diff --git a/tests/Test/wpf/Class1.cs b/tests/Test/wpf/Class1.cs new file mode 100644 index 0000000..4358668 --- /dev/null +++ b/tests/Test/wpf/Class1.cs @@ -0,0 +1,13 @@ +namespace Test.wpf +{ + public class Class1 + { + [CommandMethod("testwpf")] + public void TestWPf() + { + + var test = new TestView(); + Application.ShowModalWindow(test); + } + } +} diff --git a/tests/Test/wpf/TestView.xaml b/tests/Test/wpf/TestView.xaml index 42cb304..bd75779 100644 --- a/tests/Test/wpf/TestView.xaml +++ b/tests/Test/wpf/TestView.xaml @@ -1,9 +1,9 @@ - /// TestView.xaml 的交互逻辑 diff --git a/tests/Test/wpf/TestViewModel.cs b/tests/Test/wpf/TestViewModel.cs index dc6fce0..4cb7563 100644 --- a/tests/Test/wpf/TestViewModel.cs +++ b/tests/Test/wpf/TestViewModel.cs @@ -1,119 +1,113 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + using System.Windows; using System.Windows.Input; -using IFoxCAD.WPF; -namespace test.wpf +namespace Test.wpf; + +class TestViewModel : ViewModelBase { - class TestViewModel : ViewModelBase - { - - private string name; + + private string name; - public string Name - { - get { return name; } - set { Set(ref name, value); } - } + public string Name + { + get { return name; } + set { Set(ref name, value); } + } - private RelayCommand clickCommand; + private RelayCommand clickCommand; - public RelayCommand ClickCommand + public RelayCommand ClickCommand + { + get { - get + if (clickCommand is null) { - if (clickCommand is null) - { - clickCommand = new( - execute => Name = "hello " + Name, - can => !string.IsNullOrEmpty(Name)); - } - return clickCommand; + clickCommand = new( + execute => Name = "hello " + Name, + can => !string.IsNullOrEmpty(Name)); } + return clickCommand; } + } - private bool receiveMouseMove; + private bool receiveMouseMove; - public bool ReceiveMouseMove - { - get { return receiveMouseMove; } - set { Set(ref receiveMouseMove, value); } - } + public bool ReceiveMouseMove + { + get { return receiveMouseMove; } + set { Set(ref receiveMouseMove, value); } + } - private string tipText; + private string tipText; - public string TipText - { - get { return tipText; } - set { Set(ref tipText, value); } - } + public string TipText + { + get { return tipText; } + set { Set(ref tipText, value); } + } - private RelayCommand loadedCommand; - public RelayCommand LoadCommand + private RelayCommand loadedCommand; + public RelayCommand LoadCommand + { + get { - get + if (loadedCommand is null) { - if (loadedCommand is null) - { - loadedCommand = new( - execute => MessageBox.Show("程序加载完毕")); - } - return loadedCommand; + loadedCommand = new( + execute => MessageBox.Show("程序加载完毕")); } + return loadedCommand; } + } - private RelayCommand mouseMoveCommand; + private RelayCommand mouseMoveCommand; - public RelayCommand MouseMoveCommand + public RelayCommand MouseMoveCommand + { + get { - get + if (mouseMoveCommand is null) { - if (mouseMoveCommand is null) - { - mouseMoveCommand = new( - execute => + mouseMoveCommand = new( + execute => + { + var pt = execute.GetPosition(execute.Device.Target); + var left = "左键放开"; + var mid = "中键放开"; + var right = "右键放开"; + + if (execute.LeftButton == MouseButtonState.Pressed) + { + left = "左键放下"; + } + if (execute.MiddleButton == MouseButtonState.Pressed) + { + mid = "中键放下"; + } + if (execute.RightButton == MouseButtonState.Pressed) { - var pt = execute.GetPosition(execute.Device.Target); - var left = "左键放开"; - var mid = "中键放开"; - var right = "右键放开"; - - if (execute.LeftButton == MouseButtonState.Pressed) - { - left = "左键放下"; - } - if (execute.MiddleButton == MouseButtonState.Pressed) - { - mid = "中键放下"; - } - if (execute.RightButton == MouseButtonState.Pressed) - { - right = "右键放下"; - } - TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; - }, - can => ReceiveMouseMove); - } - return mouseMoveCommand; + right = "右键放下"; + } + TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; + }, + can => ReceiveMouseMove); } + return mouseMoveCommand; } + } - public TestViewModel() - { - Name = "world"; - } + public TestViewModel() + { + Name = "world"; + } - } } diff --git a/tests/TestConsole/Program.cs b/tests/TestConsole/Program.cs new file mode 100644 index 0000000..aa31926 --- /dev/null +++ b/tests/TestConsole/Program.cs @@ -0,0 +1,55 @@ +// See https://aka.ms/new-console-template for more information +using System; +using System.Text; + + +//表达式树例子 +TestConsole.Test_Expression.Demo3(); +//TestConsole.Test_Expression.Demo1(); + +#region 元组测试 +var valuetuple = (1, 2); + +Console.WriteLine(valuetuple.ToString()); + +int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +int lastElement = someArray[^1]; // lastElement = 5 +Console.WriteLine(lastElement); +int midElement = someArray[^3]; +Console.WriteLine(midElement); +var range = someArray[1..3]; +foreach (var item in range) + Console.WriteLine(item); +#endregion + +Console.ReadLine(); + + +#region 测试遍历枚举 +//Season a = Season.Autumn; +//Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2 +//foreach (var enumItem in Enum.GetValues(typeof(Season))) +// Console.WriteLine((byte)enumItem); + +var sb = new StringBuilder(); +/*因为 net framework 没写好的原因,导致直接使用迭代器反而更慢,到了net60就迭代器比foreach更快*/ +var enums = Enum.GetValues(typeof(Season)).GetEnumerator(); +while (enums.MoveNext()) +{ + sb.Append(((byte)enums.Current).ToString()); + sb.Append(","); +} +Console.WriteLine(sb); + +sb.Remove(sb.Length - 1, 1);//剔除末尾, +//因为有返回值所以容易理解成 sb = sb.Remove(sb.Length - 1, 1); +Console.WriteLine(sb); + +public enum Season : byte +{ + Spring, + Summer, + Autumn, + Winter +} +#endregion \ No newline at end of file diff --git a/tests/TestConsole/RuntimeHelpers.cs b/tests/TestConsole/RuntimeHelpers.cs new file mode 100644 index 0000000..9e09b29 --- /dev/null +++ b/tests/TestConsole/RuntimeHelpers.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// 如果要用range的语法比如 a[1..3],那么需要将本文件复制到你的项目里 +#if true +namespace System.Runtime.CompilerServices +{ + internal static class RuntimeHelpers + { + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + { + throw new ArgumentNullException(); + } + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + + if (length == 0) + { + //return Array.Empty(); + return new T[0]; + + + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + } +} + +#endif \ No newline at end of file diff --git a/tests/TestConsole/TestConsole.csproj b/tests/TestConsole/TestConsole.csproj new file mode 100644 index 0000000..293a6aa --- /dev/null +++ b/tests/TestConsole/TestConsole.csproj @@ -0,0 +1,22 @@ + + + + preview + enable + + Exe + net45 + enable + preview + + + + + + + + + + + + diff --git "a/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" new file mode 100644 index 0000000..f5cc477 --- /dev/null +++ "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; + +namespace TestConsole +{ + /// + /// 表达式树 + /// MSDN链接 + /// + public class Test_Expression + { + public static void Demo1() + { + // 官方例子:表达式体内只有一个式子 + // 创建表达式树 + Expression> exprTree = num => num < 5; + + // 分解表达式树 + ParameterExpression param = exprTree.Parameters[0];//num + BinaryExpression operation = (BinaryExpression)exprTree.Body;//函数体 {(num < 5)} + ParameterExpression left = (ParameterExpression)operation.Left;//左节点 num + ConstantExpression right = (ConstantExpression)operation.Right;//右表达式 5 + + Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + param.Name, left.Name, operation.NodeType, right.Value); + Console.Read(); + } + + + public static void Demo2() + { + // 这里是会报错的!! 原因就是体内有多个例子需要分解!! + // Expression> exprTree = x => x > 5 && x < 50; + // + // // 分解表达式树 + // ParameterExpression param = exprTree.Parameters[0];// x + // BinaryExpression operation = (BinaryExpression)exprTree.Body;//函数体 {((x > 5) AndAlso (x < 50))} + // + // ParameterExpression left = (ParameterExpression)operation.Left;//左节点.......这里报错 + // ConstantExpression right = (ConstantExpression)operation.Right;//右表达式.....这里报错 + // + // Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + // param.Name, left.Name, operation.NodeType, right.Value); + } + + + //博客园例子,表达式体内有多个式子 + public static void Demo3() + { + List names = new() { "Cai", "Edward", "Beauty" }; + + Console.WriteLine("******************一个表达式"); + Expression> lambda2 = name => name.Length > 2 && name.Length < 4; + var method2 = ReBuildExpression(lambda2); + var query2 = names.Where(method2); + foreach (string n in query2) + Console.WriteLine(n); + + Console.WriteLine("******************二个表达式"); + Expression> lambda0 = item => item.Length > 2; + Expression> lambda1 = item => item.Length < 4; + var method = ReBuildExpression(lambda0, lambda1); + var query = names.Where(method); + foreach (string n in query) + Console.WriteLine(n); + Console.WriteLine("******************表达式结束"); + Console.Read(); + } + + + static Func ReBuildExpression(Expression> lambda) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda.Body); + //构造一个新的表达式 + var newLambda = Expression.Lambda>(left, my.Parameter); + return newLambda.Compile(); + } + + + + /// + /// 重构表达式_合并 + /// + /// 匿名函数表达式1 + /// 匿名函数表达式2 + /// + static Func ReBuildExpression(Expression> lambda0, + Expression> lambda1) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda0.Body); + Expression right = my.Modify(lambda1.Body); + var expression = Expression.AndAlso(left, right);//就是 && 合并两个匿名函数 + + //构造一个新的表达式 + var newLambda = Expression.Lambda>(expression, my.Parameter); + return newLambda.Compile(); + } + } + + + /// + /// 表达式参数分解 + /// 博客园链接 + /// + public class MyExpressionVisitor : ExpressionVisitor + { + /// + /// 公共参数 + /// + public ParameterExpression? Parameter; + /// + /// 返回替换后的参数表达式 + /// + /// + /// + public Expression Modify(Expression exp) + { + return Visit(exp); + } + /// + /// 重写参数 + /// + /// + /// + protected override Expression? VisitParameter(ParameterExpression p) + { + return Parameter; + } + } +} -- Gitee From 116278f6cccb6ae2db18284c94d8d2721e9bb7e1 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Thu, 11 Aug 2022 23:09:37 +0800 Subject: [PATCH 12/18] =?UTF-8?q?=E9=99=A4=E4=BA=86=E5=8C=85=E4=B9=8B?= =?UTF-8?q?=E5=A4=96=E5=85=B6=E4=BB=96=E4=BF=9D=E6=8C=81=E5=92=8Cdev?= =?UTF-8?q?=E5=88=86=E6=94=AF=E4=B8=80=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 95 +++++++++++++++++------------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index d72026f..8aab0f3 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -1,27 +1,30 @@ - - - - preview - enable + - net35;net40;net45 - true - 0.1.3 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的Cad二次开发类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;CAD;AutoCad;C#;NET - Optimize and add multiple functions. - true - true - true - LICENSE - true - + + preview + enable + + net35;net40;net45 + true + true + true + 0.3.6.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + 增加四叉树测试. + true + true + true + LICENSE + true + x64 + @@ -51,25 +54,37 @@ $(Configuration);ac2015 - + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + - - True - - - - - - - - - - - + + True + + + - - - + + + + -- Gitee From a9efe02fa01a7a5fba5f84c0714e4331f823d916 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Fri, 12 Aug 2022 14:42:54 +0800 Subject: [PATCH 13/18] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E5=8F=8C=E6=84=9F?= =?UTF-8?q?=E5=8F=B9=E5=8F=B7=E5=92=8C=E7=A7=BB=E5=8A=A8Autocad=E5=91=BD?= =?UTF-8?q?=E5=90=8D=E7=A9=BA=E9=97=B4=E5=88=B0=E5=85=A8=E5=B1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs | 5 +- .../CLS/TupleElementNamesAttribute.cs | 7 ++- src/IFoxCAD.Basal/ListEx.cs | 9 +++- src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs | 32 ++++++----- .../ExtensionMethod/DBDictionaryEx.cs | 1 - src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 29 ++++------ src/IFoxCAD.Cad/ExtensionMethod/Jig.cs | 11 ++-- src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs | 10 +++- .../HatchInfo.cs" | 11 ++-- src/IFoxCAD.Cad/GlobalUsings.cs | 10 ++++ src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data | 0 src/IFoxCAD.Cad/Runtime/AcadVersion.cs | 54 +++++++++---------- src/IFoxCAD.Cad/Runtime/Env.cs | 5 +- src/IFoxCAD.Cad/Runtime/SymbolTable.cs | 2 +- src/IFoxCAD.Cad/SelectionFilter/OpList.cs | 2 +- src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs | 2 +- 16 files changed, 105 insertions(+), 85 deletions(-) create mode 100644 src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data diff --git a/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs index b5698b4..a446c59 100644 --- a/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs +++ b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs @@ -10,8 +10,11 @@ public static class RuntimeHelpers /// /// Slices the specified array using the specified range. /// - public static T[] GetSubArray(T[] array!!, Range range) + public static T[] GetSubArray(T[] array, Range range) { + if (array == null) + throw new ArgumentNullException(nameof(array)); + (int offset, int length) = range.GetOffsetAndLength(array.Length); if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) diff --git a/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs index bb0716d..69670b4 100644 --- a/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs +++ b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs @@ -2,6 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using IFoxCAD.Basal; + namespace System.Runtime.CompilerServices; /// @@ -34,8 +36,11 @@ public sealed class TupleElementNamesAttribute : Attribute /// transformNames value of { "name1", "name2", null, null, /// null }. /// - public TupleElementNamesAttribute(string[] transformNames!!) + public TupleElementNamesAttribute(string[] transformNames) { + if (transformNames == null) + throw new ArgumentNullException(nameof(transformNames)); + _transformNames = transformNames; } diff --git a/src/IFoxCAD.Basal/ListEx.cs b/src/IFoxCAD.Basal/ListEx.cs index b0d20c0..4ead97b 100644 --- a/src/IFoxCAD.Basal/ListEx.cs +++ b/src/IFoxCAD.Basal/ListEx.cs @@ -5,13 +5,18 @@ public static class ListEx { public static bool EqualsAll(this IList a, IList b) { - return EqualsAll(a, b, null); + return EqualsAll(a, b, null); // there is a slight performance gain in passing null here. // It is how it is done in other parts of the framework. } - public static bool EqualsAll(this IList a!!, IList b!!, IEqualityComparer? comparer) + public static bool EqualsAll(this IList a, IList b, IEqualityComparer? comparer) { + if (a == null) + return b == null; + else if (b == null) + return false; + if (a.Count != b.Count) return false; diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs index ad1efb5..c1d606f 100644 --- a/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs +++ b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs @@ -82,8 +82,11 @@ public IGraphVertex AddVertex(Point3d pt) /// 向该图添加一个边; /// /// - public void AddEdge(Curve3d curve!!) + public void AddEdge(Curve3d curve) { + if (curve == null) + throw new ArgumentNullException(nameof(curve)); + var start = AddVertex(curve.StartPoint); var end = AddVertex(curve.EndPoint); @@ -141,8 +144,11 @@ public void RemoveVertex(Point3d pt) /// 从此图中删除一条边; /// /// 曲线 - public void RemoveEdge(Curve3d curve!!) + public void RemoveEdge(Curve3d curve) { + if (curve == null) + throw new ArgumentNullException(nameof(curve)); + RemoveVertex(curve.StartPoint); RemoveVertex(curve.EndPoint); } @@ -303,7 +309,7 @@ public string ToReadable() i++; } return output; - } + } #endregion } @@ -473,9 +479,9 @@ public override int GetHashCode() public sealed class DepthFirst { #region 公共方法 -/// -/// 存储所有的边 -/// + /// + /// 存储所有的边 + /// #if true public List> Curve3ds { get; } = new(); #else @@ -494,13 +500,13 @@ public void FindAll(IGraph graph) //var graphtmp = graph.Clone(); foreach (var item in graph.VerticesAsEnumberable) { - Dfs(graph, new LinkedHashSet { item },total); + Dfs(graph, new LinkedHashSet { item }, total); total.Add(item); } } -#endregion + #endregion -#region 内部方法 + #region 内部方法 /// /// 递归 DFS; /// @@ -522,7 +528,7 @@ void Dfs(IGraph graph, LinkedHashSet visited, HashSet { nextNode }; sub.AddRange(visited); // O(n) - Dfs(graph, sub,totalVisited); + Dfs(graph, sub, totalVisited); } // 如果下一点遍历过,并且路径大于2,说明已经找到起点 else if (visited.Count > 2 && nextNode.Equals(visited.Last!.Value)) @@ -613,7 +619,7 @@ static List Invert(List lst, IGraphVertex vertex) return tmp.Skip(index).Concat(lst.Take(index)).ToList(); } - static (string,string) Gethashstring(List pathone, List pathtwo) + static (string, string) Gethashstring(List pathone, List pathtwo) { var one = new string[pathone.Count]; var two = new string[pathtwo.Count]; @@ -637,11 +643,11 @@ static List Invert(List lst, IGraphVertex vertex) } - bool Isnew((string,string) path) + bool Isnew((string, string) path) { return !Curved.Contains(path.Item1) && !Curved.Contains(path.Item2); } -#endregion + #endregion } \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs index 2ae0879..89dc2d5 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs @@ -1,5 +1,4 @@ namespace IFoxCAD.Cad; -using Group = Autodesk.AutoCAD.DatabaseServices.Group; /// /// 字典扩展类 diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index d1cc6a8..47f06f0 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -98,9 +98,8 @@ public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lin else ss = editor.GetSelection(pso); } - catch (Autodesk.AutoCAD.Runtime.Exception e) - { - + catch (Exception e) + { editor.WriteMessage($"\nKey is {e.Message}"); } return ss; @@ -664,13 +663,13 @@ public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) { if ((short)Env.GetVar("TILEMODE") == 1) - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Espace papier uniquement"); + throw new ArgumentException("TILEMODE == 1..Espace papier uniquement"); Database db = editor.Document.Database; Matrix3d mat; using (Transaction tr = db.TransactionManager.StartTransaction()) { - Viewport? vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; + var vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; if (vp?.Number == 1) { try @@ -681,7 +680,7 @@ public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) } catch { - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Aucun fenêtre active"); + throw new Exception("Aucun fenêtre active...ErrorStatus.InvalidInput"); } } Point3d vCtr = new(vp!.ViewCenter.X, vp.ViewCenter.Y, 0.0); @@ -725,9 +724,7 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, return editor.GetMatrixFromMDcsToWcs(); case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); + throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"); } break; case CoordinateSystemCode.Ucs: @@ -740,9 +737,7 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); } break; case CoordinateSystemCode.MDcs: @@ -762,13 +757,9 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, switch (to) { case CoordinateSystemCode.Wcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); case CoordinateSystemCode.Ucs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); case CoordinateSystemCode.MDcs: return editor.GetMatrixFromPDcsToMDcs(); } @@ -787,7 +778,7 @@ public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) - or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "To be used only with DCS"), + or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"), (_, _) => Matrix3d.Identity }; #endif diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs index 5485e6d..b9182ae 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs @@ -1,4 +1,6 @@ -/* 封装jig +namespace IFoxCAD.Cad; + +/* 封装jig * 20220726 隐藏事件,利用函数进行数据库图元重绘 * 20220710 修改SetOption()的空格结束,并添加例子到IFox * 20220503 cad22需要防止刷新过程中更改队列,是因为允许函数重入导致,08不会有. @@ -9,11 +11,6 @@ * 博客: https://www.cnblogs.com/JJBox/p/15650770.html */ -namespace IFoxCAD.Cad; - -//此命名空间容易引起Polyline等等重义,因此不放入全局空间 -using Autodesk.AutoCAD.GraphicsInterface; - public delegate void WorldDrawEvent(WorldDraw draw); public class JigEx : DrawJig { @@ -264,7 +261,7 @@ public void DatabaseEntityDraw(WorldDrawEvent action) * 在jig使用淡显ent.Unhighlight和亮显ent.Highlight() * 需要绕过重绘,否则重绘将导致图元频闪,令这两个操作失效, * 此时需要自定义一个集合 EntityList (不使用本函数的_drawEntitys) - * 再将 EntityList 传给 WorldDrawEvent 事件,事件内实现亮显和淡显. + * 再将 EntityList 传给 WorldDrawEvent 事件,事件内实现亮显和淡显(事件已经利用 DatabaseEntityDraw函数进行提供). * 0x03 * draw.Geometry.Draw(_drawEntitys[i]); * 此函数有问题,acad08克隆一份数组也可以用来刷新, diff --git a/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs index b1bae82..4898d50 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs @@ -95,8 +95,11 @@ public static double GetArcBulge(this Point2d arc1, Point2d arc2, Point2d arc3, /// /// 首尾相连 /// - public static Point2dCollection End2End(this Point2dCollection ptcol!!) + public static Point2dCollection End2End(this Point2dCollection ptcol) { + if (ptcol == null) + throw new ArgumentNullException(nameof(ptcol)); + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 return ptcol; @@ -111,8 +114,11 @@ public static Point2dCollection End2End(this Point2dCollection ptcol!!) /// /// 首尾相连 /// - public static Point3dCollection End2End(this Point3dCollection ptcol!!) + public static Point3dCollection End2End(this Point3dCollection ptcol) { + if (ptcol == null) + throw new ArgumentNullException(nameof(ptcol)); + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 return ptcol; diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" index 1d66417..1226479 100644 --- "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" @@ -259,11 +259,14 @@ void AppendLoop(IEnumerable boundaryIds, /// ˿ռ /// 뷽ʽ /// - public HatchInfo AppendLoop(Point2dCollection pts!!, - DoubleCollection bluges, - BlockTableRecord btrOfAddEntitySpace, - HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + public HatchInfo AppendLoop(Point2dCollection pts, + DoubleCollection bluges, + BlockTableRecord btrOfAddEntitySpace, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) { + if (pts == null) + throw new ArgumentNullException(nameof(pts)); + var ptsEnd2End = pts.End2End(); #if NET35 _boundaryIds.Add(CreateAddBoundary(ptsEnd2End, bluges, btrOfAddEntitySpace)); diff --git a/src/IFoxCAD.Cad/GlobalUsings.cs b/src/IFoxCAD.Cad/GlobalUsings.cs index 7051b36..a125299 100644 --- a/src/IFoxCAD.Cad/GlobalUsings.cs +++ b/src/IFoxCAD.Cad/GlobalUsings.cs @@ -11,6 +11,8 @@ global using System.ComponentModel; global using System.Runtime.InteropServices; +global using Exception = System.Exception; + /// autocad 引用 global using Autodesk.AutoCAD.ApplicationServices; global using Autodesk.AutoCAD.EditorInput; @@ -21,7 +23,15 @@ global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; global using Autodesk.AutoCAD.DatabaseServices.Filters; +global using Autodesk.AutoCAD; + +//jig此命名空间容易引起Polyline等等重义,因此不放入全局空间 +//using Autodesk.AutoCAD.GraphicsInterface; +global using WorldDraw = Autodesk.AutoCAD.GraphicsInterface.WorldDraw; +global using Manager = Autodesk.AutoCAD.GraphicsSystem.Manager; +global using Group = Autodesk.AutoCAD.DatabaseServices.Group; +global using Viewport = Autodesk.AutoCAD.DatabaseServices.Viewport; global using System.Collections.Specialized; global using Registry = Microsoft.Win32.Registry; diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data new file mode 100644 index 0000000..e69de29 diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs index 94c7947..f8f5273 100644 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs @@ -14,34 +14,30 @@ public static List Versions { get { - - string[] copys = - Registry.LocalMachine - .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") - .GetValueNames(); + string[] copys = Registry.LocalMachine + .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") + .GetValueNames(); + var _versions = new List(); foreach (var rootkey in copys) { - if (Regex.IsMatch(rootkey, _pattern)) + if (!Regex.IsMatch(rootkey, _pattern)) + continue; + + var gs = Regex.Match(rootkey, _pattern).Groups; + var ver = new CadVersion { - var gs = Regex.Match(rootkey, _pattern).Groups; - var ver = - new CadVersion - { - ProductRootKey = rootkey, - ProductName = - Registry.LocalMachine + ProductRootKey = rootkey, + ProductName = Registry.LocalMachine .OpenSubKey("SOFTWARE") .OpenSubKey(rootkey) .GetValue("ProductName") .ToString(), - Major = int.Parse(gs[1].Value), - Minor = int.Parse(gs[2].Value), - }; - - _versions.Add(ver); - } + Major = int.Parse(gs[1].Value), + Minor = int.Parse(gs[2].Value), + }; + _versions.Add(ver); } return _versions; } @@ -50,16 +46,18 @@ public static List Versions /// 已打开的cad的版本号 /// 已打开cad的application对象 /// cad版本号对象 - public static CadVersion? FromApp(object app!!) + public static CadVersion? FromApp(object app) { - string acver = - app.GetType() - .InvokeMember( - "Version", - BindingFlags.GetProperty, - null, - app, - new object[0]).ToString(); + if (app == null) + throw new ArgumentNullException(nameof(app)); + + string acver = app.GetType() + .InvokeMember( + "Version", + BindingFlags.GetProperty, + null, + app, + new object[0]).ToString(); var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; int major = int.Parse(gs[1].Value); diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/IFoxCAD.Cad/Runtime/Env.cs index c7c6e92..379504b 100644 --- a/src/IFoxCAD.Cad/Runtime/Env.cs +++ b/src/IFoxCAD.Cad/Runtime/Env.cs @@ -1,7 +1,4 @@ -namespace IFoxCAD.Cad; - -//此命名空间容易引起Polyline等等重义,因此不放入全局空间 -using Autodesk.AutoCAD.GraphicsSystem; +namespace IFoxCAD.Cad; /// /// 系统管理类 diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs index a31e0ac..00bba7b 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs @@ -1,4 +1,4 @@ -namespace IFoxCAD.Cad; +namespace IFoxCAD.Cad; public class SymbolTable : IEnumerable where TTable : SymbolTable diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs index 59d1d1f..3a02513 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs @@ -163,4 +163,4 @@ public override void Add(OpFilter value) _lst.Add(value); } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs index b61583d..4227608 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs @@ -125,4 +125,4 @@ public override IEnumerator GetEnumerator() yield return Left; yield return Right; } -} \ No newline at end of file +} -- Gitee From f03f397f4970a1df9bd05279ac91eb28c1ceab3d Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sat, 13 Aug 2022 00:03:55 +0800 Subject: [PATCH 14/18] =?UTF-8?q?=E9=99=A4=E4=BA=86IFoxCAD.Cad.csproj?= =?UTF-8?q?=E4=B8=8D=E4=B8=80=E6=A0=B7=E5=85=B6=E4=BB=96=E9=83=BD=E4=B8=80?= =?UTF-8?q?=E6=A0=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 217 ++ .gitattributes | 2 +- .gitignore | 128 +- IFoxCAD.sln | 14 +- README.md | 163 +- docs/DBTrans.md | 4 +- docs/WPF.md | 2 +- ...4\344\272\214\347\273\264\347\240\201.png" | Bin 0 -> 20425 bytes docs/png/nuget.png | Bin 0 -> 159167 bytes docs/png/nuget1.png | Bin 0 -> 27373 bytes docs/png/standard.png | Bin 0 -> 148673 bytes ...66\346\236\204\350\257\264\346\230\216.md" | 8 +- src/IFoxCAD.Basal/ArrayEx.cs | 50 + src/IFoxCAD.Basal/CLS/Index.cs | 148 + src/IFoxCAD.Basal/CLS/Range.cs | 103 + src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs | 44 + .../CLS/TupleElementNamesAttribute.cs | 53 + src/IFoxCAD.Basal/CLS/ValueTuple.cs | 2144 ++++++++++++ src/IFoxCAD.Basal/DictEx.cs | 21 + src/IFoxCAD.Basal/GlobalUsings.cs | 9 + src/IFoxCAD.Basal/IFoxCAD.Basal.csproj | 75 +- src/IFoxCAD.Basal/LinkedHashMap.cs | 171 + src/IFoxCAD.Basal/LinkedHashSet.cs | 221 ++ src/IFoxCAD.Basal/LinqEx.cs | 543 ++- src/IFoxCAD.Basal/ListEx.cs | 30 + src/IFoxCAD.Basal/LoopList.cs | 1223 +++---- src/IFoxCAD.Basal/Sortedset/ISet.cs | 71 + src/IFoxCAD.Basal/Sortedset/SR.cs | 907 +++++ src/IFoxCAD.Basal/Sortedset/Sortedset.cs | 2935 +++++++++++++++++ src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs | 382 +++ src/IFoxCAD.Basal/Sortedset/bithelper.cs | 173 + src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs | 653 ++++ src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs | 98 + .../Algorithms/QuadTree/QuadEntity.cs | 25 + .../Algorithms/QuadTree/QuadTree.cs | 259 ++ .../Algorithms/QuadTree/QuadTreeEvn.cs | 26 + .../Algorithms/QuadTree/QuadTreeNode.cs | 818 +++++ .../Algorithms/QuadTree/QuadTreeSelectMode.cs | 21 + src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs | 605 ++++ .../Algorithms/QuadTree/Utility.cs | 60 + .../ExtensionMethod/BulgeVertexWidth.cs | 85 + .../ExtensionMethod/CollectionEx.cs | 291 +- src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs | 530 ++- src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs | 920 +++--- src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs | 1400 +++----- .../ExtensionMethod/DBDictionaryEx.cs | 621 ++-- src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs | 262 +- src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs | 23 + src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs | 1591 +++++---- src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs | 730 ++-- src/IFoxCAD.Cad/ExtensionMethod/Enums.cs | 161 +- src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs | 1189 +++---- src/IFoxCAD.Cad/ExtensionMethod/Jig.cs | 340 ++ src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs | 21 + src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs | 133 +- src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs | 134 + .../ExtensionMethod/SelectionSetEx.cs | 173 +- .../ExtensionMethod/SymbolTableEx.cs | 540 +-- .../ExtensionMethod/SymbolTableRecordEx.cs | 721 ++-- src/IFoxCAD.Cad/ExtensionMethod/Tools.cs | 188 ++ .../HatchConverter.cs" | 358 ++ .../HatchEx.cs" | 15 + .../HatchInfo.cs" | 354 ++ .../AttachmentPointHelper.cs" | 95 + .../TextEntityAdd.cs" | 62 + .../TextInfo.cs" | 178 + src/IFoxCAD.Cad/GlobalUsings.cs | 41 + src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 136 +- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data | 1 - src/IFoxCAD.Cad/ResultData/LispDottedPair.cs | 106 +- src/IFoxCAD.Cad/ResultData/LispList.cs | 353 +- src/IFoxCAD.Cad/ResultData/TypedValueList.cs | 114 +- src/IFoxCAD.Cad/ResultData/XRecordDataList.cs | 111 +- src/IFoxCAD.Cad/ResultData/XdataList.cs | 115 +- src/IFoxCAD.Cad/Runtime/AOP.cs | 99 + src/IFoxCAD.Cad/Runtime/AcadVersion.cs | 166 +- src/IFoxCAD.Cad/Runtime/AssemInfo.cs | 105 +- src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs | 242 +- src/IFoxCAD.Cad/Runtime/CadVersion.cs | 92 + src/IFoxCAD.Cad/Runtime/DBTrans.cs | 699 ++-- src/IFoxCAD.Cad/Runtime/Env.cs | 793 ++--- src/IFoxCAD.Cad/Runtime/FileOpenMode.cs | 13 + src/IFoxCAD.Cad/Runtime/IAutoGo.cs | 307 ++ src/IFoxCAD.Cad/Runtime/Log.cs | 412 +++ src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs | 53 + src/IFoxCAD.Cad/Runtime/SymbolTable.cs | 544 +-- src/IFoxCAD.Cad/Runtime/Utils.cs | 98 + src/IFoxCAD.Cad/SelectionFilter/OpComp.cs | 138 +- src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs | 148 +- src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs | 582 ++-- src/IFoxCAD.Cad/SelectionFilter/OpList.cs | 274 +- src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs | 207 +- src/IFoxCAD.WPF/Converter.cs | 176 +- src/IFoxCAD.WPF/DependencyObjectExtensions.cs | 52 +- src/IFoxCAD.WPF/EnumSelection.cs | 72 + src/IFoxCAD.WPF/EventBindingExtension.cs | 337 +- src/IFoxCAD.WPF/GlobalUsings.cs | 18 + src/IFoxCAD.WPF/IFoxCAD.WPF.csproj | 67 +- src/IFoxCAD.WPF/RelayCommand.cs | 316 +- src/IFoxCAD.WPF/ViewModelBase.cs | 103 +- tests/Test/CmdINI.cs | 111 + tests/Test/GlobalUsings.cs | 30 + tests/Test/Properties/launchSettings.json | 2 +- tests/Test/Test.cs | 818 ++--- tests/Test/Test.csproj | 29 +- tests/Test/TestAOP.cs | 66 + tests/Test/TestCurve.cs | 158 + tests/Test/TestDBTrans.cs | 78 + tests/Test/TestEnt.cs | 40 + tests/Test/TestFileDatabase.cs | 29 + tests/Test/TestJig.cs | 334 ++ tests/Test/TestLisp.cs | 122 + tests/Test/TestLoop.cs | 36 + tests/Test/TestMirrorFile.cs | 73 + tests/Test/TestPoint.cs | 160 + tests/Test/TestQuadTree.cs | 461 +++ tests/Test/Testid.cs | 74 + tests/Test/testConvexHull.cs | 20 +- tests/Test/testblock.cs | 631 ++++ tests/Test/testeditor.cs | 95 +- tests/Test/testenv.cs | 136 +- tests/Test/testselectfilter.cs | 28 +- tests/Test/wpf/Class1.cs | 13 + tests/Test/wpf/TestView.xaml | 4 +- tests/Test/wpf/TestView.xaml.cs | 2 +- tests/Test/wpf/TestViewModel.cs | 156 +- tests/TestConsole/Program.cs | 55 + tests/TestConsole/RuntimeHelpers.cs | 50 + tests/TestConsole/TestConsole.csproj | 22 + ...50\350\276\276\345\274\217\346\240\221.cs" | 143 + 130 files changed, 25440 insertions(+), 9147 deletions(-) create mode 100644 .editorconfig create mode 100644 "docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" create mode 100644 docs/png/nuget.png create mode 100644 docs/png/nuget1.png create mode 100644 docs/png/standard.png create mode 100644 src/IFoxCAD.Basal/ArrayEx.cs create mode 100644 src/IFoxCAD.Basal/CLS/Index.cs create mode 100644 src/IFoxCAD.Basal/CLS/Range.cs create mode 100644 src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs create mode 100644 src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs create mode 100644 src/IFoxCAD.Basal/CLS/ValueTuple.cs create mode 100644 src/IFoxCAD.Basal/DictEx.cs create mode 100644 src/IFoxCAD.Basal/GlobalUsings.cs create mode 100644 src/IFoxCAD.Basal/LinkedHashMap.cs create mode 100644 src/IFoxCAD.Basal/LinkedHashSet.cs create mode 100644 src/IFoxCAD.Basal/ListEx.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/ISet.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/SR.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/Sortedset.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs create mode 100644 src/IFoxCAD.Basal/Sortedset/bithelper.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs create mode 100644 src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/Jig.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs create mode 100644 src/IFoxCAD.Cad/ExtensionMethod/Tools.cs create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" create mode 100644 "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" create mode 100644 src/IFoxCAD.Cad/GlobalUsings.cs delete mode 100644 src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data create mode 100644 src/IFoxCAD.Cad/Runtime/AOP.cs create mode 100644 src/IFoxCAD.Cad/Runtime/CadVersion.cs create mode 100644 src/IFoxCAD.Cad/Runtime/FileOpenMode.cs create mode 100644 src/IFoxCAD.Cad/Runtime/IAutoGo.cs create mode 100644 src/IFoxCAD.Cad/Runtime/Log.cs create mode 100644 src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs create mode 100644 src/IFoxCAD.Cad/Runtime/Utils.cs create mode 100644 src/IFoxCAD.WPF/EnumSelection.cs create mode 100644 src/IFoxCAD.WPF/GlobalUsings.cs create mode 100644 tests/Test/CmdINI.cs create mode 100644 tests/Test/GlobalUsings.cs create mode 100644 tests/Test/TestAOP.cs create mode 100644 tests/Test/TestCurve.cs create mode 100644 tests/Test/TestDBTrans.cs create mode 100644 tests/Test/TestEnt.cs create mode 100644 tests/Test/TestFileDatabase.cs create mode 100644 tests/Test/TestJig.cs create mode 100644 tests/Test/TestLisp.cs create mode 100644 tests/Test/TestLoop.cs create mode 100644 tests/Test/TestMirrorFile.cs create mode 100644 tests/Test/TestPoint.cs create mode 100644 tests/Test/TestQuadTree.cs create mode 100644 tests/Test/Testid.cs create mode 100644 tests/Test/testblock.cs create mode 100644 tests/Test/wpf/Class1.cs create mode 100644 tests/TestConsole/Program.cs create mode 100644 tests/TestConsole/RuntimeHelpers.cs create mode 100644 tests/TestConsole/TestConsole.csproj create mode 100644 "tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f0012cd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,217 @@ +# 如果要从更高级别的目录继承 .editorconfig 设置,请删除以下行 +root = true + +# c# 文件 +[*.cs] + +#### Core EditorConfig 选项 #### + +# 缩进和间距 +indent_size = 4 +indent_style = space +tab_width = 4 + +# 新行首选项 +end_of_line = crlf +insert_final_newline = false + +#### .NET 编码约定 #### + +# 组织 Using +dotnet_separate_import_directive_groups = false +dotnet_sort_system_directives_first = false +file_header_template = unset + +# this. 和 Me. 首选项 +dotnet_style_qualification_for_event = false +dotnet_style_qualification_for_field = false +dotnet_style_qualification_for_method = false +dotnet_style_qualification_for_property = false + +# 语言关键字与 bcl 类型首选项 +dotnet_style_predefined_type_for_locals_parameters_members = true +dotnet_style_predefined_type_for_member_access = true + +# 括号首选项 +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity +dotnet_style_parentheses_in_other_operators = never_if_unnecessary +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity + +# 修饰符首选项 +dotnet_style_require_accessibility_modifiers = for_non_interface_members + +# 表达式级首选项 +dotnet_style_coalesce_expression = true +dotnet_style_collection_initializer = true +dotnet_style_explicit_tuple_names = true +dotnet_style_namespace_match_folder = true +dotnet_style_null_propagation = true +dotnet_style_object_initializer = true +dotnet_style_operator_placement_when_wrapping = beginning_of_line +dotnet_style_prefer_auto_properties = true +dotnet_style_prefer_compound_assignment = true +dotnet_style_prefer_conditional_expression_over_assignment = true +dotnet_style_prefer_conditional_expression_over_return = true +dotnet_style_prefer_inferred_anonymous_type_member_names = true +dotnet_style_prefer_inferred_tuple_names = true +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:error +dotnet_style_prefer_simplified_boolean_expressions = true +dotnet_style_prefer_simplified_interpolation = true + +# 字段首选项 +dotnet_style_readonly_field = true + +# 参数首选项 +dotnet_code_quality_unused_parameters = all + +# 禁止显示首选项 +dotnet_remove_unnecessary_suppression_exclusions = none + +# 新行首选项 +dotnet_style_allow_multiple_blank_lines_experimental = true +dotnet_style_allow_statement_immediately_after_block_experimental = true + +#### c# 编码约定 #### + +# var 首选项 +csharp_style_var_elsewhere = false +csharp_style_var_for_built_in_types = false +csharp_style_var_when_type_is_apparent = false + +# Expression-bodied 成员 +csharp_style_expression_bodied_accessors = true +csharp_style_expression_bodied_constructors = false +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_lambdas = true +csharp_style_expression_bodied_local_functions = false +csharp_style_expression_bodied_methods = false +csharp_style_expression_bodied_operators = false +csharp_style_expression_bodied_properties = true + +# 模式匹配首选项 +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_prefer_not_pattern = true +csharp_style_prefer_pattern_matching = true +csharp_style_prefer_switch_expression = true + +# Null 检查首选项 +csharp_style_conditional_delegate_call = true + +# 修饰符首选项 +csharp_prefer_static_local_function = true +csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async + +# 代码块首选项 +csharp_prefer_braces = true +csharp_prefer_simple_using_statement = true + +# 表达式级首选项 +csharp_prefer_simple_default_expression = true +csharp_style_deconstructed_variable_declaration = true +csharp_style_implicit_object_creation_when_type_is_apparent = true +csharp_style_inlined_variable_declaration = true +csharp_style_pattern_local_over_anonymous_function = true +csharp_style_prefer_index_operator = true +csharp_style_prefer_range_operator = true +csharp_style_throw_expression = true +csharp_style_unused_value_assignment_preference = discard_variable +csharp_style_unused_value_expression_statement_preference = discard_variable + +# "using" 指令首选项 +csharp_using_directive_placement = outside_namespace + +# 新行首选项 +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true +csharp_style_allow_embedded_statements_on_same_line_experimental = true + +#### C# 格式规则 #### + +# 新行首选项 +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_new_line_before_members_in_anonymous_types = true +csharp_new_line_before_members_in_object_initializers = true +csharp_new_line_before_open_brace = accessors,anonymous_methods,anonymous_types,control_blocks,methods,object_collection_array_initializers,properties,types +csharp_new_line_between_query_expression_clauses = true + +# 缩进首选项 +csharp_indent_block_contents = true +csharp_indent_braces = false +csharp_indent_case_contents = true +csharp_indent_case_contents_when_block = true +csharp_indent_labels = one_less_than_current +csharp_indent_switch_labels = true + +# 空格键首选项 +csharp_space_after_cast = false +csharp_space_after_colon_in_inheritance_clause = true +csharp_space_after_comma = true +csharp_space_after_dot = false +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_after_semicolon_in_for_statement = true +csharp_space_around_binary_operators = before_and_after +csharp_space_around_declaration_statements = false +csharp_space_before_colon_in_inheritance_clause = true +csharp_space_before_comma = false +csharp_space_before_dot = false +csharp_space_before_open_square_brackets = false +csharp_space_before_semicolon_in_for_statement = false +csharp_space_between_empty_square_brackets = false +csharp_space_between_method_call_empty_parameter_list_parentheses = false +csharp_space_between_method_call_name_and_opening_parenthesis = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_space_between_method_declaration_empty_parameter_list_parentheses = false +csharp_space_between_method_declaration_name_and_open_parenthesis = false +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_parentheses = false +csharp_space_between_square_brackets = false + +# 包装首选项 +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +#### 命名样式 #### + +# 命名规则 + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# 符号规范 + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# 命名样式 + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case diff --git a/.gitattributes b/.gitattributes index 8bb03aa..63a09c5 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,4 @@ -############################################################################### +############################################################################### # Set default behavior to automatically normalize line endings. ############################################################################### * text=auto diff --git a/.gitignore b/.gitignore index 25cc348..e910608 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,11 @@ -## Ignore Visual Studio temporary files, build results, and +## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. -## 忽略Visual Studio临时文件、生成结果和由VisualStudio常用插件生成的文件。 +## Visual StudioʱļɽVisualStudioòɵļ ## ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore # User-specific files -# 用户特定文件 +# ûضļ *.rsuser *.suo *.user @@ -13,12 +13,12 @@ *.sln.docstates # User-specific files (MonoDevelop/Xamarin Studio) -# 用户特定文件 (MonoDevelop/Xamarin Studio) +# ûضļ (MonoDevelop/Xamarin Studio) *.userprefs # Build results -# Build 结果 -# 语法:[abc]匹配任何一个列在方括号中的字符(要么匹配一个 a,要么匹配一个 b,要么匹配一个 c)——如 *.[oa]表明Git忽略所有以 .o 或 .a 结尾的文件 +# Build +# ﷨[abc]ƥκһڷеַҪôƥһ aҪôƥһ bҪôƥһ c *.[oa]Git .o .a βļ [Dd]ebug/ [Dd]ebugPublic/ [Rr]elease/ @@ -33,34 +33,34 @@ bld/ [Ll]og/ # Visual Studio 2015/2017 cache/options directory -# Visual Studio 2015/2017 缓存/选项 目录 +# Visual Studio 2015/2017 /ѡ Ŀ¼ .vs/ # Uncomment if you have tasks that create the project's static files in wwwroot -# 如果您有在wwwroot中创建项目静态文件的任务,请取消注释 +# wwwrootдĿ̬ļȡע #wwwroot/ # Visual Studio 2017 auto generated files -# Visual Studio 2017自动生成的文件 +# Visual Studio 2017Զɵļ Generated\ Files/ # MSTest test Results -# MSTest测试结果 +# MSTestԽ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* # NUNIT -# NUnit 是 JUnit 的 .NET 版,支持所有 .NET 语言,完全使用 C# 编写,并进行完全重新设计以利用很多高级的 .NET 语言特性,例如定制属性以及其他相关的反射功能。 +# NUnit JUnit .NET 棬֧ .NET ԣȫʹ C# дȫúܶ߼ .NET ԣ綨Լصķ书ܡ *.VisualState.xml TestResult.xml # Build Results of an ATL Project -# ATL项目的生成结果 +# ATLĿɽ [Dd]ebugPS/ [Rr]eleasePS/ dlldata.c # Benchmark Results -# Benchmark结果 +# Benchmark BenchmarkDotNet.Artifacts/ # .NET Core @@ -69,11 +69,11 @@ project.fragment.lock.json artifacts/ # StyleCop -# 代码检测工具 +# ⹤ StyleCopReport.xml # Files built by Visual Studio -# Visual Studio built的文件 +# Visual Studio builtļ *_i.c *_p.c *_h.h @@ -103,11 +103,11 @@ StyleCopReport.xml *.scc # Chutzpah Test files -# Chutzpah测试文件 +# Chutzpahļ _Chutzpah* # Visual C++ cache files -# Visual C++ 缓存文件 +# Visual C++ ļ ipch/ *.aps *.ncb @@ -119,23 +119,23 @@ ipch/ *.VC.VC.opendb # Visual Studio profiler -# 应用程序性能分析工具 +# Ӧóܷ *.psess *.vsp *.vspx *.sap # Visual Studio Trace Files -# Visual Studio 跟踪文件 +# Visual Studio ļ *.e2e # TFS 2012 Local Workspace -# TFS 2012 本地工作区 +# TFS 2012 ع $tf/ # Guidance Automation Toolkit -# 这套工具旨在简化将可重用的代码集成到应用程序的过程,使架构师能将通常需手动执行的一系列开发工作自动化起来。 -# 使用此工具,还能确保重复性的、易出错的开发工作以合理、一致的方式完成,并能缩短软件开发时间。 +# ׹ּڼ򻯽õĴ뼯ɵӦóḶ́ʹܹʦִֶܽͨеһϵпԶ +# ʹô˹ߣȷظԵġ׳ĿԺһµķʽɣʱ䡣 *.gpState # ReSharper is a .NET coding add-in @@ -173,11 +173,11 @@ AutoTest.Net/ .sass-cache/ # Installshield output folder -# Installshield 输出文件夹 +# Installshield ļ [Ee]xpress/ # DocProject is a documentation generator add-in -# DocProject 是一个文档生成器外接程序 +# DocProject һĵӳ DocProject/buildhelp/ DocProject/Help/*.HxT DocProject/Help/*.HxC @@ -195,32 +195,32 @@ publish/ *.azurePubxml # Note: Comment the next line if you want to checkin your web deploy settings, # but database connection strings (with potential passwords) will be unencrypted -# 注意:如果要签入web部署设置,请在下一行添加注释, -# 但是数据库连接字符串(带有可能的密码)将是未加密的 +# ע⣺Ҫǩwebãһעͣ +# ݿַпܵ룩δܵ *.pubxml *.publishproj # Microsoft Azure Web App publish settings. Comment the next line if you want to # checkin your Azure Web App publish settings, but sensitive information contained # in these scripts will be unencrypted -# Microsoft Azure Web App发布设置。 -# 如果您想签入Azure Web App发布设置,请在下一行添加注释,但这些脚本中包含的敏感信息将不加密 +# Microsoft Azure Web Appá +# ǩAzure Web AppãһעͣЩűаϢ PublishScripts/ # NuGet Packages -# NuGet包 +# NuGet *.nupkg # The packages folder can be ignored because of Package Restore -# 由于Package Restore,包文件夹可以忽略 +# Package RestoreļпԺ **/[Pp]ackages/* # except build/, which is used as an MSBuild target. -# 除了build/,它用作MSBuild目标。 +# build/MSBuildĿꡣ !**/[Pp]ackages/build/ # Uncomment if necessary however generally it will be regenerated when needed -# 必要时取消注释,但通常需要时会重新生成注释 +# ҪʱȡעͣͨҪʱע #!**/[Pp]ackages/repositories.config # NuGet v3's project.json files produces more ignorable files -# NuGet v3的project.json文件生成更多可忽略的文件 +# NuGet v3project.jsonļɸɺԵļ *.nuget.props *.nuget.targets @@ -233,7 +233,7 @@ ecf/ rcf/ # Windows Store app package directories and files -# Windows应用商店应用程序包目录和文件 +# WindowsӦ̵ӦóĿ¼ļ AppPackages/ BundleArtifacts/ Package.StoreAssociation.xml @@ -241,16 +241,16 @@ _pkginfo.txt *.appx # Visual Studio cache files -# Visual Studio缓存文件 +# Visual Studioļ # files ending in .cache can be ignored -# 可以忽略以.cache结尾的文件 +# Ժ.cacheβļ *.[Cc]ache # but keep track of directories ending in .cache -# 但要跟踪以.cache结尾的目录 +# Ҫ.cacheβĿ¼ !?*.[Cc]ache/ # Others -# 其他 +# ClientBin/ ~$* *~ @@ -262,16 +262,16 @@ ClientBin/ orleans.codegen.cs # Including strong name files can present a security risk -# 包含强名称文件可能会带来安全风险 +# ǿļܻȫ # (https://github.com/github/gitignore/pull/2483#issue-259490424) #*.snk # Since there are multiple workflows, uncomment next line to ignore bower_components -#由于有多个工作流,取消注释下一行以忽略bower_components +#жȡעһԺbower_components # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) #bower_components/ # ASP.NET Core default setup: bower directory is configured as wwwroot/lib/ and bower restore is true -#ASP.NET核心默认设置:bower目录配置为wwwroot/lib/并且bower restore为true +#ASP.NETĬãbowerĿ¼Ϊwwwroot/lib/bower restoreΪtrue **/wwwroot/lib/ # RIA/Silverlight projects @@ -280,7 +280,7 @@ Generated_Code/ # Backup & report files from converting an old project file # to a newer Visual Studio version. Backup files are not needed, # because we have git ;-) -#将旧项目文件转换为新的Visual Studio版本的备份和报告文件。不需要备份文件,因为我们有git; +#ĿļתΪµVisual Studio汾ıݺͱļҪļΪgit _UpgradeReport_Files/ Backup*/ UpgradeLog*.XML @@ -294,7 +294,7 @@ ServiceFabricBackup/ *.ndf # Business Intelligence projects -# 商业智能项目 +# ҵĿ *.rdl.data *.bim.layout *.bim_*.settings @@ -304,28 +304,28 @@ ServiceFabricBackup/ FakesAssemblies/ # GhostDoc plugin setting file -# GhostDoc插件设置文件 +# GhostDocļ *.GhostDoc.xml # Node.js Tools for Visual Studio -# 用于Visual Studio的Node.js工具 +# Visual StudioNode.js .ntvs_analysis.dat node_modules/ # Visual Studio 6 build log -# Visual Studio 6生成日志 +# Visual Studio 6־ *.plg # Visual Studio 6 workspace options file -# Visual Studio 6工作区选项文件 +# Visual Studio 6ѡļ *.opt # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) -# Visual Studio 6自动生成的工作区文件(包含打开的文件等) +# Visual Studio 6ԶɵĹļ򿪵ļȣ *.vbw # Visual Studio LightSwitch build output -# Visual Studio LightSwitch生成输出 +# Visual Studio LightSwitch **/*.HTMLClient/GeneratedArtifacts **/*.DesktopClient/GeneratedArtifacts **/*.DesktopClient/ModelManifest.xml @@ -334,7 +334,7 @@ node_modules/ _Pvt_Extensions # Paket dependency manager -# Paket依赖关系管理器 +# Paketϵ .paket/paket.exe paket-files/ @@ -353,7 +353,7 @@ __pycache__/ *.pyc # Cake - Uncomment if you are using it -# Cake-如果你正在使用它,请取消注释 +# Cake-ʹȡע # tools/** # !tools/packages.config @@ -361,7 +361,7 @@ __pycache__/ *.tss # Telerik's JustMock configuration file -# Telerik的JustMock配置文件 +# TelerikJustMockļ *.jmconfig # BizTalk build output @@ -372,29 +372,39 @@ __pycache__/ *.xsd.cs # OpenCover UI analysis results -# OpenCover UI分析结果 +# OpenCover UI OpenCover/ # Azure Stream Analytics local run output -# Azure流分析本地运行输出 +# Azure ASALocalRun/ # MSBuild Binary and Structured Log -# MSBuild二进制和结构化日志 +# MSBuildƺͽṹ־ *.binlog # NVidia Nsight GPU debugger configuration file -# NVidia Nsight GPU调试器配置文件 +# NVidia Nsight GPUļ *.nvuser # MFractors (Xamarin productivity tool) working folder -# MFractors(Xamarin生产力工具)工作文件夹 +# MFractorsXamarinߣļ .mfractor/ # Local History for Visual Studio -# Visual Studio 的本地历史记录 +# Visual Studio ıʷ¼ .localhistory/ # BeatPulse healthcheck temp database -# BeatPulse healthcheck 临时数据库 +# BeatPulse healthcheck ʱݿ healthchecksdb + +#macos +*.DS_Store + +#vscode +.ionide +.vscode + +#ifoxcad +#tests/TestConsole/ \ No newline at end of file diff --git a/IFoxCAD.sln b/IFoxCAD.sln index 309172c..a9e0664 100644 --- a/IFoxCAD.sln +++ b/IFoxCAD.sln @@ -1,15 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31025.194 +# Visual Studio Version 17 +VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Cad", "src\IFoxCAD.Cad\IFoxCAD.Cad.csproj", "{D5FAED7C-A99B-4BEE-A745-45442DC44971}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DBTrans.test", "tests\DBTrans.test\DBTrans.test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "tests\Test\Test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.WPF", "src\IFoxCAD.WPF\IFoxCAD.WPF.csproj", "{D820D629-1AB3-4BE3-A772-CB753D98CCB8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IFoxCAD.Basal", "src\IFoxCAD.Basal\IFoxCAD.Basal.csproj", "{40BF07C4-100A-4810-A27B-4587CFB38E6E}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Basal", "src\IFoxCAD.Basal\IFoxCAD.Basal.csproj", "{40BF07C4-100A-4810-A27B-4587CFB38E6E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsole", "tests\TestConsole\TestConsole.csproj", "{E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -33,6 +35,10 @@ Global {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Debug|Any CPU.Build.0 = Debug|Any CPU {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.ActiveCfg = Release|Any CPU {40BF07C4-100A-4810-A27B-4587CFB38E6E}.Release|Any CPU.Build.0 = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/README.md b/README.md index f312887..6a4b012 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,90 @@ #### 介绍 -基于.NET的Cad二次开发类库 +基于.NET的Cad二次开发类库。 + +可以加群交流: + +![ifoxcad用户交流群群二维码](./docs/png/ifoxcad用户交流群群二维码.png) #### 软件架构及相关说明 - [软件架构说明](/docs/关于IFoxCAD的架构说明.md) - - [扩展函数说明](/docs/关于扩展函数的说明.md) -#### 安装教程 +#### 编译 IFox 源码工程 -1. vs新建net standord 类库 -2. 修改项目TargetFramework为net45,保存重加载项目 -3. 右键项目,管理nuget程序包,搜索ifoxcad,安装最新版就可以了 +由于vs2022抛弃了某几个net版本,所以我们同时安装vs2019和vs2022,然后使用vs2022; +其中的原因是vs2019拥有全部net版本,而vs2022拥有最新的分析器和语法. -#### 使用说明 +#### IFoxCad 项目模版 -1. 快速入门 +可以在vs扩展菜单-管理扩展中搜索ifoxcad,即可安装项目模板。使用项目模版可以方便的创建支持多目标多版本的使用ifoxcad类库的项目和类。如果无法在vs的市场里下载,就去上面的QQ群里下载。 - - 打开vs,新建一个standard类型的类库项目,修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET35以上的标准TFM(如:net35、net40、net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译]( https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 - - 右键项目文件,选择管理nuget程序包。 - - 在nuget程序里搜索**ifoxcad**,直接选择最新的版本,然后点击安装**IFoxCAD.Cad**,nuget会自动安装ifoxcad依赖的库。 - - 添加引用 +#### 安装教程 - ```c# - using Autodesk.AutoCAD.ApplicationServices; - using Autodesk.AutoCAD.EditorInput; - using Autodesk.AutoCAD.Runtime; - using Autodesk.AutoCAD.Geometry; - using Autodesk.AutoCAD.DatabaseServices; - using IFoxCAD.Cad; - ``` +1. 新建net standard 类库 +2. 修改项目`.csproj`的`TargetFrameworks`为net45,保存重加载项目,这里需要注意和cad版本对照. +3. 右键项目,管理nuget程序包,搜索ifoxcad,安装最新版就可以了. - - 添加代码 +#### 使用说明 - ```c# - [CommandMethod("hello")] - public void Hello() - { - using var tr = new DBTrans() - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line1); - } +1. 快速入门 + + - 打开vs,新建一个standard类型的类库项目,**注意,需要选择类型的时候一定要选standard2.0** ![](./docs/png/standard.png) + + - 双击项目,打开项目文件: + + - 修改项目文件里的`netcore2.0`为`NET45`。其中的net45,可以改为NET45以上的标准TFM(如:net45、net46、net47等等)。同时可以指定多版本。具体的详细的教程见 [VS通过添加不同引用库,建立多条件编译]( https://www.yuque.com/vicwjb/zqpcd0/ufbwyl)。 + + - 在 ` xxx ` 中增加 `preview`,主要是为了支持最新的语法,本项目采用了最新的语法编写。项目文件现在的内容类似如下: + + ```xml + + + net45 + preview + + ``` - - 这段代码就是在cad的当前空间内添加了一条直线。 - + + - 右键项目文件,选择管理nuget程序包。![](./docs/png/nuget1.png) + + - 在nuget程序里搜索**ifoxcad**,直接选择最新的版本(如果你是 **net40** 或者 **net35** 的用户,可以安装 **0.1.6** 版本),然后点击安装**IFoxCAD.Cad**,nuget会自动安装ifoxcad依赖的库。(按下图绿色框框里选择浏览,程序包来源选择nuget.org,安装IFoxCAD.Cad包。IFoxCAD.Basal是IFoxCAD.Cad的依赖项会自动安装,如果要开发wpf界面的话,可以安装IFoxCAD.WPF,提供了简单的mvvm支持)![](./docs/png/nuget.png) + + - 添加引用 + + ```c# + using Autodesk.AutoCAD.ApplicationServices; + using Autodesk.AutoCAD.EditorInput; + using Autodesk.AutoCAD.Runtime; + using Autodesk.AutoCAD.Geometry; + using Autodesk.AutoCAD.DatabaseServices; + using IFoxCAD.Cad; + ``` + + - 添加代码 + + ```c# + [CommandMethod("hello")] + public void Hello() + { + using var tr = new DBTrans(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line1); + // 如果你没有添加preview到项目文件里的话:按如下旧语法: + // using(var tr = new DBTrans()) + // { + // var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + // tr.CurrentSpace.AddEntity(line1); + // } + } + ``` + + 这段代码就是在cad的当前空间内添加了一条直线。 + - F6生成,然后打开cad,netload命令将刚刚生成的dll加载。 + - 运行hello命令,然后缩放一下视图,现在一条直线和一个圆已经显示在屏幕上了。 2. [事务管理器用法](/docs/DBTrans.md) @@ -60,23 +97,62 @@ 5. [WPF支持](/docs/WPF.md) 6. 天秀的自动加载与初始化 - - 为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,内裤提供了`AutoRegAssem`抽象类来完成此功能,只要在需要初始化的类继承`AutoRegAssem`类,然后实现`Initialize()` 和`Terminate()`两个函数就可以了。特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 - + + 为了将程序集的初始化和通过写注册表的方式实现自动加载统一设置,减少每次重复的工作量,类裤提供了`AutoRegAssem`抽象类来完成此功能,只要在需要初始化的类继承`AutoRegAssem`类,然后实现`Initialize()` 和`Terminate()`两个函数就可以了。 + 特别强调的是,一个程序集里只能有一个类继承,不管是不是同一个命名空间。 + + 但是为了满足开闭原则,使用特性进行分段初始化是目前最佳选择 + ```c# - public class Test : AutoRegAssem //继承 + using Autodesk.AutoCAD.Runtime; + using IFoxCAD.Cad; + using System; + using System.Reflection; + + /* + * 自动执行接口 + * 这里必须要实现一次这个接口,才能使用 IFoxInitialize 特性进行自动执行 + */ + public class CmdINI : AutoRegAssem { - public override void Initialize() //实现接口函数 - { - throw new NotImplementedException(); + // 这里可以写任何普通的函数,也可以写下面 AutoTest 类里的实现了 IFoxInitialize 特性的初始化函数 + // 继承AutoRegAssem的主要作用是写注册表用来自动加载dll,同时执行实现了 IFoxInitialize 特性的函数 + // 注意这里的自动执行是在cad启动后,加载了dll之后执行,而不是运行命令后执行。 + + [IFoxInitialize] + public void InitOne() + { + //TODO 你想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 } - public override void Terminate() //实现接口函数 + + } + + //其他的类中的函数: + //实现自动接口之后,在任意一个函数上面使用此特性,减少每次改动 CmdINI 类 + public class AutoTest + { + [IFoxInitialize] + public void Initialize() + { + //TODO 你想在加载dll之后自动执行的函数 + } + [IFoxInitialize] + public void InitTwo() + { + //TODO 你想在加载dll之后自动执行的函数 + // 可以随便在哪里类里 可以多次实现 IFoxInitialize 特性 + } + [IFoxInitialize(isInitialize: false)] // 特性的参数为false的时候就表示卸载时执行的函数 + public void Terminate() { - throw new NotImplementedException(); + //TODO 你想在关闭cad时自动执行的函数 } } ``` - + + + 7. 天秀的打开模式提权 由于cad的对象是有打开模式,是否可写等等,为了安全起见,在处理对象时,一般是用读模式打开,然后需要写数据的时候在提权为写模式,然后在降级到读模式,但是这个过程中,很容易漏掉某些步骤,然后cad崩溃。为了处理这些情况,内裤提供了提权类来保证读写模式的有序转换。 @@ -88,4 +164,5 @@ } //关闭事务自动处理读写模式 ``` -8. 未完待续。。。。 \ No newline at end of file +8. 未完待续。。。。 + diff --git a/docs/DBTrans.md b/docs/DBTrans.md index 29f88dc..5e75860 100644 --- a/docs/DBTrans.md +++ b/docs/DBTrans.md @@ -59,7 +59,7 @@ using (DBTrans tr = new DBTrans()) // 第一步,开启事务 - **向符号表里添加元素** ```c# - using (DBTransaction tr = new DBTransaction()) + using (DBTrans tr = new DBTrans()) { // 第一步,开启事务 var layerTable = tr.LayerTable; // 第二步,获取图层表 @@ -75,7 +75,7 @@ using (DBTrans tr = new DBTrans()) // 第一步,开启事务 想要添加和获取符号表内的某个元素非常的简单: ```c# - using (DBTransaction tr = new DBTransaction()) // 第一步,开启事务 + using (DBTrans tr = new DBTrans()) // 第一步,开启事务 { var layerTable = tr.LayerTable; // 第二步,获取图层表 layerTable.Add("1"); // 第三步,添加名为“1”的图层,即新建图层 diff --git a/docs/WPF.md b/docs/WPF.md index 08d7c28..73f4f11 100644 --- a/docs/WPF.md +++ b/docs/WPF.md @@ -225,7 +225,7 @@ public class TestViewModel : ViewModelBase 首先是在xaml里引入命名空间。 -`xmlns:eb="clr-namespace:IFoxCad.WPF;assembly=IFoxCad"` +`xmlns:eb="clr-namespace:IFoxCAD.WPF;assembly=IFoxCAD.WPF"` 然后 diff --git "a/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" "b/docs/png/ifoxcad\347\224\250\346\210\267\344\272\244\346\265\201\347\276\244\347\276\244\344\272\214\347\273\264\347\240\201.png" new file mode 100644 index 0000000000000000000000000000000000000000..a9d08c483b80f21d4b0ac6a0afe80f9b6b4233e6 GIT binary patch literal 20425 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Ð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 literal 0 HcmV?d00001 diff --git a/docs/png/nuget1.png b/docs/png/nuget1.png new file mode 100644 index 0000000000000000000000000000000000000000..91983ffef3b246ea796296b14fe09963663de184 GIT binary patch literal 27373 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 literal 0 HcmV?d00001 diff --git a/docs/png/standard.png b/docs/png/standard.png new file mode 100644 index 0000000000000000000000000000000000000000..dfc34fecb32120f54436115208a4abaea2dac516 GIT binary patch literal 148673 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%0DO&7ix%Xw#WJ4GhT6iE|`sg)>zmh51hyQTaFv=}2 zj`nUW|9WiI7rvAeD0qd>$6Z>Y`#zSt4nr+|XtExeZDEdJ3%T;KlEfH#+;*fxRJntY z7XW7n=31fCYfDN9+prRZX&7aJT)f|ZmuMby++b=Y~Esdi*4$U>R9aQi@`X6WOOX}T)tO_{#%a$V1V#_`SG>> zf^k^eN6T-&KFKYhg^yUfRhb!Se6(lId1gSLMFM&$W31%Hk6TpM4GvNU_%w=M;LVFj zajfa{NOjaYt>U@O1|=b6Jx*b-Do>u3Vs8Yreru=~Sl%6kXsqoHg%v*{_%RWIQ|)aE zSy`kzE7ZrQysaAug!RQRf`>$eu5><7gWs3{V>3r3(VMAZYZI{q=}Pv!bPczPun}13@(YOg z)CG@a9?e1f)kVF$c?3N_o(g^N9Pz|;*)DiWa&&V&vB9S@s&HRBnRnfDw?#MKBf%38{7OSGPzzlrn&B*!wKfMGJpztdP}+j zJh;TcjMz?bk%bk6t+Z-AmWDIuPD036DgNyBj$@<0qoG4D8cFe;YNL}0%z!j&;)k!u z6p7F~>dr0(?St9;+^@tjv-wr69+=8G=?l9qP$=P4gI+7)YK4bU!w ziaFu9b`ZuKg+OK+VQxyRkkR;XQ|gc$Z~K2pN}Da&@jmQZ(=NgS1lGAQSkml0_U>cE zO>73m0L>;`EarxT-O+O8@>$?$19mDY2|13)T;Ty>B(Qf;k$%`Eey(>mPnG6r$N;W) zX{XG#*gpEDK+oH{8?0I*k%B~M_J+>Tme`P)21E+(n*Th2*rsv#Y2_|54rNPdYcCG+ z4B_O%n$e=AVB_L`TAy;|R8$VmFh0dv01a7*b7^F!+Vkt%ddrv!$KUqF>}$BuegJK` zUlAo)c)rNKQYH|l^mjjkv1>G(D?dIVq57(L=m$bTt0lo9tSgur*4=mNTU8f<#!C!> z!DcW%K|uri_U`>K1HN@@X=Z}^RaelvBN0sKXfU2Yq59Ng9r?QvoHv+bMQ!dnI4Jc3 zHu47d=<^?D(5ZWkvXP5-nz8EP6YuXhYMl~k&{E7nfSPwX1FddnFGODmoUYQ-tQ*D9 z>mJ|eRU(_DRlY-H1ib5)G+NqTxM#XM^PL{1(bL9v@bvw+|2}>{p0rcAR`j)l$vAIH zMJ***_uf6qf*YK~qqXTrf1Xlze7b(27#6@cX|iAO5dxVqN5U2o0Btwtz$95pu3lRB zdckx&s5kqve?mWWTThuoPJ1%!t;Lk?l`EtCnN?@Mvx_H~+$=Oz>QGF|1#5tq;iP?@ zP1Z2XbRx7nCe-SOX_=M2LqI0n!%hrX-!5}}@j@QhR1jF+t!GB`31_`W}ZMma*G$B zBNjX-LpeeG`j{Db*l1+sgxjP25&m(`nF0Hes8Pmzrxm$0&f|?~)#1un7JvbfpXXmen+sC@gw|`q$M#XWmMW zWdR6bU*V`)a{Wil7k>2Hd9h5Jguc36nzRA4SDK;EC9D8D(YH;8#)0<0+(aCF&+)_U zC3e7y*|^GZjyvwB5p|B2{UX84}H)%p}fGAU^cI_OG4DrwNth~b{hpg zY|8gzm^|+A1yAAw%Y5Ymj@NO=7sw4-SZM75eeX+&5rF)h=iqj?kqZLuXcr;|bU{sq zd&+R0lfY%|ajBJdxkF^d-<>YT7SVll<6sj(Y>E3RA|f+#SS;Yv5g$2dEV8e{dt@}x zNOR6mW#W(gt1|@@<%V^G&9MPYP84!URFv~jIkV8wc0n_25=F6hlYRLt&@ zZ98(2VJ4J)zVe6=P^Y52V_-W-$Ls{>#S~Ok6;5s+-4n1tQOY~Mox5t3yxh~A@WJKm z1+zxZ~S&XS(wyD>_m+Y;|5p<9F*_4Wu@s59!nzGUX2P z2n@KRSe(jN)e>HU5Q^!Dji#ZZNmkdntF-`kz&30k#E8PuM@}L?_*0s%r`BM>9s%7& z%B+tlD4hsn-m{CGyMb^jXz-qRpVDGogeVmF5=F8ZTb~&@2h@CFPG^ZDR*TI%U&pM2 z&;xR(^O11G_Z#;&uJF++Mgzdac31tXHobo~|(z|ZV zkkXZ*HaS-4?3~!V!;{(A==8!3*4|fAz^TB$msy+c8<$Wx5yn4no)tW_#EV0{Kf)$vfg473N zDXOIG+|wTY)6%^aBZPF~+KVt!0pf?CbUWy`K&C?20YqI&5<1*Px}gGHLo=*A&E01M zZ5j)!*+XPI*m^O{HH`TcK&En7-dEr%$C2VFPt1`hl<1V1O+ZWEoitje`jqQJ_s=sw ze8Ne?sQ+%kmf0rAvn#x_9DgC=tjF$~oNm{5AMUZ`P-_io*(WS~h=ht!>=CAEmPdi% zhrxYE%wuS`%3nt*>c54Sobkx;HX6r{sSpDT36+Q%#g+oYg>Db(^&ajf&ug&d*R~G`op~D)J zcm$!f%{CGaNt>2#zndS)dK1iAeY0+XJ6#a|A0D9|o-I2Bi6F5URO;%8FL7`Vj*dJg z&TWOUq=n)$;X)XrU<*+%EW?!d!q=y38HJEhW-x>qS6Kt8Usy?_ni**=_%uwm`ve}0 zDlh9E3nWy-R%L9xhE}}YGA33G2juQY_xjZ)^B=~x%IY?mX;pz4Lkvsrm#2RTm|za? z-)V+D$!Wpwv=C%mE~xIY{g+RFP)1*H_GJ_@t-`5^gTB~MQ-x~_WNmKunTgM!G(_Ca zO$R)xkS501?{xk0n|Cu|taTM?k#)17hMhEKwsAWFh8d5juu3Np3Q>ln2=F$ny3W9x z$-=$lk6_(xo~cf>TcAGG5qOyv`48Mm96PMrpn-8PD6?#I{zhP2ICXMvoOC~cGIIg{q>6G7Km(zX>fQfQW=Pg|*;>}s{5 za6LWHv938ofD@$bH8@Uz&9bX{Gja@d`qF*bu~qydmA(mx6n1BM0m?MN^1x3@3<%DW zC7x_Y<~Zm0+;Pfh@n}4EKFfVnigtBD67V*M?dZE(kP^POz1Oe7b2K;;QxM{`2Z^(g z`;LD6SKIO6!Bfn{`rh6Q6KY?+kjb~85gdW4ZV+JHxRR_^UlV3F;D7rlDM^@u827{| zoYNHN^qmVe?S8oMofEM9vk7L?+-OREK5PW!hT-fRO0Vz=rJ~4yt}*MviAZJN@RL4Ip|gN_okio2lC3>Yx?fXa@fOe!-R67VfSbR3 zd`zgOUZ_n*j6Jzq#xicAEtQJ>TX6n-LY^?*hysfauEeZ%25)5l<^fyjGXK{$mluJ(VJNRQG?#mzi7-LfSPdwS2WCk zV{@U>dwJO;I>N`jYYcTd)fDjmfDRY9fm1I?<%2P%sMbP^XHph8|I_*ZIH7U^Vt?k8 zl^JLQ4o^XdSHze%v0_LA6IeUq+#NwMUNOW*yO%vU-w#m&dl>gIE?R%nWUFli%BGYrhTV##`{|a_Lz=_tqMX1l zyCs8V?`t1x8o(A>8j&+>&?N%qE^9=nDc~ddD>q~bbF3JnT0--Zeh9gs`%xSPiA@8= z`8-Jgu?}#RNu`3PimgjsZ;WH_BKNXwvmgI~{JSXXIk^p*U%{1p>(^CeDRKy`hzbdP zws(TV3nHtbcoM*xVK>$V%ySW>g985%oXUUon})d*q|HO_7s_H}vwkbky!?Esfl{iB z4Jpk!qXF_f-tnuX`L3ca05h$P&+%l4Xb>ZibFz%oYs$+-&=g4G0Hf z-R+RB{9ju(r)(B;y0j<<)}Qn@mZ(vHjiT$VnNl%z`dL-EIs z|8!~H=djkn9;NDQe0gxp)HcPu>)MK6>JgaPXVsb}@k`{_)zix-HL7r$oyXOHh+m3= znSSdk=xH{g>3+Oo-tArQ$eqO|obfA1j_l_iq?Zjb%&dIm+dFY{e@rHH2CuCz^TaO` zDmT=sm!uL2v4Pb6w_gOxZyT+IM}qBc$@G8E zyo=nDuRgbLAJ;TC-Cy=h_;^zP9^jM(%r^xelVNE}X$C!HOr@2VgSbV-!aA~t1LWn& zIIW3fRFz8UgU2%J=nCeecFUR*ZkU{kQ3H9X6KrCg>ZzCXKJaPCQ_kg?)^sJWHIPkm zMUiFWdYPGFe0$`#*H=fAEF+K<|9N4|G6h`)c*K=Xz~G)8@%;}NSbTU3Ki8~&fnC>q zBZml7e|h0yeN&M@0eNX#w5`;gJZfm_(x}w?2rJ5~s0g-i!({QmJsV>)BH`%gyU#8+ z=&>+Ol(@_Bo}s~7rD4zOVH3=@B^QH{xx^5=+SWX8BU_0v31*n#uqF*xxg>5ywfcF2 zvakT#*h=3B{RA3CJd7Bras~UW*cwr;@9TI>aYwayv>)W`#sHSG#-Gz5P zkj8)(;nd~EjoKk{YWb@Ec0-LH9=Oew<^w2W-z~-75V9uDrRmI8^P5 z3P|jy`F^-Z7zp*F{NEh>`+^ zE_K|N>vB3|#0o9ijwgBG?%fVMB(i#Gk?h3#h8djJ-OuP*?L4D(R@u%%j9<>w_vD^S zDC<%g3MN^=imUxpjp)v6(uV{z4;zuzbAG!8Kb*ag&c7*b<2z+(&mYtm+RY7CFb(OA z7%)>#xQq!v34$%^k3-aahtb$B>)`9dd4az>SYBTjw#Q51HXN*O)cC&PtPd(qjGT9J zIp>`jMVMLGUE>(5O32#%WnoV$=By?nMo2>i9*BtdAtyL9 z7JOmHl;Y-{TlbJ#eySapt=y!%(#!V0h`ju&sF-p4$$b|_cE?JXOZPRGKXWcITIPTr zEmUrsaJwDT+Pf_o@KD8Io z_YR+HS4~3PeMUNx7g#X9vp|Cbb8Df2_^ww%xa?}G>GJO5oK4N}hf1QMrVe7w6b5T? zo<&0Ir5lmN(qB{pA&y9^*N~&iY0rm8xig`(>*vcO`n*Pl(#PUEe@1HFZMEqg5z49m z<%pVFX*YGs<%63B)T50uecl!EUfNdnY%Rx|#jwKiSu~9RwoHT9bh_Z%*bW-Ui%8>g z_ik})Ukrg3o1@RYIn+~fGs27gCbrvy7a5TO#T=6M9d2fq(JCC_9&qUNX~~XbS2^SP z$BpHVepVbsj3k3*vS}5b%BS!26|d6nmV2#4-qzdbn&Lv9J8(Pd~ll?^^Ll!W#?*#(V0pt`FZha zwHTQ9P|X{UVcLrqcwExQR#N-V#Ur=!$A*Ovm|qtE!p^C4xO|aefD&TW8hYESVCDkx z_Y3o+WgY5N90YfQuwAjG`bN5=exA(nE*>AwVGfH#*M1 z^UOJOzPumaFaEa4CTp#G-EH0FTK!&1O|MxGu(KA>4$#UAgkXFf{9VGZeeX@KUL0jIelUR z{#kFzZQ#6T7TKC4*2ZKyCZ$bNy>U$UZ9F@`ABJt)@t~;^g&jtVm;cy_W3KfY#ztmr z)!Ai|>LQ?X>@#;7h2~tZd~Cu*9^q)eT^jpkPO&tWTJDbt@9NIx^SZDuq8L&~Ro~!> zuEU9oh0MT34AK%oO6@Akm04)mlAWe7*`ahizf~x?p#ny?Gq;TM5N|{_`EHVfi_TPj zQRX4c1Q#R-N9b{QV7IG2Mp}LBB8>$VX2Wu`Umpw(woUV^kFaPb)wP0ilC@UA(Wh(dTj#}VZU zq5kn?>sXyh9!tDy&Z)L@KOBsSqawNK-M?Gy*n5$lv#i=p^A1v!tio?z4f0Tm+z0C7 z*823q!_)Dp&nt%?Al5HKJjz!LgR3eQC5?F0=IIH7E4QmdH&kd-v|OT)ztber?qyUN z`_l`Bs_$0zUa)jH2d$;AKA;k7KgV*7Q*NV1+Lwjrhz)6(Q>BJDT2Fj~_?6!Ip62nu zMe;0L-3Dz$zLFaEJj(i#i}oDI4G1E=KrtRdI$MP)}c% z;eA^pHPCAwcQelb_0pdqDZb%mU*yOPf}XYC$Eu1lNUh%@(ZbhedLU|W#ky)z#lh09 zzdK{1jC`GbrIsFDj`DmS2jwXC7mUHUF70Eq*3=|t(B6wFLo*0H??};KsCM8r=DHTM z`4uTA@Vh>2(6oEIx9sTAkroZXohP?kJZK}Js0istn;SG4%`Xdy!{*CTj=`vskyYB@ zQQRb_%(P@BaY^*poyfRLT$>p@O`Orc%Dcl^pHs5gx`3Taj#TK-{C1LtIz=l#y|Zn4|`yun}v;@pG($f{6vF z>A7*O-%YXFXMQWzJy;itsfMMfD0d29k1ofH7}Bjq{*2F+ZH=+e2ue71GCUeKQG6>b%kc+j=5tM&Cgq@B2E4SjQJ$8}_3zP?t`K}1G*Jj@a6JyW+%Jy9iT7}o&7Lr}>p{u=GL@1N zu=SFDoP2V4o*NxEU`DSl7${RZn*{g6btpg^;TB^iuWpX$n&Dx=kxZQPHI_k)%^p)a zx*+&Lcr&_pzi$qLS~l2`BbF0bxVh(T)CMz5K4)2>Qc%rb%fKIe}@;m#+7e9etrgImKaqk7NEb} zL&Hp4zX*KjNKUl8&!JG3qL0b`-37n}yd9U!Ul?#pj5l;o#h#SsL8_J!9YIGOX}2E* zd{Wmxc!pEEoNXO<6ju6Lxyol$$Z;;IM1B_BIP-exQ~IoGR+gRv+MuRJBoQTgC5QJn z4!VV*A&2*QnlCIM;ZTR$w0GLvobs7^M_Ls!dtjYmukKhajJ{pJV$TPuP|z2+F5;rD zUUfnUZ8K(u*ERMWD)>Cq;Jr%bdf55r_!wXSt6a^+kKD9zff81U;<`gr7#*wnSZ+A~8?dM#wU3j*aJLmJ^=0;vhW9WTdgv?i^M) z3^K1Wto(EnDl12Nk^p+?*F~!sl^Na=9w7G$EVTXU4w~`JL`8^Mo0f;L^!e}D6USdP zGzSxy^n~s|@|1)01db?_^T*MIZk~w3i4i5Q8iHk7<_b%LHZDI0Vwe?I9+qX_60^`D z&!6-Kr&+4{JUYbou}kPxV9=RjWaUw^PqMc9 z;dkp@mRQGPhg#fb$*8g%JjJC~!^m9PSr9$=ey(_Z4*Q%(W*|UB6j@%;IUrh^4ZqDF zrttdG4ANL>`VUM_1Ou7ZumpMesM(Q z^O!4xto!r??lUzMIDy6Dsg>^#?mUo zkO+zHu#wdBvkfnv6(Xp%*(cq~ouTUTHyThpO(@^3yCND4 zjjXClF(AD`6Gis~;XMc?0>au4T4Z33Hqd{(Z+*U@XVX$=VZAZ8`Cwq;rl1iM=JPqF zIM~8`)Sv16I;1A-%iZvf`OO;hfDwy%!UrGWn#~{U_1j*r`R2Z!oq81TLabT&O4`{~ z5agjODLer@Q5R-p$kk7Ks(9!%k7(tM(E>I~&p+*BvucOJ}UTsnFJ;gy6)+p?I@AJ9qb0FS==GP4eVVl$f z&zTHo8&b}f#xe(*-T?BH567|L0R7f%0$u!2lxPB!BVq7Ejje}q@VvI*_(g&xUW?3w ztG4ctU-lSXq*%>iok+Km^2i#II2HOY99PPoco^m-jA+*cO_mims3mW>^)A3s;m<@6 zNo_wIOQ01I-kw&JwNoaIL|L|2?j5^7BX^+L>DDvU`x^{!P3#997MH$hMli+-qOI2k}h@wJYSDB1ybBRreddGL;a00&+ zgDxvu8~g%fP7ZB*eaZ6A#Qsgdr!4nHxrp!ZDG!)xwM6aZQeVUr5i@x*-$9&Z8H9^9 z?NzxYLaD`PRWBS)+_JM|;Z^4@`uu?9sn-@_^2;lXYQfj5 z@{w?x_Ofu*#;WC4E1Fy?wwx?V zNlQ7hh*Oqc@lTHCR7Q=RQjqAq#p&eojBn+Im{%FlvsJvtM- zCkv4v^u?*LAa*3V`CY3mQM=7VUE5jLmY5lO6O!=q_-!%)hBdnNnc65_wYvv{n=d_u1 zO9-?vPu1^ZCbu$)%x`@%we~QSWTJ z`#M)>KLO?~A=*+XB=4e%=))12*Negob5AkW0(oI>W;JxmgUR96WiQo^wSC~;#}`S@ z-aXJ*11&4Ghgor>&NVp^HoQXYwMAE!dFi;c>g@h-gu`D|blcnb0to88?A&K6FpLf{9;i!X&96)+`)Af{A+8<{ z2~W(RO%@l!oj?Hl_dEewmmHUeb}0AAM;w4ISOsY;?Vw)jNg!e*yRZAsPbDQ9;n#`! zwL8B2+f~{56xjfX8TrUFmYs9o;nw{1ZkZU+ZB*uz?f)Mc@Ba)2M`&0Blkoff-R`W< zf4v4Y!~rFT;6Yi##($e+ZUwOXhHH2Kn#(`U5BMyyY`f8OF0FW1`|3Z(Aq4(y3+J}UqkC=tzpkYWpm$kzAOV1__}@qO1IU9r zd+NXQwL9jwy%tk5fZk@^DwG}l0RNE;`TpPkFW52iRfFPxa%0b4)}s}qIAw*4k%zg)DN80meg zVsQxVH2=12#sM4J_!VY!eivDDAFytEe4yz+K~?>cCV4Z@6!aUfx!+NHk5lYr@r{wr zCvEbPl7<+^A@5u%@uzMD9R@eD=De_TduU%AyF$5gJ|1^Hfj3oKP4jBEkoDfK9Ua-` zu;6Y^x$iNTALL8tc^G7BF#m+%U6wl>JU_cX*0DZs{Eouq+oR!!1Nb=w;&RcQM1FHTwM(v#R_uHeq-bk$MGV+V;^ZmjMEA_Kz%hUX?H@ zK=$`<(qZW_FDyHTYA=U$jGvgr$tGG!cUjcv^5VP~%Ds}6iCPt0|{knIt&eXi^89w7wiYrKbjm50L`UM9VAlEQz1jjxVvh9*)x%g*u2 zxwpgiDf`5|7&{YaHW3ndj5lNQqs9Dh0lD>7CTmY2b1$e*C$Hv#w) zZ`5~5OqW|tmnR`7KcbBjYtFRDB~g`};U-x7=iA+v7ajvDtR_=*?)QIFVS+$HkZ{lY z@b_N8e|lRZG_sXn%2y%$>)Z9??*U92K`hwK%xedF{WpD|7<=RawN0e zxZ6X%zynaB8W&nHay+x6lRl$M1`3yIB63~n*{56qfCJv`9eZ|EUv~G7nICNW%2}DP z_uh&OATv1u9#ba~)`60uPm3pm7?w<%^s?gL2{wg7=l!#m`vTn^AoJ<(j*h*pNm?9~ z=%`R=Om4vIjtke`C>qkS05;aVeb=YkCs%Jz|vb#FO@lnLVkeN@qALkgt z52MT0!)3meA+&rraH#9U9YwL-x?f{>-!eD#c|ynqBtf@ZrBzamc|QP0Bt1)Yy8DV1 z?bt~mpdN78p6K5C#Gio2ncdN1;h@&W>Fgp;F!lr*FdB7nk<-t!Jbp^W}Jj zG|fV`ylqt`gR{<=#nITYJE=6W}6BE%3qebrNw7i7SoZ9d&^=4{hc#0uCI`I%mVUW;~?>x&eb(q&|-JB zI*Cm2&}M|FhhqiXUVa!o@+G>oVQZD{*~y@LfwU5zgG^|jQ@z4Bt!E}D!bux@N=S~4 z&2CvB{Y^?bJ-WiYNFOVE5BCjDalzVF@d2oqqUlz~F`U7ViFwh!jC-2`GIOJS+uOe@*A zr!h#b23J0@8LD8*4|)gu!>;CB_5w#NJ}!cv z07e{;t3L3yIT+4+tHGQ{;Acf(pAst7t4Bc1P$()63n;5l$Jk?`J`jm^< zX;nKzx5l;Gm6ss${Xrf~nH<9T@|4G2=&on1taFi5IjvRGPntXDeq{{Ts3U`7CvRt; zq9H=dl=N#6wCfc~UovBhaLPByl#jz|V5hdernAZ^miY8ZN%8>9;gdkA>Yc_`>P=jN z$%n3hgB?p~7q!{V$IQ@IQ9%)wXB&>MP# z-FkzYHZ|Tgo5S=`QocR@jZ)BX<%+f2$H4~c*dhNvedI#=c+S1~V=gxp7%8z3Al# zgrO^GZ>!~pYoKV_xG9%et5En#u42ejC?@bOEVeeRZ?A>=g-@Qffl0+GcUp+j7F^z~ z1@^;%$fi3&;lqoU!G_HeQvn=2_2&LmvyiTe(;PFzHB+ODP5zHkY~oc1%+(*}Y1D2= z4hIY*Z&Is91fUsM^}LK^xQal7#hL@0TKXLOcsxug9$p)AZz^i*Al(eINyVZpB&%HM z?i{%%N+o=yWdq2Q@Zbmp4bStR;6v>St#fL$!GuEbu=aT&s&~Cot6%o~okUfJx zZ|p%>Gf+8;M6S9I7UUNbF6v{cR{e~vqy$#txME5v*3*&5ID**5Etru4{DcudwGz%< z9Mntg_9uTTUrYb1p7FmKgTs`Y-}UBY1G79rX{uhp#O4n-ilMUeT&E zTd(;o9%Ou)tU9cd_Iz<`wm$XT=~hTL2J3iHu8+> zm_?R{mT4L5vKi>6>JCwb_Q=6`1Kn@W>1X zIT}`$gX~>&&9YwNlffxv;5RjZ3m-{Q9{?gj>gO!ovca}*?p`e=H+s@p^x^pGB4$uV zWdnq_l9vEr-(_vL9$I?FZ^J$PZ3|x*w@@vg7qf)Ph|IF&3SDDxu}e4mI%NNuj^VB_DDxWFRt&n+i5><5MHD zEVjTdu!50b%cpJFX|v;V_fC}{t{N`Di`M$VI^wd{~6rLlDLTN6;^0ub~fWKBN+?`{XQK53__wUK=`P^^e8 zerMcQc|j;2z4 z>4>x(GWUgTxcZgJN}!r}Ld}DIil|U(6`nQc5~&)@U(}4jPj$XwE#>2`6)8jF^?2^0 z#@u=}8j}DJs}Z&VbL$k>acah1-} zN!(I*IpNXoeSZ^KWANe|K+o9EEM^s~yF7$Fk)|wU)t9m9acVsSSmxflMA{D1 zAvxo8)eObFfw6u`yD`Y6zj!FkirVTdZQkPVa>bojcR=haN3HADqL_}b(GK`v)*W*FFADKAm5D19zwj} z0#)F)MsExPCG;gM+a?|FcXtIdAVmPsn+`0e11Aa_<$MI&kHg8gsE%`9L{#?5)7xB^ zAMZCy6v{NGyvkF(E#HRdwc#{@6 z%})=d&s!Ct4VFiAAAM6mhrXPe)c4sCW2Vx;i(?= zLP=2vggnZvI9rdM%L<3~NBeXLOnpruni*~#`{2ZJVJvT{uz+s6{$4l+XY0AdcfXci zL@>o$NVBQKHYuqwEb|4_w0JjCC4%}A00vV0LjA1Nt)uA)ul+om{)JwRawUCHtAsL< zzmnXD#TLaYr;jx{F~X-|oj6&OaQgh{4BK%@+wx-n!@eI*h3}oSwy>n%Z-%e`6ajLA zL&4^uA37F^{z(+uS}=9%Vfj%H^ArOEc;|Zf9;5~76(V88M^3*Zad~9nZt%CfXio=g zH0I+K!fs8qU8u(iI7P<&LSy6`e5uv>gr0~M5mTLu8fe1t@-I?m{XF#5bCx;$H_?N{rX zh$v|t{3BLz9n8YU=)-a3-46Ej9uu))Wf%Cu@8gO+0TNBc>x{XkS%xE>Gd!dHX2bMA zV?uco%-%KlwxMzbYIqIphXF1(Ka=RIjzAG-_JUElFQIPJ2*87srQatXFZX{C=@&k% za4{M|Ho%)nZ>dMqh6ZVONiImmS$mSIejIehKegHh^>H$tI}kM3%#)UuKUP*X*n}<*yA{c5Q{wYCf?6kf3hhH{ z)60yKI5VQeUv*iXK6^#ShF8&6Nb8NGBAr)xT+rDAZEbSmlLeqFo^~(U@9U_HC=<^0 zkh3n&1Y1`YENa=x;VD-fwp=pyW1HW1t}piY<7&5Ii=DNAt?8Vp{!y*;x4r%A2?V-) zx%oM%7H#*_-EZfsnOs2g9sbql!p^}V&C5hLqmrr-nPA6qL?gNeg1sZqa%6x zlewOr-bxR&Oo#Wfdwco+7NuKwL9P1VqjUp}-oLQCn#p?Et-5pWz%d}Oi&t)E4w7Y`H^&oYnlZ&kIlv>J zTZJZ|zy}ftzF4a8rnXyU(%+qUM8Wf=XNf~6?9rW$mIj1K#WN|@i=u;{LJC_z-5Jz!*4x#|<8yvOUw~+!lroQy&4e{P{<3z4RxQIR*XO zEkysfOak~^ZZ?}#kQX0UJY=*ttT@=WN>bJBiZv3A^NM>x4)Oj6);_E>SO8$eu_ZfPqD2VE>lpV*y|$y*d8}I_%g- zQD9gTyW9VohXgp_{{oP13=CV;QuQ4=@gIryCsP89C-+YrwEMIF|1sXvt9GZ(8rrr8 z{l|vGxoXAUf@E2LN9>Z?<_46M3u?$Q9cysR!;Q!~Nx zYorVCE9V@5q{()6*_{5d-+RZ4>3lP`&8MrMd`;=PXD@xSB>iFQuHSLKsqt#y>(#_9 z%(E5G7e$BPbpfV35a7zB=6!p1=%n0xkw8zZk4MMtUs;HT>Me1CO5*nf#CM5Rq}IN- zhchUs=E;Z&YlVb*CX$$luHNp}aQ?-v>i5v$?Tgg^`b{yZ9)*REJpl?0S>a!zW{>!+ zkF3<%POJ+P=k~ueBu!md+DDa5Km2AJr};18dB-zD_Braq7#SM($<%7q(Y;Cv9Ho6V zjK3^|I~}2M6yX1i7JG}Gf8yowfJtcF&Ao|Jt zu2Y)}E2QmXPqe^@-Eyz^jqU1QjYeR3C`jt!4jw2eX}JbmdbZIs?BPBtp-^pzkkRu( zzD!~5+@axe10w}LS8Uba!V6=e}PkZV|dA{n3(fD5ZAsPx#lJ)V{PG-hbb! zwexQ@L@akAmX3JdjByeQCv0v$3ND5z(^m?=F8X;|^BT5Jil$laCOELWkrhXBV_hw> z4vX!g?ub-wcFFnhdHP44>CDTLsbvkZEI1EO!=$+YVy8mGWQUY9B#{e%)$%>Gn=1iS zQlK-20$JxFj%&?HP9|{ zudk@Cc&55d;^s$3@!A!%3Ue9g?~LO&!v|5m(oFy)#U&DdC0HgL+g#eSvE4R_lN!Do z$S0QiVTTUfmLpl9l*&(QO>rj>_<&$#Dxg>MLH*VaKpAY9$m`h>;x( zf)iElUHS!Lg-)4hFV4pgt zzg1!UrK&VqF!O`4cpjmR#Q?XZ5&8T*Wwbh#shR7gEN0N!g0W)O;%2K_ocUl8ZplRa zc~q^=s~bsvZzjb*U*fsl1TB;UHlY+<7a*i#lY{NcXzhFCB&;pAQK;=cz!g|fw3~J|Iws;zU!IRD#3aK|w z^+#3mwdk`;#X3~|$89V|+adf(CEI~rU3%Q_&VK1>1yaU3s)k4d7BkQu1)zD>$`R~o z9*i%Y>@bV8PLsIp_KJ9lG1cj&NmN-IOjcQYyk85h{pEZmR^CwV-7c8|jMOP$oU=-n zrn`(vgSK5If2VgNDLLIr`Y-rJ4-ZPZ9N86ghNgnN6CxZo5!^NUIGtzNbZqc>3`H_s z;y{L}e{W60*=CxSepv#iPbQ+%7X9gJ%9)k@D@!upa7F$iw@8oRb_LUC6(f#alJrYt z%99TnmnNu1ww}JhLUk5a{eM9x4yL)ii1}zHtqZ!87?e$r!LE3i(5g&Fs}#yXHfH91 z3^OvaCq93Zm~5t+G!4Cwom4BAr!riO%U5ADHhBm=O%KP#IV2_GrH-*D5*fvj>Mn&9YX9d^f1-6dD z__zm^EgzrZxJ*zh_o(l5EY_|-ir(>|IBsn?Y`6z+gX#OW9^ zcVoZ<5x5A~;cgMy4s*XJ62;y`8&LPhhprT@9)sXK0|LS}Yyt?{BqNS8*JzM25&UdF zyM;_2YuJefd{DlabJ<-@(@wV;Th2H)7E|~VDQB)Z(TuoJAXKFjsncO{M#F_Mkwtil z4Pz=(nU8eY$BG79shwrsFvBH+lyg)SjX3nTYy-w!r0V|HU+ENvFbp`!59fGz zqr)bI=sDdguLZvXoihhv3qwt~s2h}Qs)_s-QdLJvl4IA)JI_uz971nG{k%m@v4 zHRigdoUNZhc~jalyIs9YG?m_yjsr^kA3cUE-6K}kg*beCJg86#KoFij?PJ#k;Ga zsrJnl9>&9$bja_^Hsqgp_hDhXtNXZT^Z~Vi3AAz9b;SU}$gV3>aRF!)&N*TVz4H|+ z*)CjW@yfIr?{eRcLmtQsL{*Lae9bgtApf#wU)9_737s%IzgHV;CjO=T_w~t~%7xtj z2?25QWRLA5u(8fgC7@v!1L85=T(!0Sv}|D3iPvpkVmwZ`XA%18Gpz>Y*T@<^r&(5D zR|e7@^pVq(-&e(2%5DnoYXokh`_Rc{WHIvF%5MM0>?gpZFzdQZ*%?NOOg@n5-7dP| zR~eiI!36m&+YIK^=|fQZqI6zq^^b8+p48!+rsPa;-4jtU^MedEULJ%^u}-8W2Ue_- zc6U&wWh8Ts#)E7qZ!0O(8YQ|?bqBxxXxRFp=e}L|1Ygp3+p(oHqLBePOUw$lZhQ4$ z6{LZJ)z<6)l=3k*yZFQuE~-?AYKZ*omJt;zFHym{*PYmL*Om4ztN;WtH7Mq z;2IoN%0cYkHQ)`NDv_7+6#xe1#RT*9t1M2myZVy3y7DD#Wu(&qfK<52nRJ`}3A2@; z7<8+t5|ojWNs9+tx@-fw5mRV?kMZqkWLy6=cCjHf|8@(2C&HGv zbOP@#+gmPZ0bn-%KhBrIP z`|pcJs!%U?a{Q`ksvh2_6dv3cB2Io`sBG0Y~{owK)`}Jb`W|aCae&lWwwu|?^5kK-UaN4Jc$jAO$@GjDO zyQ?3+_7b&Yn7@zrJDz$p0kR4HF=g~`Fa8hS)qj#_o0PyUGsx(npa1nToPV^@yV>JA zwKUj(1D`*4#?{@a`2U`=Mm-SBNG6Beg6z_byF}nSS^p2IlJ75GNC6n#J4@Pix82@B z!XG;-xLq)y>3;ZMZ^D@b!lmGfrR&fg3a$jv0$3#cT27X|vbDH+(%ZtX!16Rw4*Y=6 zu{O?FD34hKQ0{9kB}+Go)IKb)sJpDj{{u=#2~FDtrc+|k?Z3)XdOq;aL2`Yz4W(1( zvz=^}+X3RqJnL5(p*n_Zz27;lA0#sB?)5evv;5&~AQ!PZ#5F@`4t-km;d678gzvn^ zWtUixgTKKTyg28Cumpubw$iUjxJ!!k4`WMowNvA_zHievWkoi)!W^<%o9b4+(zT8L zIniFJ=imGRbM@)1?9P2&o!XO^(+RWv&RIi0O4J?A?Ytbhx_fsJ1US?#95FKTbG9pK z6dSw-<#}4g_DP@TD769bj_YhIEFt1rk?r;gmZlI7N5_%%^B-$?`JMR2*|;j|LJA?? zIOR)A0j*jfD+A@piJ~&^=@Hv|fUgGkI(|L+;5IHP|9<^Z@41Z6WNnADP-&~ua{*se zg+pe33Yav@DTBNWWqbbfl2xn!&!GEKDqEp`3DZs`PE+XOQJVX-Y^fmgV&dXj+t(#R z2#zkDa0|!ue{#ws%R;&0V=KAF#?rrwJlRn@vJlLEWDc8{Jg??PGw9&2s06OCPjJS{L0MZWb zm~ycTu^$&pPYWW^5#AfL>s=Ym!b{(UV7Uh1QX92K#4ba2;>Zj#*Y)ZFYgA$s5Bu$z zlT?Bq`BO`>5H9xBf#9d^nokQHSGg#ylojsbDC2Q|)w^gm3KLZ(@{7JmngJOetq zZEYkU1_Hh5*}X-gsU&9^7cKDxK`VSsAq?f5QJBv!m~=v1|v7B@q$(gTXUQS3D!Mm$BX7gOocTJh>r(4hF z#Xbg^^0o|U)1f2Mew{eRt0$nBggg}XpR&4adNI`i3_Jg02!F`EFsTal1 za(?EQmcc(VfaM{PR zVbyg19vlk*yP#@X^e~G6$-ohXJo3|w>)6TR)&X2E^N&?~%X4ot0UK=JC;telxdLz4sU-VOV; zr$2~O+;((=?E^AlRDD>q`%8K|WLj*ZgZ)6j%gu&v#Hq6*o~_)I<+ zRmyhf>PeR1nV$jRsi%%nW#%zOKuM2kEl){%km0X{FVFa%b}mJHRQE@B#i}A}=F$d( zJ4uz#gff}T@usenc(`3bMUu=7p(!fPfwLF@sw~JZ1{X(^v0f99-w^ZA0^$ov`FGbo zJ;>auwu6H_eHOG(@*JzG4<8GOQAX32tq+Bl)qUu@k<|otEjt#=hA+w@vM|G+z`f$=Aw#2=L{!g^HF=mvVk)d^t^eOcI7Q>1Y~S z=O>?-b%AOQ71Y2#8t6+_@r04)SNXoso6#sZq{xM(%A?fQTy>(7tifVAV&5n??DyQX z87BO8buBTfmrjkfcgV?!Y#|VMzz|JIW|L-n2D$t# z$IKko$V>8n01*=9w0VXH>5d?Tq>VJeoT2TFlCdg*?iQUI!GV53H zTed3;`&UrQ6&#ytPBu5|*^S%8WMqv}U3K2*y6NT~3d)bTe|2BMzElL__AmPNmoHQF zCF6gW2DwWW6vV_Fd362Vp1#*cm`B{acfjJ6kLvTDy?)~&Zk1_VK9mxuxW(97kZtat zhSlOm-I_P)9qX_ObvG(9n+BU+Q*omv4g-p1L~V4iS(&1!a!w;+4-2cw5$+NL7X&Q{ zc;0t_TSOzG<=Me`B|XV_c0+MJxF59A8^?e8Dc9GfJ6|(M-dnGURFA&&kh&bBbHa0U zkUaE8U-op5T5bi=8wG_8BPgiqgtm~UISsDz6Aj}dLn}{2Em-v5%F4F+3&tn7^p`nY zVq9jiV>akMh~<4gmEMV3aL4Jh_C9gSIuEUXSGfLBu#r8j@zp)+U>Ex|tGmH%*>1P$ zQk8IpfI60T%gjBo}m^`N|gvY7~HBKOHD(UI) z26C=HQO%xF_Vf3pRfV%e&N16GIKD0O`+Bc0Ncm15RG4=*dcja#1u9YvylGM0D11;kg^D&Y^R`ixp71d`lH=(4R5I-G!QSHk~F zlcj1}&aI^ewHHYe141W)$}SoV>oZJFLh*GPaCf4sqx*w^(tsSkgSW(vGMk%-Y zWt{!DBgf2h(=T=zpL7+P#xRM0;*s2b+<7SXjt1`;ZPiq_f)8GJ^M{q zzAf2Kmz6i$JQYkPU}s$nRQ2>D`slAV&Uviah0e6nJTSa6%biAZ$=3A1(A?(suOnY7 zU;n=EG_9@@n+IVY2C;?ux!Xfn5x?o*gNu~?^2Rf4R@eW8$JEFAikdTHzgvL(LN#@g z;7`2-HmT*O`3|1_{7l|#b-)7E2(^ebrK>#7wzy)0UeF(|(R~QkCEu>Kbi+B?kSRiPO)#c-0jKxzepH+NpA2yYw6Cm9(F3gi7mwH(|3xsVx23VAqtVi3-#!GR=@O1xHt)j3{W)sMss16 z^QEh%{Ylq!*qfBj1Ras*g|NQ(x|IiIOHs;9L&CG+CkGAtl3nrfhK3|w^!&YG28)weq!v%0fl!HGn zfjh{RRHUh_G@V8m`r`s#*9K2htDW(anVX?zip$=|DsgSaT$5xfUhCHKqOMRo07mSs4&|XlH z^4%?-CDtKx>pkXcyZTxmIaSeb%Ub#*9oF z3f4(sviI_VBzZR`>afRshJ|AHU0mx?tzC#|FLalbla)6g*&|QUA^vK4XG*Q&6?=GJ zd^)EEMSa3gMO;HK75w=lzYwZ9pKzjb9Xu4Pt-!4-(6_M9|Da}nP6MhvKfZMA{w;M# z0?+~G=rj$k)Ibt)4P3z#iwT93V)Jo{rUk8=?(XJM8on?ORc2=y(p>`Sv_nE;_3X0b$5mhmx8JL-ajo=4WMltF0pN!;XgHOm0=`y zY-!Dw{t1~eU_l}z2@XZd9d+-W&rDAm*feUU+iF`w9=HOgXnrboHT3l zOXfhvS>jwPB~OXJb9FZ3tT|1BVByd}K9RAof~b|g>(&v+X>#OHp<(q6690p+%+GCJy@pDTnc>Yjx;ge083kf1%Xj*6OfCv(I%jJvf9>Z*%#lWkgE!Yf*v zJO}-TVs7xpOOvN7M(N(0#i7Il7i4X&>aGpIM)JHLqo$FWD9871Ap#vWL6;mUdVP65 zs66ktH4K}JzE;pmdTxW3D+7z~2q_DtFoGCV9a?$&V1r{>rH`un5-H0yLGBxEQnyLd zo?l7*rh~szCljGDB2>$HHom^~D*725LY#_T3n@{Zil&4d%i+pciu6}|XE0_V+(UoB z%8RI2Vxgfr`@3A-Y)8stL?^49lU-lKZae6#Vx3*uN?*gTPc|~zm52-v%&;Vhu7cNa z6}v!!t^JAVp|iGe9o0=;;#cPOzZqnvA53GSDyM_80-}d1Isg)g z%~&lnpW_`!j5e|6JTTr3Urg+F==KSfVCauH_&phJzS+YcD-qcJ{<$04v9?>`$*Lj7 zw%A#qOG@c-(c^Zh^qz z{vA5z!2I=GwN%3!K6AP>zoal!9z?C6I~M;qs@-gE)M+?9t3hHFCja)NFrlrgTSR!# zsmLjL-^nzVhK@qVyOTcy_k^d0;K?@O6ooS>$N>6)r@wp{HYM%e^bN;y^(~L9?V>SPm3K z^q5jCGXw{s_h*y-&$-X5`{KTM-r?_u-|^X-j$y<}UG>ih*F6AOYe(+o6gxs|mT+_1%evFeX|#SaDrB{1WWp%S{nFBt zoZ9koxhir;D_&y|(>~KU%ca!1tC!5L4_^;ZZGEnYIyBtX!Ny|`ZY>o-9CfcPekyWm z^BjIe?4=sg)grecF6F=%1qf_M5QGt@DpxvDQ^6Dl&KP9Le&OyC>9w{Fyo*2|%MT=; z*=wySgPm%QRM10F<}BjNf>n#3o_=%7+SL5E4QElXiq&CszT0Lz2%l!rw_9@}uyZ#v zsdIgIJxG>^@u9t#+!e>j4sPq@woRmP;g;|& zJG{z82H_Qq+HaJdi@8>w+n)n2w_o8_e%lPqDCy{)^E>oK*xAbhYI7p*gcvv^H@fdp zn&_`$u1}ixz{UHM+ri#^#9!{j@=v)zijQ~)uI!Kvu=fBO-0GR<3~Q>rm@V(MPwbR2 zWX_tghPGYHHb=a7hrDN*`(7BYA9aNfS#Q2bRI3MHJjl?WUh!=H&qn>|G`AGi5i(Ve zP>w5v2_xt(z4>;6J~r*s2Yn2}4I`wgpRT6H?j{S}aRdaYl+*4`m0n=dZ{x>otQ2JI z8)>jc{~Fk}mOj0MMmYD7QFLJ^gizuNQm86lEGzcii@wTQCj~40BE|MpC;(Iqb?rO% zO};Oi)s52cT0ZDQ2?z``mVNc3^~aMFGrC6?*dYeQ{#Y&z(Vmqsr`REAd6#QckmbBZ z!+WBuTjf1m-(M}j?4ArU5YX)RGPOY7((Bt{gIkMfD7LV z91{xm-QRN<%>tYxxhD`C52ZIgrg|E0D;oI*|jW^(~ z&bt~q>>w0$>cr1l`8sMI?_tRNLS=dX2Gdus`kVBm^%8ZIoFe!ifk^~hVBtodoQEIV24vk&ndyU~s7Y!BDJUPEQ=!p+13g}?w8u+5qX z`tVDk7neCA`=ii`Oq8Z2GWnt*sUEF8}_2zWebcS_|0F_xVX+*4O_# z692y?tN`AJ&=!fy_kSz-)t6GDg&{U2s)* zF!_ZE(amPnvF+8VOS|CsmWmh=ZR*?|{%#-d*95nS=hkm(Fjmb0swuKYue56D&Ev*h zNV4Uab1qOT|Cv5K^dzW@OzyRJ(~rebzeeR4!HhEp36#w@`hYQxdU(laR-Jw=Tp*1# zoQv$|5IfH7(+tAaoPQbM;$Cnu!Pked?=>=GNiDOr#snyw|9p$4YDvlBo}K>QV3G~;oq5z^hyO$sVdl^2rUl- z{-XU7)M%-3V6N)6e6fv|iaD!90#zh(mtG*w`S)BMl282gE6~ncndIm-9w&bdIedns zrTYkTeP_`S7aPO-G!9v`XP?;6{&v3jI&c~XiKW0rDr{${0$s1dl{}6;5K_f+>G%YxlBq|xy!M}SMOCOMhTxuPOMZRBSSshyc9WVa^ zLtqaN)#zK-=FA;0?=|so9ASZ`pATk+4)3*9JXjU`^*{l(5;{tDzFJ8?9Z;b5F_|!{ z-pLguIShBl3!~nh@urLBb%rV$wJ&3a3_&70G)WkYyo5iJJCu%Jf_t~emy9I@x*?)I zHiwe%6XEJR*r!josl=(c^El;MG*LJy)$k(*OLCtphBL-lwHlfJ(@GKv>)!E`2xjJP z>89l@Gol)GhUpLQWM?n+5*+Q>`)3$h&gFvxmVKl9*Eke>;m_zl0PT6`BwO!C;0J(U zpHz~RnlzLf)fK$@W{du0t!L85{3INAetNEjK3_&hSscQm@Lu^8V8prL?wjt1$F$l> zi|N_f<(j)|*7}`$UN7gkwHrHb;O>@#S2;fMDspAsr1@c3X`r&S?>D9z1$N#P>R0`f zs9v+Ld&4I8Su*~TKw&b14C9!mN*0@%ccMx~*@COtddRRgP3&8&%*B|~B=z|x1jqe6 z`9gBmiA|L5ng&X}uS@V>v6A*%UTIac_1gSzOth~=lrJS(<{$e`Fwy$}BHBH8{VGtB z`h!Q*ocEmaplM%wbG~?p8~Q$pAh*_NmKzH4jS6jL)+inl?7_abNO4^mgoSGz)k25L z+P^GTuqXQshs5|Z154c=jdtmb%Pv zIGlUL@%_H98;?9*R*nYs^Wu6hT6Jyj`gXHR%&H#SJ&xEWi@ER|ieza!Q@39v_p7%6 z3L43%&^mRs?LbA_-9D%XpCFneWrr?BV+kTh`D*wX3D&I;J~fMW&~EbpZP*ADBBUr& zx1Z?8+klXwICtI2e`Xq|EO<3b-??TN0kA@5iPoFGiR@*tRA@$boGZywUtfwqu<+ga z3a$H1N}WPd=P>M~rwGQ$X^63?XDP_2MF+uA8&5x`MYxw1Py;T-c=@_0ob^Ty!dglw z?pSac!;YcBs;}`)R8pYu?}b1Ar#yUkU2*YTeH;JwGz|U$AH0#n!eA#e$XZR0ZcESt zU-NrtNiseL6jnJ#)u8zfhwYB+YwWn_hEaj5s_73$eM4Jx+%KC8-S48&-){nZ7&$D3 z>bz?}fN+Z2kAh&6HKtp1FBf^Jp8E57pUeVE&Y*csDkSZ?dbH0xEv6NwFmkI1pfmq2 zc(!$on}%=2^8vvCrlWi8GqIPQ!3ll)1F`s>$z=dzVf_E-~Z5+s4J#nUAs`H1=2>1vHrkhH?ZV?nIwP;pIYluyz!4Lk4Tm9(yRyP5I`$oqU!wwd&I=ptlXhZ(Ok(|;0W@uoTO zl0iG?%=>%I71|CKT3pdi%mdrnUw0OvK2|*MnZMVoy}s1xc^OEN;78lezHA?mB%@gp z-|@cV?FJjP>Pj#;MWD(Fq#e^5m(}ibbQturqE-#@!#pyG(opncQw0+;%$AtvDz!MC z4srVDzOY@RRra>qqa)_~Z1I*`hPX*Fd4< zg>Zf@E_g8TNVd^%FvFqqBIZVy%wMuQ$ai~W5^N57Q9sFPkOLu&T4)UO17_;6mYU=r z-(~@1gL+kwlXJ}Sw54qykGMeJVhKX*Qvbro+^JVu&g84`EP?$1CnVv!wZGB4X?ik#Bqp4o>v?)8}m zMSIXF+SGcqBO20^N4a<@l-V9$)$3o^m3?=Xlrly!vQL!T?BJ2X33t`Bmd&uI)u^-d z)DmPH>_NX8FjTe7L>%(aDVv^UTN-)0<#TMq{e~i+pQ9PQ~l@v z01kWP9tJP2SUt?5@u{~xi_do3Y5sC_q?j&$J}2elQEZlCn(E1kT}1nQ6(FQ9r6Kpk z9=YYlfwLW|zfDFggw%3MJCsA&th)#*dHRqDC0|u{&;Q1_tNQV_vB#6{nlQIM|C;>x zThlfvbIAeMGRG6k>q9eJcA-sjP%Y305rp*T`~eP*@l}9FFXuzE-d-xM;7t3Jm4b9>q-Y@$6$k1aLpGKnnV%qqeO2@4{w`8W3R5>h;>NmOg`;|o6ME zTeS#J8^R@uA){*SBEGdjVZ8$=xJ%ha^Rn!dgJD)H+^F?LETqWfDEet|=fnWik(OlK zPn7Gv2SGj`_c8~k^>5#klvuM=Y=YnXz?Xjdo6&e|17O*oXmwKmP5{e-Hp%2h&8^&2 z#gyyH?aF)YZU;a%YxnJWa=J~jNG=JZrey+y59#))^vi55=}_yxYED_^`-0HBC7rZj zhr@c;T-(T#r6EEWL>X1-P>yoJE}c!jBt_u>2 z>C+-QRbkq0qyai3ied7D!pqj$mg`J?)58I65>cl7$7dYqKj{OhXn`O{+K#miPb=o> z7t#=+nIwabw~O3y18c)gllis1=V>xKT#`Q(R`~5MG}=vZ{q3;hC9fM#xc-#>Qj(7+ z4wZWEB+6g=GVO1jQJciFF)IFGwUsVaEj%lty`;?{wbv~d0cl^~<*fF+tgB`c)_VAo z52{kzpmS_zI;u|@B2Xw+>Qg30p4vlpK?!pS^S_?!H}%p8+4ha~qg%EeGv!wh&#=T# z9uVZMdJ*~CoMw#Rc+ zZB8_U7wbo_Q1hX19=ukXS9L9+qzdxNfm#J+mC2XX5VA<+sl1#D{~}(<%z+gd&)*J9 zguU-?8 zEKU9nr}Ty7U(%uZ_0>SW@V;(!HMX_PICnsnu zjG;qg7E`O2%S-tt-$%kE)eOgrtc>-uCVY;FpK+~QONlrA*6A@n%H?0F?~Au||oaWtV&9!X~MHn2u-sqdT=@!b4w`Qqj@v(N~?dO zB5v5;5q@P+?tapsMuIYdWdd`tY@hcu{<@t5b@ie0ycSwy&IXeX&`&_d_~P)<;jKc*Ylv)KQ)0RpmVs??5E;<8xSC& zlW7<^oR1!Sv^a1GaGdAzZ~D?vJFkti{RG{UcyD*n1a=`ae-za^;mCpk9^kuWZ!N#4 zR#VJdYa0ImbNZnX0AdNCNO+^oc^d)8dtU+VjpgKS#PpjI7d6Ntq25M|ecy7i2=I=Q zS1fx+*LH^f{@pw3ksVlA(kSrG7x(=(>t2bLS)~*;+p>n%1<~O?NVsf&bz;JO!(*Ky z0y8CT^dPt(tQ1)A>9X^u4LE-nzB_-@%Wkhiu;Diiunf0eDK)po&dKFi02=VqPx6Pm zgrVbYgY2713r!1CgM$;%Ourj3k7}*$-#_#9kt$W(I&QoX#kV}q zzsA&;u@%6SNOcQOGWocmV`ltuG1jsX$6FN5Y+6`kvnz>R-UaS>%FG71p;^m@*OuG> za&J`Z<0aiCi2gnO5}+gfvk4ge0SL|)=uAYM|0Zz(`#@3>)uCa|f6IPw#z+IcBwS>% zD|r=DTa!Dw=KUv;boS^bWWaDE+8pawmTq%=Xy?Ek+jnV{Ns~`X41#${-Tzs_ z98--pw+d(Wkn;Py#6tnsn@&iob)q`dvJEvR*?{-oX*A(!3=Lt)LNjTRJXLgiGB&_v z$nd!P$?S09`=<$ApK#H2o&Ml_a9z;ud~gg?JK08SDF4lo-f>{#!@$PK2S9A&*>`y9 z0r2{8wE7{~D2V@V^aTJhiYeQA>nl=?Z5)NeCYfdZ2AiG+cmDTwHC0ipa&W z1@-Q$t~Vcl3k_sonnIAS6V+wY@!zD;4-Ecz5`722?;-7xdh>sK`<+|E04*DK^nf_C zikVkmmP<(i!X$sLLgBtkihuiX7YIPdV=q4ZafEN1{CM)eLoLJPe~#LXp+a#`y6Gmv6L5w;{Up3a0r|0> z6aMJ~XyM&pw&;d;m(i$A_B9%QW=SwfTAVr>#jJ;@NAV1K4F51Lf(M(KgWWb1_h*+y z<;HV>?|5_P#(QEfMxIC-ja{|MY!ocb_YayAJ}jls5HmELTSZsT=y-Hu4n;1Mq1ET$ z$S8pnXh{lqK1ko-i0(Y?akq&<7uBJagriC+G+rO5u6ov&Fh=#x4q_;pw<}(ZuGSI# z926P=EE%HqpA<30(bOsdf|G*1>7UvhrdQ|F+USu>#yDutcKkoV-q{QC*^xBN%;G%U zdk#K}G>W7NI({n~EF7$4&%xHfgRV(Puao_o8}a%ibm^T{8iMHS-q>HXr8U2-U1CrY z=h=1-@*0@$u;-@aCD39XNW)m@5Y@SE8|KOL zvh3N{NIl%-nJB*sKTw^(0I>$oXk7AIuDdPvC$q=Zh53VwNC7%xrmxkE=#UzcnRD#a zn0Asw!>8dvWYkC6(~@(@s)lKWrFnMq8an!IU3Jeea zrur0v)JiHBawk*s7Tr|na};C%WGEPKMy9HDm0Nyu$d)xj=}{mN>d+M^reAkunt<~s z1bENng(x4~&>Y*UPB6VGY7*J8$fpZm`jS}9{)@eneJz9C{`vi-eH0|;x~gH=4174a zEWZQ1OXd}2@bPjEd(A3T$U#q+nVLuI4bBE6?+I~o)DM`w0I_u3L6R({FT71I-5P}I zd2{Q5T$*q(f*(2~->OO8$3yqnF z>WgXm*aJZFxOA#}r`Wwh_TQ(U7d=;Bc%JC9gj9;0gpDt~Tbl3nCDEt)k)fQaT*I^1 z(>&DMy^V!VH=}(7eGfkdA5JORtmY+rK8R0!R#XE0BYlJo@kgHVf1m83e(4=136HU* zd4eygrJd9dS?wqF8aB+0NHkJkryPt5f|4}f-5)V%S6}#tAMFEP$4IGc_Jgba>^d+3 zL(xX*l1Pl+Jk&mJP>&e3Kd?w#nt4NA*l6dfdBJz7o9EVHYjGv{zDhK;`=YRz4Wu%L z6$<;H<${;QMjqcMz%*T1Pfu5vW#yghHlu_lC9_@G;F{ zowi*9i{xCVJh}CZT_rU{!0?G5u%d3>TnIJ2peRlO)lKN^xl}YDY&bd8T28Ci z>OZB55t>%i08VHWBa~5~1QdS~V9w+ASAB2S$PJ@{*ttn=0$jBz2kOn)m?%#%+G;CE zEZ>a7spp0yffn*Wbx;T3D7rI>RGn4Ya;5)RYvvPJuyO)-DC@I#@byF^JCpT%6P`(T~=A5 zd41#f6{xtTc^FsP#aMu|A-?njm&W2^>f0h@+j#GNB`=a zd}2?Kt;SsPaP3vgo>9_S-{-N5egV9&?k5<9c2cd8SkV<#g zJbQKioW)xi7ukcMtGYvW)qB65tN||<*-mnuh2aVk4mhC%wZ>}1L=3cRT`Pdx$BpJp zF~1{v?3b`s&8*|Xr^xAquHhN{Os9fqY2gt)P4t0;m}mDdjrY&-NDD*8hV67XQq;&} zNIRhy=3ThyrwowGtAp=~^Prv7%ueTK->65h#Jn5Tdih|RWk4?G%I7Uh z&pK^=bep=1_d!32R(u}VRjqhtSxT4DC9vhHKxLk`D$}J^`xNW5daEmgQi*Aq&bi^4 zxXesv^6wmKe?3?t)SCeAxLr!UXunB}>2TXN7jm5)MdD0X(-cf#BeNSv?-}b(wydEG z9}HSWvaCAs3;t14HP%}C%4ZF$UwfdZ>;I3CH5zZR0L_!i~1B@JB02|2~#Rx9Lo&Uisk(Z~uAA73qPb6tC96c&i=h z)(wjETSIkj0Xl3~+a+b;+gO#WxZC0~f~W$B*oikiA!5i0Q45%%g_5GUkjYwsOYsuN zSk%luLm1EQFk+P~^|Gx*sm9@M74sjLBEtmK$$65#e~@0*6=PD>j98(7CJZ*u1DGVEQ?by)C?Oep?Z8+<@epz5-$Cl7O znWDpSOe~q}+Z-}DnXOhY$9aNLWZ_}nL#;;GF-UU#hCrNZ0yVhTuMe7PMml=G^q971=3p`B zZA>jkz#W68`yxR*+_9-Bjjt7_UqGxYwOmnh_lc319IUV&$()R2sAyV{-Lht19O~kV z6x0c7wr*HdoDr(g&R?Q2t#Ux;5gNME^_0);3gXs>UiR<0ONmuVX~&&}vHj^?*DVSM zSLboUgM8__1(5KD&b-rokp96kRy?(QlkaxE7IA(NBjdS8=p`X&fi)OwG@V;k4IwA)A2=75cI?IB_hfbK0=`C^+1=pb z3HKer|EUER$dxzD#0+-OKEd2-Rz_j(il1<^t~~&(%J_^BF)SZ*9@&lrN=~$amY+_DBB}pE#&C~SeRi<9@Lu} zszC8UO5K9#4^f6uvCx|?e6q^0U`K-SJg%WGMn#NGH|uHvCn&mCxG09M?xP%m@KfpjeQF^ zW%CIi1jOL`>*lTC`tQ83uBd|thCsF<7e2bd9K~*FGV-gW8tBCz88^i_Q@CxGAG+Ae z_KuqH#`NFqP1H-c^dY7owNkJ8n!i-WF~N?cv~XOBz+OZQ5RS#|F0Z$n!uZEVz7Yi& zi81rq_0lQ1diy=a?W#kEJ|=MCyalYaChpp!@#r` z7d>G4t*iqm4ryAH8vfX-l!16}s+5Kp-5xdSeD8Hfuje|Oo;Q<`9OE5*bjgRF5|y?g z^n`_h1%KO@nWsIeJts?NKA01x%1K!Dv~ViigVZ`yk8v{^@6rdugkw5{@x>5wmlD|@ zucmi_V$WeE3GZYt6!<1sVNtmZY4p)T9eC*3*W$7*-VHb#dtafrd@fbFZ!*11XC3%a zGxZEw2o>@)9=R+@<`nxq>pCY3Ei+AW(=yXg`$ycoURTPsh9s1vqDZ2@)zc^2{Msn} zs<_uYytYc(sfO2eA%T9KmKI^YVjkD^rXrx`!U}eo|Fuu*bGgiI3F+TK0_0-n;AfM} z+h`0820XzY=voKaCwN}wWQM4%d{M|-Auv`eC3T6`UbcUyh@~639J=M_?>~fDgwaKY zApjko(^l>gj%sf~r;j(=euo7bunUN#WYry_tW1CRd&dWtj#bp`egj^i!Lm{6> z;J$p?cI3DJ6Q|sjbzQzVR_CCz*o&rksV#pU+~EduI&|KRzpG80A(#LC-<%J%vmSlc zb}133R*jwb8Dt|syt{af_MWsamAs!p(DYLJ?gqQtMcZ`_c8P7g%qtr$05ZI??}?$< zDz1f1Rm+R#wv1jDdkhGpk&)%ab@uNRTcyct-TK?b&wJx`OEbj0j?X7Q1nBgJ<8OWy z69a-h-OERT?~p7IuYv&1vgU9AZzblBN8(tk*bW4_YpN4`)4vvCf5*@*_XWg!QZvRFIUxYzQ&UKeX%YbfJ>X*O&lN3I4K_H;U11-;q*IfP|y$ zaZ47r*uNj?+lL*8Ms#e6C%@NS0?Rk8TxYPva&HJ^aT|E?gN?|sar7ahsXGefJP8TO zc$X2IF8U_?m{xffVw~JCQmbF}v_F{ycnFhhW8+z_Vy0OQZp-Wf&Nx)?)_;M8{^974 zUUe5L(v5^Da^qrQYaNPY;ZCq4>PL+xgO{E1V^_9jQvqEH{_LuY7FI2VRAhX0#G0no zrQ}8|cowb^!?NG0k!GIt$g;iStu)^qi?L2u#vHi6X^|HVqX&^3kmGD()a)9tlgnB^ zn~^%uloyW~gKC6i`97*=YTYL{n_+4q@pF0VgU{x8p0ciaqPh05%Jz1Uz=G%5nlyRT zfnkK1x$ijM7)UnndQfo;q~_@TTI(d(m8IwIwXnUW07c4x)5dvOypvBi-m}_viFsJ< z_7^g~uzcR$mVznq^V;N`?z2fNO50S;T1B@gt4F`tawW10(0e^T3?kuM_=6c!zsO7$ zC58)m&9i$uRPBEGtfrsb?pCF5tr74nRo-G`H z8cWG!)u3L7YzuDOt^)VpSsdCS5b)D^s_D0>4&IF|M3EQJT3nuW6AonzQF5A6%Nu$984&cVy=VTIi-!k``Un zi;jBsYzRNP$N60Y@UuIMxq((;_A8yKSg1Y)A&khVsF+VX7Lj;AI8&P*8f89G^_qp7 z)MJ-m!C@gStr2v~9SvO<5lV1&_W*`S=3k%&#j9KuK3SZgKUE=ojB8)jS0BSzGre#K zQt{Ptid6+95PRy4o|pvElTeR(!(+HC+W`$2#+s#31(e}#UXG}<4?l0IYU{SOzBjgk zCa+dD)_oTnSwZkC^4#7>D2# z3>U%e80Idxy^%tHCxN%qblwfS)*SU+czUo05A}Po-S^F62_?)OE?i1VH#*=DrJIM6Zp!R&$N43##9ly4k z`|7JpWs46sO~%a1#T+}+{gc*6Xa+n_23afVrLyw4>aBo7yp5Gj#e^I`gWVw7ey7V z02G?)qz!y8L;^HYwDh%>-sQID{?*wmZ7j3!SWSP8A^)Il&F1&AK;-iJo>3E{7smZ2 zH6qm(d};wqfqrWr3_gzU*4%5U%Ia8Kx*1c>EuMMOFXy-Zwz=Fa7Q56?h`DD-vR!xTH(+ydtc9C?s;;JzLHogla>~zUY`JAFtJ3IXT~j;Xd>&)OzK3 zdE5LG`FF?5)a|da6ez>0I)ZTi)A9vtU8<`7*1tm9Ye>Pv4OZjkE9>l4zbF78?GDr@ z?C@^%vuNv zXe%XF4$tdEn?Ms%S{i8cd3VPLBHA?=%r28MggiZ0 zJ{O`89GMxKXsyM#gE;*bzq!VccrXELh)=KfQXsY#Hz;4RY^*FNo49Z4o4jjr&kDYn zmPvt@L{xFzx7VnXUsTO}dTe-WygX_Ul&a&|L9k&}V--Hm^j#<~z$0oP9jwwe=dZS7 zJAm%?dymB|9S+Ph|Km}V_jsi0q5NSF4ku;!0QsxiY zzb?Ccjj)xJ!PC2;aEz#7KEugd?(q1oI!sl$!Y*=6vWC|~Vk~C*wi`r&42=1Zs)dLk z0}9yW#((TMMH30KMh7G#fan`2Oh{AM=z7VN^1xSCTv zbR%_M5VHq113p6T^?izkd!99Ffw7*n)MK#o*r6v3Pw%Gq)YM13YV_XinKQo%#!W>Q z{-X5FR}Qm3#B9U8w}Bs}Ynz-u`{2M{GgX-HFfiXl#+{d5TB0I%tM@qKl%qgZ!}*%X zzR{GWL|7_AyJpiCZF49a;#m}5i+Sd-fTgD9WdlYqcIf+})Ti03GdNX7C*LvJ8K<6r zTj?5#q}WGKTYm&*8qW+IC$U0RapZH2<=3bVjA9Wi{`jTfY=k0c@x5|FOYWOG%<|ry= z9JSY~51X&Jay~|x=}tBsr*L2#4E5{O7)cah7r04_fooT(N$2J=`KL7~4%KJYtn(Jl z9^R*!PXAkLqulU#iwGw11mZMdx*+qL?73^kz4ZR@5Id{WqcZz}fgkT5+&^x9yD&V% zWeJRNQb@5qnPPl%R5(0o5 zs5*Avs_vJqdY7$*AFH4DI+Fz!RxsuzAE*;$`(3|(k~YQ18X0i=T~?9?HhsVmXAG^v z9I#g{njU!^BFP#}O94NhcltSA4_m2UB73xLbua z)$#Ii2IWrab^R0(tfPvb!t(q)-h=fkr*mw&X8w`DzT0{AV1J{as?7iOaXTb^(E?Kg zc@{(Ap~yesp0zI z*90Bu;9ya2^3K*>1?#_R#|%I_j_uMZ`w`YZb|m`iI3dt>&+{n}j4?uA9rxreEFxNf zd05nVqdoGz(bi%pK~ASXCFHWUr%{8IOR<|yl0k>6R*4FSbn%oK=&bYikfuyN%1!EM zdSSo+D|_Tc2dNf0N3aWc)a#F>nVNU={w7jSwj(nW*db(D(cEVJ z(LZkF-I5T>aJGOGVW)2ykk*Vs4u_V#StH2F9Noc43+$gc71g=BbWcs|JL}W&X4P_n zYEf|r1M^I7eO~-dTw0(j)?{}uj_%gLWzJizaK!Gmg!e28w437Q32pdZTEw-4vim6Y z`+la4cBk{1XYTzmlVM5<#;b64^z)rYXS*v|e)N~K_WBn?;fQ`gC2JbH;T&~L!@jap zh}i8P>~sy_N&~rv4v4tvd$?z+pYSv-tBX-}>hb}-5f5-B*3BCweVA}4~{-NACp zcK8ye8nFdfsX(u4{zpV@L{f^p8FOSNZ zs2N}N33~S7CZ250{-Kg%!Ah(7qY;z`d$^8CVfNQkj@3Lb#DA<1Dfab;C64%k)DWl6 zP%=&SpR%8~A=`)d9+%st&h%zga*X=fY*&_C;l!@1R*OMgnQ37c4O4uedSF5Fv3GRcQ#`p3u`T>FyL`ltU79SaZ1a5Z%f`BmgDwngh?MAvx&xL%@_# z7rOni%=!5nanRYcAmUq+%;ox1ft2hOYUSxNW|YlOPZxTLhWc1)WF!TvlGEYnSM``w ztoa)?_@LxD7a*HM=K3o+T%keDD==LTomeOR-9!0lB#ca14Ab?*BlTg3QivA54^vEN z6!>KG&mhTl?oVU?eCxh1j$*qsk$)ae>mxvDo-E6itcKkMW4x?(Ow$t z19-a2$11qZcax?)0vl0AZ)HteP@(18@AVkX;lF z8u0`2^fT8-@(jT|hXrr!caY9nzI$>pGIK>4Q+FJ8&v#z;1g?&{Eizc~qh*dy(tO zP7;3u$VRwrrHt1ySyim^HVAc}rfTx-I!|JFp{tZ0Wb;Yj_X-398>RB%;#u6I?OcHw zNi&}B6>}HXiqh|oiw9xKDb<{*GM>Gt9fejl&V#`byCVp!b({OT=AZu#t__{cvG&rK z;M6kg+iTv{pFQkK+|+kryzHwR!2trRSpVYR#Rz>Z+bu^l5}tnPxmjpwqsYVPtfgaB z+eLKamd*Fvwmj}F&9ONBsFv(kSIIEC6%>fFE&x05Yf3VkYp<>#J1nVT`X(dPWd0U* zx$K3v2EVClC%d5v+z3f5epNB)*GI4!yN`{`DfyebT+DA@tot3e*WbW5#3K<@Jv=@; z(GX5(e@M<5|2?|ih!rR0*^1h**T1w9Y3`;ef|IHB@7F2Nc2bcu%=*rnWc6)!74>bU zOsMGFC=V7;O-c?9{#IrCIo!9=IR|_Vx^KtTmOFO(jjt2EX8;PKL+#mE(I%^rvq z#uKwY;(BdFa0(%wRGabGeh&YBPJ_hXxt|bJ z*=mP@VQ#rg43`HZ#~g!DDMVAOp~aO)@_qaWd7;@2u1bTzwSBjGg#W97%S%r@Dk+q# z0!#YWxj+LfO;Fz{VwZP$ZR#18aXWQ!Ar(hsUBsj|qPTlp{nKv~DZ~N8&(S zsOniNzBZw|nDy;}TJRdEqU%`Yd#;oncrcvvbz(;=aP6PNlPjX1{_-leu4ex$=jF7* z&T9$g*D+VFuIh%?*%{e(RYE>ivRmfOAeL^@aT`UeW~+OuT%yxxNx%e-PoF1n+!jXA zRDF7sg+2o4+_WMUuSONcfipL7M`7V*-$thmjzoFeA=a#S{G5GEWqOZLl9-)BoLK`gpDaH%`}K z^rM9CW6~ZDqUX<3=-Z*DPjY9fdTq=pneySL*l0zHJ5k+*Vd$4Umd?^#luJ?dB^3*C zlaJ)vcVZ!~`Xw0ezE3prWHE8_N#sHd{(5hhnp+5WmJy0X-xbX$lY?&zsA$FW4s{}v z5b{MwA~=^owQxwmm-wv5KN87VpLz?jErv{H{xtv{FfAeXH*`xODwzGI<-?a~x8tvX zizIPb=jWKXm}j_5^*wU61~e4w^zHyn6E|N1O%sb5 ztu0fR-U*#*nT8P>+wNSHU32O z<^rrk<)L^EZ3rrZXy{iG#l$~ic`{0uqn?VwlWa))f^D!q(#eX{QlK?+_at7 z>vB35h^IiMJ+<23^H3>&H{}%=@IMgY*#$*+A4Z8MK0P)>w|j3Qwe+^@&^3g!R<)c$ z)81SzD4gBKUNwolB6zh=DRp}|`>kxu->4ddOpI@H#>Z2@oQI8SKpPS+WG7kXM;p>F z0|JN8f_()A;{2`a5k05sL-LPHKgRz9E1G(=-lhdPm*0*C_Ik}gLyf^kJ5wLv%Dj|g zsGsx_BbFOQ{s0<9LO+~9NqtYt&GC2l@bMN_o>6+y7B&!m19LS*!fW2+~muyWYrGq%;HN<&LBBp?8wZy z5nxnGy5(3`;WtCaBLYyCAMVwO*D(crl5p_8VmTf{(8@?w_vc~%EV$~J)bAh#;GuEL za^D)Nr#ARU-`c-SF8L4Hy!I~O7{%vb{EvY>0kaZTjV#n4HHr)B#7`YiH(F@Ty0T7l zwefrJ@}T$j()nO zcCOI5q*9y)_f&*N6Bb?~uZB!w8$QK^49)B_))R8cgwYsI&0h34ce!;NF`O-2$FqGD*XKa$VplHgtJXlx@w(B?qXR|gRj zKSVxV+8pM(ryRSW>dd6aAgR9KrGLm^<-5t@;u#e9Pf(>YT7#WB11-0$BR&@3W@!~91TM2vTT5PiF z67hETHMT}Xz1c2$`D-f4q*3rJygLRp-q-I&O*1vvU-8fcmFq>m5G8v8wDCP$RDMn4 z_7Br#S1M2p_G=1$I(ihic%ZXE0pbN74={qMl;eT!9^6JRlsima<9ECFYJQb`21_335qn-iiIOK zW+G*%!u7nJU7QXoHQ1{8w9>bY3zn-7$KUknh{N9RR1VltO~xiUGhd>)UJBYqTGe;Z z!_A&p$t~Y}5*;d(VB9K`K4+sAZ3iw5Syi&cxg@)8{kK^FB;;a)a8rYbLu|{T>2Gm7 zR9!+Bu(gYBG;sxjxhg8zeh{BiDFA%=?#iz1Ch}p%p_z zgyXM(J#SAy0&GVE>7}%J#bK0bmrZj}JW1o)aE4~{a4|?C#UbMI(j6O}W5!i#SAI0X zMBfB@{B|#FzPd+DrAP`6S+BIG0n_!8_K&pPZE;pU%O0j+1lg2&D8ogBTC@uTLRK5x zWw$0-oPMM7dDunfNVR?s`Z=~7F>FY_jVmg-(a&8^%FU_iqIbRU^*#zlb`G1<%H9y>+Xq#$@rGs^+|HxU97mw!pL@0`qg`|GhcB*N((|lZP&xNq zwz6$TJ{ehYhs0TRPrxn-noB3@E%VlTCyKT9DadJbk&4#SDv4fZC@`-*tKa__=8{L} z7O03h>&3!*975kf+om|dF8}VG1Wf_zdXH(gRQq2|V8s+2$*w4Bt#1RvDM{lf;;JzN z%YgK}U6d8|sHb{r3;27NJly>8!0YJhLX{_$F#`T@Gq>%g1yyt3g69m`|Za} zVKB`z-_O1Lp-DzUE~O@y4a^HzqIDOkaPD?6gQZmDc7x&acGf8nzAoWqJ@XR+sB_#9 zYa2$TUAQ!9JfAD9$4!t*HqkQMuORv{TjQNqq(xTHws+lN+ z6FD$EW29#OBANVz9?^Nc0Q2Ig=1m+j@HHqDo|QbMylqDGDq+~#0b>EXT23w6`Tm;| z=W%YiN&ctl|A0rg+TteU!uMo%?YI+Mc_*N97nH^N=uiwm}OpX^mK)A zvv8;1yA-v0GN<(2nytX$Qz47kcE_gQY{|gj}-6 zyfA-qX?QrNT!I0^P3qW|C%5~W%J};1v~7AaX6JpeP6^Rb}inrFR^JNC`dk5K)1dQNRXB7nNQD1VRZA z6eUV%3M7ya0wOIT2`vdEBzXrNF>`0$eec6t>%Fz!{T2e8v(G+z|NH;9z1#g^NJW}e zgG6?bw>*Wdkpp`(5d2}j*WSspsqGa*lI~^i?uQTfmk+cIhETsCVICM?J>sE2=npEj z@<39|_yty{$2W@A4F!;;_3L8dk@x zMvReW$H&9WWd2Y!1$OCBykl$j4&VK(l*t1|Y9?$sgL$-ZBC#U?fNg@oojVe>_j zCX|#;X)%75k{jWW1TkzPCPVFgJ8Vk%tjMU2j14ictC=-u)wRRhli53)4Qm*s1m6xP z`Q7&qheBQ^7PmheYF+9*`+Y3`bfm;yzwKf>-fUX$ju4;-gMP}N+An#74;(yWRIA>X z+X(f&^EM~9QNt(Z4@0+_Iz8kg((N*8S%v&=-flv>xA890F+)Y?q{4@%yU^-p^9}Bc z)d!*8WgnU}jYoFVI(M5C=xy)DDqBH~QjK31mL);Tyjp{tx=t{iYUp)TmIWdwI2*ev z)3N*frVg9JxC65%t*?H!QNI4xCAHZYyyA(?DLG}XuPa$TOcyp%>cW*ru*dXny!0_1 z@Y`1_t-IJ=Rw#M?e)ysCGLkPrMzJUouR&@+9T+rymL>beP(7-6x}Sx5Lo}R6+^%X* z4zpNt+)zXcu)~9R-*0EW1CZcF`w*1XQGNT+{88d&Yl!-zE4>BV8w^=>sB8dgYJF4YzVFvxy{ybJv*^h@B?jKS#%Ji& z5ChVrRY=>KXuOzVZ?9V4!Bj-qr>_bVO=&+IrDIv{FSNS5D|C{PdCkm@tOcyTNAP@y zmb?QmxbL|#7>taI;^@s&ff8-z$;}}enpiso-~sN#0^ZtxvM!F9B5by7P}T_ zT0r`f!9HmJ#K`@~E9zPt&*u%7`Igw;O)Z zoTwM9ufv+_Yx3=<9p3G8DL5bwoyXRBwZeKNc?{bASJvafy?$=VwnZuKiMn%-26z;X zGdM9bE#77Zjn!ycVw-ETx5Ze zi_G-NvTLjB;~$a4wc*Nx^HQHDw6JKXbB1T7Zl0n$jc z$>{D?(#UYtdK7O|EgzB_vbsV4>4T9G;H*}xB{+&YOn))Q4Ae^^w>ikeqn zcXjhyAoL==$B(=!DgGlk{q^=P4M4=WA?fiyjb-(={~HuA75|$lz|r{Mn)=`FTMTp5q z#E_PANVc5$;C}MIFLBflT{`sO@~*Vc*-r{(ANv=n zoOvpFecQV0pZ6Z$tGE7^shb+=rN!CjcDegwo*PO+*Ss^(XnEy%P~G+JC294XBQe*i z7N9k|F8(Sf0Z(Ia#-qAu6gn%I3G37sGV}m`LQ%bMb|Z)Ag)zCnn%!5wGX7 zYx+jU<(xxC@=CK@Gh0*qL+AIvz4NhZU$*ib*8L@@ln&7Tpgk{$B5g;*?LRlrGJWm) z3D!UlFF9YIRX7?B=OpI`Cx$P-Od!mE&Pt@H+Y6~1oym^EcLWf#IJKbqhIgZTnUx-{ zLsEBXT&ZJsG73 zI6_OG>V>w%K5iOcw0DZ|;#`zr?$$FGWB9_^HAhRdh-ZAUAyL>scl&KcKBK!AOu#=* z?OPMQ4j@-5bidQI5SgxI>DtiQ&z3hl73N8?g%}o0TZ*~jf;G#Wsk;b&vB0qBYArReHTMjMm>))n!K$w ze!b0r_1L(Qp8v_)o*-8e$`PAzRJ!EAZ;spX3G$d}wj>OjU|)qvpv86jhmT1`!s$-6 zMz_(E4z901cN_I;4!5hc=KzKHgB8It!S&XPH*UHo=6&JTjNC#{EsIUzM00ldF*Nm znlX4+iQkG@>K{##5>kETF+o0#!~GKU87G5_hLvPD9PT z?|&+8@2ajW-LQH6J>)v6>Fj5H)189pwi2ZU6C0iFj|u*`pOS(0jVgOWMWdV{L6_Hq zeVRBJ27R6uHSw(Q^>oRbG*n9>PQ3=-+!jYb}rNf9z$W0wuhDO$^>uTxy9^sFzopuiv@OFe58Tl!mu_pz(yDe)dR}Uq zPLB9^LX6YY7YOwit} zxy=2dB)plYu9>bue`V1)(DoL7q(pwBgpy|9@jk}#q;Hp4Uyf~o#NohrF4Dm3r&;aj zF&nef;?9a;aOZRLPaotQ)sWWaeC)0;RCfuC6n?}(()k39eY~SJm}(48Thu1SuhjAx z7l?kf<=XI7%=iuRT}raHikZ5pmpiqeJM)nSfq?khC+Dlq)!uVvH5M4=fIFg$eCPUy z+^)uy8$wh`c#l5QhXb9p4*8(4AYY+>6C~n9=cZ2rmEI!th0re**y-_S>`0CK$jzo+ z>|52@Zs)o*+uKzL?bj8wL*!F1$&EeRy zz_ggsPBgK|d8s2Q-AIG<>N|w?sOheNOtm8IE^VYzeg@bc`uT*$D|?g_CrVPgM3_#(1f#usSQB^buxf*VRc1@a%MSXSfr6tZ+kDX-x)zqcfyalD*Jhn5I_>) zG5%#34{1w~_(i(AFN~WQ4dGROAe^RjySizGB|vH{DM{L65n$z+&XOMJ6&)H9^lV(` zva)wP#9$2s5r~mWg{037aT+lguQ8UUcv+OxdX#c@bF`>aG6@t{MeMZ_RqbYC6J6O% z21fY$42!Z%1O-tRdX<6*@L3k}2z<1V(mOWsRVkUiPn(2Tu3rGpDaGgS)n zX9^3pK=F)l-v9gBL%*&j$9+0sZoe48n||r+C7_JS9+&zK~Y^+x%bMgG~l>UCqhWb zSG34{Q5EHb9m&%+@op(g5a79Yz`!So1wB`FF9u+7SWjW=vHPDI+i3eHd*11O`k=0i z-aC)C3Cwbs*W8;)xFANz&^qhR_`UsB=d6UQbbSrS^xT5(v06}Yrx!dGCTnPs;l}51~tgJxRiR_MA@b>gJniO+Icz0nI^!`g9v(zbj)m#3u{Oi z>dT4CUVNFkJFsJS$wf|H;EvrW29mR7jPzDf|^+2H~t*3tFZQM@ksj`wJ<@ubuu zPZ*9+`fz6$PJu!77B=^maDzcignj6t9IGav*t4 z=~!HSBf|EKHU_c+S~&%qMW&SPG#tCfXEJI5L?Hqggyk!oA&yY>Noz!t4k6P3-tnrQ zRP=QC@N0X8#I)npVz1jB=qc<73+X1Ssjh?)dR%SE!=+b7+pNYTE(P+py~0;NfUPf? zFEdO)G^;0WQ8;2q|H1{2Pxj7)6p+~;n+=tZPtoqO zXJy?Mcs}{+-h5rr(TzJo*~o0Y-33|bK`0>fuRaLh{wj+idsi2LkvNHwG1V>K|1X45l9r%V0jOoSf}q>6M!kudimp zfska&&i+h;RQ+k1%Yvw|?N2*npxg1Fui_JYyWbm|T$-9r^|_ArI~}yb*F)M-si|of znR)xoN@QCGl3{UXp6-6@i{g&EyZ-1GJmXR$-fl)NrR8Y-<-&iQxljsgFMuFF=H`l-(7f>(imP6jB@# zjmjW~IKe(}oO=VC_A0afLTX*=^BO&&+6Z-Hmarj)pP`j3nG)BEDul?o@)etX2WQjM zJ*{*^pWo&RFtqI{ZKB`p^}8e{Aunx{w*bzuI^^(ryz-dD&)9^_ZJra0%e@c!GBBI1 z)eY5=+FrpBmhaG}u_B~04uF-+Sp9Gg0Snp#P>`lqhr&b_79fpB#b~>B0gpoZessV<-h!3$kYf6JESL_XI`5G^}L*HrT zzdSN9UX!0QD8D;Rii?_7qA%=Qc}6gRnyJ4lb3WzozY_VTe}(_=19d}QDPB@fd%-<3=*I}BL zIsPay@@@B&X64aQlgq=H_{Y3k<_h<@DHNu}Mr--2TK}g7d)eX0S9D8sspPYqdP}GH)p{hLq%2wdhy^)6wP>liPXD1J$8G`q%h+9h zzV6bM+vPr_5XwB3Tx^!GM>)t3#)$v!Y#1y_MSF@2uHg z;MLTA8cW6Vj@{^P7%m)|=wnu}xg;4!P87cco8MadhbhT@Pg@qt_BD&=H}s$-c)Ga3 zhaOm}6;>vBDo_o12rb$7pQdAq#B*2TO&edZ?lxJO$U%66g*&D8ga z8nU6@_0nw8TQ^Ohj}TajF`bYW-xyxCTpHr0<>_% z7nK@s_~4;M2>%#LTedZsl|`3HaG5Ih8GOz!X$uVEJd zU?Op_?vz<6=ThcNj1g%2Hwo*qf*cBuO8C(qduI6FdM9q9YDVTP#9+Q`R2>7v9(AJh z#?JG|g@*J47W}4^#v(K$jaKH#Sn-gG@jT@fhl!W|6G5h0J+yoGo{5-aEXC;tDXiB) z6U;_K5Q;d&spT)18%wQDl}u+AiMRN7_CE%>mcUQiJ2%T@o7;KVhFn^*;kbQ(kAkWH5U5Y znKxZv-BDRAiDbqK9o?q7vzimb!yNZ21(h80m$cM6)GcKQEWpiKzXq5uH8*u;OMS?{ zhkLTeVC0XbGy0%GLTIdsGp~pdvr%iyPdhbqR9Jb*EA$ap_0c?3jZvdOue!a0=M1{1h#GS~Nad=n9m&Y5h zQ-*}a$gQf04!*|HZ}T>-Pd1z=9!M|D*3_~*veg6)5qbjDJcNB~+1d3ir?F;d>KI|P z#n5Bs+wM&$<#JDH#-hj$FQ2YJO8z8V`BekqFi$Bj(d}4p#$b6cNK3guUeY7K^H8Rp zZx4)uWpmZT#^e2mrZd^KmhOHiERaw?c&#(@xEjc%1~ko~G3LVELoJ`DJ%3?2j&r&X;w`)?-=gj zorJGB?z?Hnay51Wy1vrU`T$L3*i8;?-!If<3>-a#0Rki;v`zMW2{iBH*={6rVY&E`p!ac{ zrHA~IpNi-&b>iH!tbThu^FjfkyFL71XmMrRgJ#Jaam2DNA154F$u*pH7_Q#e#asg62*3Twl zrYl=d1P$7pA|Z5k{>G+N18uiaQsaqRxbWdm`1wD*`ct-mc=-^nvq!{MxGL$bY*gxF z5o&kC^Qcz@wJ+r5v9xGARuR2o>_u>Ef;}`s|C!_VGNa6~I!{otp3l$*YM)iS$Hxu$ z9SObu=?;0>eThi!Bqh=%TNYer8ta~gca5Z>jRO&{eD1`vTLh9$JyvK~5kNS}9Pl;o z>cg)PPJ*|2x@AZ_j0IdrAL8+Ph&n=x-&B0O5}s8rtZbAw#0(YD>XOY#M{ya&2J5QX zW88#}pCPd(T`FEUv zI_TgXNa2ZNd@E-9LRbMsL#LW+iL1ZJ8jhI2cA5jH+Q$iZ4$~k+LD+^>JGVjQ0~g5c_1(dXN-oB^pEkh z`R`#*BdX5TqvEL%=%Wa0iP(TFK;_e!)Nq(J^(pgcnZdJ5yJ}24z%UfYjMxr)!m)OT zx1vC|Q5YpI?7fE32Yz+or`YB`W=?ak`NUNnEpq7Sv|i9B%m%H~KS92kIxv9H#GoZ< z924;XG!Gt!UYP0)-$Uzv8`~?9m1>VY^m@oEfdPp7-a#D} z?;!Twz5}Eh*2OLUSEewSZOaK{9CBUqvJ&P#4dygj#5$0k=Ah^LEX25Vx80baI3lFP znDkZFG#>*hRzjAjlRq|L^g5XW*%unFRms&bb6q73>0onypNUq)b}zXj4Y)XFvG_0P zmF;oU8umwX&I%1V#+hJyx<&b8w)8HmLjWNq_grs78PmyoUA*LnN_ZxA*ktq5fGp!A zs;^E~Rc3@TiF?*7CK$?u6oGJ6=857RCnOGSd-G+tl}ojeeWA{mtlPQH)Wr4IG3Us= z12>=d8ccYUm@YY1c0M!RS1uoDbSSwzXh@}R(E4z(c(MjQLy4KMxmTWMj+)s0r9nb% zUZDe|`DJl1g*8;NLQ8?wl~T|v(a@;Jx#y-c0#z&^aINf{l#FjgP`MX?_SuT4Kk>tS zK4mEOl^TR2HZjcDdPW`luEyPY1Rb1v&FGIq^MjR#Q@M)a!o?icy%5$jytuAJaAnq+ zz(c!=>UV70QeF2u5N7CLU&%fanf8gTJ<|S^o!@&Iw9TgXWkjISKsEA{9X74c3aJ^0 zsOIHoXhHJ^LFsBmGc`jpQ@&2%5{;~d!aI1i_b5SQ3dHaL)`DlqBMji#iW7wbOE|#; z4W9<)rOuZsCCP={gtf8gpqeXYy;A3t7mSA;5WI;G;dhEP8;Z1x+nbAB36559Ehr_C zrG&vrUl`xLg+J8BPlg>f7sGKgv$>@nQE^6m=7j`fk1lJB>ky!{z$4V8-BJP)hAqXg zWm@v0a7>^onuyY73PAN7_4(i{AK+cv##<`{w|;H&r+)XMfIOiO*0NYci+h_r|4OB@ zC0XnBHN_Pc*H3f(>8_w2g5S`2w;VIKxxDwJFWaX6sgxIO9m#z1^A~dzGIxmA!~ua)q*A5N?6+x)W9RQ69%w&8 zi+rEWiZmm~24FLXDrD%Ju%`$Q%5$WM%3lii0-H-4bjMiWm9!YD{GZ zdh1PG^+4PpXYWWzlt;I*!;ssoJrHms7A(s!iZTW++2oj#Q}2o~m`9&Hz`PsAIZJZy zOx2&jD1|kCP^*@rWgzcNd+y_y?2#m}?Z!ELv%U7bxMV}heMe?)4p$&XGxXw6ff(0oBsUh`O7!$J;w{AFg=Ol{@~oVNU^u~3U^Fyiix9h3Q(;$ad7^a z+V)>wQ6yBh-Er52`X*hJH-A0b;Ez&BS8(m<>}+(3_UwEl_@XpXb_PBah*2Qbvkc3 z`bt8VX zW9hWi)c5@B7z7@M;gG7oB|X{K08b&&y6@Q!Phq;E*=(k+bJNEhHaiC4{<$Mv&m44; zW0-_@p^k1RxuRyZVWp{AuAt}Oh&Q(dg2-o)*K_rkJEt@61%VHD7hVLwD)Pu2mmE-2 z-jJ;O{C^HY4&-;y}SF*_me%@jYr@b{s?Qg{Y@`|W1qb|vO*;Gy>S=jlqX3eS|E zLshsEhs&t@7wvLy;=?=BIgX7J$kKDU6`B(rRrfjFy8=K9@U92d6iOE58Vg;5Q3Xiz zV^FRW#}!Xf!`GlX)ezF5R+J+6ygT?<58JlTepEr-pwxd8`K}?XP?!gHj<_aZH7x6DdE6fhmjoE1fn{kYxyhZrmLlje52ib8V(m9>WYaweA-br4m^ z=SK$9tT7nGX6L;2TT~FRhZZws?=5Xb-^tAhN25xC5Ps8vr@J@qJo0lfEx?L_l-$WZ z>(9ct-*H>6OW1Mqi`2&J+_Tf(uJc`}p%9=E2cBlGfN8JZE!liz{~lsUjzM=%-;Y^XKW)8XrL<^?l_48CST zS6Bdq#tDh?J*uM|InY#O`Zbu^Sq~_)66dr0@69PI!nygkGwb(BeCQ1L5Z9dQpZs{( zl?3AAA7o~zBCulbrn9ZT3jM^&4C{RMQSO9W@PNkMUVI{Up5aA>zryj!2!`eTOrxT;j%Eons;+hc(Y2Q1e$_MR*F=+<_P z0yCt$u~JE~nYZkHH>%ZSmTLfiURtP|poP%#*Grk|^5}&yg*~T)Y7->}7K+(4g~5= zBte>(pV5%faEuB~z3XNljBfPWjd*L_2RZAUk7kEJ#V@`73my3iS_mgbQlSv$EkxS^ zVnVBqbHs)Wl%CAD`hdQ=K&K7^tNQX~9nn)Q#7)ee>p!LZa{M|n*J?_jjkS9Ca2{Wh z>c)L2^_y3$J?xDB*kYI>P7P!?;c%ad(Ajv)$vOW4w4k$B|7alN{1!ug0mPhV&cY_s z`73;udlg(m(tK!p_%H5gjKWk{8W0!R@4>@2nJlVXY%98X0ov%CCr~^KPaZTQxst~D zKJTkP2pX8!E39fVW%vGFMz@_bTLXRD8U=YY& zsewU+;=^$-fjc}JX5|kf6Bp?hzY_PU;9H2Zarla9ocV}tN#?i zI*;QHM;18gJ7ilE*Hk(Crb@vAy?N2X7g9YFtkaOOyFy3dZC$E zPOxUGhC=4KRqah_jrRJ6SQHZ%ENn<^hcq@Mz=obcP7x61G*;YJ{@wz1e&!KKrerkn zcvJ}!y4>KVP}4wvEWqy16^4>`V_#69uCR6{rouJ9{Y^=Ese8$|E}eDtc#4-L+{}z` zGu~9-YT`8@i0TgpQ3c*zJwupk*mPQ-pjMU344`hQ*6WSyEsZE0QcX41*^bID`vx%m z1}(Q94E+%w*xDZv9k~eWiP!3cInZ{!v6)GgvXDuVyv$_fk4OFK2T{eu*d{#O-_dd@m9~P26;3Kk-$wM+37+C~F;GiaLsJ2c2*Ca-G!6TBDha zdwQ6nIv9J(^|b@B@NpElb#S=%43!5fcNjqF>BbwK@i1ih3(2n*0FWH&)Zx!I_=&85e#L0X z^8^VC>(jw;KL>p+ncQLlw0J+487dcIauQK<&TS<0B##(4SVe$TxC% zD^+9&^tB(3%Rh|$_*tNBVeRD||2%Jh{=*h?V6L7K$OZp(uAc1$T5@i`lKk6~ize;j zfLaH=cA8JG$VmS_b|WB@XL9EHUus+b^B-=0g2sV&?%g+;5ao^wBskb+y}!lA z&iqo_*?oD?JU^mh(RAKGFW-Z2{{5Vc@~#y<+f?8B4RiNxRY&iOo+tCkbvO<^{``wx z@i)I)qH&y%l*Ds=SII`=clUm00cc!QMjjogpRM$b-LpY^baQ>a2}f)$dmO&oq$fOY z=btm?A_OLr2ak^z;blbM-Ek`ITG-M?WL<{h;tR~&0dh4#uLRBD($KUrJ+x3Klmg>G zm+Ck}UC?Y^dw$}Z&%DIXy;m<*%zk{<3E`$g*gZ2Ylx_Rgg_y=SXLP#@dY+r&J?yf3 zzn{G;_eXQxMFX}3m~+}RE+FZFNM#AoRQz)WCX%UQqz9)%$tm!6)|pdVq27xr3`{To zUAEAc6Qdd#Wi=)Ul-g5V|A}pQ+(E)UQ7XVRlkslMpaV_Ic#)r+lWv{|_1)fwVEYbj-r2IZYc7C(}$OFt6#YwckK%mb?j zt+#$GJSl`|8`Glg{=8Il%Ss(Jo~3CgvOl(*8=mBy2Q7B?wgXS#6G1szubkn`w#HQI zzy)b9W$RMh4QL$N%qD^s39)nK&Fd|h&QltCs^*KnBQ8&!1Y}f>qyMN#f07GxeJ3d9 zkHbryy~t(9LYzZ^S6SWotp?}dv4=S8R!sXWzdr6(zt*V*D5rO7XzgCA05GH!3{!JeW+kCX%^8(S*PKE^Olkd24?pB z%FMnr=`kr>o!PJFa)f-?EZW8glX>szYhH#HTxqUlj+V|kMl8T8kd@IuU#_Q3a1{K8xPpbLmM4ZbHWZK@=9@Gg5`r4W%lvSxKDF` z+>W3IF~tQv%UZOP^bF7MhiCkgg8sWSezwAjG|K(=e+mp2&UyvjGi(rO86!+&Y;a4sQ@2FSp!|khVu!+>H!=K z;KNh^e3+f#f<_F-dHV2G`><0srukY;WDpLA(yZI+^&)Bhsi( zRdSLL?hPZcw`p!EsQ?J;w02OIBZb{1ToUT1kEBHrKbhf>*M2$_ig>C=F7 zFn^{9MR;lJ)kt)>>6jQ?$~8Ac=Xhn6FQS`x5k-XqmDJ5K3#`zooWKM+;AT%|SVNoJ z9HIr#5BwfV@=w(8DvmQGJ>rfYX#gYi3{B0gFHi|rJ*GVr^<5Z8KUry!<@Q#uLmyBxdqAC9G^dcV(&lfHLfN+!m+ z)A5`cwtPUAWO86+TYp$!r+5TT@UP~QkLqmG3~uz2?4CP}?+gCtt^HxZ3-5IPx4cNH z;O*uq35(m7iKUL!N)okdqvcoD|IWWi!V6&bKslh-phlCVlyn~Iwbz|Dm6s2ted6}b z-cOb=;i>B>NAfmM_;-rmkgF`udck8SY`X0xe8HcqkvotWcV;eK(hu1l&bS|3QkV^K zT^kfr(r2P)S8dQ*m3mPLbZ#mU*7nRRcIIh4Rb^*^!S=%2@FcIwMHSCg^C=^*d8tRY zE>J|B08)Ek>!g8aaH^Bi%}XAJ1LD;Z9b0mHAGArDc@6GRunM)KJR-(MYh0~m6_3gs zjk}ZCE@@JD97%ltpz~?epH!Hjf+{SlrMK6#--1w2b&udC&pq29HrfDCVXkk1ta0RA zlTQiXv6$kE%6&V()ji)ywiF(6)}T>lL3qc8mhMtD{A`c z9PaMip7H5*`!DS!CCe7SJ#>TMrr%2{R31&-OXgfho>ab6I#YhG_W?%ib$As7#R6Bj zlj?K1*KdRnabee~Xa}&ev>-LWL8sNTQtY$j1Ee0p>nG1UJ)UT`n=N(>dWW$4D%p9y ziKc;_w*TGQ{U;9m^W$s*yCh{+Z6{L@;~aU(ejvCYx6xOsuetWS3X9wC5=&Kdgt&@W zDz}>-iy5kK%s=_)d9nMyt(q+<>d(%c7_nB+)6ujKXgl3$mswdNo4Vt@#cy__mw!&) zdm?cwtbJYLS1e2;vZ=k=7n?SFb5O%jGjZ0qJcD=OLRco3ACbo>=9b~B9z7Inbr&3v zbIfgEvuer$p1F?y+zabRT*olCj6_W*0!8rOB!EjcAj)|`8u5Q|a`xd zp+t&IQv1nLgWEP11c9>H(%s(%d8*gK7JCpPTDM21z_0x zO!(-Y+kDK^+nNhEYHOllkU5+`E{r-i{ z6eJPk8(*ye&$wNb5iqDyq8CsQXeyB`z3HHt@drKur9}#D6@K9PPU}6J6-?_0rd+zM zqvwlSIPyi-xUt56n>7;O?p~eE^^tM^q>qT$y9X^wFD|mvJ2}tU9KG>d<2SDG8dH#G zSce*P-d<#+3kc-vQI1>=gT)|pj~IWP!rQ|A$-5vvJpx?9<`H@$lwc;&B3s_7Ll3>pSo_{)iUI*JdOo+U0NZ&Gs-SYYoy zR|n<(D_b9p@?H;^+4E{kn`BP@ba?C&=qniJ$toD8N9rO;pT$dOu7F`a8I(~`ip~&R zC9vU3@B+JRviildBW8v3Y|eo-y7xZmi-k^qY-9;a2Y@`_=dBHzCtBUyjeI{x5?5{0 zQ!qg6PdTsCTm1(3(44xpV5_^zaWows*M| zDmY~o@`R+mr*89P5xpMeN@yzcaz`C}0KwV3w}u|M*%=Q1$rJAt>+E$MJDw#+?NJ_0KzoWg5!2w@vB(XtlW56{&j3~^{ZkRhX!t2uWF1;aNIb!D*5js;wHsA5b zPL_>}VwF1jeLPmo5#n{ z>F-f=xuO-ePxx58Nbk(og?TDaEOjIwyyAD^*KV6j;kX5Y2d;-%3*9q zu6CG8@DOavWLccKoTAp{ixl_U+aB1kJz9XY)t{8(tvx#28WpEO;8)))D z8}Oc>!5s+UdrHg6L45V;V)}|>-ZuLWF42UP?|V^?XiYSKClF6a-#pXAYbvL#qsKFv zfP=9r#|F8=;)sG+y-#BczJeZl6d2JPGIU-Ragr~-V-QTVb9r)1i5iPln z*~kghA$J*vFg9j)m3(UNXxw%P(Gykee=C;uj6PbyDC}u0T5%>nc@Sm)ubjz=GTm`b zVD?f_$duqJPoFrjc+9bW5H_w3KM?_2n&U=$hTM+b;QvzP#3fGeHXHkbHu!F8D=<{6 z)uFxyYW1ZUkN1c&M|sB{4kV>W|EvJZpLAYc%BJ)Dz@t2&;4>v8g^3Z~3Z^b8yE+H< z#@(ttI4~s7$kHZXZ38AD54f`B>Xk=UuFOo^BMP+Hu-C5;r_vWlmT8(guxt8 zOgzs_Hk{|-QE%(O}}ioG9?dJrliNiPU=TYBl%lQBSp&V z{RL4ZaCFPoQv^rAvJ<@VE}!Uf?ty)DUaDkp;~XiD5kQDzX?{DAWAZVDsX6r@DiN{iCy8;W74LZm5G^nOyl1`Zs~+j874NwYf8ok%_!2m3 z>nk9p7BSZ58YP|QgP2B-u)lP=<5tG*1B^ZUUQbn4!S{eeKRnut1OM&9ci&@06aft| z@7p_UpNB8qYNgh_?4P|AHjb-%$-L4L^3Ex0nD=Xq?(@czgOvlRUPC~5B;^4R9+3`C z!ym5xZffyVfihpAm-ju`qyhYe0L}Q@y2;T?M}XZ;eV&iM6WEo={tM=;!^%?>^*>+)l=FlYW#~D zfL{mr?WEP<*PB!R_NyQ7eogcgupp+aJXeL?swNcq0JJ-c^{qn>$BHi1zdd7f5TKlb zlr_HFd;d92-(>J{;E$pm{aQqgz`qU91;~s->yy&{@e<#4pWHJbNY#9-cI3Z~{Wx%t zu{QaN@qc|aTL4^8xQoo7-+vwZGe8ey`s5z`+mnlq|9{Kst3deQvI17g{|}axLxjty z#$V)~_b1WAJ?>uNRD^*GnKj$+mCOQk z>{L|yM3H0C>oW}hBPj&7Y~Iv|3?Ew%u?VA+)~~eJ8SAqxYk>G?p(%`7E1)!v6_Q^n z2n$A84Yd=aoOV2h`^GwCr57H{^E~JY*=3JNEw=Q1f)h{kjckFC0UtQR%2V^kB;A13 zLtbfBRF)U6bQul!dw~1&OM-sg@UQ-B(cE=+p+nX>+9%21BkTZlv<48Wjg3`5iKJ|r z>&1jrXTE`TOkr5N@18sX10!f;)C|p5Fw{Abf<=o$iRx<;9^~;pUyku+eBQz+BSKIc!8(fYP3Leo zZS2)R@MxQ)fCTQUhRE;$gnDXIr1rPEmapondF34254Z`1C}A$Ij0hhYN9Z^S7qSIz z9Mz~VoujPqSsrMyCw!o>2)S-UNYp4La(cEc4$i7caL$gAk#IkwE{Oq`rhNV85%f;I?R7I`0NiI)rUNN^>!^Nh`D?l`Xe>gyamW& zhRnQ}6z#1;tFEF>P(M&7sA@S$9(N+`dOp0pyW~@E;hK<-Mueycdbk25{c)g*eZY`z ztJVRekM-u8DS8O5xaQG^JOd<}j7?AOQ&`nT(H_8p@-SJ=a;&_DVIzr~7f8L2Wed%z zs(T*b+dr-Jtrj|lU1|*fWu`KbTN*pZe>D|%QJDPOnCLNsF_~&^K*IPni8@hES=NR_wpjYboQ<5lXIq= zO(n!FzwedgeC|%J7(Nc&7TdLPt2wxUESJKg0l5}1cO5t1;L(?H1-r3;m=@ zCv!PrqCJ%KTx}0E>T>9*?U8w%W_z7E*E>_a`m-}_d2vMJerH86#$ZR)ct)Mlwgl8# z4+dKEmeFlHju|(6y+uWac_Dp ziaBSOHdVX+sDxiDM*4eI^K^9c()naSsV zk}9m!TWfp{A(?$-rpUF@;o!~Ehnnh!q{qQ261K$#kp4bYO*>5-LeuAJnHXwLVQqd! z8G-WgnS{g~T6(bS>EO)NDRRtI^ihe1#RIC@y>mK4f+tU?9mPS$K>k0)-BLfW zYXa>o_$U=b5wuXk<%--dt}mP?p8f!XoH3uEcU2YBT@~#+OiIFc?BBB9(NYN~m-5)< zR_1l&2I*Kxk=)!%cpx9!)<4~IcGQ8v=`oA)c=(|_q8icU|~k2Q1q{B=XhOVpceuQUirEz`t*TzhtR!JByKAI zq9ExVx=N9Z1v0RUkw##*%!uC(UsR-Qv3$3eFt{B}xFJ2feL(3Rwh8_CN`DDU* z`Y|E}<-B&XA>vi3GeY@!M?v*Xc=1+tFkM_PZs_7)^TM@fr#X@y; z1r-sLUW2g8(m@ElDGNvmAP|t27!grvx=L><(n~-f^r8r;5RejDAQ6#H5?Tl(Bzgbn zDna()JRjco!+Xy9DQ6h}b7$tx@0PjuP6ZaWtfd-3t#s{nN9AbTisqM&ys4Q!~msg3M|*2vNVu!Jax`Vp;YRPHaAhebo2$X&ssuks_*rg z+r066v8Y=Ik->a)Geo~y0zA6q3Gk4{huBNv@nw8u|Gxt)ZMV5>O(QF0LS11%>TwcO zp{aj}obkD@W-MYSN`@FGoaphcEWi~qSJ}6ETdQR4gP3dL|FVPC668rzQpVUeEfLMV z2}^XkT$k@3ll37X{DKuZ77C`fr=Darp}LV)_!-j(27v^6MrIE^>+{t)Ho2=usNZ}z z&dAMl&@C*x;h8OWTsK2DuOUJ9Y;g^W5)CB*Wp)^ulaL3dc#u5Vn9M4FWDs;Z;M^1Q z6kwr+!{GCXAR~;}k-TJ1k>jtKrDt82_Ga|!h5A+l;UM!2n8ctScW0Rx!*Elj9I`FeT+?}}5f zD1<|}7B}6=l%h{)^=0Dd?-ThG(LIUX!Tk}O{b`Ky(_(>hy!A5n${3%BN$tdEu$pc{ z;LxhBw?78()FhH@L|As1{pwcBrAN`N3ssA!jiORGz1>vN_nVsEJ(K5&$=%W-r+B%uvOHEME?+E_EuU)Hj2c)dXi$mBf=q^2wNMQ01a36wZSFN52pF(z zC}R*Pw*@p4smI2&dcT%~C^D^a77_RQcUEE*hvhNI7ocM|`DyXx^!&oN?Fpw+tF54@ z*;-Ht^|7dAfQFj28D(NiFtX@RY10TMx=MOMvAIm2dU-}&5W1g*dJVwk68+^G3ubO+ zv$uNAhQ8;(X*_%&Z71hZ^yBO{qf&i@BLx5T^>?xf=^(<{#%!OeiO{MQovL@LcBo;b z()jQ;B-d1n%y5@Cy`2UpOP|Q0z0@}S%_=wf-K9dYZ2p~|(L13|_nm@<==oxkJ5`*b z?3uS}B&z3sBL#MHPpS}Uwv5H+eSBtVt`at~bpBo7ImJISRZm3}+p<1d8V{&tE;xe_ z7AV-?qlUOA&MA3%zazhJoLk0%;HQu0OMMV}L2?qi-EKBm^Iuo5AYA0U15U;U?5sQI z%)Pszk7|QF^v*8IrC7qy)vBaX8jpxqZ9%2J9gGX>C1MvQ`!xufpBDVf49TjS2n&^Y zwA08&yIXdC13GiciE`v#0z=D!$OkG&w`n>p6E#5Li7LlWm=-X%U`}Xpp$l9f!FTwaL`NRP z+VYIV;gjS#465bXiQ<+j=jU-l=e{O(k2>Q2uC3nHnG#MT)d%n*Z~X}J0Srlr6+@En zXlzY{b{Furb}GL4nW#YF@@xm{N$f51v92P$)4z?PH0(Z%^v4z(i7lc6v*ceO<#^$7~3Kau=LqAg#2MFvRa^3*5P z6+%v5I(tb3P%zXEFBBVFD9HKs0#^i`9VM(AQK|R=WZkn6Q?zQeeLfH1ck35$hdm20 z*|E_@CAbge2v{qO>tas#=GC6tO!V@51!cn?b;G;jdycPSo92P(_xQ8wKhn%r)-2zH zP35{H^tCdkQ*X87Kepo}aG%-T=5e0oIHsQzJ)E|{KO*3VmQ z?}MMBzZSMTG$<30F54@jVWiziF8`n`BS;a8RQlf4Sh?0a;{qb}9P3y@q9U3=ZAs2B&I9%xIp=_j>n!(hmf)Jt@3yWK0)s-7;u@Fj>>Rgdj(-wMnh6bV zHc0GlyH~WdeyS>f&hi^4mSqWm^LhnX8DGZ?Q!&d!ox8vqt*eI1ccO0s5R{ekG^Zbz&@{|7tF`*S37|-%4wAQ zd@NwIa!K)Yf9b;S+Fy8Bh}|!(hG2iSWRhk;UpVlFGYhwJpmuuw8i4(I8mPjhQ0e5t zawpdEtkxbx>m{Q2nSulLAxBGau43R4?J0KF&jS{CGaz1C`%iaVUtQN=LDAC&->L70whA+2s)L{I|~Ks@VJMohz#eyr=ne# z3u3!VNgQDY)tfZBNy$nme&T)f=@F#V$%lqcEVzu@t$;9%UH@#cY1(o9IT8s~M6h)JOWE z4GGLow~p&j#X8gbaQt&U3ojOeQ3`Yio#TDWk0C3ChV8Mip>Vwad&SkqmH;FUy+hI+ z2u(3$;eP4Oh+W_xn--IJ>kPX^s*nm{N+l+)*NL+<=Sw@EN0XB)fid>QqA~uUf#UE7 z9a&4O(fXe?gi9V8Rn6Kq((pg(AM+qdbs+^#P#RDPSW4u)#wM-^D@LRh>06O9z2E!& zR%-B;jP?9hvTOF>4itm~11Z7C6cW<7U7fv!$y6;@#>3t{J^f}o^PMy`B-{qTVFLS{#uQY}e@R5^} zU7+tN+bi78p#fYk7J-oSV+@0WLhjD^EMN>Pc;;LtJ0+Y- zhUItubx1sq>phA9`2yuWoDQE;o}JQBV`4!!#waOybE-sho{TQP6#UiPMm-jIv|f1v zcj|@^3!=)Mf9y6N%xsIeFi`hG7kWlht1h{6RHvN(c0F89u5rZ%^=Ku3iLUe~GWyCg z60=IihbtdvD@QT*6r%QVMim2$`0;#&td}K??nRp%w*m2J=*F1Ds=RPyE`qygo5w$$ z|5Q|QW>2Cw(XntM6m1?lSZUHA!CkahM<3K3V0p}Fl-F#8YPd;OWK(msdGZTuj~oKT zRcA-yrD47SFRQ+Vj^zHralGzxQ&P07Kpx$#CP2g%uHKh@vAdv{rvm8+ui%M0ZCP{5 zu&g?pM}O3bHoTgM06e(@xWg7W-F`+60&^GG{q;cQQ9y6fR?N@rxu4b4>;{ROo@L*9 zu*){;2I#OlFoBGfYAvs+*A0UV`IkQh>o|Lk`$fX;m|+Xe5ARZ(eG(A^cCG7R&$?(< zvd4ssO<3^-8hjg?@mbWTM7?J;#jt;StPP~O55GD@y#kU?r!h=@Vwo?dIQ z>3*SbAwVnB1{<&%9kyTkFo2aW%pHbSK^ySheKW)$AVym7y)3dtI-c^{NQ=uB?N&8- zpSzr&-U#yV#HsM}F27e*c-gdc&0bF?H#R+l3hkm8jdkqPcQT*6mxZ|+6>A&!cP!k; z--X+=#Y>zSs&E&@)_Zm{I8GzR@bsH$*;Km}L+;q6xY-Xn3^hnxQO-rpOnGq+_pwsj zz_vKsr6??3wzK~gq=IjgK#V58lh^pC=lA(c1_0lO>c#Xu81#ZK^DDXY5-FyDW`pD3 zqN#xNUBx;t;99SI(&$enE213vv$S^~SV%MRpg*6K=lmCSGtlkB&n;r;e_7E2a$IFpYG(EY<)>ssURMnoilq3!=v8_KGVA`ZP-YlQP6Dqh9!L$mq${+kWJzJqX{=A zD**3oB1IW&1M zP~b@)R|D=|RayR}I>lzT6t->)kAbmju=k#zEQf5nth~Owp{PTD{`o9_q>ALn>~ADp z=$^n7AKEYQNnO-<@Gmo)^XAw;lnn4bU`kmF8()xK#HUX+hzQUgkW6Utb$U$JdNyH&Hn2=X$GcSCIx>x0 zYQtNOh(cE}c)zg_Tw3Y_iv3`;Q}SGa&FZwFG!HDF>-Pu%fn-Qw!qp6CC{Zk%qBBlT2M z86Vh9A)>~kr0w@sN{A5h4x+$gB@dN~-*zk%AbXD#g4MUi6AI{eF4De4F}jUS>ts=` z#@OH}&h`a+^R$72Nl>RYIh}kgtJ@{Janug`$=C|0U@gU$b)lmMh4cyN9`NR89#F!C zoK=Az7R464fV=aK(D{zbX))=S43PuXA4$;*w~?zebzYhKUND43M*=i;f_GXtlo&2Y zES>N((09dipbQ>qH$xqESumNzl(auon<(uf{=BXNA zh0f#xRYrJFgbJh+6{lq~sUM{hFxmQAOAZ1`Z=P-@H|Gq@JLqkam2@hS$URXe&VQ36#9wMEP&UIDZtxxVt{nU}f^eN0h5({um%lI6!|{w20- zwJ=fe=IegVS%S#ehn!!9JG80bfNYag$R^XmKN{_xw8$B8KM2Rf%WjdpKSzB$tub1;E#8pzcG}g2Or;un&SK^dJzMFM}WwXXjfU)Guip}kHG~eUW=-{n9z-h z^Ah3Ps=NaCXodQ6dMtKvo3}t|b|W%eLQYc7Er)Tz8UC%lV8iP{^S5o!n&0QdqQ04q z*b#JE0=TCZ#9ua-Qb;wB+29A?_>*L%Fe0l4~WllA6yY^^|M!Qdv>M_<3Ns!1#$m#cmeBlB=7 z2^#RrUFZ(zuF4`+-YO=o~V35#I?Jx*l%&x z7Dr^{z5#zKS64EC6g4%UX(p>Zh@KP7;MrY#s5Gr;2sted7>U=q{kRK39^n(Jd#>F$ zN-Nb7woRl$q~%D9$I#i>a`1DmY1h1QWrgs_YxpaE z2Mb>c?MS2qAAkZyZX7i)3dQ$ZWw)f|Eu3G4uLLk~4o#}usjOMPh^=fpUtUvOe6dd? z!)L*}+c^5T+T9}m2uJ1WNxwKjB#xt92gWJz)k%@cb=ofQL{K4=;P4}KIIRs*06$IyxjA{t7q>zhtVP~f! zoUMCTUjx!6Tyo}^PV8J0zuypRp2 z!wf2*B3)F0C@50PA-js-Ebw($N1C0J&Ul3EDzVyTfVI+RRJ+7VZT?mSJO5d4*nCO1 zOVwW9Xoz#7&7}EUUxuzHE=6CcGHck+i zUV)@!v4WU3ZNz5KL>XfqD0x!@MCHiT?|uO)1S(eLdASo4gu12kz-jls*!bD50bH6_{dZ62r=$%>FTRJ`ZhA3D;B$vy};CU$QRi)*eWACMw@tmG|L zy#qk#@nfa8&(1-f8(>F-g?PBL3ePMb-}NOc(N;N8R$#Pgs`Tt;d$jM=c4drVUuJt( zrf7z8N~ej0Cc1zWfTtBod!$M@$l>>0#Ribe@1js)>~X^}l3AS;WEWL3sw_&z`)#Ey zi3yoc$JG5GMo;5Q|6A`LL{hu&H<;(hZ-N zF|!JVC)}plc9yP`QY8WfOfCCvoL|(mew|^k*o%y|IT}$w3^0#UYmhws2k)gnxjC)7&NuWQ;;gkbBk;?@o?<8DbUa#Q znF3g=;@o+`({wYkLR%8BQE_27m)1$!Jexd?Y8~lRX6a;a0|Gv)sJ1`;Z`hv5f>^W) z)CsFwYDQx{g+Jn0btb>IthzD(i7)KAuM||Cea{y=)q3gjs3i5)L5Sg*@<(*p#-w*W z2|u5J`Clh#j|z6`v4kf46n!i|wyz|#zi)E+w93vb!2Kg~P!qT(*4AUK;Lh@ifBq*T za3!JrtF6>c7J0h{Rs>x~VC{+tdH6JR z{mfZf{x$b=-5jlfh#csnzgdumV$ny#~z$Zj1F8}J!{JBgkKSTj-LH3H;_c~td zr}n*t;~IctkD~{kv*Osg&M`oJ;l!7(IM&UamHz*$Y*o5&LKU)xL8YY;O#@5BYuy&r z{fSMlEBx)YUb;&hvq#B$qa%kOhT6gR=-iv!llV?Sf^Z`-siTPTGujz&iJJlY2>IZ+ zCd?PObFHqZk@H@v<{d{<2mX8(rzBnA=)J@s&gy0OW>$>cv_+jZld@buyA3G;!3Wff zn>I-asQq!-pz<=MvuOA2HJSf_ra%B#xMVUL%6b@pyXUshk4;uF=wVI4>M^9hnl}Nu z4|v{#K_!+uwGx>eIB$G_1&j!!4GfGb&W)$!FEUc3$?ba1{^DsK@eI3oy10COyt|+d zAt}zwM0-u%KOi9welLxmFfb~gc<-?X3q&e?4&C)Hng3G<&C8kBC`aK8`)^VDCXp-2 zB{>u7*nMx@PRA%Hz6VzDiwoAlRODwYJ}}NDws=Ozz|-;KHR+#sYR%VY{@1R_{1=O8 zem`I!bGS~+hoB)x%yckvj|WN$JpYmSzT$KSV#AXXip5s1rGQg>k3GEFY92w-L^pTN zjiM+6F9pS)2c<8)uAxQ(G5*~===W9PG?Ux4Z+8XTmp=Yld#*G+&0-oYrzM9pT|Eif zD;)lJU(1is_ivH6hxSXu51&i>DyQE%>z#T*1&WsF<_V)TeWO1!T}ecn(?TTL9T0^$U}0k!8eOt!^A^iLchg(9 z{U#_;8hPzT=Ie*UJy%c#%AM-PpCe@d9{TtsYg_$QPR6MR>R~tj${6m+bjv?k=JM*D zl8w-%7Pti1JoJO?{g`3Q=e;z^jybu)R$O7Bn8#>KqQ^)=Y-^^qU5ii4+^kQFmPUaso)XV#Fm(1P*CDl5#Cd?DloUWZ+ zWz;Z$b-9PTjN2%DvHJ5cSpKnII*@Xu85~Tw3kganT1=$WU!T}uY^7Nn-K}0%vjXe=VGC|ljYAS zKvub^y<&x|mH)Dz4G&uuX@Z~3@hCF;TtZyDUA{cqbT5mLB(VBO%Gd*7Ql}23#K)S+SgR|F0Q395x3JtH3OsKKx)>fiaRM^M&NqjweRf%wjy5aJp-ULF#H` zF(UGCzB2T5`dTTGuYlO$nuTa?gwNsAtYDNH>EYxZwgS8dcJM9E*gf~ffQ_EjN6YNU z3D4!aW0(jd8!?AJB+G~^_IIkF6fhBwm@BM>5W#t`R2m$O^xTU4u-PfadXCCxn;`%a~Fd@lkt0Hv(UG?@6+V6 z(DG5X%RGE=KAW6t0~0+HCOs1{mdNFtyLULVvPGMIR#Nx|p~tss=Y^hUD<<$As2!%{ zT&?9~e#Ia7@=h<{=;Z6_?c!|cV}3j`O2Y8N%#YYjsxj0~pn{>!z8X~tlq>rBlvmgS z2~Kmk?kIln&4QcN7ypEiX^E1fqhB5C77NclpZ# zC|8M#CbbecC{i%h#fcvZ(aL7l%CqO_CI-KhWkrkpS8P7Ef8oNtx~>BR3USOm34Ax` z9W*l8v~V1D{c&SrV{~2J4t z@Zx=ZM()#;a9jCV^ZL>%Ip}fwp4DQPM})0|CSKYreJ>IEj}|+2r*_(~5IgREPyUvH zO7AQ^^U6)sIaN!&*e#v%L>|78Y4Ms<#a!D)z;D4d{!GK{@r zj`48l5Bajk_<(O+NCst7_6BF_3B0Y&5DPb#tRB5{o5{Fr%`-ZCu4_tn@AEm2XQYr> znd#Q*TNlEo`-w*|fpS0;=&kFHF;^cXK`P{w6;@5eeBdr};>%Pq>m@ZJa{qgTiNrJE zEtngakt`mcE4KGymfmkR;@CIjFZuEE^@Jw;kVOtwfibq*N}dqKgX6Gzf1RXaMV*~a zo%QCco376#hRphemN_RhZN9}3I%Lk^yMFUtK4wM zfeTdBebDKSe)^IL517(=s)7OAfHG`z5wTt29*>LlU?x>$yg~e7{zI&6k^Ky6y^# zo-BfBdRRe2%WypVM{7%`RA$0EE7>IOc9?rtA4y)SUQ7z?UQF6jr$+J(b8m7t7>Kgf zY*@wg=ecaS7kyo^Z6d4yQ>1jla1+jJDfnaLU2Lzi#K)0Yna>|dKA}n%-y(Z_z4{$L z9N7~{|EmWhoa@5(*;;~sid*l{Z?HOyq6}<*qplSFwHYcK(NiV-PPo_9;~T{p#C$K#FOkBEx1jrA`~)AdvpPwIwx zm%^5Wtb}!jBioj!uLG{0NG1So6P9Yj&mq8;qRpdU8ez%XL{pjCp5+kk>n;I3}vG48NlY>P@bUpi52s%~rWb(aD z*e7w&(cZHk|FAwb>VfTGZ}PGA>`zXvS*&(el~?6p*{V3e5|$jwSrhNzX}BW7n?F>m zX2g|DEBMU5i@v>YV!GJa=a{Nx;&FV_=bYQ1k#l@92`!s#FsJ>#M#c^rmA?6 zG@)r?!I5nv({hlZ#L^Xkek#6svt9pDGXe*{j}UH$szgx)~86w;8_R&RhyD)>>#M>Nem=V~}-gb&yz57bI1Eo+_Q5;F7HpFj$;E~M`H zI=3Vi+&tsAxbzTTCIWuy?<)T^1qkVEHMT6^Zq@JES7DpH*gO|In(vGDX4&DiL&g=e z6O+^qPVhgKn*Nb2gLU8X>#b7PFS5_{-2vkx)!ghS>H+XL zfDhoRbP1~l{Y^f^9eDuGERcn^OV(&n)cjTtY?MW9R85m&UBmY`SiVWX+K)S@Ciu@b z^y|QEJo-&P{p-}1bZ$ZleGPp$7rHZ)wLUZ|=GPoRup9jru1{InJk^Hp!_vI!g@x#&BzM&g0ao))7w_dfV1QXei6g;ynPMTb&9z!2RS}O z5xsyF)R%QlA2(%TAEiP7xa_KmSu)o)A+ECfo;A$OOHEPTmmX~;9}|A``p_l50$E~Y zw3VhRPTGJ2S%dO(sWa!SD?!SM_bE3@-CxC@L!42@diu&p)%h0i|7KzS8 zCOfh!=WA&FcCwU)fSqmlAi-$L2FE|UtOp-%>m^`w!CksIAd)cPVZ(P=(0q`}4J#|{mY5bP~5qW?EyyB8RmF13n zeYgMEYGr5tW2+VA0P+8nt!mqM2pP9Ek`Tw1&Olykt?(kRiW30c>j7V6jbjr|Va|Jdds|AQc9dV! zY5UZ$D(+10O7GeJCvf>|FnpE$sa!(eGTy9eEPC5!%L{TuiVkxj=qYsR5*#6hUmo#X zUMwgo(8I&otlVcroE z(0yrpN}p%AQvB90uD^xbC)0xG`<##^W!XQU6u^||rM{SCh0p8@Zc{xo57|{8 ziDq!65f$uDL8dOe?X(FLC5Nd5ceGkbKl4?U+LCqVX@vO&rptub-PDpKKbPMAeaVcr zcXohn&LdJYtL{6V8r>Mh3(o6n$>5IQQHX;r57qEP&suo41&!y3wet7~CntEqFNmQ> zkJ~3axsi9uAY<}Dxd?W#zQ~?Jbaw{P$LBnpCZ-Bo)GNFQ>aH@Q2`3Nx2=gk+E51tT zxj7SaUxhvUY4{FH4kE0;|Ne!FsiTc8gBh|X{|u|XDu)r30135%;G$Iv)Dt}qVb9=! zPAQIkjIx-(5Z}PjbA9pi@;X@xpm;O7)*rk+<}(UxB!>|N%^{iNV7QCi1h@BBuP(6K786SZ(|8heo7teYhnLbgUq-w$9%+Pc0Fls**{EpjP0MJWqoC< zBvS)^W%C}^`U@D&_ix$}7hhO$nSLmtg(_si{im5?0hK$zR({0M^g&)m?}%epAjm(n z62{XpfiN(d4%f|j)CiZFFR-mfnwm;09hFflT#5bVI?OTaY zhhGx?-*we5G5qtWWljMK^^WyMR&(lHK!NWr_H5Sr-Yhi#FuH-{NdkjsZGGHz z-6VjN5vRLg;a^458sb;t{k-5DVQU(Hod+TgP}L>gWJUcV4*wzN`^X3)pfgs0g0HUU z0hSgeEnu7DtikKTJXpV_L;-Zhfn=3atc~l&el7rPvsQlAhBcu6W2m?y@(WnIFwCg>#q0ou}B%AD^`1tjtu(TL&1#YX(HNU>N zp(qsvc{B7zWL;O&~cx97P} zt^WW^ix?nq?QeJ-Vrg2xo7upNIu=1Dl2`v{H2|T#YaU5R4Tj65f^K^wpVF=FYwtFQ zzJ4SOv3{DXEx;=wl8%|+)b(Sowm2Sq0FJlvb5)q!mb`cs6hQn?9OtW8-Y1`w>|YX1 z^sP*~HL%REEcamgW}$9fDVQMRxVI6=NfW926fcz#U^US$HK$T31i9iQbBZ`L^vF$$ z3Bs{rRF(A0lC4>TR~jLo=LWeOR15;!2d^FIxf;WT7%i>!sw!p3b+;T50C8QOdRXh# zC3~ONpN40{NTTmouV?#r2hNER9qd0G_{o_+35 zv93)RV=#5x>>6P0jWa;P4U*SylS)&QT^O*w`tWab5=nw=bn}kXSM;2ZjyC#SOLhk)(+9$=z#XTsD%b%GRMcyqGsp} z({kkPMn|6Et{O4+mj&u%WC*G&Br;Fx2t=gmdK2C@R&8hlwdc7qY8 z4~}sT;^>qkz+(!4r(LkSiEtC|?WfKx@4+nuMub}W-JYZQ+4jmF^A+r)*FJ@I@I;6@>07v{ipc6S&A+&8~%mzP8{YRf=lt(2i{k#Ni`aV>p*gFeK zl&^_2T}7yG^eKgG?c&HwuTZpdhL}0maI2OboC{}+XUlVQsocw?;6xt~7$fmcXSY|F zNidVlwqJL2c3Pg=Z5D~ck%5&zqzG_#ose~jkwv=3ZUZ@m0M?pq(rloaNDF& zB~{GbPUEqMgbeI+U886>;_*CbiZfy3hIgZ|%Z!nSoVi!<%&8hAr_i6H{ItEUbZ#M| zeU;)(XDR|(mu)KuFDSoEf^{6B-}nDEI0{4}b>!_zJgOyeI7~L3L*J(2i;hd4W|%1X zMc^qEH9#@dKIMyte)&m7dud?hI9!^|m@T?mqpk-sykNWbZh#ukoq(_o-x|hePa%C9 ziOFskD-k@M3%iVilVU{4>5!< zK0S3L%`WiGLSz=(-g$Gz9yx2`=JN`Y*2 zrw~X$2qq)&6nw-x$AyuUrGXbAR-{JbZh_UMC^R|Ki{)N!3IPnidi7rJh7ul9P*uTA zp}Tkz9pAg9e@6KH$Rj1_MWX3OChBADy}Dce5)&UVf5J8Rbz#d#V-sq(4&@QVx4Q$0 zH*kJKTg-&M^LopFd-;#}bjwFi(|O~EhuZBx9&dzP`}&EL7lHZ)F|N)_=}N`JhhDK< zdas=iz>ZC)Kh->sCem)o76cH--+VoUA4_kIG@DO?K`IW;Tx!>pU>OQV}(l4_T~!K>)JJ&1MHhMj;%F)zRC!gHU9Y*^d<=@@fxK| zhMBO`o-7KLyQ@RcMc6P?@ls8>hwIRsLJ5aqI^%0aRgq@4zKt^*Mt6RsN3II2WXSrP`E>I!gf?R=j&BL{ z@=?nQ{uOT88hr^w&IY^Vc;NQ9 z4h$LP3s!%Q2Ex5iK3?-1fx{+5(5@T{A2O64w4gM^GS1{7r6G8=WP`Z?(a@EE)i&eteEWuk6#%(*`U<$1&N#|r`fGC@GGtzQXl0&VyG*T4QaVM-c+^Y6dq`fs^b r)#U%U>mLO8?=b!Y9sj>$7n6Tc6526%x9N{fz(2Js+JEFJTi^XZFauyo literal 0 HcmV?d00001 diff --git "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" index d828220..69a9b79 100644 --- "a/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" +++ "b/docs/\345\205\263\344\272\216IFoxCAD\347\232\204\346\236\266\346\236\204\350\257\264\346\230\216.md" @@ -7,10 +7,10 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ## 一、组织结构图 - IFoxCAD - - IFoxCAD.Cad - cad相关的类库 + - IFoxCAD.Basal - cad以外常用的类库 - LinqEx - linq扩展类 - LoopList - 环链表 - - IFoxCAD.Basal - cad以外常用的类库 + - IFoxCAD.Cad - cad相关的类库 - Runtime - 包含系统级别的功能 - AcadVersion - cad版本号类 - AssemInfo - 程序集信息 @@ -45,7 +45,7 @@ IFoxCAD是基于NFOX类库的重制版,主要是提供一个最小化的内核 ### 2.2 关于DBTrans类的具体构成元素的意义 -DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而抓这些其实都是cad二次开发关于图元操作经常打交道的概念。 +DBTrans类里基本的封装就是Transaction,然后是Document、Database、Editor、符号表、命名字典等,而这些其实都是cad二次开发关于图元操作经常打交道的概念。 DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的相关类库,通过这些属性就可以对数据进行相应的操作。特别是符号表中最常用的就是块表,通过对块表的操作来实现添加图元等。 @@ -109,7 +109,7 @@ DBTrans的每个实例都具有这些属性,而这些属性就对应于cad的 - Has --- 判断符号表是否有符号表记录的函数 - 。。。 -特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法。 +特殊说明:当符号表为块表时,上述函数实际操作的是块定义、属性定义等。所以为了添加图元,需要特殊写法,原因在于cad的实体都是存在符号表记录里的,通常为模型这个块表记录。 # 慢慢完善,想到哪写到哪。。。 diff --git a/src/IFoxCAD.Basal/ArrayEx.cs b/src/IFoxCAD.Basal/ArrayEx.cs new file mode 100644 index 0000000..34fb4b7 --- /dev/null +++ b/src/IFoxCAD.Basal/ArrayEx.cs @@ -0,0 +1,50 @@ +namespace IFoxCAD.Basal +{ + /* + * 由于linq的函数大部分带有状态机,而cad是一个单机程序, + * 使用状态机会变得缓慢,因此我们设计的时候着重于时间优化, + * 本工具类在着重于数组遍历时候替代linq + */ + public static class ArrayEx + { + ///

+ /// 合并数组 + /// + /// + /// + public static T[] Combine2(this T[] a, T[] b) + { + var c = new T[a.Length + b.Length]; + Array.Copy(a, 0, c, 0, a.Length); + Array.Copy(b, 0, c, a.Length, b.Length); + return c; + } + + /// + /// 一维数组消重,此函数建议更改为: + /// set = new(); + /// foreach (var item in listInOut) + /// set.Add(item); + /// ]]> + /// + /// + /// 传入有重复成员的数组,传出没有重复的 + /// 传出参数1:数组开头;传出参数2:数组结尾;返回值比较结尾为就移除 + [Obsolete] + public static void Deduplication(List listInOut, Func func) + { + for (int i = 0; i < listInOut.Count; i++) + { + var first = listInOut[i]; + for (int j = listInOut.Count - 1; j > i; j--) + { + var last = listInOut[j]; + if (func(first, last)) + listInOut.RemoveAt(j); + } + } + } + + } +} diff --git a/src/IFoxCAD.Basal/CLS/Index.cs b/src/IFoxCAD.Basal/CLS/Index.cs new file mode 100644 index 0000000..97b51e5 --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/Index.cs @@ -0,0 +1,148 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System; + +using System.Runtime.CompilerServices; + +/// Represent a type can be used to index a collection either from the start or the end. +/// +/// Index is used by the C# compiler to support the new index syntax +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 } ; +/// int lastElement = someArray[^1]; // lastElement = 5 +/// +/// +public readonly struct Index : IEquatable +{ + private readonly int _value; + + /// Construct an Index using a value and indicating if the index is from the start or from the end. + /// The index value. it has to be zero or positive number. + /// Indicating if the index is from the start or from the end. + /// + /// If the Index constructed from the end, index value 1 means pointing at the last element and index value 0 means pointing at beyond last element. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public Index(int value, bool fromEnd = false) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + if (fromEnd) + _value = ~value; + else + _value = value; + } + + // The following private constructors mainly created for perf reason to avoid the checks + private Index(int value) + { + _value = value; + } + + /// Create an Index pointing at first element. + public static Index Start => new(0); + + /// Create an Index pointing at beyond last element. + public static Index End => new(~0); + + /// Create an Index from the start at the position indicated by the value. + /// The index value from the start. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromStart(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(value); + } + + /// Create an Index from the end at the position indicated by the value. + /// The index value from the end. +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public static Index FromEnd(int value) + { + if (value < 0) + { + throw new ArgumentOutOfRangeException(nameof(value), "value must be non-negative"); + } + + return new Index(~value); + } + + /// Returns the index value. + public int Value + { + get + { + if (_value < 0) + return ~_value; + else + return _value; + } + } + + /// Indicates whether the index is from the start or the end. + public bool IsFromEnd => _value < 0; + + /// Calculate the offset from the start using the giving collection length. + /// The length of the collection that the Index will be used with. length has to be a positive value + /// + /// For performance reason, we don't validate the input length parameter and the returned offset value against negative values. + /// we don't validate either the returned offset is greater than the input length. + /// It is expected Index will be used with collections which always have non negative length/count. If the returned offset is negative and + /// then used to index a collection will get out of range exception which will be same affect as the validation. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + public int GetOffset(int length) + { + int offset = _value; + if (IsFromEnd) + { + // offset = length - (~value) + // offset = length + (~(~value) + 1) + // offset = length + value + 1 + + offset += length + 1; + } + return offset; + } + + /// Indicates whether the current Index object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => value is Index index && _value == index._value; + + /// Indicates whether the current Index object is equal to another Index object. + /// An object to compare with this object + public bool Equals(Index other) => _value == other._value; + + /// Returns the hash code for this instance. + public override int GetHashCode() => _value; + + /// Converts integer number to an Index. + public static implicit operator Index(int value) => FromStart(value); + + /// Converts the value of the current Index object to its equivalent string representation. + public override string ToString() + { + if (IsFromEnd) + return "^" + ((uint)Value).ToString(); + + return ((uint)Value).ToString(); + } +} + diff --git a/src/IFoxCAD.Basal/CLS/Range.cs b/src/IFoxCAD.Basal/CLS/Range.cs new file mode 100644 index 0000000..221167b --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/Range.cs @@ -0,0 +1,103 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System; + +using System.Runtime.CompilerServices; + + +/// Represent a range has start and end indexes. +/// +/// Range is used by the C# compiler to support the range syntax. +/// +/// int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +/// int[] subArray1 = someArray[0..2]; // { 1, 2 } +/// int[] subArray2 = someArray[1..^0]; // { 2, 3, 4, 5 } +/// +/// +public readonly struct Range : IEquatable +{ + /// Represent the inclusive start index of the Range. + public Index Start { get; } + + /// Represent the exclusive end index of the Range. + public Index End { get; } + + /// Construct a Range object using the start and end indexes. + /// Represent the inclusive start index of the range. + /// Represent the exclusive end index of the range. + public Range(Index start, Index end) + { + Start = start; + End = end; + } + + /// Indicates whether the current Range object is equal to another object of the same type. + /// An object to compare with this object + public override bool Equals(object? value) => + value is Range r && + r.Start.Equals(Start) && + r.End.Equals(End); + + /// Indicates whether the current Range object is equal to another Range object. + /// An object to compare with this object + public bool Equals(Range other) => other.Start.Equals(Start) && other.End.Equals(End); + + /// Returns the hash code for this instance. + public override int GetHashCode() + { + return Start.GetHashCode() * 31 + End.GetHashCode(); + } + + /// Converts the value of the current Range object to its equivalent string representation. + public override string ToString() + { + return Start + ".." + End; + } + + /// Create a Range object starting from start index to the end of the collection. + public static Range StartAt(Index start) => new(start, Index.End); + + /// Create a Range object starting from first element in the collection to the end Index. + public static Range EndAt(Index end) => new(Index.Start, end); + + /// Create a Range object starting from first element to the end. + public static Range All => new(Index.Start, Index.End); + + /// Calculate the start offset and length of range object using a collection length. + /// The length of the collection that the range will be used with. length has to be a positive value. + /// + /// For performance reason, we don't validate the input length parameter against negative values. + /// It is expected Range will be used with collections which always have non negative length/count. + /// We validate the range is inside the length scope though. + /// +#if NET45 + [MethodImpl(MethodImplOptions.AggressiveInlining)] +#endif + //[CLSCompliant(false)] + public (int Offset, int Length) GetOffsetAndLength(int length) + { + int start; + Index startIndex = Start; + if (startIndex.IsFromEnd) + start = length - startIndex.Value; + else + start = startIndex.Value; + + int end; + Index endIndex = End; + if (endIndex.IsFromEnd) + end = length - endIndex.Value; + else + end = endIndex.Value; + + if ((uint)end > (uint)length || (uint)start > (uint)end) + { + throw new ArgumentOutOfRangeException(nameof(length)); + } + + return (start, end - start); + } +} + diff --git a/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs new file mode 100644 index 0000000..a446c59 --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/RuntimeHelpers.cs @@ -0,0 +1,44 @@ +//#if NET35 +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace System.Runtime.CompilerServices; + +public static class RuntimeHelpers +{ + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + if (length == 0) + { + //return Array.Empty(); + return new T[0]; + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + + +} +//#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs new file mode 100644 index 0000000..69670b4 --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/TupleElementNamesAttribute.cs @@ -0,0 +1,53 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using IFoxCAD.Basal; + +namespace System.Runtime.CompilerServices; + +/// +/// Indicates that the use of on a member is meant to be treated as a tuple with element names. +/// +[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Event)] +public sealed class TupleElementNamesAttribute : Attribute +{ + private readonly string[] _transformNames; + + /// + /// Initializes a new instance of the class. + /// + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which occurrences are + /// meant to carry element names. + /// + /// + /// This constructor is meant to be used on types that contain an + /// instantiation of that contains + /// element names. For instance, if C is a generic type with + /// two type parameters, then a use of the constructed type C{, might be intended to + /// treat the first type argument as a tuple with element names and the + /// second as a tuple without element names. In which case, the + /// appropriate attribute specification should use a + /// transformNames value of { "name1", "name2", null, null, + /// null }. + /// + public TupleElementNamesAttribute(string[] transformNames) + { + if (transformNames == null) + throw new ArgumentNullException(nameof(transformNames)); + + _transformNames = transformNames; + } + + /// + /// Specifies, in a pre-order depth-first traversal of a type's + /// construction, which elements are + /// meant to carry element names. + /// + public IList TransformNames => _transformNames; +} \ No newline at end of file diff --git a/src/IFoxCAD.Basal/CLS/ValueTuple.cs b/src/IFoxCAD.Basal/CLS/ValueTuple.cs new file mode 100644 index 0000000..bece1ed --- /dev/null +++ b/src/IFoxCAD.Basal/CLS/ValueTuple.cs @@ -0,0 +1,2144 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +//#pragma warning disable SA1141 // explicitly not using tuple syntax in tuple implementation + + +using System.Diagnostics; +using System.Numerics.Hashing; +/* + * 惊惊: + * 首先是因为有人想要编译的时候只形成一个dll,然后把元组塞入IFox,同时也补充了net35没有元组的遗憾. + * 而利用nuget元组包必然会形成依赖地狱. + * + * 如果你的工程使用了nuget元组包,就造成了必须要剔除IFox. + * + * 改IFox的元组命名空间倒是可以分离两者,但是 vs编译器 无法识别带其他命名空间的元组. + * 所以元组本身就是冲突的,需要把其他元组卸载掉,由IFox提供. + */ + +#if NET35 +namespace System.Collections +{ + public interface IStructuralComparable + { + int CompareTo(object? other, IComparer comparer); + } + public interface IStructuralEquatable + { + bool Equals(object? other, IEqualityComparer comparer); + int GetHashCode(IEqualityComparer comparer); + } +} +#endif + + + +namespace System.Numerics.Hashing +{ + internal static class HashHelpers + { + public static readonly int RandomSeed = Guid.NewGuid().GetHashCode(); + + public static int Combine(int h1, int h2) + { + unchecked + { + // RyuJIT optimizes this to use the ROL instruction + // Related GitHub pull request: dotnet/coreclr#1830 + + // RyuJIT 对此进行了优化以使用 ROL 指令 + // 相关 GitHub 拉取请求:dotnet/coreclr#1830 + uint rol5 = ((uint)h1 << 5) | ((uint)h1 >> 27); + return ((int)rol5 + h1) ^ h2; + } + } + } +} + + + + +namespace System +{ + //internal static class SR + internal sealed partial class SR + { + // public const string ArgumentException_ValueTupleIncorrectType = "The parameter should be a ValueTuple type of appropriate arity."; + // public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "The TRest type argument of ValueTuple`8 must be a ValueTuple."; + public const string ArgumentException_ValueTupleIncorrectType = "该参数应该是适当数量的 ValueTuple 类型."; + public const string ArgumentException_ValueTupleLastArgumentNotAValueTuple = "ValueTuple`8 的 TREST 类型参数必须是 ValueTuple."; + } + + // Helper so we can call some tuple methods recursively without knowing the underlying types. + /// + /// 帮助器,因此我们可以在不知道底层类型的情况下递归调用一些元组方法. + /// + internal interface ITupleInternal + { + int GetHashCode(IEqualityComparer comparer); + int Size { get; } + string ToStringEnd(); + } + + + // The ValueTuple types (from arity 0 to 8) comprise the runtime implementation that underlies tuples in C# and struct tuples in F#. + // Aside from created via language syntax, they are most easily created via the ValueTuple.Create factory methods. + // The System.ValueTuple types differ from the System.Tuple types in that: + // - they are structs rather than classes, + // - they are mutable rather than readonly, and + // - their members (such as Item1, Item2, etc) are fields rather than properties. + /// + /// ValueTuple 类型(从 arity 0 到 8)包含运行时实现,它是 C# 中的元组和 F# 中的结构元组的基础. + /// 除了通过语言语法创建之外,它们最容易通过 ValueTuple.Create 工厂方法创建. + /// System.ValueTuple 类型与 System.Tuple 类型的不同之处在于: + /// - 它们是结构而不是类, + /// - 它们是可变的而不是只读的,并且 + /// - 它们的成员(例如 Item1、Item2 等)是字段而不是属性. + /// + public struct ValueTuple + : IEquatable, IStructuralEquatable, IStructuralComparable, IComparable, IComparable, ITupleInternal + { + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if is a . + public override bool Equals(object obj) + { + return obj is ValueTuple; + } + + /// Returns a value indicating whether this instance is equal to a specified value. + /// An instance to compare to this instance. + /// true if has the same value as this instance; otherwise, false. + public bool Equals(ValueTuple other) + { + return true; + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + return other is ValueTuple; + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return 0; + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return 0; + } + + /// Returns the hash code for this instance. + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return 0; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return 0; + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (). + /// + public override string ToString() + { + return "()"; + } + + string ITupleInternal.ToStringEnd() + { + return ")"; + } + + int ITupleInternal.Size => 0; + + /// Creates a new struct 0-tuple. + /// A 0-tuple. + public static ValueTuple Create() => new(); + + /// Creates a new struct 1-tuple, or singleton. + /// The type of the first component of the tuple. + /// The value of the first component of the tuple. + /// A 1-tuple (singleton) whose value is (item1). + public static ValueTuple Create(T1 item1) => new(item1); + + /// Creates a new struct 2-tuple, or pair. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// A 2-tuple (pair) whose value is (item1, item2). + public static ValueTuple Create(T1 item1, T2 item2) => new(item1, item2); + + /// Creates a new struct 3-tuple, or triple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// A 3-tuple (triple) whose value is (item1, item2, item3). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3) => + new(item1, item2, item3); + + /// Creates a new struct 4-tuple, or quadruple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// A 4-tuple (quadruple) whose value is (item1, item2, item3, item4). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4) => + new(item1, item2, item3, item4); + + /// Creates a new struct 5-tuple, or quintuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// A 5-tuple (quintuple) whose value is (item1, item2, item3, item4, item5). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) => + new(item1, item2, item3, item4, item5); + + /// Creates a new struct 6-tuple, or sextuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// A 6-tuple (sextuple) whose value is (item1, item2, item3, item4, item5, item6). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) => + new(item1, item2, item3, item4, item5, item6); + + /// Creates a new struct 7-tuple, or septuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// A 7-tuple (septuple) whose value is (item1, item2, item3, item4, item5, item6, item7). + public static ValueTuple Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) => + new(item1, item2, item3, item4, item5, item6, item7); + + /// Creates a new struct 8-tuple, or octuple. + /// The type of the first component of the tuple. + /// The type of the second component of the tuple. + /// The type of the third component of the tuple. + /// The type of the fourth component of the tuple. + /// The type of the fifth component of the tuple. + /// The type of the sixth component of the tuple. + /// The type of the seventh component of the tuple. + /// The type of the eighth component of the tuple. + /// The value of the first component of the tuple. + /// The value of the second component of the tuple. + /// The value of the third component of the tuple. + /// The value of the fourth component of the tuple. + /// The value of the fifth component of the tuple. + /// The value of the sixth component of the tuple. + /// The value of the seventh component of the tuple. + /// The value of the eighth component of the tuple. + /// An 8-tuple (octuple) whose value is (item1, item2, item3, item4, item5, item6, item7, item8). + public static ValueTuple> Create(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, T8 item8) => + new(item1, item2, item3, item4, item5, item6, item7, ValueTuple.Create(item8)); + + internal static int CombineHashCodes(int h1, int h2) + { + return HashHelpers.Combine(HashHelpers.Combine(HashHelpers.RandomSeed, h1), h2); + } + + internal static int CombineHashCodes(int h1, int h2, int h3) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2), h3); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3), h4); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4), h5); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5), h6); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6), h7); + } + + internal static int CombineHashCodes(int h1, int h2, int h3, int h4, int h5, int h6, int h7, int h8) + { + return HashHelpers.Combine(CombineHashCodes(h1, h2, h3, h4, h5, h6, h7), h8); + } + } + + /// Represents a 1-tuple, or singleton, as a value type. + /// The type of the tuple's only component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + public ValueTuple(T1 item1) + { + Item1 = item1; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its field + /// is equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return Comparer.Default.Compare(Item1, objTuple.Item1); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + return Comparer.Default.Compare(Item1, other.Item1); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + return comparer.Compare(Item1, objTuple.Item1); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return EqualityComparer.Default.GetHashCode(Item1); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return comparer.GetHashCode(Item1); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1), + /// where Item1 represents the value of . If the field is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ")"; + } + + int ITupleInternal.Size => 1; + } + + /// + /// Represents a 2-tuple, or pair, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + + /// + /// The current instance's first component. + /// + public T2 Item2; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public ValueTuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2); + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object based on a specified comparison method. + /// + /// The object to compare with this instance. + /// An object that defines the method to use to evaluate whether the two objects are equal. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// + /// This member is an explicit interface member implementation. It can be used only when the + /// instance is cast to an interface. + /// + /// The implementation is called only if other is not , + /// and if it can be successfully cast (in C#) or converted (in Visual Basic) to a + /// whose components are of the same types as those of the current instance. The IStructuralEquatable.Equals(Object, IEqualityComparer) method + /// first passes the values of the objects to be compared to the + /// implementation. If this method call returns , the method is + /// called again and passed the values of the two instances. + /// + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other is null or not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + return Comparer.Default.Compare(Item2, other.Item2); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + return comparer.Compare(Item2, objTuple.Item2); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2), + /// where Item1 and Item2 represent the values of the + /// and fields. If either field value is , + /// it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ")"; + } + + int ITupleInternal.Size => 2; + } + + /// + /// Represents a 3-tuple, or triple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + public ValueTuple(T1 item1, T2 item2, T3 item3) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + return Comparer.Default.Compare(Item3, other.Item3); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + return comparer.Compare(Item3, objTuple.Item3); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ")"; + } + + int ITupleInternal.Size => 3; + } + + /// + /// Represents a 4-tuple, or quadruple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + return Comparer.Default.Compare(Item4, other.Item4); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + return comparer.Compare(Item4, objTuple.Item4); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ")"; + } + + int ITupleInternal.Size => 4; + } + + /// + /// Represents a 5-tuple, or quintuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + return Comparer.Default.Compare(Item5, other.Item5); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + return comparer.Compare(Item5, objTuple.Item5); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ")"; + } + + int ITupleInternal.Size => 5; + } + + /// + /// Represents a 6-tuple, or sixtuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + return Comparer.Default.Compare(Item6, other.Item6); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + return comparer.Compare(Item6, objTuple.Item6); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ")"; + } + + int ITupleInternal.Size => 6; + } + + /// + /// Represents a 7-tuple, or sentuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7) + { + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + return Comparer.Default.Compare(Item7, other.Item7); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + return comparer.Compare(Item7, objTuple.Item7); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + string ITupleInternal.ToStringEnd() + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ")"; + } + + int ITupleInternal.Size => 7; + } + + /// + /// Represents an 8-tuple, or octuple, as a value type. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + /// The type of the tuple's third component. + /// The type of the tuple's fourth component. + /// The type of the tuple's fifth component. + /// The type of the tuple's sixth component. + /// The type of the tuple's seventh component. + /// The type of the tuple's eighth component. + public struct ValueTuple + : IEquatable>, IStructuralEquatable, IStructuralComparable, IComparable, IComparable>, ITupleInternal + where TRest : struct + { + /// + /// The current instance's first component. + /// + public T1 Item1; + /// + /// The current instance's second component. + /// + public T2 Item2; + /// + /// The current instance's third component. + /// + public T3 Item3; + /// + /// The current instance's fourth component. + /// + public T4 Item4; + /// + /// The current instance's fifth component. + /// + public T5 Item5; + /// + /// The current instance's sixth component. + /// + public T6 Item6; + /// + /// The current instance's seventh component. + /// + public T7 Item7; + /// + /// The current instance's eighth component. + /// + public TRest Rest; + + /// + /// Initializes a new instance of the value type. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + /// The value of the tuple's third component. + /// The value of the tuple's fourth component. + /// The value of the tuple's fifth component. + /// The value of the tuple's sixth component. + /// The value of the tuple's seventh component. + /// The value of the tuple's eight component. + public ValueTuple(T1 item1, T2 item2, T3 item3, T4 item4, T5 item5, T6 item6, T7 item7, TRest rest) + { + if (rest is not ITupleInternal) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleLastArgumentNotAValueTuple); + } + + Item1 = item1; + Item2 = item2; + Item3 = item3; + Item4 = item4; + Item5 = item5; + Item6 = item6; + Item7 = item7; + Rest = rest; + } + + /// + /// Returns a value that indicates whether the current instance is equal to a specified object. + /// + /// The object to compare with this instance. + /// if the current instance is equal to the specified object; otherwise, . + /// + /// The parameter is considered to be equal to the current instance under the following conditions: + /// + /// It is a value type. + /// Its components are of the same types as those of the current instance. + /// Its components are equal to those of the current instance. Equality is determined by the default object equality comparer for each component. + /// + /// + public override bool Equals(object obj) + { + return obj is ValueTuple tuple && Equals(tuple); + } + + /// + /// Returns a value that indicates whether the current + /// instance is equal to a specified . + /// + /// The tuple to compare with this instance. + /// if the current instance is equal to the specified tuple; otherwise, . + /// + /// The parameter is considered to be equal to the current instance if each of its fields + /// are equal to that of the current instance, using the default comparer for that field's type. + /// + public bool Equals(ValueTuple other) + { + return EqualityComparer.Default.Equals(Item1, other.Item1) + && EqualityComparer.Default.Equals(Item2, other.Item2) + && EqualityComparer.Default.Equals(Item3, other.Item3) + && EqualityComparer.Default.Equals(Item4, other.Item4) + && EqualityComparer.Default.Equals(Item5, other.Item5) + && EqualityComparer.Default.Equals(Item6, other.Item6) + && EqualityComparer.Default.Equals(Item7, other.Item7) + && EqualityComparer.Default.Equals(Rest, other.Rest); + } + + bool IStructuralEquatable.Equals(object? other, IEqualityComparer comparer) + { + if (other == null || other is not ValueTuple) return false; + + var objTuple = (ValueTuple)other; + + return comparer.Equals(Item1, objTuple.Item1) + && comparer.Equals(Item2, objTuple.Item2) + && comparer.Equals(Item3, objTuple.Item3) + && comparer.Equals(Item4, objTuple.Item4) + && comparer.Equals(Item5, objTuple.Item5) + && comparer.Equals(Item6, objTuple.Item6) + && comparer.Equals(Item7, objTuple.Item7) + && comparer.Equals(Rest, objTuple.Rest); + } + + int IComparable.CompareTo(object other) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + return CompareTo((ValueTuple)other); + } + + /// Compares this instance to a specified instance and returns an indication of their relative values. + /// An instance to compare. + /// + /// A signed number indicating the relative values of this instance and . + /// Returns less than zero if this instance is less than , zero if this + /// instance is equal to , and greater than zero if this instance is greater + /// than . + /// + public int CompareTo(ValueTuple other) + { + int c = Comparer.Default.Compare(Item1, other.Item1); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item2, other.Item2); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item3, other.Item3); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item4, other.Item4); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item5, other.Item5); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item6, other.Item6); + if (c != 0) return c; + + c = Comparer.Default.Compare(Item7, other.Item7); + if (c != 0) return c; + + return Comparer.Default.Compare(Rest, other.Rest); + } + + int IStructuralComparable.CompareTo(object? other, IComparer comparer) + { + if (other == null) return 1; + + if (other is not ValueTuple) + { + throw new ArgumentException(SR.ArgumentException_ValueTupleIncorrectType, nameof(other)); + } + + var objTuple = (ValueTuple)other; + + int c = comparer.Compare(Item1, objTuple.Item1); + if (c != 0) return c; + + c = comparer.Compare(Item2, objTuple.Item2); + if (c != 0) return c; + + c = comparer.Compare(Item3, objTuple.Item3); + if (c != 0) return c; + + c = comparer.Compare(Item4, objTuple.Item4); + if (c != 0) return c; + + c = comparer.Compare(Item5, objTuple.Item5); + if (c != 0) return c; + + c = comparer.Compare(Item6, objTuple.Item6); + if (c != 0) return c; + + c = comparer.Compare(Item7, objTuple.Item7); + if (c != 0) return c; + + return comparer.Compare(Rest, objTuple.Rest); + } + + /// + /// Returns the hash code for the current instance. + /// + /// A 32-bit signed integer hash code. + public override int GetHashCode() + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 2: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 3: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 4: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 5: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 6: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + case 7: + case 8: + return ValueTuple.CombineHashCodes(EqualityComparer.Default.GetHashCode(Item1), + EqualityComparer.Default.GetHashCode(Item2), + EqualityComparer.Default.GetHashCode(Item3), + EqualityComparer.Default.GetHashCode(Item4), + EqualityComparer.Default.GetHashCode(Item5), + EqualityComparer.Default.GetHashCode(Item6), + EqualityComparer.Default.GetHashCode(Item7), + rest.GetHashCode()); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int IStructuralEquatable.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + private int GetHashCodeCore(IEqualityComparer comparer) + { + // We want to have a limited hash in this case. We'll use the last 8 elements of the tuple + if (Rest is not ITupleInternal rest) + { + return ValueTuple.CombineHashCodes( + comparer.GetHashCode(Item1), + comparer.GetHashCode(Item2), + comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7)); + } + + int size = rest.Size; + if (size >= 8) { return rest.GetHashCode(comparer); } + + // In this case, the rest member has less than 8 elements so we need to combine some our elements with the elements in rest + int k = 8 - size; + switch (k) + { + case 1: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 2: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 3: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 4: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 5: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), + comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + case 6: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), comparer.GetHashCode(Item4), + comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), comparer.GetHashCode(Item7), + rest.GetHashCode(comparer)); + case 7: + case 8: + return ValueTuple.CombineHashCodes(comparer.GetHashCode(Item1), comparer.GetHashCode(Item2), comparer.GetHashCode(Item3), + comparer.GetHashCode(Item4), comparer.GetHashCode(Item5), comparer.GetHashCode(Item6), + comparer.GetHashCode(Item7), rest.GetHashCode(comparer)); + } + + Debug.Assert(false, "Missed all cases for computing ValueTuple hash code"); + return -1; + } + + int ITupleInternal.GetHashCode(IEqualityComparer comparer) + { + return GetHashCodeCore(comparer); + } + + /// + /// Returns a string that represents the value of this instance. + /// + /// The string representation of this instance. + /// + /// The string returned by this method takes the form (Item1, Item2, Item3, Item4, Item5, Item6, Item7, Rest). + /// If any field value is , it is represented as . + /// + public override string ToString() + { + if (Rest is not ITupleInternal rest) + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return "(" + Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + string ITupleInternal.ToStringEnd() + { + if (Rest is not ITupleInternal rest) + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + Rest.ToString() + ")"; + } + else + { + return Item1?.ToString() + ", " + Item2?.ToString() + ", " + Item3?.ToString() + ", " + Item4?.ToString() + ", " + Item5?.ToString() + ", " + Item6?.ToString() + ", " + Item7?.ToString() + ", " + rest.ToStringEnd(); + } + } + + int ITupleInternal.Size + { + get + { + //ITupleInternal? rest = Rest as ITupleInternal; + //return rest == null ? 8 : 7 + rest.Size; + return Rest is not ITupleInternal rest ? 8 : 7 + rest.Size; + } + } + } +} + + + + diff --git a/src/IFoxCAD.Basal/DictEx.cs b/src/IFoxCAD.Basal/DictEx.cs new file mode 100644 index 0000000..6fbccc4 --- /dev/null +++ b/src/IFoxCAD.Basal/DictEx.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace IFoxCAD.Basal +{ + public static class DictEx + { + //public static TKey? GetKey(this IDictionary dict!!, TKey key!!) + //{ + // if (dict.ContainsKey(key)) + // { + // foreach (var item in dict.Keys) + // if (key.Equals(item)) + // return item; + // } + // return default; + //} + } +} diff --git a/src/IFoxCAD.Basal/GlobalUsings.cs b/src/IFoxCAD.Basal/GlobalUsings.cs new file mode 100644 index 0000000..15c8a97 --- /dev/null +++ b/src/IFoxCAD.Basal/GlobalUsings.cs @@ -0,0 +1,9 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; + diff --git a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj index a15060d..73d940d 100644 --- a/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj +++ b/src/IFoxCAD.Basal/IFoxCAD.Basal.csproj @@ -1,45 +1,34 @@ - - - - preview - enable - - - 1.0.0.* - 1.0.0.0 - False - net35;net40 - 0.1.1 - - true - true - InspireFunction - xsfhlzh;vicwjb - 基于.NET的二次开发基本类库 - InspireFunction - git - https://gitee.com/inspirefunction/ifoxcad.git - - https://gitee.com/inspirefunction/ifoxcad - IFoxCAD;C#;NET;Common;Basal - 增加在net35支持 - true - true - - LICENSE - true - - - - - - - - - True - - - - + + + + preview + enable + + net35;net40;net45 + true + 0.3.5.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的二次开发基本类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;C#;NET;Common;Basal + 直接集成元组和索引切片. + true + true + LICENSE + true + True + none + + + + + True + + + diff --git a/src/IFoxCAD.Basal/LinkedHashMap.cs b/src/IFoxCAD.Basal/LinkedHashMap.cs new file mode 100644 index 0000000..0e6232c --- /dev/null +++ b/src/IFoxCAD.Basal/LinkedHashMap.cs @@ -0,0 +1,171 @@ +namespace IFoxCAD.Basal; + + +/// +/// A least-recently-used cache stored like a dictionary. +/// +/// +/// The type of the key to the cached item +/// +/// +/// The type of the cached item. +/// +/// +/// Derived from https://stackoverflow.com/a/3719378/240845 +/// https://stackoverflow.com/users/240845/mheyman +/// +public class LinkedHashMap +{ + private readonly Dictionary> cacheMap = new(); + + private readonly LinkedList lruList = new(); + + private readonly Action? dispose; + + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// Maximum number of elements to cache. + /// + /// + /// When elements cycle out of the cache, disposes them. May be null. + /// + public LinkedHashMap(int capacity, Action? dispose = null) + { + this.Capacity = capacity; + this.dispose = dispose; + } + + /// + /// Gets the capacity of the cache. + /// + public int Capacity { get; } + + /// Gets the value associated with the specified key. + /// + /// The key of the value to get. + /// + /// + /// When this method returns, contains the value associated with the specified + /// key, if the key is found; otherwise, the default value for the type of the + /// parameter. This parameter is passed + /// uninitialized. + /// + /// + /// true if the + /// contains an element with the specified key; otherwise, false. + /// + public bool TryGetValue(TKey key, out TValue? value) + { + lock (this.cacheMap) + { + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + return true; + } + + value = default; + return false; + } + } + + /// + /// Looks for a value for the matching . If not found, + /// calls to retrieve the value and add it to + /// the cache. + /// + /// + /// The key of the value to look up. + /// + /// + /// Generates a value if one isn't found. + /// + /// + /// The requested value. + /// + public TValue Get(TKey key, Func valueGenerator) + { + lock (this.cacheMap) + { + TValue value; + if (this.cacheMap.TryGetValue(key, out LinkedListNode.MapItem> node)) + { + value = node.Value.Value; + this.lruList.Remove(node); + this.lruList.AddLast(node); + } + else + { + value = valueGenerator(); + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + node = new LinkedListNode(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + + return value; + } + } + + /// + /// Adds the specified key and value to the dictionary. + /// + /// + /// The key of the element to add. + /// + /// + /// The value of the element to add. The value can be null for reference types. + /// + public void Add(TKey key, TValue value) + { + lock (this.cacheMap) + { + if (this.cacheMap.Count >= this.Capacity) + { + this.RemoveFirst(); + } + + MapItem cacheItem = new(key, value); + LinkedListNode node = new(cacheItem); + this.lruList.AddLast(node); + this.cacheMap.Add(key, node); + } + } + + private void RemoveFirst() + { + // Remove from LRUPriority + LinkedListNode node = this.lruList.First; + this.lruList.RemoveFirst(); + + // Remove from cache + this.cacheMap.Remove(node.Value.Key); + + // dispose + this.dispose?.Invoke(node.Value.Value); + } + + private class MapItem + { + public MapItem(TKey k, TValue v) + { + this.Key = k; + this.Value = v; + } + + public TKey Key { get; } + + public TValue Value { get; } + } +} + diff --git a/src/IFoxCAD.Basal/LinkedHashSet.cs b/src/IFoxCAD.Basal/LinkedHashSet.cs new file mode 100644 index 0000000..8659c24 --- /dev/null +++ b/src/IFoxCAD.Basal/LinkedHashSet.cs @@ -0,0 +1,221 @@ +namespace IFoxCAD.Basal; + +public class LinkedHashSet : ICollection where T : IComparable +{ + private readonly IDictionary> m_Dictionary; + private readonly LoopList m_LinkedList; + + public LinkedHashSet() + { + m_Dictionary = new Dictionary>(); + m_LinkedList = new LoopList(); + } + + public LoopListNode? First => m_LinkedList.First; + + public LoopListNode? Last => m_LinkedList.Last; + + public LoopListNode? MinNode { get; set; } + + public bool Add(T item) + { + if (m_Dictionary.ContainsKey(item)) + return false; + var node = m_LinkedList.AddLast(item); + m_Dictionary.Add(item, node); + + if (MinNode is null) + { + MinNode = node; + } + else + { + if (item.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + + + + return true; + } + + void ICollection.Add(T item) + { + Add(item); + } + + public LoopListNode AddFirst(T value) + { + if (m_Dictionary.ContainsKey(value)) + { + return m_Dictionary[value]; + } + var node = m_LinkedList.AddFirst(value); + m_Dictionary.Add(value, node); + if (MinNode is null) + { + MinNode = node; + } + else + { + if (value.CompareTo(MinNode.Value) < 0) + { + MinNode = node; + } + } + return node; + } + + public void AddRange(IEnumerable collection) + { + foreach (var item in collection) + { + Add(item); + } + } + + + public void Clear() + { + m_LinkedList.Clear(); + m_Dictionary.Clear(); + } + + public bool Remove(T item) + { + bool found = m_Dictionary.TryGetValue(item, out LoopListNode node); + if (!found) return false; + m_Dictionary.Remove(item); + m_LinkedList.Remove(node); + return true; + } + + public int Count + { + get { return m_Dictionary.Count; } + } + + public void For(LoopListNode from, Action action) + { + var first = from; + var last = from; + if(first is null) return; + + for (int i = 0; i < Count; i++) + { + + action.Invoke(i,first!.Value, last!.Value); + first = first.Next; + last = last.Previous; + } + } + + public List ToList() + { + return m_LinkedList.ToList(); + } + + public IEnumerator GetEnumerator() + { + return m_LinkedList.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + + public bool Contains(T item) + { + return m_Dictionary.ContainsKey(item); + } + + public void CopyTo(T[] array, int arrayIndex) + { + //m_LinkedList.CopyTo(array, arrayIndex); + return; + } + + public bool SetFirst(LoopListNode node) + { + return m_LinkedList.SetFirst(node); + } + + public LinkedHashSet Clone() + { + var newset = new LinkedHashSet(); + foreach (var item in this) + { + newset.Add(item); + } + return newset; + } + + public virtual bool IsReadOnly + { + get { return m_Dictionary.IsReadOnly; } + } + + public override string ToString() + { + return m_LinkedList.ToString(); + } + + public void UnionWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void IntersectWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void ExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public void SymmetricExceptWith(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSupersetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool IsProperSubsetOf(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool Overlaps(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + public bool SetEquals(IEnumerable other) + { + throw GetNotSupportedDueToSimplification(); + } + + private static Exception GetNotSupportedDueToSimplification() + { + return new NotSupportedException("This method is not supported due to simplification of example code."); + } +} diff --git a/src/IFoxCAD.Basal/LinqEx.cs b/src/IFoxCAD.Basal/LinqEx.cs index 7b20401..8112e63 100644 --- a/src/IFoxCAD.Basal/LinqEx.cs +++ b/src/IFoxCAD.Basal/LinqEx.cs @@ -1,342 +1,335 @@ -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Basal; -namespace IFoxCAD.Linq +/// +/// linq 扩展类 +/// +public static class LinqEx { + #region FindByMax + /// - /// linq 扩展类 + /// 按转换函数找出序列中最大键值的对应值 /// - public static class LinqEx + /// + /// + /// 序列 + /// 转换函数 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, Func func) + where TKey : IComparable { - #region FindByMax - - /// - /// 按转换函数找出序列中最大键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) > 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) > 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - return value; } + return value; + } - /// - /// 按转换函数找出序列中最大键值的对应值 - /// - /// - /// - /// 序列 - /// 对应的最大键值 - /// 转换函数 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, out TKey maxResult, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最大键值的对应值 + /// + /// + /// + /// 序列 + /// 对应的最大键值 + /// 转换函数 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, out TKey maxResult, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) > 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) > 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - maxResult = key; - return value; } + maxResult = key; + return value; + } - /// - /// 按比较器找出序列中最大键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最大键值的对应值 - public static TValue FindByMax(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); - - TValue value = itor.Current; + /// + /// 按比较器找出序列中最大键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最大键值的对应值 + public static TValue FindByMax(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - while (itor.MoveNext()) - { - if (comparison(itor.Current, value) > 0) - value = itor.Current; - } - return value; - } + TValue value = itor.Current; - #endregion FindByMax - - #region FindByMin - - /// - /// 按转换函数找出序列中最小键值的对应值 - /// - /// - /// - /// 序列 - /// 对应的最小键值 - /// 转换函数 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, out TKey minKey, Func func) - where TKey : IComparable + while (itor.MoveNext()) { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + if (comparison(itor.Current, value) > 0) + value = itor.Current; + } + return value; + } - TValue value = itor.Current; - TKey key = func(value); + #endregion FindByMax - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) < 0) - { - key = tkey; - value = itor.Current; - } - } - minKey = key; - return value; - } + #region FindByMin - /// - /// 按转换函数找出序列中最小键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最小键值的对应值 + /// + /// + /// + /// 序列 + /// 对应的最小键值 + /// 转换函数 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, out TKey minKey, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; - TKey key = func(value); + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) < 0) { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(key) < 0) - { - key = tkey; - value = itor.Current; - } + key = tkey; + value = itor.Current; } - return value; } + minKey = key; + return value; + } - /// - /// 按比较器找出序列中最小键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最小键值的对应值 - public static TValue FindByMin(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按转换函数找出序列中最小键值的对应值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TValue value = itor.Current; + TValue value = itor.Current; + TKey key = func(value); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(key) < 0) { - if (comparison(itor.Current, value) < 0) - value = itor.Current; + key = tkey; + value = itor.Current; } - return value; } + return value; + } - #endregion FindByMin + /// + /// 按比较器找出序列中最小键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最小键值的对应值 + public static TValue FindByMin(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - #region FindByExt + TValue value = itor.Current; - /// - /// 按转换函数找出序列中最(小/大)键值的对应值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最(小/大)键值的对应值 - public static TValue[] FindByExt(this IEnumerable source, Func func) - where TKey : IComparable + while (itor.MoveNext()) { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + if (comparison(itor.Current, value) < 0) + value = itor.Current; + } + return value; + } - TValue[] values = new TValue[2]; - values[0] = values[1] = itor.Current; + #endregion FindByMin - TKey[] keys = new TKey[2]; - keys[0] = keys[1] = func(itor.Current); + #region FindByExt - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(keys[0]) < 0) - { - keys[0] = tkey; - values[0] = itor.Current; - } - else if (tkey.CompareTo(keys[1]) > 0) - { - keys[1] = tkey; - values[1] = itor.Current; - } - } - return values; - } + /// + /// 按转换函数找出序列中最(小/大)键值的对应值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最(小/大)键值的对应值 + public static TValue[] FindByExt(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - /// - /// 按比较器找出序列中最(小/大)键值的对应值 - /// - /// - /// 序列 - /// 比较器 - /// 最(小/大)键值的对应值 - public static TValue[] FindByExt(this IEnumerable source, Comparison comparison) - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + TValue[] values = new TValue[2]; + values[0] = values[1] = itor.Current; - TValue[] values = new TValue[2]; - values[0] = values[1] = itor.Current; + TKey[] keys = new TKey[2]; + keys[0] = keys[1] = func(itor.Current); - while (itor.MoveNext()) + while (itor.MoveNext()) + { + TKey tkey = func(itor.Current); + if (tkey.CompareTo(keys[0]) < 0) { - if (comparison(itor.Current, values[0]) < 0) - values[0] = itor.Current; - else if (comparison(itor.Current, values[1]) > 0) - values[1] = itor.Current; + keys[0] = tkey; + values[0] = itor.Current; + } + else if (tkey.CompareTo(keys[1]) > 0) + { + keys[1] = tkey; + values[1] = itor.Current; } - return values; } + return values; + } - /// - /// 按转换函数找出序列中最(小/大)键值的对应键值 - /// - /// - /// - /// 序列 - /// 转换函数 - /// 最(小/大)键值 - public static TKey[] FindExt(this IEnumerable source, Func func) - where TKey : IComparable - { - var itor = source.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(); + /// + /// 按比较器找出序列中最(小/大)键值的对应值 + /// + /// + /// 序列 + /// 比较器 + /// 最(小/大)键值的对应值 + public static TValue[] FindByExt(this IEnumerable source, Comparison comparison) + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - TKey[] keys = new TKey[2]; - keys[0] = keys[1] = func(itor.Current); + TValue[] values = new TValue[2]; + values[0] = values[1] = itor.Current; - while (itor.MoveNext()) - { - TKey tkey = func(itor.Current); - if (tkey.CompareTo(keys[0]) < 0) - keys[0] = tkey; - else if (tkey.CompareTo(keys[1]) > 0) - keys[1] = tkey; - } - return keys; + while (itor.MoveNext()) + { + if (comparison(itor.Current, values[0]) < 0) + values[0] = itor.Current; + else if (comparison(itor.Current, values[1]) > 0) + values[1] = itor.Current; } + return values; + } - #endregion FindByExt + /// + /// 按转换函数找出序列中最(小/大)键值的对应键值 + /// + /// + /// + /// 序列 + /// 转换函数 + /// 最(小/大)键值 + public static TKey[] FindExt(this IEnumerable source, Func func) + where TKey : IComparable + { + var itor = source.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(source), "对象为 null"); - #region Order + TKey[] keys = new TKey[2]; + keys[0] = keys[1] = func(itor.Current); - /// - /// 自定义的比较泛型类 - /// - /// 泛型 - private class SpecComparer : IComparer + while (itor.MoveNext()) { - private Comparison _comp; - - internal SpecComparer(Comparison comp) - { - _comp = comp; - } + TKey tkey = func(itor.Current); + if (tkey.CompareTo(keys[0]) < 0) + keys[0] = tkey; + else if (tkey.CompareTo(keys[1]) > 0) + keys[1] = tkey; + } + return keys; + } - #region IComparer 成员 + #endregion FindByExt - public int Compare(T x, T y) - { - return _comp(x, y); - } + #region Order - #endregion IComparer 成员 - } + /// + /// 自定义的比较泛型类 + /// + /// 泛型 + private class SpecComparer : IComparer + { + private readonly Comparison _comp; - /// - /// 使用指定的比较器将序列按升序排序 - /// - /// 输入泛型 - /// 输出泛型 - /// 序列 - /// 用于从元素中提取键的函数 - /// 比较器 - /// 排序的序列 - public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Comparison comparison) + internal SpecComparer(Comparison comp) { - return source.OrderBy(keySelector, new SpecComparer(comparison)); + _comp = comp; } - /// - /// 使用指定的比较器将其后的序列按升序排序 - /// - /// 输入泛型 - /// 输出泛型 - /// 序列 - /// 用于从元素中提取键的函数 - /// 比较器 - /// 排序的序列 - public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, Comparison comparison) + #region IComparer 成员 + public int Compare(T x, T y) { - return source.ThenBy(keySelector, new SpecComparer(comparison)); + return _comp(x, y); } + #endregion IComparer 成员 + } + + /// + /// 使用指定的比较器将序列按升序排序 + /// + /// 输入泛型 + /// 输出泛型 + /// 序列 + /// 用于从元素中提取键的函数 + /// 比较器 + /// 排序的序列 + public static IOrderedEnumerable OrderBy(this IEnumerable source, Func keySelector, Comparison comparison) + { + return source.OrderBy(keySelector, new SpecComparer(comparison)); + } - #endregion Order + /// + /// 使用指定的比较器将其后的序列按升序排序 + /// + /// 输入泛型 + /// 输出泛型 + /// 序列 + /// 用于从元素中提取键的函数 + /// 比较器 + /// 排序的序列 + public static IOrderedEnumerable ThenBy(this IOrderedEnumerable source, Func keySelector, Comparison comparison) + { + return source.ThenBy(keySelector, new SpecComparer(comparison)); } + + #endregion Order } diff --git a/src/IFoxCAD.Basal/ListEx.cs b/src/IFoxCAD.Basal/ListEx.cs new file mode 100644 index 0000000..4ead97b --- /dev/null +++ b/src/IFoxCAD.Basal/ListEx.cs @@ -0,0 +1,30 @@ + +namespace IFoxCAD.Basal; + +public static class ListEx +{ + public static bool EqualsAll(this IList a, IList b) + { + return EqualsAll(a, b, null); + // there is a slight performance gain in passing null here. + // It is how it is done in other parts of the framework. + } + + public static bool EqualsAll(this IList a, IList b, IEqualityComparer? comparer) + { + if (a == null) + return b == null; + else if (b == null) + return false; + + if (a.Count != b.Count) + return false; + + comparer ??= EqualityComparer.Default; + + for (int i = 0; i < a.Count; i++) + if (!comparer.Equals(a[i], b[i])) + return false; + return true; + } +} diff --git a/src/IFoxCAD.Basal/LoopList.cs b/src/IFoxCAD.Basal/LoopList.cs index 2c0b088..c0e021c 100644 --- a/src/IFoxCAD.Basal/LoopList.cs +++ b/src/IFoxCAD.Basal/LoopList.cs @@ -1,281 +1,265 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Text; +namespace IFoxCAD.Basal; -namespace IFoxCAD.Collections +#line hidden //调试的时候跳过它 + +/// +/// 环链表节点 +/// +/// +public class LoopListNode { + #region 成员 /// - /// 环链表节点 - /// - /// - public class LoopListNode - { - #region 成员 - /// - /// 取值 - /// - public T Value; - - /// - /// 上一个节点 - /// - public LoopListNode? Previous { internal set; get; } - - /// - /// 下一个节点 - /// - public LoopListNode? Next { internal set; get; } - - /// - /// 环链表序列 - /// - public LoopList? List { internal set; get; } - #endregion - #region 构造 - /// - /// 环链表节点构造函数 - /// - /// 节点值 - public LoopListNode(T value, LoopList ts) - { - Value = value; - List = ts; - } + /// 取值 + ///
+ public T Value; - /// - /// 获取当前节点的临近节点 - /// - /// 搜索方向标志,为向前搜索,为向后搜索 - /// - public LoopListNode? GetNext(bool forward) - { - return forward ? Next : Previous; - } - #endregion - #region 方法 - /// - /// 无效化成员 - /// - internal void Invalidate() - { - List = null; - Next = null; - Previous = null; - } - #endregion + /// + /// 上一个节点 + /// + public LoopListNode? Previous { internal set; get; } + + /// + /// 下一个节点 + /// + public LoopListNode? Next { internal set; get; } + + /// + /// 环链表序列 + /// + public LoopList? List { internal set; get; } + #endregion + + #region 构造 + /// + /// 环链表节点构造函数 + /// + /// 节点值 + public LoopListNode(T value, LoopList ts) + { + Value = value; + List = ts; } /// - /// 环链表 + /// 获取当前节点的临近节点 /// - /// - public class LoopList : IEnumerable, IFormattable + /// 搜索方向标志,为向前搜索,为向后搜索 + /// + public LoopListNode? GetNext(bool forward) { - #region 成员 - /// - /// 节点数 - /// - public int Count { get; private set; } + return forward ? Next : Previous; + } + #endregion - /// - /// 首节点 - /// - public LoopListNode? First { get; private set; } + #region 方法 + /// + /// 无效化成员 + /// + internal void Invalidate() + { + List = null; + Next = null; + Previous = null; + } + #endregion +} + +/// +/// 环链表 +/// +/// +public class LoopList : IEnumerable, IFormattable +{ + #region 成员 - /// - /// 尾节点 - /// - public LoopListNode? Last => First?.Previous; + /// + /// 节点数 + /// + public int Count { get; private set; } - #endregion - #region 构造 - /// - /// 默认构造函数 - /// - public LoopList() { } + /// + /// 首节点 + /// + public LoopListNode? First { get; private set; } - /// - /// 环链表构造函数 - /// - /// 节点迭代器 - public LoopList(IEnumerable values) - { - var ge = values.GetEnumerator(); - while (ge.MoveNext()) - Add(ge.Current); - } + /// + /// 尾节点 + /// + public LoopListNode? Last => First?.Previous; - #endregion - #region 方法 - /// - /// 设置首节点 - /// - /// 节点 - /// - public bool SetFirst(LoopListNode node) - { - if (!Contains(node)) - return false; - First = node; - return true; - } - /// - /// 交换两个节点的值 - /// - /// 第一个节点 - /// 第二个节点 - public void Swap(LoopListNode node1, LoopListNode node2) - { -#if NET35 - var value = node1.Value; - node1.Value = node2.Value; - node2.Value = value; -#else - (node2.Value, node1.Value) = (node1.Value, node2.Value); -#endif - } - /// - /// 链内翻转 - /// - public void Reverse() - { - var first = First; - if (first is null) - return; - var last = Last; - for (int i = 0; i < Count / 2; i++) - { - Swap(first!, last!); - first = first!.Next; - last = last!.Previous; - } - } + #endregion - /// - /// 清理 - /// - public void Clear() - { - //移除头部,表示链表再也无法遍历得到 - First = null; - Count = 0; - } + #region 构造 + + /// + /// 默认构造函数 + /// + public LoopList() { } + + /// + /// 环链表构造函数 + /// + /// 节点迭代器 + public LoopList(IEnumerable values) + { + var ge = values.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } - /// - /// 从头遍历_非迭代器 - /// - /// - public void ForEach(Func, bool> action) + #endregion + + #region 方法 + + /// + /// 设置首节点 + /// + /// 节点 + /// + public bool SetFirst(LoopListNode node) + { + if (!Contains(node)) + return false; + + First = node; + return true; + } + + /// + /// 交换两个节点的值 + /// + /// 第一个节点 + /// 第二个节点 + public void Swap(LoopListNode node1, LoopListNode node2) + { + (node2.Value, node1.Value) = (node1.Value, node2.Value); + } + + /// + /// 链内翻转 + /// + public void Reverse() + { + var first = First; + if (first is null) + return; + var last = Last; + for (int i = 0; i < Count / 2; i++) { - var node = First; - if (node is null) - return; - for (int i = 0; i < Count; i++) - { - if (action(node!)) - break; - node = node!.Next; - } + Swap(first!, last!); + first = first!.Next; + last = last!.Previous; } + } - #region Contains - /// - /// 是否包含节点 - /// - /// - /// - public bool Contains(LoopListNode node) + /// + /// 清理 + /// + public void Clear() + { + //移除头部,表示链表再也无法遍历得到 + First = null; + Count = 0; + } + + /// + /// 从头遍历_非迭代器(此处和通用ForEach冲突,所以内部用) + /// + /// + void ForEach(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) { - return node is not null && node.List == this; + if (action(node!)) + break; + node = node!.Next; } + } - /// - /// 是否包含值 - /// - /// - /// - public bool Contains(T value) + /// + /// 从头遍历_非迭代器(扔出计数) + /// + /// + public void For(Func, bool> action) + { + var node = First; + if (node is null) + return; + for (int i = 0; i < Count; i++) { - bool result = false; - ForEach(node => { - if (node.Value!.Equals(value)) - { - result = true; - return true; - } - return false; - }); - return result; + if (action(i, node!)) + break; + node = node!.Next; } + } - /// - /// 查找第一个出现的节点 - /// - /// - /// - public LoopListNode? Find(T value) - { - //LoopListNode result = null; - //ForEach(node => - //{ - // if (node.Value.Equals(t2)) - // { - // result = node; - // return true; - // } - // return false; - //}); - //return result; - - LoopListNode? node = First; - var c = EqualityComparer.Default; - if (node is not null) + #region Contains + + /// + /// 是否包含节点 + /// + /// + /// + public bool Contains(LoopListNode node) + { + return node is not null && node.List == this; + } + + /// + /// 是否包含值 + /// + /// + /// + public bool Contains(T value) + { + bool result = false; + ForEach(node => { + if (node.Value!.Equals(value)) { - if (value is not null) - { - do - { - if (c.Equals(node!.Value, value)) - return node; - node = node.Next; - } while (node != First); - } - else - { - do - { - if (node!.Value is null) - return node; - node = node.Next; - } while (node != First); - } + result = true; + return true; } - return null; - } + return false; + }); + return result; + } - /// - /// 查找所有出现的节点 - /// - /// - /// - public IEnumerable>? Finds(T value) + /// + /// 查找第一个出现的节点 + /// + /// + /// + public LoopListNode? Find(T value) + { + //LoopListNode result = null; + //ForEach(node => + //{ + // if (node.Value.Equals(t2)) + // { + // result = node; + // return true; + // } + // return false; + //}); + //return result; + + LoopListNode? node = First; + var c = EqualityComparer.Default; + if (node is not null) { - LoopListNode? node = First; - if (node is null) - return null; - - List> result = new(); - var c = EqualityComparer.Default; if (value is not null) { do { if (c.Equals(node!.Value, value)) - result.Add(node); + return node; node = node.Next; } while (node != First); } @@ -284,422 +268,475 @@ public bool Contains(T value) do { if (node!.Value is null) - result.Add(node); + return node; node = node.Next; } while (node != First); } - return result; } + return null; + } - /// - /// 获取节点 - /// - /// - /// - public LoopListNode? GetNode(Func func) - { - LoopListNode? result = null; - ForEach(node => { - if (func(node.Value)) - { - result = node; - return true; - } - return false; - }); - return result; - } + /// + /// 查找所有出现的节点 + /// + /// + /// + public IEnumerable>? Finds(T value) + { + LoopListNode? node = First; + if (node is null) + return null; - #endregion - #region Add - /// - /// 在首节点之前插入节点,并设置新节点为首节点 - /// - /// - /// - public LoopListNode AddFirst(T value) + List> result = new(); + var c = EqualityComparer.Default; + if (value is not null) { - var node = new LoopListNode(value, this); - - if (Count == 0) + do { - First = node; - First.Previous = First.Next = node; - } - else - { - LoopListNode last = Last!; - First!.Previous = last.Next = node; - node.Next = First; - node.Previous = last; - First = node; - } - Count++; - return First; + if (c.Equals(node!.Value, value)) + result.Add(node); + node = node.Next; + } while (node != First); } - - /// - /// 在尾节点之后插入节点,并设置新节点为尾节点 - /// - /// - /// - public LoopListNode Add(T value) + else { - var node = new LoopListNode(value, this); - - if (Count == 0) + do { - First = node; - First.Previous = First.Next = node; - } - else + if (node!.Value is null) + result.Add(node); + node = node.Next; + } while (node != First); + } + return result; + } + + /// + /// 获取节点 + /// + /// + /// + public LoopListNode? GetNode(Func func) + { + LoopListNode? result = null; + ForEach(node => { + if (func(node.Value)) { - var last = First!.Previous!; - First.Previous = last.Next = node; - node.Next = First; - node.Previous = last; + result = node; + return true; } - Count++; - return Last!; - } + return false; + }); + return result; + } - /// - /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 - /// - /// - /// - public LoopListNode AddLast(T value) - { - return Add(value); - } + #endregion + #region Add - /// - /// 容器内容全部加入到末尾 - /// - /// - public void AddRange(IEnumerable list) - { - var ge = list.GetEnumerator(); - while (ge.MoveNext()) - Add(ge.Current); - } + /// + /// 在首节点之前插入节点,并设置新节点为首节点 + /// + /// + /// + public LoopListNode AddFirst(T value) + { + var node = new LoopListNode(value, this); - /// - /// 前面增加节点 - /// - /// - /// - /// - public LoopListNode AddBefore(LoopListNode node, T value) + if (Count == 0) { - if (node == First) - return AddFirst(value); - - var tnode = new LoopListNode(value, this); - node.Previous!.Next = tnode; - tnode.Previous = node.Previous; - node.Previous = tnode; - tnode.Next = node; - Count++; - return tnode; + First = node; + First.Previous = First.Next = node; } - - /// - /// 后面增加节点 - /// - /// - /// - /// - public LoopListNode AddAfter(LoopListNode node, T value) + else { - var tnode = new LoopListNode(value, this); - node.Next!.Previous = tnode; - tnode.Next = node.Next; - node.Next = tnode; - tnode.Previous = node; - Count++; - return tnode; + LoopListNode last = Last!; + First!.Previous = last.Next = node; + node.Next = First; + node.Previous = last; + First = node; } + Count++; + return First; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点 + /// + /// + /// + public LoopListNode Add(T value) + { + var node = new LoopListNode(value, this); - #endregion - #region Remove - /// - /// 删除首节点 - /// - /// - public bool RemoveFirst() + if (Count == 0) { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last!; - First = First!.Next; - First!.Previous = last; - last.Next = First; - break; - } - return true; + First = node; + First.Previous = First.Next = node; } - - /// - /// 删除尾节点 - /// - /// - public bool RemoveLast() + else { - switch (Count) - { - case 0: - return false; - - case 1: - First = null; - break; - - default: - LoopListNode last = Last!.Previous!; - last.Next = First; - First!.Previous = last; - break; - } - Count--; - return true; + var last = First!.Previous!; + First.Previous = last.Next = node; + node.Next = First; + node.Previous = last; } + Count++; + return Last!; + } + + /// + /// 在尾节点之后插入节点,并设置新节点为尾节点_此函数仅为与LinkedList同名方法 + /// + /// + /// + public LoopListNode AddLast(T value) + { + return Add(value); + } - /// - /// 删除节点 - /// - /// 指定节点 - /// - public bool Remove(LoopListNode node) + /// + /// 容器内容全部加入到末尾 + /// + /// + public void AddRange(IEnumerable list) + { + var ge = list.GetEnumerator(); + while (ge.MoveNext()) + Add(ge.Current); + } + + /// + /// 前面增加节点 + /// + /// + /// + /// + public LoopListNode AddBefore(LoopListNode node, T value) + { + if (node == First) + return AddFirst(value); + + var tnode = new LoopListNode(value, this); + node.Previous!.Next = tnode; + tnode.Previous = node.Previous; + node.Previous = tnode; + tnode.Next = node; + Count++; + return tnode; + } + + /// + /// 后面增加节点 + /// + /// + /// + /// + public LoopListNode AddAfter(LoopListNode node, T value) + { + var tnode = new LoopListNode(value, this); + node.Next!.Previous = tnode; + tnode.Next = node.Next; + node.Next = tnode; + tnode.Previous = node; + Count++; + return tnode; + } + + #endregion + + #region Remove + + /// + /// 删除首节点 + /// + /// + public bool RemoveFirst() + { + switch (Count) { - if (!Contains(node)) + case 0: return false; - InternalRemove(node); - return true; + + case 1: + First = null; + break; + + default: + LoopListNode last = Last!; + First = First!.Next; + First!.Previous = last; + last.Next = First; + break; } + Count--; + return true; + } - /// - /// 删除节点 - /// - /// 将移除所有含有此值 - /// - public bool Remove(T value) + /// + /// 删除尾节点 + /// + /// + public bool RemoveLast() + { + switch (Count) { - var lst = Finds(value); - if (lst is null) + case 0: return false; - var ge = lst!.GetEnumerator(); - while (ge.MoveNext()) - InternalRemove(ge.Current); - return true; + case 1: + First = null; + break; + + default: + LoopListNode last = Last!.Previous!; + last.Next = First; + First!.Previous = last; + break; } + Count--; + return true; + } - /// - /// 删除节点_内部调用 - /// - /// 此值肯定存在当前链表 - /// - void InternalRemove(LoopListNode node) - { - if (Count == 1 || node == First) - { - RemoveFirst(); - } - else - { - node.Next!.Previous = node.Previous; - node.Previous!.Next = node.Next; - } + /// + /// 删除此参数节点(唯一) + /// + /// 指定节点 + /// + public bool Remove(LoopListNode node) + { + if (!Contains(node)) + return false; + InternalRemove(node); + return true; + } - node.Invalidate(); - Count--; - } + /// + /// 删除含有此参数节点(所有) + /// + /// 将移除所有含有此值 + /// + public bool Remove(T value) + { + var lst = Finds(value); + if (lst is null) + return false; + + var ge = lst!.GetEnumerator(); + while (ge.MoveNext()) + InternalRemove(ge.Current); + return true; + } - #endregion - #region LinkTo - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to) + /// + /// 删除节点_内部调用 + /// + /// 此值肯定存在当前链表 + /// + void InternalRemove(LoopListNode node) + { + if (Count == 1 || node == First) { - if (from != to && Contains(from) && Contains(to)) - { - LoopListNode node = from.Next!; - bool isFirstChanged = false; - int number = 0; + RemoveFirst();//此处会减数字 + } + else + { + node.Next!.Previous = node.Previous; + node.Previous!.Next = node.Next; + Count--; + } + node.Invalidate(); + } - while (node != to) - { - if (node == First) - isFirstChanged = true; + #endregion - node = node.Next!; - number++; - } + #region LinkTo - from.Next = to; - to.Previous = from; + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to) + { + if (from != to && Contains(from) && Contains(to)) + { + LoopListNode node = from.Next!; + bool isFirstChanged = false; + int number = 0; - if (number > 0 && isFirstChanged) - First = to; + while (node != to) + { + if (node == First) + isFirstChanged = true; - Count -= number; + node = node.Next!; + number++; } - } - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; + from.Next = to; + to.Previous = from; + + if (number > 0 && isFirstChanged) First = to; - Count -= number; - } - } - /// - /// 链接两节点,并去除这两个节点间的所有节点 - /// - /// - /// - /// - /// - public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) - { - if (from != to && Contains(from) && Contains(to)) - { - from.Next = to; - to.Previous = from; - if (isFirstChanged) - First = to; - Count -= number; - } + Count -= number; } + } - #endregion - #endregion - #region IEnumerable - /// - /// 获取节点的查询器 - /// - /// - /// - public IEnumerable> GetNodes(LoopListNode from) + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number) + { + if (from != to && Contains(from) && Contains(to)) { - var node = from; - for (int i = 0; i < Count; i++) - { - yield return node!; - node = node!.Next; - } + from.Next = to; + to.Previous = from; + First = to; + Count -= number; } + } - /// - /// 获取节点的查询器 - /// - /// - public IEnumerable> GetNodes() + /// + /// 链接两节点,并去除这两个节点间的所有节点 + /// + /// + /// + /// + /// + public void LinkTo(LoopListNode from, LoopListNode to, int number, bool isFirstChanged) + { + if (from != to && Contains(from) && Contains(to)) { - LoopListNode node = First!; - for (int i = 0; i < Count; i++) - { - yield return node!; - node = node.Next!; - } + from.Next = to; + to.Previous = from; + if (isFirstChanged) + First = to; + Count -= number; } + } + + #endregion - /// - /// 获取节点值的查询器 - /// - /// - public IEnumerator GetEnumerator() + #endregion + + #region IEnumerable + + /// + /// 获取节点的查询器 + /// + /// + /// + public IEnumerable> GetNodes(LoopListNode from) + { + var node = from; + for (int i = 0; i < Count; i++) { - LoopListNode node = First!; - for (int i = 0; i < Count; i++) - { - yield return node!.Value; - node = node.Next!; - } + yield return node!; + node = node!.Next; } + } - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #region IEnumerable 成员 - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - - #endregion IEnumerable 成员 - #endregion - #region IFormattable - /// - /// 转换为字符串_格式化实现 - /// - /// - /// - /// - string IFormattable.ToString(string? format, IFormatProvider? formatProvider) + + + /// + /// 获取节点的查询器 + /// + /// + public IEnumerable> GetNodes() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) { - return ToString(format, formatProvider); + yield return node!; + node = node.Next!; } + } - /// - /// 转换为字符串_无参调用 - /// - /// - public override string ToString() + /// + /// 获取节点值的查询器 + /// + /// + public IEnumerator GetEnumerator() + { + LoopListNode node = First!; + for (int i = 0; i < Count; i++) { - return ToString(null, null); + yield return node!.Value; + node = node.Next!; } + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #region IEnumerable 成员 + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); - /// - /// 转换为字符串_有参调用 - /// - /// - public string ToString(string? format, IFormatProvider? formatProvider = null) + #endregion IEnumerable 成员 + + #endregion + + #region IFormattable + /// + /// 转换为字符串_格式化实现 + /// + /// + /// + /// + string IFormattable.ToString(string? format, IFormatProvider? formatProvider) + { + return ToString(format, formatProvider); + } + + /// + /// 转换为字符串_无参调用 + /// + /// + public override string ToString() + { + return ToString(null, null); + } + + /// + /// 转换为字符串_有参调用 + /// + /// + public string ToString(string? format, IFormatProvider? formatProvider = null) + { + var s = new StringBuilder(); + s.Append($"Count = {Count};"); + if (format is null) { - var s = new StringBuilder(); - s.Append($"Count = {Count};"); - if (format is null) - { - s.Append("{ "); - foreach (T value in this) - s.Append($"{value} "); - s.Append(" }"); - } - return s.ToString(); + s.Append("{ "); + foreach (T value in this) + s.Append($"{value} "); + s.Append(" }"); } - #endregion - #region ICloneable - /* 山人说无法分辨ICloneable接口是深浅克隆,因此不要在泛型模板实现克隆函数,让用户自己来 - * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; - * public object Clone() - * { - * var lst = new LoopList>(); - * ForEach(node => { - * lst.Add(node); - * return false; - * }); - * return lst; - * } - */ - #endregion - } -} \ No newline at end of file + return s.ToString(); + } + #endregion + + #region ICloneable + /* 山人说无法分辨ICloneable接口是深浅克隆, + * 因此不要在泛型模板实现克隆函数, + * 让用户自己来 new(xx)实现浅克隆,所以也不提供Clone()了 + * + * 因此约定了:CopyTo(T,index)是深克隆;MemberwiseClone()是浅克隆; + * public object Clone() + * { + * var lst = new LoopList>(); + * ForEach(node => { + * lst.Add(node); + * return false; + * }); + * return lst; + * } + */ + #endregion +} + +#line default \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/ISet.cs b/src/IFoxCAD.Basal/Sortedset/ISet.cs new file mode 100644 index 0000000..549e9f0 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/ISet.cs @@ -0,0 +1,71 @@ +#if NET35 +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Interface: ISet +** +** kimhamil +** +** +** Purpose: Base interface for all generic sets. +** +** +===========================================================*/ +namespace System.Collections.Generic +{ + + using System; + using System.Runtime.CompilerServices; + + + /// + /// Generic collection that guarantees the uniqueness of its elements, as defined + /// by some comparer. It also supports basic set operations such as Union, Intersection, + /// Complement and Exclusive Complement. + /// + public interface ISet : ICollection + { + + //Add ITEM to the set, return true if added, false if duplicate + new bool Add(T item); + + //Transform this set into its union with the IEnumerable other + void UnionWith(IEnumerable other); + + //Transform this set into its intersection with the IEnumberable other + void IntersectWith(IEnumerable other); + + //Transform this set so it contains no elements that are also in other + void ExceptWith(IEnumerable other); + + //Transform this set so it contains elements initially in this or in other, but not both + void SymmetricExceptWith(IEnumerable other); + + //Check if this set is a subset of other + bool IsSubsetOf(IEnumerable other); + + //Check if this set is a superset of other + bool IsSupersetOf(IEnumerable other); + + //Check if this set is a subset of other, but not the same as it + bool IsProperSupersetOf(IEnumerable other); + + //Check if this set is a superset of other, but not the same as it + bool IsProperSubsetOf(IEnumerable other); + + //Check if this set has any elements in common with other + bool Overlaps(IEnumerable other); + + //Check if this set contains the same and only the same elements as other + bool SetEquals(IEnumerable other); + + + + } + +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/SR.cs b/src/IFoxCAD.Basal/Sortedset/SR.cs new file mode 100644 index 0000000..093de14 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/SR.cs @@ -0,0 +1,907 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +namespace System; + + +using System; +using System.Reflection; +using System.Globalization; +using System.Resources; +using System.Text; +using System.ComponentModel; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDescriptionAttribute : DescriptionAttribute +{ + public SRDescriptionAttribute(string description) + { + DescriptionValue = SR.GetString(description); + } + + public SRDescriptionAttribute(string description, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DescriptionValue = rm.GetString(description); + System.Diagnostics.Debug.Assert(DescriptionValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { description })); + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRCategoryAttribute : CategoryAttribute +{ + private string resourceSet = String.Empty; + + public SRCategoryAttribute(string category) + : base(category) + { + } + + public SRCategoryAttribute(string category, string resourceSet) + : base(category) + { + this.resourceSet = resourceSet; + } + + protected override string GetLocalizedString(string value) + { + if (this.resourceSet.Length > 0) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + String localizedString = rm.GetString(value); + System.Diagnostics.Debug.Assert(localizedString != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { value })); + return localizedString; + } + else + { + return SR.GetString(value); + } + } +} + +[AttributeUsage(AttributeTargets.All)] +internal sealed class SRDisplayNameAttribute : DisplayNameAttribute +{ + public SRDisplayNameAttribute(string name) + { + DisplayNameValue = SR.GetString(name); + } + + public SRDisplayNameAttribute(string name, string resourceSet) + { + ResourceManager rm = new ResourceManager(resourceSet, Assembly.GetExecutingAssembly()); + DisplayNameValue = rm.GetString(name); + System.Diagnostics.Debug.Assert(DisplayNameValue != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + } +} + +/// +/// AutoGenerated resource class. Usage: +/// +/// string s = SR.GetString(SR.MyIdenfitier); +/// +internal sealed partial class SR +{ +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + static SR loader = null; +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 + ResourceManager resources; + + internal SR() + { + resources = new System.Resources.ResourceManager("System.Workflow.ComponentModel.StringResources", Assembly.GetExecutingAssembly()); + } + + private static SR GetLoader() + { + if (loader == null) + loader = new SR(); + return loader; + } + + private static CultureInfo Culture + { + get { return null/*use ResourceManager default, CultureInfo.CurrentUICulture*/; } + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(string name, params object[] args) + { + return GetString(SR.Culture, name, args); + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal static string GetString(CultureInfo culture, string name, params object[] args) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + if (args != null && args.Length > 0) + { + return string.Format(CultureInfo.CurrentCulture, res, args); + } + else + { + return res; + } + } + + internal static string GetString(string name) + { + return GetString(SR.Culture, name); + } + + internal static string GetString(CultureInfo culture, string name) + { + SR sys = GetLoader(); + if (sys == null) + return null; + string res = sys.resources.GetString(name, culture); + System.Diagnostics.Debug.Assert(res != null, string.Format(CultureInfo.CurrentCulture, "String resource {0} not found.", new object[] { name })); + return res; + } + + // All these strings should be present in StringResources.resx + internal const string Activity = "Activity"; + internal const string Handlers = "Handlers"; + internal const string Conditions = "Conditions"; + internal const string ConditionedActivityConditions = "ConditionedActivityConditions"; + internal const string Correlations = "Correlations"; + internal const string CorrelationSet = "CorrelationSet"; + internal const string NameDescr = "NameDescr"; + internal const string EnabledDescr = "EnabledDescr"; + internal const string DescriptionDescr = "DescriptionDescr"; + internal const string UnlessConditionDescr = "UnlessConditionDescr"; + internal const string InitializeDescr = "InitializeDescr"; + internal const string CatchTypeDescr = "CatchTypeDescr"; + internal const string ExceptionTypeDescr = "ExceptionTypeDescr"; + internal const string FaultDescription = "FaultDescription"; + internal const string FaultTypeDescription = "FaultTypeDescription"; + internal const string ContainingAssemblyDescr = "ContainingAssemblyDescr"; + internal const string ExecutionModeDescr = "ExecutionModeDescr"; + internal const string Error_ReadOnlyTemplateActivity = "Error_ReadOnlyTemplateActivity"; + internal const string Error_TypeNotString = "Error_TypeNotString"; + internal const string Error_InvalidErrorType = "Error_InvalidErrorType"; + internal const string Error_LiteralConversionFailed = "Error_LiteralConversionFailed"; + internal const string Error_TypeNotPrimitive = "Error_TypeNotPrimitive"; + internal const string CompletedCaleeDescr = "CompletedCaleeDescr"; + internal const string ProxyClassDescr = "ProxyClassDescr"; + internal const string ActivitySetDescr = "ActivitySetDescr"; + internal const string VersionDescr = "VersionDescr"; + internal const string ActivationDescr = "ActivationDescr"; + internal const string CorrelationSetsDescr = "CorrelationSetsDescr"; + internal const string CompanionClassDescr = "CompanionClassDescr"; + internal const string TransactionTypeDescr = "TransactionTypeDescr"; + internal const string SynchronizedDescr = "SynchronizedDescr"; + internal const string IsolationLevelDescr = "IsolationLevelDescr"; + internal const string TimeoutDescr = "TimeoutDescr"; + internal const string BatchableDescr = "BatchableDescr"; + internal const string LRTTimeoutDescr = "LRTTimeoutDescr"; + internal const string OnGetCalleeCountDescr = "OnGetCalleeCountDescr"; + internal const string CompensatableActivityDescr = "CompensatableActivityDescr"; + internal const string OnAfterEventDescr = "OnAfterEventDescr"; + internal const string OnBeforeMethodInvokeDescr = "OnBeforeMethodInvokeDescr"; + internal const string AssignedToDescr = "AssignedToDescr"; + internal const string TypeDescr = "TypeDescr"; + internal const string TemplateActivityDescr = "TemplateActivityDescr"; + internal const string ErrorMessageDescr = "ErrorMessageDescr"; + internal const string WebServiceSynchronizedDescr = "WebServiceSynchronizedDescr"; + internal const string CorrelationSetDescr = "CorrelationSetDescr"; + internal const string ExecutionTypeDescr = "ExecutionTypeDescr"; + internal const string RoleDescr = "RoleDescr"; + internal const string OnInitializeClonesDescr = "OnInitializeClonesDescr"; + internal const string CorrelationSetDisplayName = "CorrelationSetDisplayName"; + internal const string PastingActivities = "PastingActivities"; + internal const string DeletingActivities = "DeletingActivities"; + internal const string DragDropActivities = "DragDropActivities"; + internal const string ChangingEnabled = "ChangingEnabled"; + internal const string ChangingHandler = "ChangingHandler"; + internal const string ChangingParameter = "ChangingParameter"; + internal const string CollectionItem = "CollectionItem"; + internal const string AddingConditionalBranch = "AddingConditionalBranch"; + internal const string AddingEventActivity = "AddingEventActivity"; + internal const string AddingListenBranch = "AddingListenBranch"; + internal const string AddingParallelBranch = "AddingParallelBranch"; + internal const string CurrentProject = "CurrentProject"; + internal const string ReferencedAssemblies = "ReferencedAssemblies"; + internal const string CollectionText = "CollectionText"; + internal const string ParameterDescription = "ParameterDescription"; + internal const string InvokeParameterDescription = "InvokeParameterDescription"; + internal const string ParametersDescription = "ParametersDescription"; + internal const string ChangingParameters = "ChangingParameters"; + internal const string Condition = "ConditionRule"; + internal const string MovingActivities = "MovingActivities"; + internal const string MemberNameDescr = "MemberNameDescr"; + internal const string OnScopeInitializedDescr = "OnScopeInitializedDescr"; + internal const string OnGeneratorInitializedDescr = "OnGeneratorInitializedDescr"; + internal const string OnScopeCompletedDescr = "OnScopeCompletedDescr"; + internal const string OnGeneratorCompletedDescr = "OnGeneratorCompletedDescr"; + internal const string DataElementRuntimeTypeDescr = "DataElementRuntimeTypeDescr"; + internal const string RuleConditionReferencesDescr = "RuleConditionReferencesDescr"; + internal const string CreateActivityFromToolbox = "CreateActivityFromToolbox"; + internal const string MoveMultipleActivities = "MoveMultipleActivities"; + internal const string MoveSingleActivity = "MoveSingleActivity"; + internal const string CutMultipleActivities = "CutMultipleActivities"; + internal const string CutSingleActivity = "CutSingleActivity"; + internal const string CutActivity = "CutActivity"; + internal const string FaultActivityDescription = "FaultActivityDescription"; + internal const string NullConditionExpression = "NullConditionExpression"; + internal const string ParameterTypeDescription = "ParameterTypeDescription"; + internal const string ParameterCategory = "ParameterCategory"; + internal const string ParameterDirectionDescription = "ParameterDirectionDescription"; + internal const string ParameterElementDescription = "ParameterElementDescription"; + internal const string ParameterDlgDescription = "ParameterDlgDescription"; + internal const string ParameterDlgHeader = "ParameterDlgHeader"; + internal const string SuspendActivityDescription = "SuspendActivityDescription"; + internal const string SuspendErrorMessageDescr = "SuspendErrorMessageDescr"; + internal const string TerminateActivityDescription = "TerminateActivityDescription"; + internal const string TerminateErrorMessageDescr = "TerminateErrorMessageDescr"; + internal const string DeclarationCategory = "DeclarationCategory"; + internal const string NoValidActivityPropertiesAvailable = "NoValidActivityPropertiesAvailable"; + internal const string ChooseActivityDatasource = "ChooseActivityDatasource"; + internal const string Promote = "Promote"; + internal const string Type = "Type"; + internal const string NoMatchingActivityProperties = "NoMatchingActivityProperties"; + internal const string ActivityBindIDDescription = "ActivityBindIDDescription"; + internal const string ActivityBindPathDescription = "ActivityBindPathDescription"; + internal const string XPathDescription = "XPathDescription"; + internal const string TransformerDescription = "TransformerDescription"; + internal const string CustomPropertiesCollectionFormHeader = "CustomPropertiesCollectionFormHeader"; + internal const string CustomPropertiesCollectionFormDescription = "CustomPropertiesCollectionFormDescription"; + internal const string BaseTypePropertyName = "BaseTypePropertyName"; + internal const string CustomActivityBaseClassTypeFilterProviderDesc = "CustomActivityBaseClassTypeFilterProviderDesc"; + internal const string CustomActivityDesignerTypeFilterProviderDesc = "CustomActivityDesignerTypeFilterProviderDesc"; + internal const string CustomActivityValidatorTypeFilterProviderDesc = "CustomActivityValidatorTypeFilterProviderDesc"; + internal const string CustomActivityExecutorTypeFilterProviderDesc = "CustomActivityExecutorTypeFilterProviderDesc"; + internal const string GenericParameters = "GenericParameters"; + internal const string ToolboxItem = "ToolboxItem"; + internal const string ToolboxItemCompanionClassDesc = "ToolboxItemCompanionClassDesc"; + internal const string Error_SerializationInsufficientState = "Error_SerializationInsufficientState"; + internal const string Error_ActivityHasParent = "Error_ActivityHasParent"; + internal const string Error_CompensantionParentNotScope = "Error_CompensantionParentNotScope"; + internal const string Error_ConditionedActivityParentNotCAG = "Error_ConditionedActivityParentNotCAG"; + internal const string Error_CorrelationTypeNotComparable = "Error_CorrelationTypeNotComparable"; + internal const string Error_ArgumentTypeNotMatchParameter = "Error_ArgumentTypeNotMatchParameter"; + internal const string Error_TypeTypeMismatch = "Error_TypeTypeMismatch"; + internal const string Error_ParameterTypeMismatch = "Error_ParameterTypeMismatch"; + internal const string Error_InvokeParameterTypeMismatch = "Error_InvokeParameterTypeMismatch"; + internal const string Error_ParameterPropertyNotSet = "Error_ParameterPropertyNotSet"; + internal const string Error_DataSourceNameNotSet = "Error_DataSourceNameNotSet"; + internal const string Error_DataSourceInvalidIdentifier = "Error_DataSourceInvalidIdentifier"; + internal const string Error_ParameterTypeNotExist = "Error_ParameterTypeNotExist"; + internal const string Error_InvalidParameterName = "Error_InvalidParameterName"; + internal const string Error_InvalidParameterType = "Error_InvalidParameterType"; + internal const string Error_InvalidParameterElement = "Error_InvalidParameterElement"; + internal const string Error_InvalidPropertyType = "Error_InvalidPropertyType"; + internal const string Error_TypeNotResolvedInMethodName = "Error_TypeNotResolvedInMethodName"; + internal const string Error_DelegateNoInvoke = "Error_DelegateNoInvoke"; + internal const string Error_TypeNotDelegate = "Error_TypeNotDelegate"; + internal const string Error_MethodSignatureMismatch = "Error_MethodSignatureMismatch"; + internal const string Error_MethodReturnTypeMismatch = "Error_MethodReturnTypeMismatch"; + internal const string Error_PropertyNotSet = "Error_PropertyNotSet"; + internal const string Error_ScopeCouldNotBeResolved = "Error_ScopeCouldNotBeResolved"; + internal const string Error_IfElseNotAllIfElseBranchDecl = "Error_ConditionalNotAllConditionalBranchDecl"; + internal const string Error_TypeTypeMismatchAmbiguity = "Error_TypeTypeMismatchAmbiguity"; + internal const string Error_InvalidCorrelationSetDatasource = "Error_InvalidCorrelationSetDatasource"; + internal const string Error_InvalidCorrelationSetType = "Error_InvalidCorrelationSetType"; + internal const string Error_MissingCorrelationParameterAttribute = "Error_MissingCorrelationParameterAttribute"; + internal const string Error_CorrelationTypeNotConsistent = "Error_CorrelationTypeNotConsistent"; + internal const string Error_CorrelationInvalid = "Error_CorrelationInvalid"; + internal const string Error_MissingDelegateMethod = "Error_MissingDelegateMethod"; + internal const string Error_MissingHostInterface = "Error_MissingHostInterface"; + internal const string Error_MissingMethodName = "Error_MissingMethodName"; + internal const string Error_NoBoundType = "Error_NoBoundType"; + internal const string Error_PortTypeNotAnInterface = "Error_PortTypeNotAnInterface"; + internal const string Error_MethodNotExists = "Error_MethodNotExists"; + internal const string Error_InvalidRequestResponseMethod = "Error_InvalidRequestResponseMethod"; + internal const string General_MissingService = "General_MissingService"; + internal const string Error_ScopeDuplicatedNameActivity = "Error_ScopeDuplicatedNameActivity"; + internal const string Error_DuplicatedActivityID = "Error_DuplicatedActivityID"; + internal const string Error_DuplicatedParameterName = "Error_DuplicatedParameterName"; + internal const string Error_ScopeMissingSerializableAttribute = "Error_ScopeMissingSerializableAttribute"; + internal const string Error_FieldNotExists = "Error_FieldNotExists"; + internal const string Error_PropertyNotExists = "Error_PropertyNotExists"; + internal const string Error_FieldTypeMismatch = "Error_FieldTypeMismatch"; + internal const string Error_PropertyTypeMismatch = "Error_PropertyTypeMismatch"; + internal const string Error_TypeNotResolvedInFieldName = "Error_TypeNotResolvedInFieldName"; + internal const string Error_TypeNotResolvedInPropertyName = "Error_TypeNotResolvedInPropertyName"; + internal const string Error_FieldGenericParamTypeMismatch = "Error_FieldGenericParamTypeMismatch"; + internal const string Error_TypeNotResolved = "Error_TypeNotResolved"; + internal const string Error_TypeIsUnboundedGeneric = "Error_TypeIsUnboundedGeneric"; + internal const string Error_MissingRootActivity = "Error_MissingRootActivity"; + internal const string Error_PropertyNotReadable = "Error_PropertyNotReadable"; + internal const string Error_PropertyNotWritable = "Error_PropertyNotWritable"; + internal const string Error_NotCompositeActivity = "Error_NotCompositeActivity"; + internal const string Error_TypeNotExist = "Error_TypeNotExist"; + internal const string Error_ActivityRefNotResolved = "Error_ActivityRefNotResolved"; + internal const string Error_ActivityRefNotMatchType = "Error_ActivityRefNotMatchType"; + internal const string Error_ActivityValidation = "Error_ActivityValidation"; + internal const string Error_ActiveChildExist = "Error_ActiveChildExist"; + internal const string Error_ActiveChildContextExist = "Error_ActiveChildContextExist"; + internal const string Error_CannotCompleteContext = "Error_CannotCompleteContext"; + internal const string Error_NoPasteSupport = "Error_NoPasteSupport"; + internal const string Error_UnknownSerializationStore = "Error_UnknownSerializationStore"; + internal const string Error_MissingCorrelationSet = "Error_MissingCorrelationSet"; + internal const string Error_CreateVariable = "Error_CreateVariable"; + internal const string Error_DuplicateCorrelationSetName = "Error_DuplicateCorrelationSetName"; + internal const string Error_DragDropInvalid = "Error_DragDropInvalid"; + internal const string AddingImplicitActivity = "AddingImplicitActivity"; + internal const string Failure_DoDefaultAction = "Failure_DoDefaultAction"; + internal const string Failure_DoDefaultActionCaption = "Failure_DoDefaultActionCaption"; + internal const string Error_FaultInsideAtomicScope = "Error_FaultInsideAtomicScope"; + internal const string Error_ListenNotMoreThanOneDelay = "Error_ListenNotMoreThanOneDelay"; + internal const string Error_AtomicScopeWithFaultHandlersActivityDecl = "Error_AtomicScopeWithFaultHandlersActivityDecl"; + internal const string Error_AtomicScopeWithCancellationHandlerActivity = "Error_AtomicScopeWithCancellationHandlerActivity"; + internal const string Error_ScopeDuplicateFaultHandlerActivityForAll = "Error_ScopeDuplicateFaultHandlerActivityForAll"; + internal const string Error_ScopeDuplicateFaultHandlerActivityFor = "Error_ScopeDuplicateFaultHandlerActivityFor"; + internal const string Error_AtomicScopeNestedInNonLRT = "Error_AtomicScopeNestedInNonLRT"; + internal const string Error_LRTScopeNestedInNonLRT = "Error_LRTScopeNestedInNonLRT"; + internal const string Error_CAGNotAllChildrenConditioned = "Error_CAGNotAllChildrenConditioned"; + internal const string Error_ConditionedActivityChildCount = "Error_ConditionedActivityChildCount"; + internal const string Error_NegativeValue = "Error_NegativeValue"; + internal const string Error_MethodWithReturnType = "Error_MethodWithReturnType"; + internal const string Error_SendReceiveOrderIncorrect = "Error_SendReceiveOrderIncorrect"; + internal const string Error_ReceiveSendOrderIncorrect = "Error_ReceiveSendOrderIncorrect"; + internal const string Error_CompensateBadNesting = "Error_CompensateBadNesting"; + internal const string Error_ReferencedAssemblyIsInvalid = "Error_ReferencedAssemblyIsInvalid"; + internal const string Error_TypeToXsdConversion = "Error_TypeToXsdConversion"; + internal const string Error_FieldTypeNotResolved = "Error_FieldTypeNotResolved"; + internal const string Error_PropertyTypeNotResolved = "Error_PropertyTypeNotResolved"; + internal const string Error_CouldNotDeserializeXomlFile = "Error_CouldNotDeserializeXomlFile"; + internal const string Error_InternalCompilerError = "Error_InternalCompilerError"; + internal const string Error_TypeNotAsseblyQualified = "Error_TypeNotAsseblyQualified"; + internal const string CompilerWarning_StandardAssemlbyInReferences = "CompilerWarning_StandardAssemlbyInReferences"; + internal const string Error_SuspendInAtomicScope = "Error_SuspendInAtomicScope"; + internal const string Error_InvalidActivityExecutionContext = "Error_InvalidActivityExecutionContext"; + internal const string Error_NoRuntimeAvailable = "Error_NoRuntimeAvailable"; + internal const string Error_CanNotChangeAtRuntime = "Error_CanNotChangeAtRuntime"; + internal const string Error_DataContextNotInitialized = "Error_DataContextNotInitialized"; + internal const string Error_DataContextAlreadyInitialized = "Error_DataContextAlreadyInitialized"; + internal const string Error_ParseActivityNameDoesNotExist = "Error_ParseActivityNameDoesNotExist"; + internal const string Error_NoParameterPropertyDeclared = "Error_NoParameterPropertyDeclared"; + internal const string Error_PropertyInvalidIdentifier = "Error_PropertyInvalidIdentifier"; + internal const string Error_WorkflowDefinitionModified = "Error_WorkflowDefinitionModified"; + internal const string Error_FieldAlreadyExist = "Error_FieldAlreadyExist"; + internal const string Failure_FieldAlreadyExist = "Failure_FieldAlreadyExist"; + internal const string Error_DifferentTypeFieldExists = "Error_DifferentTypeFieldExists"; + internal const string Error_RootActivityTypeInvalid = "Error_RootActivityTypeInvalid"; + internal const string Error_RootActivityTypeInvalid2 = "Error_RootActivityTypeInvalid2"; + internal const string Error_CannotCompile_No_XClass = "Error_CannotCompile_No_XClass"; + internal const string Error_TemplateActivityIsNotActivity = "Error_TemplateActivityIsNotActivity"; + internal const string Error_TypeIsNotRootActivity = "Error_TypeIsNotRootActivity"; + internal const string Error_NoTypeProvider = "Error_NoTypeProvider"; + internal const string Error_NotCodeGeneratorType = "Error_NotCodeGeneratorType"; + internal const string Error_NotDataContext = "Error_NotDataContext"; + internal const string Error_MissingDefaultConstructor = "Error_MissingDefaultConstructor"; + internal const string Error_ContextStackItemMissing = "Error_ContextStackItemMissing"; + internal const string Error_UnexpectedArgumentType = "Error_UnexpectedArgumentType"; + internal const string Error_EmptyArgument = "Error_EmptyArgument"; + internal const string Error_DPAlreadyExist = "Error_DPAlreadyExist"; + internal const string Error_DuplicateDynamicProperty = "Error_DuplicateDynamicProperty"; + internal const string Error_DynamicPropertyTypeValueMismatch = "Error_DynamicPropertyTypeValueMismatch"; + internal const string Error_DynamicPropertyNoSupport = "Error_DynamicPropertyNoSupport"; + internal const string Error_NoContextForDatasource = "Error_NoContextForDatasource"; + internal const string Error_NoContextForDatasourceCaption = "Error_NoContextForDatasourceCaption"; + internal const string Error_DataSourceHasParent = "Error_DataSourceHasParent"; + internal const string OnTaskCompletedDescr = "OnTaskCompletedDescr"; + internal const string OnTaskInitializedDescr = "OnTaskInitializedDescr"; + internal const string Error_InvalidXmlData = "Error_InvalidXmlData"; + internal const string Error_HandlerNotOnRoot = "Error_HandlerNotOnRoot"; + internal const string Error_InvalidArgumentIndex = "Error_InvalidArgumentIndex"; + internal const string Error_UITypeEditorTypeNotUITypeEditor = "Error_UITypeEditorTypeNotUITypeEditor"; + internal const string FilterDescription_UITypeEditor = "FilterDescription_UITypeEditor"; + internal const string Error_UserCodeFilesNotAllowed = "Error_UserCodeFilesNotAllowed"; + internal const string Error_CodeWithinNotAllowed = "Error_CodeWithinNotAllowed"; + internal const string Error_TypeNotAuthorized = "Error_TypeNotAuthorized"; + internal const string Error_CantDetermineBaseType = "Error_CantDetermineBaseType"; + internal const string Error_MultipleSelectNotSupportedForBindAndPromote = "Error_MultipleSelectNotSupportedForBindAndPromote"; + internal const string Error_CantDetermineBaseTypeCaption = "Error_CantDetermineBaseTypeCaption"; + internal const string Error_CantDeterminePropertyBaseType = "Error_CantDeterminePropertyBaseType"; + internal const string Error_NullCustomActivityTypeName = "Error_NullCustomActivityTypeName"; + internal const string Error_InvalidAttribute = "Error_InvalidAttribute"; + internal const string Error_InvalidAttributes = "Error_InvalidAttributes"; + internal const string Error_ConfigFileMissingOrInvalid = "Error_ConfigFileMissingOrInvalid"; + internal const string Error_CantHaveContextActivity = "Error_CantHaveContextActivity"; + internal const string Error_SynchronizedNeedsDataContext = "Error_SynchronizedNeedsDataContext"; + internal const string Error_MoreThanOneFaultHandlersActivityDecl = "Error_MoreThanOneFaultHandlersActivityDecl"; + internal const string Error_MoreThanOneEventHandlersDecl = "Error_MoreThanOneEventHandlersDecl"; + internal const string Error_MoreThanOneCancelHandler = "Error_MoreThanOneCancelHandler"; + internal const string Error_MetaDataInterfaceMissing = "Error_MetaDataInterfaceMissing"; + internal const string Error_NonActivityExecutor = "Error_NonActivityExecutor"; + internal const string Error_DynamicUpdateEvaluation = "Error_DynamicUpdateEvaluation"; + internal const string Error_CollectionHasNullEntry = "Error_CollectionHasNullEntry"; + internal const string Error_MissingContextProperty = "Error_MissingContextProperty"; + internal const string Error_AssociatedDesignerMissing = "Error_AssociatedDesignerMissing"; + internal const string Error_MissingContextActivityProperty = "Error_MissingContextActivityProperty"; + internal const string Error_MissingActivityProperty = "Error_MissingActivityProperty"; + internal const string Error_MissingOwnerTypeProperty = "Error_MissingOwnerTypeProperty"; + internal const string Error_DOIsNotAnActivity = "Error_DOIsNotAnActivity"; + internal const string Error_PropertyCanBeOnlyCleared = "Error_PropertyCanBeOnlyCleared"; + internal const string Error_PropertyDefaultTypeMismatch = "Error_PropertyDefaultTypeMismatch"; + internal const string Error_PropertyDefaultIsReference = "Error_PropertyDefaultIsReference"; + // workflow load errors + internal const string Error_WorkflowLoadFailed = "Error_WorkflowLoadFailed"; + internal const string Error_WorkflowLoadValidationFailed = "Error_WorkflowLoadValidationFailed"; + internal const string Error_WorkflowLoadDeserializationFailed = "Error_WorkflowLoadDeserializationFailed"; + internal const string Error_WorkflowLoadTypeMismatch = "Error_WorkflowLoadTypeMismatch"; + internal const string Error_WorkflowLoadInvalidXoml = "Error_WorkflowLoadInvalidXoml"; + internal const string Error_WorkflowLoadNotValidRootType = "Error_WorkflowLoadNotValidRootType"; + internal const string Error_CantCreateInstanceOfComponent = "Error_CantCreateInstanceOfComponent"; + internal const string Error_NotComponentFactoryType = "Error_NotComponentFactoryType"; + internal const string Error_WorkflowTerminated = "Error_WorkflowTerminated"; + + // serializer errrors + internal const string Error_SerializerAttributesFoundInComplexProperty = "Error_SerializerAttributesFoundInComplexProperty"; + internal const string Error_InvalidDataFound = "Error_InvalidDataFound"; + internal const string Error_InvalidDataFoundForType = "Error_InvalidDataFoundForType"; + internal const string Error_InvalidDataFoundForType1 = "Error_InvalidDataFoundForType1"; + internal const string Error_SerializerTypeNotResolved = "Error_SerializerTypeNotResolved"; + internal const string Error_MarkupSerializerTypeNotResolved = "Error_MarkupSerializerTypeNotResolved"; + internal const string Error_SerializerTypeNotResolvedWithInnerError = "Error_SerializerTypeNotResolvedWithInnerError"; + internal const string Error_SerializerNotAvailable = "Error_SerializerNotAvailable"; + internal const string Error_SerializerNotAvailableForSerialize = "Error_SerializerNotAvailableForSerialize"; + internal const string Error_SerializerCreateInstanceFailed = "Error_SerializerCreateInstanceFailed"; + internal const string Error_SerializerAddChildFailed = "Error_SerializerAddChildFailed"; + internal const string Error_SerializerNoPropertyAvailable = "Error_SerializerNoPropertyAvailable"; + internal const string Error_SerializerPrimitivePropertyReadOnly = "Error_SerializerPrimitivePropertyReadOnly"; + internal const string Error_SerializerCantChangeIsLocked = "Error_SerializerCantChangeIsLocked"; + internal const string Error_SerializerPrimitivePropertySetFailed = "Error_SerializerPrimitivePropertySetFailed"; + internal const string Error_SerializerPropertyGetFailed = "Error_SerializerPropertyGetFailed"; + internal const string Error_SerializerPrimitivePropertyNoLogic = "Error_SerializerPrimitivePropertyNoLogic"; + internal const string Error_SerializerPrimitivePropertyParentIsNull = "Error_SerializerPrimitivePropertyParentIsNull"; + internal const string Error_SerializerComplexPropertySetFailed = "Error_SerializerComplexPropertySetFailed"; + internal const string Error_SerializerNoChildNotion = "Error_SerializerNoChildNotion"; + internal const string Error_SerializerNoDynamicPropertySupport = "Error_SerializerNoDynamicPropertySupport"; + internal const string Error_SerializerNoSerializeLogic = "Error_SerializerNoSerializeLogic"; + internal const string Error_SerializerReadOnlyPropertyAndValueIsNull = "Error_SerializerReadOnlyPropertyAndValueIsNull"; + internal const string Error_SerializerReadOnlyParametersNoChild = "Error_SerializerReadOnlyParametersNoChild"; + internal const string Error_SerializerNotParameterBindingObject = "Error_SerializerNotParameterBindingObject"; + internal const string Error_SerializerThrewException = "Error_SerializerThrewException"; + internal const string Error_ActivityCollectionSerializer = "Error_ActivityCollectionSerializer"; + internal const string Error_MissingClassAttribute = "Error_MissingClassAttribute"; + internal const string Error_MissingClassAttributeValue = "Error_MissingClassAttributeValue"; + internal const string ExecutorCreationFailedErrorMessage = "ExecutorCreationFailedErrorMessage"; + internal const string VariableGetterCode_VB = "VariableGetterCode_VB"; + internal const string VariableGetterCode_CS = "VariableGetterCode_CS"; + internal const string VariableSetterCode_VB = "VariableSetterCode_VB"; + internal const string VariableSetterCode_CS = "VariableSetterCode_CS"; + internal const string StaticVariableGetterCode_VB = "StaticVariableGetterCode_VB"; + internal const string StaticVariableGetterCode_CS = "StaticVariableGetterCode_CS"; + internal const string StaticVariableSetterCode_VB = "StaticVariableSetterCode_VB"; + internal const string StaticVariableSetterCode_CS = "StaticVariableSetterCode_CS"; + internal const string EnterCodeBesidesCode_VB = "EnterCodeBesidesCode_VB"; + internal const string EnterCodeBesidesCode_CS = "EnterCodeBesidesCode_CS"; + internal const string LeaveCodeBesides1Code_VB = "LeaveCodeBesides1Code_VB"; + internal const string LeaveCodeBesides2Code_VB = "LeaveCodeBesides2Code_VB"; + internal const string LeaveCodeBesides1Code_CS = "LeaveCodeBesides1Code_CS"; + internal const string LeaveCodeBesides2Code_CS = "LeaveCodeBesides2Code_CS"; + internal const string VariableSetterName = "VariableSetterName"; + internal const string VariableGetterName = "VariableGetterName"; + internal const string HandlerGetterName = "HandlerGetterName"; + internal const string WorkflowCreatorName = "WorkflowCreatorName"; + internal const string ActivityMethod = "ActivityMethod"; + internal const string CustomActivityPrivateField = "CustomActivityPrivateField"; + internal const string InitializedVariableDeclaration_VB = "InitializedVariableDeclaration_VB"; + internal const string InitializedVariableDeclaration_CS = "InitializedVariableDeclaration_CS"; + internal const string In = "In"; + internal const string Out = "Out"; + internal const string Ref = "Ref"; + internal const string Required = "Required"; + internal const string Optional = "Optional"; + internal const string Parameters = "Parameters"; + internal const string Properties = "Properties"; + internal const string Error_RecursionDetected = "Error_RecursionDetected"; + internal const string Warning_UnverifiedRecursion = "Warning_UnverifiedRecursion"; + internal const string AddConstructorCode = "AddConstructorCode"; + internal const string Error_UninitializedCorrelation = "Error_UninitializedCorrelation"; + internal const string Error_CorrelationAlreadyInitialized = "Error_CorrelationAlreadyInitialized"; + internal const string Error_CorrelatedSendReceiveAtomicScope = "Error_CorrelatedSendReceiveAtomicScope"; + internal const string Warning_ActivityValidation = "Warning_ActivityValidation"; + internal const string Warning_EmptyBehaviourActivity = "Warning_EmptyBehaviourActivity"; + internal const string Error_ParallelActivationNoCorrelation = "Error_ParallelActivationNoCorrelation"; + internal const string Error_MethodNotAccessible = "Error_MethodNotAccessible"; + internal const string Error_FieldNotAccessible = "Error_FieldNotAccessible"; + internal const string Error_PropertyNotAccessible = "Error_PropertyNotAccessible"; + internal const string Error_GenericArgumentsNotAllowed = "Error_GenericArgumentsNotAllowed"; + internal const string Error_InvalidIdentifier = "Error_InvalidIdentifier"; + internal const string Error_InvalidLanguageIdentifier = "Error_InvalidLanguageIdentifier"; + internal const string DuplicateActivityIdentifier = "DuplicateActivityIdentifier"; + internal const string Error_MissingAttribute = "Error_MissingAttribute"; + internal const string Error_LoadUIPropertiesFile = "Error_LoadUIPropertiesFile"; + internal const string Error_SerializerEventGetFailed = "Error_SerializerEventGetFailed"; + internal const string Error_SerializerEventFailed = "Error_SerializerEventFailed"; + internal const string Error_SerializerNoMemberFound = "Error_SerializerNoMemberFound"; + internal const string Error_DynamicEventConflict = "Error_DynamicEventConflict"; + internal const string Error_SerializerMemberSetFailed = "Error_SerializerMemberSetFailed"; + internal const string Error_ContentPropertyCouldNotBeFound = "Error_ContentPropertyCouldNotBeFound"; + internal const string Error_ContentPropertyValueInvalid = "Error_ContentPropertyValueInvalid"; + internal const string Error_ContentPropertyNoSetter = "Error_ContentPropertyNoSetter"; + internal const string Error_ContentCanNotBeConverted = "Error_ContentCanNotBeConverted"; + internal const string Error_ContentPropertyCanNotBeNull = "Error_ContentPropertyCanNotBeNull"; + internal const string Error_SerializerTypeMismatch = "Error_SerializerTypeMismatch"; + internal const string Error_CouldNotAddValueInContentProperty = "Error_CouldNotAddValueInContentProperty"; + internal const string Error_SerializerTypeRequirement = "Error_SerializerTypeRequirement"; + internal const string Error_CanNotAddActivityInBlackBoxActivity = "Error_CanNotAddActivityInBlackBoxActivity"; + internal const string Error_ContentPropertyCanNotSupportCompactFormat = "Error_ContentPropertyCanNotSupportCompactFormat"; + internal const string Error_ContentPropertyNoMultipleContents = "Error_ContentPropertyNoMultipleContents"; + internal const string Error_InternalSerializerError = "Error_InternalSerializerError"; + internal const string Error_DictionarySerializerNonDictionaryObject = "Error_DictionarySerializerNonDictionaryObject"; + internal const string Error_DictionarySerializerKeyNotFound = "Error_DictionarySerializerKeyNotFound"; + internal const string Error_InvalidCancelActivityState = "Error_InvalidCancelActivityState"; + internal const string Error_InvalidCompensateActivityState = "Error_InvalidCompensateActivityState"; + internal const string Error_InvalidCloseActivityState = "Error_InvalidCloseActivityState"; + internal const string Error_SealedPropertyMetadata = "Error_SealedPropertyMetadata"; + internal const string Error_MemberNotFound = "Error_MemberNotFound"; + internal const string Error_EmptyPathValue = "Error_EmptyPathValue"; + internal const string Error_InvalidCompensatingState = "Error_InvalidCompensatingState"; + internal const string Error_InvalidCancelingState = "Error_InvalidCancelingState"; + internal const string Error_InvalidClosingState = "Error_InvalidClosingState"; + internal const string Error_InvalidStateToExecuteChild = "Error_InvalidStateToExecuteChild"; + internal const string Error_InvalidExecutionState = "Error_InvalidExecutionState"; + internal const string Error_InvalidInitializingState = "Error_InvalidInitializingState"; + internal const string Error_InvalidInvokingState = "Error_InvalidInvokingState"; + internal const string Error_NotRegisteredAs = "Error_NotRegisteredAs"; + internal const string Error_AlreadyRegisteredAs = "Error_AlreadyRegisteredAs"; + internal const string Error_InsertingChildControls = "Error_InsertingChildControls"; + internal const string Error_EmptyToolTipRectangle = "Error_EmptyToolTipRectangle"; + internal const string Error_EmptyRectangleValue = "Error_EmptyRectangleValue"; + internal const string Error_InvalidShadowRectangle = "Error_InvalidShadowRectangle"; + internal const string Error_InvalidShadowDepth = "Error_InvalidShadowDepth"; + internal const string Error_InvalidLightSource = "Error_InvalidLightSource"; + internal const string Error_ChangingDock = "Error_ChangingDock"; + internal const string Error_NullOrEmptyValue = "Error_NullOrEmptyValue"; + internal const string Error_InvalidStateImages = "Error_InvalidStateImages"; + internal const string Error_InvalidConnectorSegment = "Error_InvalidConnectorSegment"; + internal const string Error_InvalidConnectorSource = "Error_InvalidConnectorSource"; + internal const string Error_CreatingToolTip = "Error_CreatingToolTip"; + internal const string Error_InvalidDockStyle = "Error_InvalidDockStyle"; + internal const string Error_InvalidConnectorValue = "Error_InvalidConnectorValue"; + internal const string Error_InvalidDesignerVerbValue = "Error_InvalidDesignerVerbValue"; + internal const string Error_InvalidRuntimeType = "Error_InvalidRuntimeType"; + internal const string Error_InvalidArgumentValue = "Error_InvalidArgumentValue"; + internal const string Error_InvalidRadiusValue = "Error_InvalidRadiusValue"; + internal const string ToolTipString = "ToolTipString"; + + //Collection Editor Resources + internal const string CollectionEditorCaption = "CollectionEditorCaption"; + internal const string CollectionEditorProperties = "CollectionEditorProperties"; + internal const string CollectionEditorPropertiesMultiSelect = "CollectionEditorPropertiesMultiSelect"; + internal const string CollectionEditorPropertiesNone = "CollectionEditorPropertiesNone"; + internal const string CollectionEditorCantRemoveItem = "CollectionEditorCantRemoveItem"; + internal const string CollectionEditorUndoBatchDesc = "CollectionEditorUndoBatchDesc"; + internal const string CollectionEditorInheritedReadOnlySelection = "CollectionEditorInheritedReadOnlySelection"; + internal const string Error_ParameterAlreadyExists = "Error_ParameterAlreadyExists"; + internal const string Error_PropertyAlreadyExists = "Error_PropertyAlreadyExists"; + internal const string Error_HiddenPropertyAlreadyExists = "Error_HiddenPropertyAlreadyExists"; + internal const string Error_CorrelationInUse = "Error_CorrelationInUse"; + internal const string Error_ItemNotExists = "Error_ItemNotExists"; + internal const string Error_NoHelpAvailable = "Error_NoHelpAvailable"; + internal const string Error_DuplicateWorkflow = "Error_DuplicateWorkflow"; + internal const string Error_Recursion = "Error_Recursion"; + internal const string Error_RootActivity = "Error_RootActivity"; + internal const string Error_ConditionDefinitionDeserializationFailed = "Error_ConditionDefinitionDeserializationFailed"; + internal const string Error_InvalidConditionDefinition = "Error_InvalidConditionDefinition"; + internal const string SR_InvokeTransactionalFromAtomic = "SR_InvokeTransactionalFromAtomic"; + internal const string Error_SuspendInAtomicCallChain = "Error_SuspendInAtomicCallChain"; + internal const string Error_LiteralPassedToOutRef = "Error_LiteralPassedToOutRef"; + internal const string Error_GeneratorShouldContainSingleActivity = "Error_GeneratorShouldContainSingleActivity"; + internal const string Error_DeclaringPropertyNotSupported = "Error_DeclaringPropertyNotSupported"; + internal const string Error_DeclaringEventNotSupported = "Error_DeclaringEventNotSupported"; + internal const string Error_DynamicEventNotSupported = "Error_DynamicEventNotSupported"; + internal const string Error_DynamicPropertyNotSupported = "Error_DynamicPropertyNotSupported"; + internal const string Error_ParameterTypeResolution = "Error_ParameterTypeResolution"; + + // Dynamic Validations + internal const string Error_DynamicActivity = "Error_DynamicActivity"; + internal const string Error_DynamicActivity2 = "Error_DynamicActivity2"; + internal const string Error_CompilerValidationFailed = "Error_CompilerValidationFailed"; + internal const string Error_RuntimeValidationFailed = "Error_RuntimeValidationFailed"; + internal const string Error_TransactionAlreadyCanceled = "Error_TransactionAlreadyCanceled"; + internal const string Error_RemoveExecutingActivity = "Error_RemoveExecutingActivity"; + internal const string Error_InsideAtomicScope = "Error_InsideAtomicScope"; + internal const string SuspendReason_WorkflowChange = "SuspendReason_WorkflowChange"; + + //type filtering + internal const string FilterDescription_ParameterDeclaration = "FilterDescription_ParameterDeclaration"; + internal const string FilterDescription_GenericArgument = "FilterDescription_GenericArgument"; + + + internal const string LibraryPathIsInvalid = "LibraryPathIsInvalid"; + + // Activity Set + internal const string Error_CreateValidator = "Error_CreateValidator"; + internal const string Error_InvalidPackageFile = "Error_InvalidPackageFile"; + internal const string Error_AddAssemblyRef = "Error_AddAssemblyRef"; + internal const string Error_AssemblyBadImage = "Error_AssemblyBadImage"; + internal const string BindPropertySetterName = "BindPropertySetterName"; + + // Bind validations + internal const string Error_CannotResolveActivity = "Error_CannotResolveActivity"; + internal const string Error_CannotResolveRelativeActivity = "Error_CannotResolveRelativeActivity"; + internal const string Error_PathNotSetForActivitySource = "Error_PathNotSetForActivitySource"; + internal const string Error_InvalidMemberPath = "Error_InvalidMemberPath"; + internal const string Error_TargetTypeMismatch = "Error_TargetTypeMismatch"; + internal const string Warning_ParameterBinding = "Warning_ParameterBinding"; + internal const string Error_ReferencedActivityPropertyNotBind = "Error_ReferencedActivityPropertyNotBind"; + internal const string Error_TargetTypeDataSourcePathMismatch = "Error_TargetTypeDataSourcePathMismatch"; + internal const string Bind_ActivityDataSourceRecursionDetected = "Bind_ActivityDataSourceRecursionDetected"; + internal const string Bind_DuplicateDataSourceNames = "Bind_DuplicateDataSourceNames"; + internal const string Error_PathNotSetForXmlDataSource = "Error_PathNotSetForXmlDataSource"; + internal const string Error_XmlDocumentLoadFailed = "Error_XmlDocumentLoadFailed"; + internal const string Error_XmlDataSourceInvalidPath = "Error_XmlDataSourceInvalidPath"; + internal const string Error_XmlDataSourceMultipleNodes = "Error_XmlDataSourceMultipleNodes"; + internal const string Error_XmlDataSourceInvalidXPath = "Error_XmlDataSourceInvalidXPath"; + internal const string Error_InvalidObjectRefFormat = "Error_InvalidObjectRefFormat"; + internal const string Error_ReadOnlyDataSource = "Error_ReadOnlyDataSource"; + internal const string Error_HandlerReadOnly = "Error_HandlerReadOnly"; + internal const string Error_XmlDataSourceReadOnly = "Error_XmlDataSourceReadOnly"; + internal const string Error_DataSourceNotExist = "Error_DataSourceNotExist"; + internal const string Error_PropertyNoGetter = "Error_PropertyNoGetter"; + internal const string Error_PropertyNoSetter = "Error_PropertyNoSetter"; + internal const string Error_PropertyHasNoGetterDefined = "Error_PropertyHasNoGetterDefined"; + internal const string Error_PropertyHasNoSetterDefined = "Error_PropertyHasNoSetterDefined"; + internal const string Error_PropertyReferenceNoGetter = "Error_PropertyReferenceNoGetter"; + internal const string Error_PropertyReferenceGetterNoAccess = "Error_PropertyReferenceGetterNoAccess"; + internal const string Error_PropertyHasIndexParameters = "Error_PropertyHasIndexParameters"; + internal const string Error_ReadOnlyField = "Error_ReadOnlyField"; + internal const string Error_NoEnclosingContext = "Error_NoEnclosingContext"; + internal const string Error_NestedPersistOnClose = "Error_NestedPersistOnClose"; + internal const string Error_NestedCompensatableActivity = "Error_NestedCompensatableActivity"; + internal const string Error_InvalidActivityForObjectDatasource = "Error_InvalidActivityForObjectDatasource"; + internal const string Error_DataSourceTypeConversionFailed = "Error_DataSourceTypeConversionFailed"; + internal const string Error_BindDialogWrongPropertyType = "Error_BindDialogWrongPropertyType"; + internal const string Error_BindDialogNoValidPropertySelected = "Error_BindDialogNoValidPropertySelected"; + internal const string Error_BindDialogBindNotValid = "Error_BindDialogBindNotValid"; + internal const string Error_BindDialogCanNotBindToItself = "Error_BindDialogCanNotBindToItself"; + internal const string Error_BindActivityReference = "Error_BindActivityReference"; + internal const string Error_NoTargetTypeForMethod = "Error_NoTargetTypeForMethod"; + internal const string Error_MethodDataSourceIsReadOnly = "Error_MethodDataSourceIsReadOnly"; + internal const string Error_NotMethodDataSource = "Error_NotMethodDataSource"; + internal const string Error_MethodDataSourceWithPath = "Error_MethodDataSourceWithPath"; + internal const string Error_PathSyntax = "Error_PathSyntax"; + internal const string Error_UnmatchedParen = "Error_UnmatchedParen"; + internal const string Error_UnmatchedBracket = "Error_UnmatchedBracket"; + internal const string Error_MemberWithSameNameExists = "Error_MemberWithSameNameExists"; + internal const string Error_ActivityIdentifierCanNotBeEmpty = "Error_ActivityIdentifierCanNotBeEmpty"; + internal const string Error_InvalidActivityIdentifier = "Error_InvalidActivityIdentifier"; + internal const string Error_ActivityBindTypeConversionError = "Error_ActivityBindTypeConversionError"; + internal const string EmptyValue = "EmptyValue"; + internal const string Error_PropertyTypeNotDefined = "Error_PropertyTypeNotDefined"; + + internal const string Error_CompilationFailed = "Error_CompilationFailed"; + internal const string Error_MissingCompilationContext = "Error_MissingCompilationContext"; + + internal const string InvokeWorkflowReference_VB = "InvokeWorkflowReference_VB"; + internal const string InvokeWorkflowReference_CS = "InvokeWorkflowReference_CS"; + internal const string Error_InvalidListItem = "Error_InvalidListItem"; + + internal const string ParserMapPINoWhitespace = "ParserMapPINoWhitespace"; + internal const string ParserMapPIBadCharEqual = "ParserMapPIBadCharEqual"; + internal const string ParserMapPIBadCharQuote = "ParserMapPIBadCharQuote"; + internal const string ParserMapPIBadKey = "ParserMapPIBadKey"; + internal const string ParserMapPIMissingKey = "ParserMapPIMissingKey"; + internal const string ParserMapPIKeyNotSet = "ParserMapPIKeyNotSet"; + internal const string ParserMismatchDelimiter = "ParserMismatchDelimiter"; + internal const string ParserDanglingClause = "ParserDanglingClause"; + internal const string UnknownDefinitionTag = "UnknownDefinitionTag"; + internal const string CDATASection = "CDATASection"; + internal const string TextSection = "TextSection"; + internal const string IncorrectSyntax = "IncorrectSyntax"; + internal const string IncorrectTypeSyntax = "IncorrectTypeSyntax"; + internal const string Error_MultipleRootActivityCreator = "Error_MultipleRootActivityCreator"; + internal const string Error_MustHaveParent = "Error_MustHaveParent"; + + // Workflow References + internal const string Error_ReferenceObjNotInitialized = "Error_ReferenceObjNotInitialized"; + internal const string Error_ReferenceInitResourceManager = "Error_ReferenceInitResourceManager"; + internal const string Error_ResourceReferenceGetObject = "Error_ResourceReferenceGetObject"; + internal const string Error_RefBindCantFindRef = "Error_RefBindCantFindRef"; + internal const string Error_RefBindMissingReferenceName = "Error_RefBindMissingReferenceName"; + internal const string Error_RefBindMissingAttribute = "Error_RefBindMissingAttribute"; + internal const string Error_ReferenceLoad = "Error_ReferenceLoad"; + internal const string Error_ReferenceMissingAttribute = "Error_ReferenceMissingAttribute"; + internal const string Error_ReferenceInvalidResourceFile = "Error_ReferenceInvalidResourceFile"; + internal const string Error_ReferenceEmptyName = "Error_ReferenceEmptyName"; + + internal const string HandlerInvokerName = "HandlerInvokerName"; + internal const string HandlerInvokerSwitchPrefix_CS = "HandlerInvokerSwitchPrefix_CS"; + internal const string HandlerInvokerSwitchPrefix_VB = "HandlerInvokerSwitchPrefix_VB"; + internal const string HandlerInvokerSwitchSuffix_CS = "HandlerInvokerSwitchSuffix_CS"; + internal const string HandlerInvokerSwitchSuffix_VB = "HandlerInvokerSwitchSuffix_VB"; + internal const string HandlerInvokerCaseBegin_CS = "HandlerInvokerCaseBegin_CS"; + internal const string HandlerInvokerCaseBegin_VB = "HandlerInvokerCaseBegin_VB"; + + // Activity Category + internal const string Standard = "Standard"; + internal const string Base = "Base"; + + //CustomActivityDesigner + internal const string ValidatorCompanionClassDesc = "ValidatorCompanionClassDesc"; + internal const string ExecutorCompanionClassDesc = "ExecutorCompanionClassDesc"; + internal const string DesignerCompanionClassDesc = "DesignerCompanionClassDesc"; + internal const string CustomActivityBaseTypeDesc = "CustomActivityBaseTypeDesc"; + internal const string ActivityProperties = "ActivityProperties"; + internal const string ActivityPropertiesDesc = "ActivityPropertiesDesc"; + internal const string CompanionClasses = "CompanionClasses"; + internal const string ActivityDesc = "Activity"; + internal const string Error_TypeConversionFailed = "Error_TypeConversionFailed"; + internal const string SupportDataContext = "SupportDataContext"; + internal const string AdvancedCategory = "AdvancedCategory"; + internal const string SupportDataContextDesc = "SupportDataContextDesc"; + internal const string BaseCompanionClassName = "BaseCompanionClassName"; + internal const string BaseCompanionClassDesc = "BaseCompanionClassDesc"; + internal const string Designer = "Designer"; + internal const string Validator = "Validator"; + internal const string Executor = "Executor"; + internal const string BaseActivityType = "BaseActivityType"; + internal const string Error_NotBuiltInActivity = "Error_NotBuiltInActivity"; + internal const string NoChildActivities_Message = "NoChildActivities_Message"; + internal const string NoChildActivities_Caption = "NoChildActivities_Caption"; + internal const string Error_CustomActivityCantCreate = "Error_CustomActivityCantCreate"; + internal const string Error_CantChangeBuiltInActivity = "Error_CantChangeBuiltInActivity"; + internal const string Error_CantAddBeforeBuiltInActivity = "Error_CantAddBeforeBuiltInActivity"; + internal const string Error_CantAddAfterNonBuiltInActivity = "Error_CantAddAfterNonBuiltInActivity"; + internal const string Error_CannotAddRemoveChildActivities = "Error_CannotAddRemoveChildActivities"; + internal const string Error_CantFindBuiltInActivity = "Error_CantFindBuiltInActivity"; + internal const string Error_MissingBaseCompanionClassAttribute = "Error_MissingBaseCompanionClassAttribute"; + internal const string Error_CantFindBuiltInParent = "Error_CantFindBuiltInParent"; + internal const string Error_CantCreateInstanceOfBaseType = "Error_CantCreateInstanceOfBaseType"; + internal const string Error_CustomActivityTypeCouldNotBeFound = "Error_CustomActivityTypeCouldNotBeFound"; + internal const string None = "None"; + internal const string AtomicTransaction = "AtomicTransaction"; + internal const string LocalDataContext = "LocalDataContext"; + internal const string LocalDataContextDesc = "LocalDataContextDesc"; + internal const string CompanionClass = "CompanionClass"; + internal const string Error_AlreadyRootActivity = "Error_AlreadyRootActivity"; + internal const string RootActivityName = "RootActivityName"; + internal const string RootActivityNameDesc = "RootActivityNameDesc"; + internal const string CustomProperties = "CustomProperties"; + internal const string VisibleDescr = "VisibleDescr"; + internal const string EditableDescr = "EditableDescr"; + internal const string Error_CantCreateMethod = "Error_CantCreateMethod"; + internal const string Error_CantEditNullValue = "Error_CantEditNullValue"; + internal const string Error_CompanionTypeNotSet = "Error_CompanionTypeNotSet"; + internal const string Error_CompanionClassNameCanNotBeEmpty = "Error_CompanionClassNameCanNotBeEmpty"; + internal const string Error_CouldNotEmitFieldInLocalDataContext = "Error_CouldNotEmitFieldInLocalDataContext"; + internal const string Error_CouldNotEmitMethodInLocalDataContext = "Error_CouldNotEmitMethodInLocalDataContext"; + internal const string Error_DerivationFromTypeWithLocalDataContext = "Error_DerivationFromTypeWithLocalDataContext"; + internal const string Error_CompanionTypeDerivationError = "Error_CompanionTypeDerivationError"; + internal const string Error_CantCreateDataContextClass = "Error_CantCreateDataContextClass"; + internal const string ArrayExistingBind = "ArrayExistingBind"; + internal const string Error_NoMatchingFieldsOrProperties = "Error_NoMatchingFieldsOrProperties"; + internal const string ChooseFieldPropertyDatasource = "ChooseFieldPropertyDatasource"; + + internal const string SupportsTransaction = "SupportsTransaction"; + internal const string SupportsExceptions = "SupportsExceptions"; + internal const string SupportsCancellationHandlerActivity = "SupportsCancellationHandlerActivity"; + internal const string SupportsEvents = "SupportsEvents"; + internal const string SupportsDataSources = "SupportsDataSources"; + internal const string SupportsCompensationHandler = "SupportsCompensationHandler"; + internal const string SupportsCompensationHandlerDesc = "SupportsCompensationHandlerDesc"; + internal const string SupportsTransactionDesc = "SupportsTransactionDesc"; + internal const string SupportsExceptionsDesc = "SupportsExceptionsDesc"; + internal const string SupportsCancelHandlerDesc = "SupportsCancelHandlerDesc"; + internal const string SupportsEventsDesc = "SupportsEventsDesc"; + internal const string TransactionDesc = "TransactionDesc"; + + internal const string Error_BaseTypeMustBeActivity = "Error_BaseTypeMustBeActivity"; + internal const string ExistingActivityBindTitle = "ExistingActivityBindTitle"; + internal const string ExistingActivityBindLabel = "ExistingActivityBindLabel"; + internal const string ExistingFieldPropBindTitle = "ExistingFieldPropBindTitle"; + internal const string ExistingFieldPropBindLabel = "ExistingFieldPropBindLabel"; + internal const string ProvidesSynchronization = "ProvidesSynchronization"; + internal const string ProvidesSynchronizationDesc = "ProvidesSynchronizationDesc"; + internal const string SynchronizationHandles = "SynchronizationHandles"; + internal const string SynchronizationHandlesDesc = "SynchronizationHandlesDesc"; + + internal const string Error_TransactionAlreadyApplied = "Error_TransactionAlreadyApplied"; + internal const string Error_BindBaseTypeNotSpecified = "Error_BindBaseTypeNotSpecified"; + internal const string NonDelegateTargetType = "NonDelegateTargetType"; + internal const string Error_ClassnameNotInRootNamespace = "Error_ClassnameNotInRootNamespace"; + internal const string Error_CantUseCurrentProjectTypeAsBase = "Error_CantUseCurrentProjectTypeAsBase"; + internal const string Error_UnboundGenericType = "Error_UnboundGenericType"; + internal const string Error_UnboundGenericTypeDataSource = "Error_UnboundGenericTypeDataSource"; + internal const string Error_BaseTypeUnknown = "Error_BaseTypeUnknown"; + internal const string Error_UnconfiguredBind = "Error_UnconfiguredBind"; + internal const string Error_CanNotEmitMemberInLocalDataContext = "Error_CanNotEmitMemberInLocalDataContext"; + internal const string Error_DesignedTypeNotFound = "Error_DesignedTypeNotFound"; + internal const string Error_PathCouldNotBeResolvedToMember = "Error_PathCouldNotBeResolvedToMember"; + internal const string Error_EdittingNullCollection = "Error_EdittingNullCollection"; + internal const string Error_MoreThanOneCompensationDecl = "Error_MoreThanOneCompensationDecl"; + internal const string Error_ParentDoesNotSupportCompensation = "Error_ParentDoesNotSupportCompensation"; + internal const string Error_CantResolveEventHandler = "Error_CantResolveEventHandler"; + internal const string Error_XSDObjectTypeNotSerializable = "Error_XSDObjectTypeNotSerializable"; + internal const string AEC_InvalidActivity = "AEC_InvalidActivity"; + internal const string GetDynamicActivities_InvalidActivity = "GetDynamicActivities_InvalidActivity"; + internal const string AEC_InvalidNestedActivity = "AEC_InvalidNestedActivity"; + internal const string Error_IDNotSetForActivitySource = "Error_IDNotSetForActivitySource"; + internal const string Error_InvalidCustomPropertyName = "Error_InvalidCustomPropertyName"; + internal const string Error_InvalidCustomPropertyType = "Error_InvalidCustomPropertyType"; + + internal const string Error_DPReadOnly = "Error_DPReadOnly"; + internal const string Error_DPMetaPropertyBinding = "Error_DPMetaPropertyBinding"; + internal const string Error_DPSetValueBind = "Error_DPSetValueBind"; + internal const string Error_DPSetValueHandler = "Error_DPSetValueHandler"; + internal const string Error_DPGetValueHandler = "Error_DPGetValueHandler"; + internal const string Error_DPAddHandlerNonEvent = "Error_DPAddHandlerNonEvent"; + internal const string Error_DPAddHandlerMetaProperty = "Error_DPAddHandlerMetaProperty"; + internal const string Error_DPRemoveHandlerBind = "Error_DPRemoveHandlerBind"; + internal const string Error_LanguageNeedsToBeVBCSharp = "Error_LanguageNeedsToBeVBCSharp"; + internal const string Error_TargetFxNotSupported = "Error_TargetFxNotSupported"; + internal const string Error_CantConvertValueValue = "Error_CantConvertValueValue"; + internal const string Error_TypeIsNotValid = "Error_TypeIsNotValid"; + internal const string Error_TypePropertyInvalid = "Error_TypePropertyInvalid"; + internal const string Error_EventCantBeMetaProperty = "Error_EventCantBeMetaProperty"; + internal const string Error_EventMustBeDelegate = "Error_EventMustBeDelegate"; + internal const string Error_DPPropertyTypeMissing = "Error_DPPropertyTypeMissing"; + + internal const string TransactionalContextActivityDescription = "TransactionalContextActivityDescription"; + internal const string CompensatableTransactionalContextActivityDescription = "CompensatableTransactionalContextActivityDescription"; + internal const string SynchronizationScopeActivityDescription = "SynchronizationScopeActivityDescription"; + internal const string SequenceActivityDescription = "SequenceActivityDescription"; + internal const string CompensateActivityDescription = "CompensateActivityDescription"; + internal const string Error_CompensateBadTargetTX = "Error_CompensateBadTargetTX"; + internal const string Error_CancelHandlerParentNotScope = "Error_CancelHandlerParentNotScope"; + internal const string FaultHandlerActivityDescription = "FaultHandlerActivityDescription"; + internal const string Error_ExceptionTypeNotException = "Error_ExceptionTypeNotException"; + internal const string Error_FaultIsNotOfFaultType = "Error_FaultIsNotOfFaultType"; + internal const string Error_FaultTypeNoDefaultConstructor = "Error_FaultTypeNoDefaultConstructor"; + internal const string FilterDescription_FaultHandlerActivity = "FilterDescription_FaultHandlerActivity"; + internal const string Error_FaultHandlerActivityParentNotFaultHandlersActivity = "Error_FaultHandlerActivityParentNotFaultHandlersActivity"; + internal const string Error_FaultHandlerActivityAllMustBeLast = "Error_FaultHandlerActivityAllMustBeLast"; + internal const string Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl = "Error_FaultHandlersActivityDeclNotAllFaultHandlerActivityDecl"; + internal const string Error_FaultHandlerActivityWrongOrder = "Error_FaultHandlerActivityWrongOrder"; + internal const string Error_SenderMustBeActivityExecutionContext = "Error_SenderMustBeActivityExecutionContext"; + internal const string Error_XomlWorkflowHasCode = "Error_XomlWorkflowHasCode"; + internal const string Error_WrongParamForActivityResolveEventArgs = "Error_WrongParamForActivityResolveEventArgs"; + internal const string Error_ValidatorThrewException = "Error_ValidatorThrewException"; + internal const string Error_Missing_CanModifyProperties_True = "Error_Missing_CanModifyProperties_True"; + internal const string Error_Missing_CanModifyProperties_False = "Error_Missing_CanModifyProperties_False"; + internal const string Error_ModelingConstructsCanNotContainModelingConstructs = "Error_ModelingConstructsCanNotContainModelingConstructs"; + internal const string Error_RootIsNotEnabled = "Error_RootIsNotEnabled"; + internal const string Error_MissingSetAccessor = "Error_MissingSetAccessor"; + internal const string Error_MissingAddHandler = "Error_MissingAddHandler"; + internal const string Error_MissingCLRProperty = "Error_MissingCLRProperty"; + + internal const string Error_NotReadOnlyProperty = "Error_NotReadOnlyProperty"; + internal const string Error_InvalidDependencyProperty = "Error_InvalidDependencyProperty"; + internal const string Error_ActivityNameExist = "Error_ActivityNameExist"; + internal const string CannotCreateAttribute = "CannotCreateAttribute"; + internal const string NamespaceAndDeclaringTypeCannotBeNull = "NamespaceAndDeclaringTypeCannotBeNull"; + internal const string NotElementType = "NotElementType"; + + //Layout persistence errors + internal const string Error_LayoutSerializationActivityNotFound = "Error_LayoutSerializationActivityNotFound"; + internal const string Error_LayoutSerializationAssociatedActivityNotFound = "Error_LayoutSerializationAssociatedActivityNotFound"; + internal const string Error_LayoutSerializationPersistenceSupport = "Error_LayoutSerializationPersistenceSupport"; + internal const string Error_LayoutSerializationRootDesignerNotFound = "Error_LayoutSerializationRootDesignerNotFound"; + internal const string Error_ParameterCannotBeEmpty = "Error_ParameterCannotBeEmpty"; + internal const string InvalidExecutionStatus = "InvalidExecutionStatus"; + internal const string Error_LayoutDeserialization = "Error_LayoutDeserialization"; + internal const string Error_LayoutSerialization = "Error_LayoutSerialization"; + + internal const string Error_SerializerStackOverflow = "Error_SerializerStackOverflow"; + internal const string Error_InvalidActivityForWorkflowChanges = "Error_InvalidActivityForWorkflowChanges"; + internal const string Error_InvalidMemberType = "Error_InvalidMemberType"; + internal const string Error_BindPathNullValue = "Error_BindPathNullValue"; + internal const string Error_MarkupExtensionMissingTerminatingCharacter = "Error_MarkupExtensionMissingTerminatingCharacter"; + internal const string Error_MarkupExtensionDeserializeFailed = "Error_MarkupExtensionDeserializeFailed"; + internal const string Error_ApplyDynamicChangeFailed = "Error_ApplyDynamicChangeFailed"; + internal const string Error_ActivityCircularReference = "Error_ActivityCircularReference"; + internal const string Error_ValidatorTypeIsInvalid = "Error_ValidatorTypeIsInvalid"; + internal const string Error_InvalidServiceProvider = "Error_InvalidServiceProvider"; + internal const string Error_InvalidRootForWorkflowChanges = "Error_InvalidRootForWorkflowChanges"; + internal const string Error_ExtraCharacterFoundAtEnd = "Error_ExtraCharacterFoundAtEnd"; + internal const string Error_WorkflowChangesNotSupported = "Error_WorkflowChangesNotSupported"; + internal const string Error_TypeSystemAttributeArgument = "Error_TypeSystemAttributeArgument"; + + internal const string Error_InvalidElementFoundForType = "Error_InvalidElementFoundForType"; + internal const string Error_ActivitySaveLoadNotCalled = "Error_ActivitySaveLoadNotCalled"; + internal const string Error_CanNotBindProperty = "Error_CanNotBindProperty"; +} +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/Sortedset.cs b/src/IFoxCAD.Basal/Sortedset/Sortedset.cs new file mode 100644 index 0000000..9405fc1 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/Sortedset.cs @@ -0,0 +1,2935 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8601 // 引用类型赋值可能为 null。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning disable CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning disable CS8602 // 解引用可能出现空引用。 +#pragma warning disable CS8604 // 引用类型参数可能为 null。 +// #define USING_HASH_SET +// ==++== +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +// ==--== +/*============================================================ +** +** Class: SortedSet +** +** Purpose: A generic sorted set. +** +** Date: August 15, 2008 +** +===========================================================*/ + + +namespace System.Collections.Generic +{ + using System; + using System.Diagnostics; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.Serialization; + + + + // + // A binary search tree is a red-black tree if it satisfies the following red-black properties: + // 1. Every node is either red or black + // 2. Every leaf (nil node) is black + // 3. If a node is red, then both its children are black + // 4. Every simple path from a node to a descendant leaf contains the same number of black nodes + // + // The basic idea of red-black tree is to represent 2-3-4 trees as standard BSTs but to add one extra bit of information + // per node to encode 3-nodes and 4-nodes. + // 4-nodes will be represented as: B + // R R + // 3 -node will be represented as: B or B + // R B B R + // + // For a detailed description of the algorithm, take a look at "Algorithms" by Robert Sedgewick. + // + + internal delegate bool TreeWalkPredicate(SortedSet.Node node); + + internal enum TreeRotation + { + LeftRotation = 1, + RightRotation = 2, + RightLeftRotation = 3, + LeftRightRotation = 4, + } + + [SuppressMessage("Microsoft.Naming", "CA1710:IdentifiersShouldHaveCorrectSuffix", Justification = "by design name choice")] + [DebuggerTypeProxy(nameof(SortedSet))]/*这句改了*/ + [DebuggerDisplay("Count = {Count}")] +#if !FEATURE_NETCORE + [Serializable] + public class SortedSet : ISet, ICollection, ICollection, ISerializable, IDeserializationCallback//, IReadOnlyCollection + { +#else + public class SortedSet : ISet, ICollection, ICollection, IReadOnlyCollection { +#endif //!FEATURE_NETCORE + #region local variables/constants + Node root; + IComparer comparer; + int count; + int version; + private Object _syncRoot; + + private const String ComparerName = "Comparer"; + private const String CountName = "Count"; + private const String ItemsName = "Items"; + private const String VersionName = "Version"; + //needed for enumerator + private const String TreeName = "Tree"; + private const String NodeValueName = "Item"; + private const String EnumStartName = "EnumStarted"; + private const String ReverseName = "Reverse"; + private const String EnumVersionName = "EnumVersion"; + +#if !FEATURE_NETCORE + //needed for TreeSubset + private const String minName = "Min"; + private const String maxName = "Max"; + private const String lBoundActiveName = "lBoundActive"; + private const String uBoundActiveName = "uBoundActive"; + + private SerializationInfo siInfo; //A temporary variable which we need during deserialization. +#endif + internal const int StackAllocThreshold = 100; + + #endregion + + #region Constructors + public SortedSet() + { + this.comparer = Comparer.Default; + + } + + public SortedSet(IComparer comparer) + { + if (comparer == null) + { + this.comparer = Comparer.Default; + } + else + { + this.comparer = comparer; + } + } + + + public SortedSet(IEnumerable collection) : this(collection, Comparer.Default) { } + + public SortedSet(IEnumerable collection, IComparer comparer) + : this(comparer) + { + + if (collection == null) + { + throw new ArgumentNullException("collection"); + } + + // these are explicit type checks in the mould of HashSet. It would have worked better + // with something like an ISorted (we could make this work for SortedList.Keys etc) + SortedSet baseSortedSet = collection as SortedSet; + SortedSet baseTreeSubSet = collection as TreeSubSet; + if (baseSortedSet != null && baseTreeSubSet == null && AreComparersEqual(this, baseSortedSet)) + { + //breadth first traversal to recreate nodes + if (baseSortedSet.Count == 0) + { + count = 0; + version = 0; + root = null; + return; + } + + + //pre order way to replicate nodes + Stack theirStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Stack myStack = new Stack.Node>(2 * log2(baseSortedSet.Count) + 2); + Node theirCurrent = baseSortedSet.root; + Node myCurrent = (theirCurrent != null ? new SortedSet.Node(theirCurrent.Item, theirCurrent.IsRed) : null); + root = myCurrent; + while (theirCurrent != null) + { + theirStack.Push(theirCurrent); + myStack.Push(myCurrent); + myCurrent.Left = (theirCurrent.Left != null ? new SortedSet.Node(theirCurrent.Left.Item, theirCurrent.Left.IsRed) : null); + theirCurrent = theirCurrent.Left; + myCurrent = myCurrent.Left; + } + while (theirStack.Count != 0) + { + theirCurrent = theirStack.Pop(); + myCurrent = myStack.Pop(); + Node theirRight = theirCurrent.Right; + Node myRight = null; + if (theirRight != null) + { + myRight = new SortedSet.Node(theirRight.Item, theirRight.IsRed); + } + myCurrent.Right = myRight; + + while (theirRight != null) + { + theirStack.Push(theirRight); + myStack.Push(myRight); + myRight.Left = (theirRight.Left != null ? new SortedSet.Node(theirRight.Left.Item, theirRight.Left.IsRed) : null); + theirRight = theirRight.Left; + myRight = myRight.Left; + } + } + count = baseSortedSet.count; + version = 0; + } + else + { //As it stands, you're doing an NlogN sort of the collection + + List els = new List(collection); + els.Sort(this.comparer); + for (int i = 1; i < els.Count; i++) + { + if (comparer.Compare(els[i], els[i - 1]) == 0) + { + els.RemoveAt(i); + i--; + } + } + root = ConstructRootFromSortedArray(els.ToArray(), 0, els.Count - 1, null); + count = els.Count; + version = 0; + } + } + + +#if !FEATURE_NETCORE + + protected SortedSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + } +#endif + #endregion + + #region Bulk Operation Helpers + private void AddAllElements(IEnumerable collection) + { + + foreach (T item in collection) + { + if (!this.Contains(item)) + Add(item); + } + } + + private void RemoveAllElements(IEnumerable collection) + { + T min = this.Min; + T max = this.Max; + foreach (T item in collection) + { + if (!(comparer.Compare(item, min) < 0 || comparer.Compare(item, max) > 0) && this.Contains(item)) + this.Remove(item); + } + } + + private bool ContainsAllElements(IEnumerable collection) + { + foreach (T item in collection) + { + if (!this.Contains(item)) + { + return false; + } + } + return true; + } + + // + // Do a in order walk on tree and calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal bool InOrderTreeWalk(TreeWalkPredicate action) + { + return InOrderTreeWalk(action, false); + } + + // Allows for the change in traversal direction. Reverse visits nodes in descending order + internal virtual bool InOrderTreeWalk(TreeWalkPredicate action, bool reverse) + { + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + // note: this should be logbase2, but since the stack grows itself, we + // don't want the extra cost + Stack stack = new Stack(2 * (int)(SortedSet.log2(Count + 1))); + Node current = root; + while (current != null) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + } + return true; + } + + // + // Do a left to right breadth first walk on tree and + // calls the delegate for each node. + // If the action delegate returns false, stop the walk. + // + // Return true if the entire tree has been walked. + // Otherwise returns false. + // + internal virtual bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (!action(current)) + { + return false; + } + if (current.Left != null) + { + processQueue.Add(current.Left); + } + if (current.Right != null) + { + processQueue.Add(current.Right); + } + } + return true; + } + #endregion + + #region Properties + public int Count + { + get + { + VersionCheck(); + return count; + } + } + + public IComparer Comparer + { + get + { + return comparer; + } + } + + bool ICollection.IsReadOnly + { + get + { + return false; + } + } + + bool ICollection.IsSynchronized + { + get + { + return false; + } + } + + object ICollection.SyncRoot + { + get + { + if (_syncRoot == null) + { + System.Threading.Interlocked.CompareExchange(ref _syncRoot, new Object(), null); + } + return _syncRoot; + } + } + #endregion + + #region Subclass helpers + + //virtual function for subclass that needs to update count + internal virtual void VersionCheck() { } + + + //virtual function for subclass that needs to do range checks + internal virtual bool IsWithinRange(T item) + { + return true; + + } + #endregion + + #region ICollection Members + /// + /// Add the value ITEM to the tree, returns true if added, false if duplicate + /// + /// item to be added + public bool Add(T item) + { + return AddIfNotPresent(item); + } + + void ICollection.Add(T item) + { + AddIfNotPresent(item); + } + + + /// + /// Adds ITEM to the tree if not already present. Returns TRUE if value was successfully added + /// or FALSE if it is a duplicate + /// + internal virtual bool AddIfNotPresent(T item) + { + if (root == null) + { // empty tree + root = new Node(item, false); + count = 1; + version++; + return true; + } + + // + // Search for a node at bottom to insert the new node. + // If we can guanratee the node we found is not a 4-node, it would be easy to do insertion. + // We split 4-nodes along the search path. + // + Node current = root; + Node parent = null; + Node grandParent = null; + Node greatGrandParent = null; + + //even if we don't actually add to the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + + int order = 0; + while (current != null) + { + order = comparer.Compare(item, current.Item); + if (order == 0) + { + // We could have changed root node to red during the search process. + // We need to set it to black before we return. + root.IsRed = false; + return false; + } + + // split a 4-node into two 2-nodes + if (Is4Node(current)) + { + Split4Node(current); + // We could have introduced two consecutive red nodes after split. Fix that by rotation. + if (IsRed(parent)) + { + InsertionBalance(current, ref parent, grandParent, greatGrandParent); + } + } + greatGrandParent = grandParent; + grandParent = parent; + parent = current; + current = (order < 0) ? current.Left : current.Right; + } + + Debug.Assert(parent != null, "Parent node cannot be null here!"); + // ready to insert the new node + Node node = new Node(item); + if (order > 0) + { + parent.Right = node; + } + else + { + parent.Left = node; + } + + // the new node will be red, so we will need to adjust the colors if parent node is also red + if (parent.IsRed) + { + InsertionBalance(node, ref parent, grandParent, greatGrandParent); + } + + // Root node is always black + root.IsRed = false; + ++count; + return true; + } + + /// + /// Remove the T ITEM from this SortedSet. Returns true if successfully removed. + /// + /// + /// + public bool Remove(T item) + { + return this.DoRemove(item); // hack so it can be made non-virtual + } + + internal virtual bool DoRemove(T item) + { + + if (root == null) + { + return false; + } + + + // Search for a node and then find its succesor. + // Then copy the item from the succesor to the matching node and delete the successor. + // If a node doesn't have a successor, we can replace it with its left child (if not empty.) + // or delete the matching node. + // + // In top-down implementation, it is important to make sure the node to be deleted is not a 2-node. + // Following code will make sure the node on the path is not a 2 Node. + + //even if we don't actually remove from the set, we may be altering its structure (by doing rotations + //and such). so update version to disable any enumerators/subsets working on it + version++; + + Node current = root; + Node parent = null; + Node grandParent = null; + Node match = null; + Node parentOfMatch = null; + bool foundMatch = false; + while (current != null) + { + if (Is2Node(current)) + { // fix up 2-Node + if (parent == null) + { // current is root. Mark it as red + current.IsRed = true; + } + else + { + Node sibling = GetSibling(current, parent); + if (sibling.IsRed) + { + // If parent is a 3-node, flip the orientation of the red link. + // We can acheive this by a single rotation + // This case is converted to one of other cased below. + Debug.Assert(!parent.IsRed, "parent must be a black node!"); + if (parent.Right == sibling) + { + RotateLeft(parent); + } + else + { + RotateRight(parent); + } + + parent.IsRed = true; + sibling.IsRed = false; // parent's color + // sibling becomes child of grandParent or root after rotation. Update link from grandParent or root + ReplaceChildOfNodeOrRoot(grandParent, parent, sibling); + // sibling will become grandParent of current node + grandParent = sibling; + if (parent == match) + { + parentOfMatch = sibling; + } + + // update sibling, this is necessary for following processing + sibling = (parent.Left == current) ? parent.Right : parent.Left; + } + Debug.Assert(sibling != null || sibling.IsRed == false, "sibling must not be null and it must be black!"); + + if (Is2Node(sibling)) + { + Merge2Nodes(parent, current, sibling); + } + else + { + // current is a 2-node and sibling is either a 3-node or a 4-node. + // We can change the color of current to red by some rotation. + TreeRotation rotation = RotationNeeded(parent, current, sibling); + Node newGrandParent = null; + switch (rotation) + { + case TreeRotation.RightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + sibling.Left.IsRed = false; + newGrandParent = RotateRight(parent); + break; + case TreeRotation.LeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + sibling.Right.IsRed = false; + newGrandParent = RotateLeft(parent); + break; + + case TreeRotation.RightLeftRotation: + Debug.Assert(parent.Right == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Left.IsRed, "Left child of sibling must be red!"); + newGrandParent = RotateRightLeft(parent); + break; + + case TreeRotation.LeftRightRotation: + Debug.Assert(parent.Left == sibling, "sibling must be left child of parent!"); + Debug.Assert(sibling.Right.IsRed, "Right child of sibling must be red!"); + newGrandParent = RotateLeftRight(parent); + break; + } + + newGrandParent.IsRed = parent.IsRed; + parent.IsRed = false; + current.IsRed = true; + ReplaceChildOfNodeOrRoot(grandParent, parent, newGrandParent); + if (parent == match) + { + parentOfMatch = newGrandParent; + } + grandParent = newGrandParent; + } + } + } + + // we don't need to compare any more once we found the match + int order = foundMatch ? -1 : comparer.Compare(item, current.Item); + if (order == 0) + { + // save the matching node + foundMatch = true; + match = current; + parentOfMatch = parent; + } + + grandParent = parent; + parent = current; + + if (order < 0) + { + current = current.Left; + } + else + { + current = current.Right; // continue the search in right sub tree after we find a match + } + } + + // move successor to the matching node position and replace links + if (match != null) + { + ReplaceNode(match, parentOfMatch, parent, grandParent); + --count; + } + + if (root != null) + { + root.IsRed = false; + } + return foundMatch; + } + + public virtual void Clear() + { + root = null; + count = 0; + ++version; + } + + + public virtual bool Contains(T item) + { + + return FindNode(item) != null; + } + + + + + public void CopyTo(T[] array) { CopyTo(array, 0, Count); } + + public void CopyTo(T[] array, int index) { CopyTo(array, index, Count); } + + public void CopyTo(T[] array, int index, int count) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.index); + } + + if (count < 0) + { + throw new ArgumentOutOfRangeException("count", SR.GetString("SR.ArgumentOutOfRange_NeedNonNegNum")); + } + + // will array, starting at arrayIndex, be able to hold elements? Note: not + // checking arrayIndex >= array.Length (consistency with list of allowing + // count of 0; subsequent check takes care of the rest) + if (index > array.Length || count > array.Length - index) + { + throw new ArgumentException(SR.GetString("SR.Arg_ArrayPlusOffTooSmall")); + } + //upper bound + count += index; + + InOrderTreeWalk(delegate (Node node) + { + if (index >= count) + { + return false; + } + else + { + array[index++] = node.Item; + return true; + } + }); + } + + void ICollection.CopyTo(Array array, int index) + { + if (array == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.array); + } + + if (array.Rank != 1) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_RankMultiDimNotSupported); + } + + if (array.GetLowerBound(0) != 0) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_NonZeroLowerBound); + } + + if (index < 0) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.arrayIndex, ExceptionResource.ArgumentOutOfRange_NeedNonNegNum); + } + + if (array.Length - index < Count) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Arg_ArrayPlusOffTooSmall); + } + + T[] tarray = array as T[]; + if (tarray != null) + { + CopyTo(tarray, index); + } + else + { + object[] objects = array as object[]; + if (objects == null) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + + try + { + InOrderTreeWalk(delegate (Node node) { objects[index++] = node.Item; return true; }); + } + catch (ArrayTypeMismatchException) + { + ThrowHelper.ThrowArgumentException(ExceptionResource.Argument_InvalidArrayType); + } + } + } + + #endregion + + #region IEnumerable members + public Enumerator GetEnumerator() + { + return new Enumerator(this); + } + + + + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new Enumerator(this); + } + #endregion + + #region Tree Specific Operations + + private static Node GetSibling(Node node, Node parent) + { + if (parent.Left == node) + { + return parent.Right; + } + return parent.Left; + } + + // After calling InsertionBalance, we need to make sure current and parent up-to-date. + // It doesn't matter if we keep grandParent and greatGrantParent up-to-date + // because we won't need to split again in the next node. + // By the time we need to split again, everything will be correctly set. + // + private void InsertionBalance(Node current, ref Node parent, Node grandParent, Node greatGrandParent) + { + Debug.Assert(grandParent != null, "Grand parent cannot be null here!"); + bool parentIsOnRight = (grandParent.Right == parent); + bool currentIsOnRight = (parent.Right == current); + + Node newChildOfGreatGrandParent; + if (parentIsOnRight == currentIsOnRight) + { // same orientation, single rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeft(grandParent) : RotateRight(grandParent); + } + else + { // different orientaton, double rotation + newChildOfGreatGrandParent = currentIsOnRight ? RotateLeftRight(grandParent) : RotateRightLeft(grandParent); + // current node now becomes the child of greatgrandparent + parent = greatGrandParent; + } + // grand parent will become a child of either parent of current. + grandParent.IsRed = true; + newChildOfGreatGrandParent.IsRed = false; + + ReplaceChildOfNodeOrRoot(greatGrandParent, grandParent, newChildOfGreatGrandParent); + } + + private static bool Is2Node(Node node) + { + Debug.Assert(node != null, "node cannot be null!"); + return IsBlack(node) && IsNullOrBlack(node.Left) && IsNullOrBlack(node.Right); + } + + private static bool Is4Node(Node node) + { + return IsRed(node.Left) && IsRed(node.Right); + } + + private static bool IsBlack(Node node) + { + return (node != null && !node.IsRed); + } + + private static bool IsNullOrBlack(Node node) + { + return (node == null || !node.IsRed); + } + + private static bool IsRed(Node node) + { + return (node != null && node.IsRed); + } + + private static void Merge2Nodes(Node parent, Node child1, Node child2) + { + Debug.Assert(IsRed(parent), "parent must be be red"); + // combing two 2-nodes into a 4-node + parent.IsRed = false; + child1.IsRed = true; + child2.IsRed = true; + } + + // Replace the child of a parent node. + // If the parent node is null, replace the root. + private void ReplaceChildOfNodeOrRoot(Node parent, Node child, Node newChild) + { + if (parent != null) + { + if (parent.Left == child) + { + parent.Left = newChild; + } + else + { + parent.Right = newChild; + } + } + else + { + root = newChild; + } + } + + // Replace the matching node with its succesor. + private void ReplaceNode(Node match, Node parentOfMatch, Node succesor, Node parentOfSuccesor) + { + if (succesor == match) + { // this node has no successor, should only happen if right child of matching node is null. + Debug.Assert(match.Right == null, "Right child must be null!"); + succesor = match.Left; + } + else + { + Debug.Assert(parentOfSuccesor != null, "parent of successor cannot be null!"); + Debug.Assert(succesor.Left == null, "Left child of succesor must be null!"); + Debug.Assert((succesor.Right == null && succesor.IsRed) || (succesor.Right.IsRed && !succesor.IsRed), "Succesor must be in valid state"); + if (succesor.Right != null) + { + succesor.Right.IsRed = false; + } + + if (parentOfSuccesor != match) + { // detach succesor from its parent and set its right child + parentOfSuccesor.Left = succesor.Right; + succesor.Right = match.Right; + } + + succesor.Left = match.Left; + } + + if (succesor != null) + { + succesor.IsRed = match.IsRed; + } + + ReplaceChildOfNodeOrRoot(parentOfMatch, match, succesor); + + } + + internal virtual Node FindNode(T item) + { + Node current = root; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return current; + } + else + { + current = (order < 0) ? current.Left : current.Right; + } + } + + return null; + } + + //used for bithelpers. Note that this implementation is completely different + //from the Subset's. The two should not be mixed. This indexes as if the tree were an array. + //http://en.wikipedia.org/wiki/Binary_Tree#Methods_for_storing_binary_trees + internal virtual int InternalIndexOf(T item) + { + Node current = root; + int count = 0; + while (current != null) + { + int order = comparer.Compare(item, current.Item); + if (order == 0) + { + return count; + } + else + { + current = (order < 0) ? current.Left : current.Right; + count = (order < 0) ? (2 * count + 1) : (2 * count + 2); + } + } + return -1; + } + + + + internal Node FindRange(T from, T to) + { + return FindRange(from, to, true, true); + } + internal Node FindRange(T from, T to, bool lowerBoundActive, bool upperBoundActive) + { + Node current = root; + while (current != null) + { + if (lowerBoundActive && comparer.Compare(from, current.Item) > 0) + { + current = current.Right; + } + else + { + if (upperBoundActive && comparer.Compare(to, current.Item) < 0) + { + current = current.Left; + } + else + { + return current; + } + } + } + + return null; + } + + internal void UpdateVersion() + { + ++version; + } + + + private static Node RotateLeft(Node node) + { + Node x = node.Right; + node.Right = x.Left; + x.Left = node; + return x; + } + + private static Node RotateLeftRight(Node node) + { + Node child = node.Left; + Node grandChild = child.Right; + + node.Left = grandChild.Right; + grandChild.Right = node; + child.Right = grandChild.Left; + grandChild.Left = child; + return grandChild; + } + + private static Node RotateRight(Node node) + { + Node x = node.Left; + node.Left = x.Right; + x.Right = node; + return x; + } + + private static Node RotateRightLeft(Node node) + { + Node child = node.Right; + Node grandChild = child.Left; + + node.Right = grandChild.Left; + grandChild.Left = node; + child.Left = grandChild.Right; + grandChild.Right = child; + return grandChild; + } + /// + /// Testing counter that can track rotations + /// + + + private static TreeRotation RotationNeeded(Node parent, Node current, Node sibling) + { + Debug.Assert(IsRed(sibling.Left) || IsRed(sibling.Right), "sibling must have at least one red child"); + if (IsRed(sibling.Left)) + { + if (parent.Left == current) + { + return TreeRotation.RightLeftRotation; + } + return TreeRotation.RightRotation; + } + else + { + if (parent.Left == current) + { + return TreeRotation.LeftRotation; + } + return TreeRotation.LeftRightRotation; + } + } + + /// + /// Used for deep equality of SortedSet testing + /// + /// + public static IEqualityComparer> CreateSetComparer() + { + return new SortedSetEqualityComparer(); + } + + /// + /// Create a new set comparer for this set, where this set's members' equality is defined by the + /// memberEqualityComparer. Note that this equality comparer's definition of equality must be the + /// same as this set's Comparer's definition of equality + /// + public static IEqualityComparer> CreateSetComparer(IEqualityComparer memberEqualityComparer) + { + return new SortedSetEqualityComparer(memberEqualityComparer); + } + + + /// + /// Decides whether these sets are the same, given the comparer. If the EC's are the same, we can + /// just use SetEquals, but if they aren't then we have to manually check with the given comparer + /// + internal static bool SortedSetEquals(SortedSet set1, SortedSet set2, IComparer comparer) + { + // handle null cases first + if (set1 == null) + { + return (set2 == null); + } + else if (set2 == null) + { + // set1 != null + return false; + } + + if (AreComparersEqual(set1, set2)) + { + if (set1.Count != set2.Count) + return false; + + return set1.SetEquals(set2); + } + else + { + bool found = false; + foreach (T item1 in set1) + { + found = false; + foreach (T item2 in set2) + { + if (comparer.Compare(item1, item2) == 0) + { + found = true; + break; + } + } + if (!found) + return false; + } + return true; + } + + } + + + //This is a little frustrating because we can't support more sorted structures + private static bool AreComparersEqual(SortedSet set1, SortedSet set2) + { + return set1.Comparer.Equals(set2.Comparer); + } + + + private static void Split4Node(Node node) + { + node.IsRed = true; + node.Left.IsRed = false; + node.Right.IsRed = false; + } + + /// + /// Copies this to an array. Used for DebugView + /// + /// + internal T[] ToArray() + { + T[] newArray = new T[Count]; + CopyTo(newArray); + return newArray; + } + + + #endregion + + #region ISet Members + + /// + /// Transform this set into its union with the IEnumerable OTHER + ///Attempts to insert each element and rejects it if it exists. + /// NOTE: The caller object is important as UnionWith uses the Comparator + ///associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void UnionWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + + if (t != null) + VersionCheck(); + + if (s != null && t == null && this.count == 0) + { + SortedSet dummy = new SortedSet(s, this.comparer); + this.root = dummy.root; + this.count = dummy.count; + this.version++; + return; + } + + + if (s != null && t == null && AreComparersEqual(this, s) && (s.Count > this.Count / 2)) + { //this actually hurts if N is much greater than M the /2 is arbitrary + //first do a merge sort to an array. + T[] merged = new T[s.Count + this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + merged[c++] = mine.Current; + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + merged[c++] = theirs.Current; + theirsEnded = !theirs.MoveNext(); + } + } + + if (!mineEnded || !theirsEnded) + { + Enumerator remaining = (mineEnded ? theirs : mine); + do + { + merged[c++] = remaining.Current; + } while (remaining.MoveNext()); + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + AddAllElements(other); + } + } + + + private static Node ConstructRootFromSortedArray(T[] arr, int startIndex, int endIndex, Node redNode) + { + + + + //what does this do? + //you're given a sorted array... say 1 2 3 4 5 6 + //2 cases: + // If there are odd # of elements, pick the middle element (in this case 4), and compute + // its left and right branches + // If there are even # of elements, pick the left middle element, save the right middle element + // and call the function on the rest + // 1 2 3 4 5 6 -> pick 3, save 4 and call the fn on 1,2 and 5,6 + // now add 4 as a red node to the lowest element on the right branch + // 3 3 + // 1 5 -> 1 5 + // 2 6 2 4 6 + // As we're adding to the leftmost of the right branch, nesting will not hurt the red-black properties + // Leaf nodes are red if they have no sibling (if there are 2 nodes or if a node trickles + // down to the bottom + + + //the iterative way to do this ends up wasting more space than it saves in stack frames (at + //least in what i tried) + //so we're doing this recursively + //base cases are described below + int size = endIndex - startIndex + 1; + if (size == 0) + { + return null; + } + Node root = null; + if (size == 1) + { + root = new Node(arr[startIndex], false); + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 2) + { + root = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + root.Right.IsRed = true; + if (redNode != null) + { + root.Left = redNode; + } + } + else if (size == 3) + { + root = new Node(arr[startIndex + 1], false); + root.Left = new Node(arr[startIndex], false); + root.Right = new Node(arr[endIndex], false); + if (redNode != null) + { + root.Left.Left = redNode; + + } + } + else + { + int midpt = ((startIndex + endIndex) / 2); + root = new Node(arr[midpt], false); + root.Left = ConstructRootFromSortedArray(arr, startIndex, midpt - 1, redNode); + if (size % 2 == 0) + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 2, endIndex, new Node(arr[midpt + 1], true)); + } + else + { + root.Right = ConstructRootFromSortedArray(arr, midpt + 1, endIndex, null); + } + } + return root; + + } + + + /// + /// Transform this set into its intersection with the IEnumerable OTHER + /// NOTE: The caller object is important as IntersectionWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public virtual void IntersectWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return; + + //HashSet optimizations can't be done until equality comparers and comparers are related + + //Technically, this would work as well with an ISorted + SortedSet s = other as SortedSet; + TreeSubSet t = this as TreeSubSet; + if (t != null) + VersionCheck(); + //only let this happen if i am also a SortedSet, not a SubSet + if (s != null && t == null && AreComparersEqual(this, s)) + { + + + //first do a merge sort to an array. + T[] merged = new T[this.Count]; + int c = 0; + Enumerator mine = this.GetEnumerator(); + Enumerator theirs = s.GetEnumerator(); + bool mineEnded = !mine.MoveNext(), theirsEnded = !theirs.MoveNext(); + T max = Max; + T min = Min; + + while (!mineEnded && !theirsEnded && Comparer.Compare(theirs.Current, max) <= 0) + { + int comp = Comparer.Compare(mine.Current, theirs.Current); + if (comp < 0) + { + mineEnded = !mine.MoveNext(); + } + else if (comp == 0) + { + merged[c++] = theirs.Current; + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + else + { + theirsEnded = !theirs.MoveNext(); + } + } + + //now merged has all c elements + + //safe to gc the root, we have all the elements + root = null; + + root = SortedSet.ConstructRootFromSortedArray(merged, 0, c - 1, null); + count = c; + version++; + } + else + { + IntersectWithEnumerable(other); + } + } + + internal virtual void IntersectWithEnumerable(IEnumerable other) + { + // + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + AddAllElements(toSave); + + } + + + + /// + /// Transform this set into its complement with the IEnumerable OTHER + /// NOTE: The caller object is important as ExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void ExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (count == 0) + return; + + if (other == this) + { + this.Clear(); + return; + } + + SortedSet asSorted = other as SortedSet; + + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + //outside range, no point doing anything + if (!(comparer.Compare(asSorted.Max, this.Min) < 0 || comparer.Compare(asSorted.Min, this.Max) > 0)) + { + T min = this.Min; + T max = this.Max; + foreach (T item in other) + { + if (comparer.Compare(item, min) < 0) + continue; + if (comparer.Compare(item, max) > 0) + break; + Remove(item); + } + } + + } + else + { + RemoveAllElements(other); + } + } + + /// + /// Transform this set so it contains elements in THIS or OTHER but not both + /// NOTE: The caller object is important as SymmetricExceptWith uses the + /// comparator associated with THIS to check equality + /// Throws ArgumentNullException if OTHER is null + /// + /// + public void SymmetricExceptWith(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + { + this.UnionWith(other); + return; + } + + if (other == this) + { + this.Clear(); + return; + } + + + SortedSet asSorted = other as SortedSet; + +#if USING_HASH_SET + HashSet asHash = other as HashSet; +#endif + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + SymmetricExceptWithSameEC(asSorted); + } +#if USING_HASH_SET + else if (asHash != null && this.comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + SymmetricExceptWithSameEC(asHash); + } +#endif + else + { + //need perf improvement on this + T[] elements = (new List(other)).ToArray(); + Array.Sort(elements, this.Comparer); + SymmetricExceptWithSameEC(elements); + } + } + + //OTHER must be a set + internal void SymmetricExceptWithSameEC(ISet other) + { + foreach (T item in other) + { + //yes, it is classier to say + //if (!this.Remove(item))this.Add(item); + //but this ends up saving on rotations + if (this.Contains(item)) + { + this.Remove(item); + } + else + { + this.Add(item); + } + } + } + + //OTHER must be a sorted array + internal void SymmetricExceptWithSameEC(T[] other) + { + if (other.Length == 0) + { + return; + } + T last = other[0]; + for (int i = 0; i < other.Length; i++) + { + while (i < other.Length && i != 0 && comparer.Compare(other[i], last) == 0) + i++; + if (i >= other.Length) + break; + if (this.Contains(other[i])) + { + this.Remove(other[i]); + } + else + { + this.Add(other[i]); + } + last = other[i]; + } + } + + + /// + /// Checks whether this Tree is a subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return true; + + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count > asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + else + { + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount >= 0); + } + } + + private bool IsSubsetOfSortedSetWithSameEC(SortedSet asSorted) + { + SortedSet prunedOther = asSorted.GetViewBetween(this.Min, this.Max); + foreach (T item in this) + { + if (!prunedOther.Contains(item)) + return false; + } + return true; + + } + + + /// + /// Checks whether this Tree is a proper subset of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSubsetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null) + { + if (Count == 0) + return (other as ICollection).Count > 0; + } + + +#if USING_HASH_SET + //do it one way for HashSets + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSupersetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count >= asSorted.Count) + return false; + return IsSubsetOfSortedSetWithSameEC(asSorted); + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN). + ElementCount result = CheckUniqueAndUnfoundElements(other, false); + return (result.uniqueCount == Count && result.unfoundCount > 0); + } + + + /// + /// Checks whether this Tree is a super set of the IEnumerable other + /// + /// + /// + public bool IsSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + + //do it one way for HashSets +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsSubsetOf(this); + } +#endif + //another for sorted sets with the same comparer + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + if (this.Count < asSorted.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + //and a third for everything else + return ContainsAllElements(other); + } + + /// + /// Checks whether this Tree is a proper super set of the IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool IsProperSupersetOf(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (Count == 0) + return false; + + if ((other as ICollection) != null && (other as ICollection).Count == 0) + return true; + +#if USING_HASH_SET + //do it one way for HashSets + + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.IsProperSubsetOf(this); + } +#endif + //another way for sorted sets + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(asSorted, this)) + { + if (asSorted.Count >= this.Count) + return false; + SortedSet pruned = GetViewBetween(asSorted.Min, asSorted.Max); + foreach (T item in asSorted) + { + if (!pruned.Contains(item)) + return false; + } + return true; + } + + + //worst case: mark every element in my set and see if i've counted all + //O(MlogN) + //slight optimization, put it into a HashSet and then check can do it in O(N+M) + //but slower in better cases + wastes space + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount < Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has all elements in common with IEnumerable other + /// + /// + /// + [System.Security.SecuritySafeCritical] + public bool SetEquals(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.SetEquals(this); + } +#endif + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted)) + { + IEnumerator mine = this.GetEnumerator(); + IEnumerator theirs = asSorted.GetEnumerator(); + bool mineEnded = !mine.MoveNext(); + bool theirsEnded = !theirs.MoveNext(); + while (!mineEnded && !theirsEnded) + { + if (Comparer.Compare(mine.Current, theirs.Current) != 0) + { + return false; + } + mineEnded = !mine.MoveNext(); + theirsEnded = !theirs.MoveNext(); + } + return mineEnded && theirsEnded; + } + + //worst case: mark every element in my set and see if i've counted all + //O(N) by size of other + ElementCount result = CheckUniqueAndUnfoundElements(other, true); + return (result.uniqueCount == Count && result.unfoundCount == 0); + } + + + + /// + /// Checks whether this Tree has any elements in common with IEnumerable other + /// + /// + /// + public bool Overlaps(IEnumerable other) + { + if (other == null) + { + throw new ArgumentNullException("other"); + } + + if (this.Count == 0) + return false; + + if ((other as ICollection != null) && (other as ICollection).Count == 0) + return false; + + SortedSet asSorted = other as SortedSet; + if (asSorted != null && AreComparersEqual(this, asSorted) && (comparer.Compare(Min, asSorted.Max) > 0 || comparer.Compare(Max, asSorted.Min) < 0)) + { + return false; + } +#if USING_HASH_SET + HashSet asHash = other as HashSet; + if (asHash != null && comparer.Equals(Comparer.Default) && asHash.Comparer.Equals(EqualityComparer.Default)) { + return asHash.Overlaps(this); + } +#endif + foreach (T item in other) + { + if (this.Contains(item)) + { + return true; + } + } + return false; + } + + /// + /// This works similar to HashSet's CheckUniqueAndUnfound (description below), except that the bit + /// array maps differently than in the HashSet. We can only use this for the bulk boolean checks. + /// + /// Determines counts that can be used to determine equality, subset, and superset. This + /// is only used when other is an IEnumerable and not a HashSet. If other is a HashSet + /// these properties can be checked faster without use of marking because we can assume + /// other has no duplicates. + /// + /// The following count checks are performed by callers: + /// 1. Equals: checks if unfoundCount = 0 and uniqueFoundCount = Count; i.e. everything + /// in other is in this and everything in this is in other + /// 2. Subset: checks if unfoundCount >= 0 and uniqueFoundCount = Count; i.e. other may + /// have elements not in this and everything in this is in other + /// 3. Proper subset: checks if unfoundCount > 0 and uniqueFoundCount = Count; i.e + /// other must have at least one element not in this and everything in this is in other + /// 4. Proper superset: checks if unfound count = 0 and uniqueFoundCount strictly less + /// than Count; i.e. everything in other was in this and this had at least one element + /// not contained in other. + /// + /// An earlier implementation used delegates to perform these checks rather than returning + /// an ElementCount struct; however this was changed due to the perf overhead of delegates. + /// + /// + /// Allows us to finish faster for equals and proper superset + /// because unfoundCount must be 0. + /// + // + // + // + // + // + // + [System.Security.SecurityCritical] + private unsafe ElementCount CheckUniqueAndUnfoundElements(IEnumerable other, bool returnIfUnfound) + { + ElementCount result; + + // need special case in case this has no elements. + if (Count == 0) + { + int numElementsInOther = 0; + foreach (T item in other) + { + numElementsInOther++; + // break right away, all we want to know is whether other has 0 or 1 elements + break; + } + result.uniqueCount = 0; + result.unfoundCount = numElementsInOther; + return result; + } + + + int originalLastIndex = Count; + int intArrayLength = BitHelper.ToIntArrayLength(originalLastIndex); + + BitHelper bitHelper; + if (intArrayLength <= StackAllocThreshold) + { + int* bitArrayPtr = stackalloc int[intArrayLength]; + bitHelper = new BitHelper(bitArrayPtr, intArrayLength); + } + else + { + int[] bitArray = new int[intArrayLength]; + bitHelper = new BitHelper(bitArray, intArrayLength); + } + + // count of items in other not found in this + int unfoundCount = 0; + // count of unique items in other found in this + int uniqueFoundCount = 0; + + foreach (T item in other) + { + int index = InternalIndexOf(item); + if (index >= 0) + { + if (!bitHelper.IsMarked(index)) + { + // item hasn't been seen yet + bitHelper.MarkBit(index); + uniqueFoundCount++; + } + } + else + { + unfoundCount++; + if (returnIfUnfound) + { + break; + } + } + } + + result.uniqueCount = uniqueFoundCount; + result.unfoundCount = unfoundCount; + return result; + } + public int RemoveWhere(Predicate match) + { + if (match == null) + { + throw new ArgumentNullException("match"); + } + List matches = new List(this.Count); + + BreadthFirstTreeWalk(delegate (Node n) + { + if (match(n.Item)) + { + matches.Add(n.Item); + } + return true; + }); + // reverse breadth first to (try to) incur low cost + int actuallyRemoved = 0; + for (int i = matches.Count - 1; i >= 0; i--) + { + if (this.Remove(matches[i])) + { + actuallyRemoved++; + } + } + + return actuallyRemoved; + + } + + + #endregion + + #region ISorted Members + + + public T Min + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }); + return ret; + } + } + + public T Max + { + get + { + T ret = default(T); + InOrderTreeWalk(delegate (SortedSet.Node n) { ret = n.Item; return false; }, true); + return ret; + } + } + + public IEnumerable Reverse() + { + Enumerator e = new Enumerator(this, true); + while (e.MoveNext()) + { + yield return e.Current; + } + } + + + /// + /// Returns a subset of this tree ranging from values lBound to uBound + /// Any changes made to the subset reflect in the actual tree + /// + /// Lowest Value allowed in the subset + /// Highest Value allowed in the subset + public virtual SortedSet GetViewBetween(T lowerValue, T upperValue) + { + if (Comparer.Compare(lowerValue, upperValue) > 0) + { + throw new ArgumentException("lowerBound is greater than upperBound"); + } + return new TreeSubSet(this, lowerValue, upperValue, true, true); + } + +#if DEBUG + + /// + /// debug status to be checked whenever any operation is called + /// + /// + internal virtual bool versionUpToDate() + { + return true; + } +#endif + + + /// + /// This class represents a subset view into the tree. Any changes to this view + /// are reflected in the actual tree. Uses the Comparator of the underlying tree. + /// + /// +#if !FEATURE_NETCORE + [Serializable] + internal sealed class TreeSubSet : SortedSet, ISerializable, IDeserializationCallback + { +#else + internal sealed class TreeSubSet : SortedSet { +#endif + SortedSet underlying; + T min, max; + //these exist for unbounded collections + //for instance, you could allow this subset to be defined for i>10. The set will throw if + //anything <=10 is added, but there is no upperbound. These features Head(), Tail(), were punted + //in the spec, and are not available, but the framework is there to make them available at some point. + bool lBoundActive, uBoundActive; + //used to see if the count is out of date + + +#if DEBUG + internal override bool versionUpToDate() + { + return (this.version == underlying.version); + } +#endif + + public TreeSubSet(SortedSet Underlying, T Min, T Max, bool lowerBoundActive, bool upperBoundActive) + : base(Underlying.Comparer) + { + underlying = Underlying; + min = Min; + max = Max; + lBoundActive = lowerBoundActive; + uBoundActive = upperBoundActive; + root = underlying.FindRange(min, max, lBoundActive, uBoundActive); // root is first element within range + count = 0; + version = -1; + VersionCheckImpl(); + } + +#if !FEATURE_NETCORE + /// + /// For serialization and deserialization + /// + private TreeSubSet() + { + comparer = null; + } + + + [SuppressMessage("Microsoft.Usage", "CA2236:CallBaseClassMethodsOnISerializableTypes", Justification = "special case TreeSubSet serialization")] + private TreeSubSet(SerializationInfo info, StreamingContext context) + { + siInfo = info; + OnDeserializationImpl(info); + } +#endif // !FEATURE_NETCORE + + /// + /// Additions to this tree need to be added to the underlying tree as well + /// + + internal override bool AddIfNotPresent(T item) + { + + if (!IsWithinRange(item)) + { + ThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.collection); + } + + bool ret = underlying.AddIfNotPresent(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + + return ret; + } + + + public override bool Contains(T item) + { + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.Contains(item); + } + + internal override bool DoRemove(T item) + { // todo: uppercase this and others + + if (!IsWithinRange(item)) + { + return false; + } + + bool ret = underlying.Remove(item); + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return ret; + } + + public override void Clear() + { + + + if (count == 0) + { + return; + } + + List toRemove = new List(); + BreadthFirstTreeWalk(delegate (Node n) { toRemove.Add(n.Item); return true; }); + while (toRemove.Count != 0) + { + underlying.Remove(toRemove[toRemove.Count - 1]); + toRemove.RemoveAt(toRemove.Count - 1); + } + root = null; + count = 0; + version = underlying.version; + } + + + internal override bool IsWithinRange(T item) + { + + int comp = (lBoundActive ? Comparer.Compare(min, item) : -1); + if (comp > 0) + { + return false; + } + comp = (uBoundActive ? Comparer.Compare(max, item) : 1); + if (comp < 0) + { + return false; + } + return true; + } + + internal override bool InOrderTreeWalk(TreeWalkPredicate action, Boolean reverse) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + // The maximum height of a red-black tree is 2*lg(n+1). + // See page 264 of "Introduction to algorithms" by Thomas H. Cormen + Stack stack = new Stack(2 * (int)SortedSet.log2(count + 1)); //this is not exactly right if count is out of date, but the stack can grow + Node current = root; + while (current != null) + { + if (IsWithinRange(current.Item)) + { + stack.Push(current); + current = (reverse ? current.Right : current.Left); + } + else if (lBoundActive && Comparer.Compare(min, current.Item) > 0) + { + current = current.Right; + } + else + { + current = current.Left; + } + } + + while (stack.Count != 0) + { + current = stack.Pop(); + if (!action(current)) + { + return false; + } + + Node node = (reverse ? current.Left : current.Right); + while (node != null) + { + if (IsWithinRange(node.Item)) + { + stack.Push(node); + node = (reverse ? node.Right : node.Left); + } + else if (lBoundActive && Comparer.Compare(min, node.Item) > 0) + { + node = node.Right; + } + else + { + node = node.Left; + } + } + } + return true; + } + + internal override bool BreadthFirstTreeWalk(TreeWalkPredicate action) + { + VersionCheck(); + + if (root == null) + { + return true; + } + + List processQueue = new List(); + processQueue.Add(root); + Node current; + + while (processQueue.Count != 0) + { + current = processQueue[0]; + processQueue.RemoveAt(0); + if (IsWithinRange(current.Item) && !action(current)) + { + return false; + } + if (current.Left != null && (!lBoundActive || Comparer.Compare(min, current.Item) < 0)) + { + processQueue.Add(current.Left); + } + if (current.Right != null && (!uBoundActive || Comparer.Compare(max, current.Item) > 0)) + { + processQueue.Add(current.Right); + } + + } + return true; + } + + internal override SortedSet.Node FindNode(T item) + { + + if (!IsWithinRange(item)) + { + return null; + } + VersionCheck(); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return base.FindNode(item); + } + + //this does indexing in an inefficient way compared to the actual sortedset, but it saves a + //lot of space + internal override int InternalIndexOf(T item) + { + int count = -1; + foreach (T i in this) + { + count++; + if (Comparer.Compare(item, i) == 0) + return count; + } +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + return -1; + } + /// + /// checks whether this subset is out of date. updates if necessary. + /// + internal override void VersionCheck() + { + VersionCheckImpl(); + } + + private void VersionCheckImpl() + { + Debug.Assert(underlying != null, "Underlying set no longer exists"); + if (this.version != underlying.version) + { + this.root = underlying.FindRange(min, max, lBoundActive, uBoundActive); + this.version = underlying.version; + count = 0; + InOrderTreeWalk(delegate (Node n) { count++; return true; }); + } + } + + + + //This passes functionality down to the underlying tree, clipping edges if necessary + //There's nothing gained by having a nested subset. May as well draw it from the base + //Cannot increase the bounds of the subset, can only decrease it + public override SortedSet GetViewBetween(T lowerValue, T upperValue) + { + + if (lBoundActive && Comparer.Compare(min, lowerValue) > 0) + { + //lBound = min; + throw new ArgumentOutOfRangeException("lowerValue"); + } + if (uBoundActive && Comparer.Compare(max, upperValue) < 0) + { + //uBound = max; + throw new ArgumentOutOfRangeException("upperValue"); + } + TreeSubSet ret = (TreeSubSet)underlying.GetViewBetween(lowerValue, upperValue); + return ret; + } + + internal override void IntersectWithEnumerable(IEnumerable other) + { + + List toSave = new List(this.Count); + foreach (T item in other) + { + if (this.Contains(item)) + { + toSave.Add(item); + this.Remove(item); + } + } + this.Clear(); + this.AddAllElements(toSave); +#if DEBUG + Debug.Assert(this.versionUpToDate() && this.root == this.underlying.FindRange(min, max)); +#endif + } + +#if !FEATURE_NETCORE + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected override void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + info.AddValue(maxName, max, typeof(T)); + info.AddValue(minName, min, typeof(T)); + info.AddValue(lBoundActiveName, lBoundActive); + info.AddValue(uBoundActiveName, uBoundActive); + base.GetObjectData(info, context); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + //don't do anything here as its already been done by the constructor + //OnDeserialization(sender); + + } + + protected override void OnDeserialization(Object sender) + { + OnDeserializationImpl(sender); + } + + private void OnDeserializationImpl(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + max = (T)siInfo.GetValue(maxName, typeof(T)); + min = (T)siInfo.GetValue(minName, typeof(T)); + lBoundActive = siInfo.GetBoolean(lBoundActiveName); + uBoundActive = siInfo.GetBoolean(uBoundActiveName); + underlying = new SortedSet(); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + underlying.Add(items[i]); + } + } + underlying.version = siInfo.GetInt32(VersionName); + count = underlying.count; + version = underlying.version - 1; + VersionCheck(); //this should update the count to be right and update root to be right + + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + + } +#endif // !FEATURE_NETCORE + + + + + } + + + #endregion + + #region Serialization methods + +#if !FEATURE_NETCORE + // LinkDemand here is unnecessary as this is a methodimpl and linkdemand from the interface should suffice + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + protected virtual void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + } + + info.AddValue(CountName, count); //This is the length of the bucket array. + info.AddValue(ComparerName, comparer, typeof(IComparer)); + info.AddValue(VersionName, version); + + if (root != null) + { + T[] items = new T[Count]; + CopyTo(items, 0); + info.AddValue(ItemsName, items, typeof(T[])); + } + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + protected virtual void OnDeserialization(Object sender) + { + if (comparer != null) + { + return; //Somebody had a dependency on this class and fixed us up before the ObjectManager got to it. + } + + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + comparer = (IComparer)siInfo.GetValue(ComparerName, typeof(IComparer)); + int savedCount = siInfo.GetInt32(CountName); + + if (savedCount != 0) + { + T[] items = (T[])siInfo.GetValue(ItemsName, typeof(T[])); + + if (items == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MissingValues); + } + + for (int i = 0; i < items.Length; i++) + { + Add(items[i]); + } + } + + version = siInfo.GetInt32(VersionName); + if (count != savedCount) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_MismatchedCount); + } + siInfo = null; + } +#endif //!FEATURE_NETCORE + #endregion + + #region Helper Classes + internal class Node + { + public bool IsRed; + public T Item; + public Node Left; + public Node Right; + + public Node(T item) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + IsRed = true; + } + + public Node(T item, bool isRed) + { + // The default color will be red, we never need to create a black node directly. + this.Item = item; + this.IsRed = isRed; + } + } + + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes", Justification = "not an expected scenario")] +#if !FEATURE_NETCORE + [Serializable] + public struct Enumerator : IEnumerator, IEnumerator, ISerializable, IDeserializationCallback + { +#else + public struct Enumerator : IEnumerator, IEnumerator { +#endif + private SortedSet tree; + private int version; + + + private Stack.Node> stack; + private SortedSet.Node current; + static SortedSet.Node dummyNode = new SortedSet.Node(default(T)); + + private bool reverse; + +#if !FEATURE_NETCORE + private SerializationInfo siInfo; +#endif + internal Enumerator(SortedSet set) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + reverse = false; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + } + + internal Enumerator(SortedSet set, bool reverse) + { + tree = set; + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + version = tree.version; + + // 2lg(n + 1) is the maximum height + stack = new Stack.Node>(2 * (int)SortedSet.log2(set.Count + 1)); + current = null; + this.reverse = reverse; +#if !FEATURE_NETCORE + siInfo = null; +#endif + Intialize(); + + } + +#if !FEATURE_NETCORE + private Enumerator(SerializationInfo info, StreamingContext context) + { + tree = null; + version = -1; + current = null; + reverse = false; + stack = null; + this.siInfo = info; + } + + void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) + { + GetObjectData(info, context); + } + + private void GetObjectData(SerializationInfo info, StreamingContext context) + { + if (info == null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.info); + + } + info.AddValue(TreeName, tree, typeof(SortedSet)); + info.AddValue(EnumVersionName, version); + info.AddValue(ReverseName, reverse); + info.AddValue(EnumStartName, !NotStartedOrEnded); + info.AddValue(NodeValueName, (current == null ? dummyNode.Item : current.Item), typeof(T)); + } + + void IDeserializationCallback.OnDeserialization(Object sender) + { + OnDeserialization(sender); + } + + private void OnDeserialization(Object sender) + { + if (siInfo == null) + { + ThrowHelper.ThrowSerializationException(ExceptionResource.Serialization_InvalidOnDeser); + } + + tree = (SortedSet)siInfo.GetValue(TreeName, typeof(SortedSet)); + version = siInfo.GetInt32(EnumVersionName); + reverse = siInfo.GetBoolean(ReverseName); + bool EnumStarted = siInfo.GetBoolean(EnumStartName); + stack = new Stack.Node>(2 * (int)SortedSet.log2(tree.Count + 1)); + current = null; + if (EnumStarted) + { + T item = (T)siInfo.GetValue(NodeValueName, typeof(T)); + Intialize(); + //go until it reaches the value we want + while (this.MoveNext()) + { + if (tree.Comparer.Compare(this.Current, item) == 0) + break; + } + } + + + } +#endif //!FEATURE_NETCORE + + + private void Intialize() + { + + + current = null; + SortedSet.Node node = tree.root; + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (next == null || !tree.IsWithinRange(next.Item)) + { + node = other; + } + else + { + node = next; + } + } + } + + public bool MoveNext() + { + + //this is a hack to make sure that the underlying subset has not been changed since + // + tree.VersionCheck(); + + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + if (stack.Count == 0) + { + current = null; + return false; + } + + current = stack.Pop(); + SortedSet.Node node = (reverse ? current.Left : current.Right); + + Node next = null, other = null; + while (node != null) + { + next = (reverse ? node.Right : node.Left); + other = (reverse ? node.Left : node.Right); + if (tree.IsWithinRange(node.Item)) + { + stack.Push(node); + node = next; + } + else if (other == null || !tree.IsWithinRange(other.Item)) + { + node = next; + } + else + { + node = other; + } + } + return true; + } + + public void Dispose() + { + } + + public T Current + { + get + { + if (current != null) + { + return current.Item; + } + return default(T); + } + } + + object IEnumerator.Current + { + get + { + if (current == null) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumOpCantHappen); + } + + return current.Item; + } + } + + internal bool NotStartedOrEnded + { + get + { + return current == null; + } + } + + internal void Reset() + { + if (version != tree.version) + { + ThrowHelper.ThrowInvalidOperationException(ExceptionResource.InvalidOperation_EnumFailedVersion); + } + + stack.Clear(); + Intialize(); + } + + void IEnumerator.Reset() + { + Reset(); + } + + + + + } + + + + internal struct ElementCount + { + internal int uniqueCount; + internal int unfoundCount; + } + #endregion + + #region misc + + /// + /// Searches the set for a given value and returns the equal value it finds, if any. + /// + /// The value to search for. + /// The value from the set that the search found, or the default value of when the search yielded no match. + /// A value indicating whether the search was successful. + /// + /// This can be useful when you want to reuse a previously stored reference instead of + /// a newly constructed one (so that more sharing of references can occur) or to look up + /// a value that has more complete data than the value you currently have, although their + /// comparer functions indicate they are equal. + /// + public bool TryGetValue(T equalValue, out T actualValue) + { + Node node = FindNode(equalValue); + if (node != null) + { + actualValue = node.Item; + return true; + } + actualValue = default(T); + return false; + } + + // used for set checking operations (using enumerables) that rely on counting + private static int log2(int value) + { + //Contract.Requires(value>0) + int c = 0; + while (value > 0) + { + c++; + value >>= 1; + } + return c; + } + #endregion + + + } + + /// + /// A class that generates an IEqualityComparer for this SortedSet. Requires that the definition of + /// equality defined by the IComparer for this SortedSet be consistent with the default IEqualityComparer + /// for the type T. If not, such an IEqualityComparer should be provided through the constructor. + /// + internal class SortedSetEqualityComparer : IEqualityComparer> + { + private IComparer comparer; + private IEqualityComparer e_comparer; + + public SortedSetEqualityComparer() : this(null, null) { } + + public SortedSetEqualityComparer(IComparer comparer) : this(comparer, null) { } + + public SortedSetEqualityComparer(IEqualityComparer memberEqualityComparer) : this(null, memberEqualityComparer) { } + + /// + /// Create a new SetEqualityComparer, given a comparer for member order and another for member equality (these + /// must be consistent in their definition of equality) + /// + public SortedSetEqualityComparer(IComparer comparer, IEqualityComparer memberEqualityComparer) + { + if (comparer == null) + this.comparer = Comparer.Default; + else + this.comparer = comparer; + if (memberEqualityComparer == null) + e_comparer = EqualityComparer.Default; + else + e_comparer = memberEqualityComparer; + } + + + // using comparer to keep equals properties in tact; don't want to choose one of the comparers + public bool Equals(SortedSet x, SortedSet y) + { + return SortedSet.SortedSetEquals(x, y, comparer); + } + //IMPORTANT: this part uses the fact that GetHashCode() is consistent with the notion of equality in + //the set + public int GetHashCode(SortedSet obj) + { + int hashCode = 0; + if (obj != null) + { + foreach (T t in obj) + { + hashCode = hashCode ^ (e_comparer.GetHashCode(t) & 0x7FFFFFFF); + } + } // else returns hashcode of 0 for null HashSets + return hashCode; + } + + // Equals method for the comparer itself. + public override bool Equals(Object obj) + { + SortedSetEqualityComparer comparer = obj as SortedSetEqualityComparer; + if (comparer == null) + { + return false; + } + return (this.comparer == comparer.comparer); + } + + public override int GetHashCode() + { + return comparer.GetHashCode() ^ e_comparer.GetHashCode(); + } + + + } + +} + + + +#pragma warning restore CS8604 // 引用类型参数可能为 null。 +#pragma warning restore CS8602 // 解引用可能出现空引用。 +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#pragma warning restore CS8625 // 无法将 null 字面量转换为非 null 的引用类型。 +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8601 // 引用类型赋值可能为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs b/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs new file mode 100644 index 0000000..205dd1c --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/ThrowHelper.cs @@ -0,0 +1,382 @@ +#if NET35 +#pragma warning disable IDE0059 // 不需要赋值 +#pragma warning disable CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 + +namespace System +{ + // This file defines an internal class used to throw exceptions in BCL code. + // The main purpose is to reduce code size. + // + // The old way to throw an exception generates quite a lot IL code and assembly code. + // Following is an example: + // C# source + // throw new ArgumentNullException("key", SR.GetString("ArgumentNull_Key")); + // IL code: + // IL_0003: ldstr "key" + // IL_0008: ldstr "ArgumentNull_Key" + // IL_000d: call string System.Environment::GetResourceString(string) + // IL_0012: newobj instance void System.ArgumentNullException::.ctor(string,string) + // IL_0017: throw + // which is 21bytes in IL. + // + // So we want to get rid of the ldstr and call to Environment.GetResource in IL. + // In order to do that, I created two enums: ExceptionResource, ExceptionArgument to represent the + // argument name and resource name in a small integer. The source code will be changed to + // ThrowHelper.ThrowArgumentNullException(ExceptionArgument.key, ExceptionResource.ArgumentNull_Key); + // + // The IL code will be 7 bytes. + // IL_0008: ldc.i4.4 + // IL_0009: ldc.i4.4 + // IL_000a: call void System.ThrowHelper::ThrowArgumentNullException(valuetype System.ExceptionArgument) + // IL_000f: ldarg.0 + // + // This will also reduce the Jitted code size a lot. + // + // It is very important we do this for generic classes because we can easily generate the same code + // multiple times for different instantiation. + // + // < + + + + + + + + + + +#if !SILVERLIGHT + using System.Runtime.Serialization; +#endif + + using System.Diagnostics; + internal static class ThrowHelper + { + internal static void ThrowWrongKeyTypeArgumentException(object key, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", key, targetType), "key"); + } + + internal static void ThrowWrongValueTypeArgumentException(object value, Type targetType) + { + throw new ArgumentException(SR.GetString("SR.Arg_WrongType", value, targetType), "value"); + } + + internal static void ThrowKeyNotFoundException() + { + throw new System.Collections.Generic.KeyNotFoundException(); + } + + internal static void ThrowArgumentException(ExceptionResource resource) + { + throw new ArgumentException(SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowArgumentNullException(ExceptionArgument argument) + { + throw new ArgumentNullException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument)); + } + + internal static void ThrowArgumentOutOfRangeException(ExceptionArgument argument, ExceptionResource resource) + { + throw new ArgumentOutOfRangeException(GetArgumentName(argument), SR.GetString(GetResourceName(resource))); + } + + internal static void ThrowInvalidOperationException(ExceptionResource resource) + { + throw new InvalidOperationException(SR.GetString(GetResourceName(resource))); + } + +#if !SILVERLIGHT + internal static void ThrowSerializationException(ExceptionResource resource) + { + throw new SerializationException(SR.GetString(GetResourceName(resource))); + } +#endif + + internal static void ThrowNotSupportedException(ExceptionResource resource) + { + throw new NotSupportedException(SR.GetString(GetResourceName(resource))); + } + + // Allow nulls for reference types and Nullable, but not for value types. + internal static void IfNullAndNullsAreIllegalThenThrow(object value, ExceptionArgument argName) + { + // Note that default(T) is not equal to null for value types except when T is Nullable. + if (value == null && !(default(T) == null)) + ThrowHelper.ThrowArgumentNullException(argName); + } + + // + // This function will convert an ExceptionArgument enum value to the argument name string. + // + internal static string GetArgumentName(ExceptionArgument argument) + { + string argumentName = null; + + switch (argument) + { + case ExceptionArgument.array: + argumentName = "array"; + break; + + case ExceptionArgument.arrayIndex: + argumentName = "arrayIndex"; + break; + + case ExceptionArgument.capacity: + argumentName = "capacity"; + break; + + case ExceptionArgument.collection: + argumentName = "collection"; + break; + + case ExceptionArgument.converter: + argumentName = "converter"; + break; + + case ExceptionArgument.count: + argumentName = "count"; + break; + + case ExceptionArgument.dictionary: + argumentName = "dictionary"; + break; + + case ExceptionArgument.index: + argumentName = "index"; + break; + + case ExceptionArgument.info: + argumentName = "info"; + break; + + case ExceptionArgument.key: + argumentName = "key"; + break; + + case ExceptionArgument.match: + argumentName = "match"; + break; + + case ExceptionArgument.obj: + argumentName = "obj"; + break; + + case ExceptionArgument.queue: + argumentName = "queue"; + break; + + case ExceptionArgument.stack: + argumentName = "stack"; + break; + + case ExceptionArgument.startIndex: + argumentName = "startIndex"; + break; + + case ExceptionArgument.value: + argumentName = "value"; + break; + + case ExceptionArgument.item: + argumentName = "item"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return argumentName; + } + + // + // This function will convert an ExceptionResource enum value to the resource string. + // + internal static string GetResourceName(ExceptionResource resource) + { + string resourceName = null; + + switch (resource) + { + case ExceptionResource.Argument_ImplementIComparable: + resourceName = "SR.Argument_ImplementIComparable"; + break; + + case ExceptionResource.Argument_AddingDuplicate: + resourceName = "SR.Argument_AddingDuplicate"; + break; + + case ExceptionResource.ArgumentOutOfRange_Index: + resourceName = "SR.ArgumentOutOfRange_Index"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNum: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNum"; + break; + + case ExceptionResource.ArgumentOutOfRange_NeedNonNegNumRequired: + resourceName = "SR.ArgumentOutOfRange_NeedNonNegNumRequired"; + break; + + case ExceptionResource.ArgumentOutOfRange_SmallCapacity: + resourceName = "SR.ArgumentOutOfRange_SmallCapacity"; + break; + + case ExceptionResource.Arg_ArrayPlusOffTooSmall: + resourceName = "SR.Arg_ArrayPlusOffTooSmall"; + break; + + case ExceptionResource.Arg_RankMultiDimNotSupported: + resourceName = "SR.Arg_MultiRank"; + break; + + case ExceptionResource.Arg_NonZeroLowerBound: + resourceName = "SR.Arg_NonZeroLowerBound"; + break; + + case ExceptionResource.Argument_InvalidArrayType: + resourceName = "SR.Invalid_Array_Type"; + break; + + case ExceptionResource.Argument_InvalidOffLen: + resourceName = "SR.Argument_InvalidOffLen"; + break; + + case ExceptionResource.InvalidOperation_CannotRemoveFromStackOrQueue: + resourceName = "SR.InvalidOperation_CannotRemoveFromStackOrQueue"; + break; + + case ExceptionResource.InvalidOperation_EmptyCollection: + resourceName = "SR.InvalidOperation_EmptyCollection"; + break; + + case ExceptionResource.InvalidOperation_EmptyQueue: + resourceName = "SR.InvalidOperation_EmptyQueue"; + break; + + case ExceptionResource.InvalidOperation_EnumOpCantHappen: + resourceName = "SR.InvalidOperation_EnumOpCantHappen"; + break; + + case ExceptionResource.InvalidOperation_EnumFailedVersion: + resourceName = "SR.InvalidOperation_EnumFailedVersion"; + break; + + case ExceptionResource.InvalidOperation_EmptyStack: + resourceName = "SR.InvalidOperation_EmptyStack"; + break; + + case ExceptionResource.InvalidOperation_EnumNotStarted: + resourceName = "SR.InvalidOperation_EnumNotStarted"; + break; + + case ExceptionResource.InvalidOperation_EnumEnded: + resourceName = "SR.InvalidOperation_EnumEnded"; + break; + + case ExceptionResource.NotSupported_KeyCollectionSet: + resourceName = "SR.NotSupported_KeyCollectionSet"; + break; + + case ExceptionResource.NotSupported_SortedListNestedWrite: + resourceName = "SR.NotSupported_SortedListNestedWrite"; + break; + +#if !SILVERLIGHT + case ExceptionResource.Serialization_InvalidOnDeser: + resourceName = "SR.Serialization_InvalidOnDeser"; + break; + + case ExceptionResource.Serialization_MissingValues: + resourceName = "SR.Serialization_MissingValues"; + break; + + case ExceptionResource.Serialization_MismatchedCount: + resourceName = "SR.Serialization_MismatchedCount"; + break; +#endif + + case ExceptionResource.NotSupported_ValueCollectionSet: + resourceName = "SR.NotSupported_ValueCollectionSet"; + break; + + default: + Debug.Assert(false, "The enum value is not defined, please checked ExceptionArgumentName Enum."); + return string.Empty; + } + + return resourceName; + } + + } + + // + // The convention for this enum is using the argument name as the enum name + // + internal enum ExceptionArgument + { + obj, + dictionary, + array, + info, + key, + collection, + match, + converter, + queue, + stack, + capacity, + index, + startIndex, + value, + count, + arrayIndex, + item, + } + + // + // The convention for this enum is using the resource name as the enum name + // + internal enum ExceptionResource + { + Argument_ImplementIComparable, + ArgumentOutOfRange_NeedNonNegNum, + ArgumentOutOfRange_NeedNonNegNumRequired, + Arg_ArrayPlusOffTooSmall, + Argument_AddingDuplicate, + Serialization_InvalidOnDeser, + Serialization_MismatchedCount, + Serialization_MissingValues, + Arg_RankMultiDimNotSupported, + Arg_NonZeroLowerBound, + Argument_InvalidArrayType, + NotSupported_KeyCollectionSet, + ArgumentOutOfRange_SmallCapacity, + ArgumentOutOfRange_Index, + Argument_InvalidOffLen, + NotSupported_ReadOnlyCollection, + InvalidOperation_CannotRemoveFromStackOrQueue, + InvalidOperation_EmptyCollection, + InvalidOperation_EmptyQueue, + InvalidOperation_EnumOpCantHappen, + InvalidOperation_EnumFailedVersion, + InvalidOperation_EmptyStack, + InvalidOperation_EnumNotStarted, + InvalidOperation_EnumEnded, + NotSupported_SortedListNestedWrite, + NotSupported_ValueCollectionSet, + } +} + +#pragma warning restore CS8600 // 将 null 字面量或可能为 null 的值转换为非 null 类型。 +#pragma warning restore IDE0059 // 不需要赋值 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Basal/Sortedset/bithelper.cs b/src/IFoxCAD.Basal/Sortedset/bithelper.cs new file mode 100644 index 0000000..d68c4d8 --- /dev/null +++ b/src/IFoxCAD.Basal/Sortedset/bithelper.cs @@ -0,0 +1,173 @@ +#if NET35 +#pragma warning disable CS8603 // 可能返回 null 引用。 +#pragma warning disable CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 + + +using System; +using System.Collections; +using System.Text; + +namespace System.Collections.Generic +{ + + /// + /// ABOUT: + /// Helps with operations that rely on bit marking to indicate whether an item in the + /// collection should be added, removed, visited already, etc. + /// + /// BitHelper doesn't allocate the array; you must pass in an array or ints allocated on the + /// stack or heap. ToIntArrayLength() tells you the int array size you must allocate. + /// + /// USAGE: + /// Suppose you need to represent a bit array of length (i.e. logical bit array length) + /// BIT_ARRAY_LENGTH. Then this is the suggested way to instantiate BitHelper: + /// *************************************************************************** + /// int intArrayLength = BitHelper.ToIntArrayLength(BIT_ARRAY_LENGTH); + /// BitHelper bitHelper; + /// if (intArrayLength less than stack alloc threshold) + /// int* m_arrayPtr = stackalloc int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// else + /// int[] m_arrayPtr = new int[intArrayLength]; + /// bitHelper = new BitHelper(m_arrayPtr, intArrayLength); + /// *************************************************************************** + /// + /// IMPORTANT: + /// The second ctor args, length, should be specified as the length of the int array, not + /// the logical bit array. Because length is used for bounds checking into the int array, + /// it's especially important to get this correct for the stackalloc version. See the code + /// samples above; this is the value gotten from ToIntArrayLength(). + /// + /// The length ctor argument is the only exception; for other methods -- MarkBit and + /// IsMarked -- pass in values as indices into the logical bit array, and it will be mapped + /// to the position within the array of ints. + /// + /// + + + + + unsafe internal class BitHelper + { // should not be serialized + + private const byte MarkedBitFlag = 1; + private const byte IntSize = 32; + + // m_length of underlying int array (not logical bit array) + private int m_length; + + // ptr to stack alloc'd array of ints + [System.Security.SecurityCritical] + private int* m_arrayPtr; + + // array of ints + private int[] m_array; + + // whether to operate on stack alloc'd or heap alloc'd array + private bool useStackAlloc; + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + // + // + // + // + [System.Security.SecurityCritical] + internal BitHelper(int* bitArrayPtr, int length) + { + this.m_arrayPtr = bitArrayPtr; + this.m_length = length; + useStackAlloc = true; + } + + /// + /// Instantiates a BitHelper with a heap alloc'd array of ints + /// + /// int array to hold bits + /// length of int array + internal BitHelper(int[] bitArray, int length) + { + this.m_array = bitArray; + this.m_length = length; + } + + /// + /// Mark bit at specified position + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe void MarkBit(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_arrayPtr[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + m_array[bitArrayIndex] |= (MarkedBitFlag << (bitPosition % IntSize)); + } + } + } + + /// + /// Is bit at specified position marked? + /// + /// + /// + // + // + // + [System.Security.SecurityCritical] + internal unsafe bool IsMarked(int bitPosition) + { + if (useStackAlloc) + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_arrayPtr[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + else + { + int bitArrayIndex = bitPosition / IntSize; + if (bitArrayIndex < m_length && bitArrayIndex >= 0) + { + return ((m_array[bitArrayIndex] & (MarkedBitFlag << (bitPosition % IntSize))) != 0); + } + return false; + } + } + + /// + /// How many ints must be allocated to represent n bits. Returns (n+31)/32, but + /// avoids overflow + /// + /// + /// + internal static int ToIntArrayLength(int n) + { + return n > 0 ? ((n - 1) / IntSize + 1) : 0; + } + + } +} + +#pragma warning restore CS8618 // 在退出构造函数时,不可为 null 的字段必须包含非 null 值。请考虑声明为可以为 null。 +#pragma warning restore CS8603 // 可能返回 null 引用。 + +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs new file mode 100644 index 0000000..c1d606f --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs @@ -0,0 +1,653 @@ +namespace IFoxCAD.Cad; +using Exception = System.Exception; + +/// +/// 无权无向图实现 +/// IEnumerable 枚举所有顶点; +/// +public sealed class Graph : IGraph, IEnumerable +{ + #region 字段及属性 + /// + /// 存储所有节点的字典,key为顶点的类型,value为邻接表,类型是hashset,不可重复添加点 + /// + /// + readonly Dictionary> vertices = new(); + /// + /// 邻接边表,key为顶点的类型,value为邻接边表,类型是hashset,不可重复添加边 + /// + readonly Dictionary> edges = new(); + /// + /// 为加快索引,引入hash检索 + /// + readonly Dictionary vertexs = new(); + + public int VerticesCount => vertices.Count; + + /// + /// Returns a reference vertex. + /// Time complexity: O(1). + /// + private IGraphVertex? ReferenceVertex + { + get + { + using (var enumerator = vertexs.GetEnumerator()) + { + if (enumerator.MoveNext()) + { + return enumerator.Current.Value; + } + } + + return null; + } + } + IGraphVertex? IGraph.ReferenceVertex => ReferenceVertex; + /// + /// 目前点增加点的顺序号,这个点号不随删点而减少的 + /// + private int insertCount; + #endregion + + #region 构造函数 + public Graph() + { + insertCount = 0; // 每次新建对象就将顶点顺序号归零 + } + #endregion + + #region 顶点及边_增 + /// + /// 向该图添加一个新顶点,但是无边; + /// + /// 点 + /// 创建的顶点 + public IGraphVertex AddVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + return vertexs[str]; + + var vertex = new GraphVertex(pt, insertCount++); + vertices.Add(vertex, new HashSet()); + edges.Add(vertex, new HashSet()); + + vertexs[str] = vertex; + + return vertex; + } + + /// + /// 向该图添加一个边; + /// + /// + public void AddEdge(Curve3d curve) + { + if (curve == null) + throw new ArgumentNullException(nameof(curve)); + + var start = AddVertex(curve.StartPoint); + var end = AddVertex(curve.EndPoint); + + // 添加起点的邻接表和邻接边 + vertices[start].Add(end); + edges[start].Add(new GraphEdge(end, curve)); + + // 为了保证点顺序,每个点的邻接边必须按起点-终点,所以添加曲线终点时,将添加一个方向的曲线 + var curtmp = (Curve3d)curve.Clone(); + curtmp = curtmp.GetReverseParameterCurve(); + + // 添加终点的邻接表和邻接边 + vertices[end].Add(start); + edges[end].Add(new GraphEdge(start, curtmp)); + } + #endregion + + #region 顶点及边_删 + /// + /// 从此图中删除现有顶点; + /// + /// 点 + public void RemoveVertex(Point3d pt) + { + var str = pt.GetHashString(); + if (vertexs.ContainsKey(str)) + { + var vertex = vertexs[str]; + + // 删除邻接表里的vertex点,先删除后面的遍历可以少一轮 + vertices.Remove(vertex!); + + // 删除其他顶点的邻接表里的vertex点 + foreach (var item in vertices.Values) + item.Remove(vertex!); + + // 删除邻接边表里的vertex点,先删除后面的遍历可以少一轮 + edges.Remove(vertex!); + + // 删除其他顶点的邻接边表的指向vertex的边 + foreach (var item in edges.Values) + { + item.RemoveWhere(x => vertex.Equals(x.TargetVertex)); + //foreach (var edge in item) + //{ + // if (vertex.Equals(edge.TargetVertex)) + // item.Remove(edge); + //} + } + vertexs.Remove(str); + } + } + + /// + /// 从此图中删除一条边; + /// + /// 曲线 + public void RemoveEdge(Curve3d curve) + { + if (curve == null) + throw new ArgumentNullException(nameof(curve)); + + RemoveVertex(curve.StartPoint); + RemoveVertex(curve.EndPoint); + } + #endregion + + #region 顶点和边_查 + /// + /// 我们在给定的来源和目的地之间是否有边? + /// + /// 起点 + /// 终点 + /// 有边返回 ,反之返回 + public bool HasEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex == dest) + return true; + } + return false; + } + + /// + /// 获取边 + /// + /// 起点 + /// 终点 + /// + /// 传入的点不在图中时抛出参数异常 + public IEdge? GetEdge(IGraphVertex source, IGraphVertex dest) + { + if (!vertices.ContainsKey(source) || !vertices.ContainsKey(dest)) + throw new ArgumentException("源或目标不在此图中;"); + + foreach (var item in edges[source]) + { + if (item.TargetVertex.Equals(dest)) + return item; + } + return null; + } + + /// + /// 是否存在顶点,此函数目前未发现有啥用 + /// + /// 顶点 + /// 存在顶点返回 ,反之返回 + public bool ContainsVertex(IGraphVertex value) + { + return vertices.ContainsKey(value); + } + #endregion + + #region 获取邻接表和曲线 + /// + /// 获取顶点的邻接表 + /// + /// 顶点 + /// 邻接表 + public HashSet GetAdjacencyList(IGraphVertex vertex) + { + return vertices[vertex]; + } + + /// + /// 获取顶点的邻接边表 + /// + /// 顶点 + /// 邻接边表 + public HashSet GetAdjacencyEdge(IGraphVertex vertex) + { + return edges[vertex]; + } + + /// + /// 根据顶点表获取曲线集合 + /// + /// 顶点表 + /// 曲线表 + public List GetCurves(List graphVertices) + { + var curves = new List(); + for (int i = 0; i < graphVertices.Count - 1; i++) + { + var cur = graphVertices[i]; + var next = graphVertices[i + 1]; + var edge = GetEdge(cur, next); + if (edge is not null) + curves.Add(edge.TargetEdge); + } + var lastedge = GetEdge(graphVertices[^1], graphVertices[0]); + if (lastedge is not null) + curves.Add(lastedge.TargetEdge); + + return curves; + } + #endregion + + #region 克隆及接口实现 + /// + /// 克隆此图;目测是深克隆 + /// + public Graph Clone() + { + var newGraph = new Graph(); + + foreach (var vertex in edges.Values) + foreach (var item in vertex) + newGraph.AddEdge(item.TargetEdge); + + return newGraph; + } + + IGraph IGraph.Clone() + { + return Clone(); + } + + public IEnumerator GetEnumerator() + { + return VerticesAsEnumberable.GetEnumerator(); + } + + IEnumerator? IEnumerable.GetEnumerator() + { + return GetEnumerator() as IEnumerator; + } + + public IEnumerable VerticesAsEnumberable => + vertices.Select(x => x.Key); + #endregion + + #region 方法 + /// + /// 输出点的邻接表的可读字符串 + /// + /// + public string ToReadable() + { + int i = 1; + string output = string.Empty; + foreach (var node in vertices) + { + var adjacents = string.Empty; + + output = string.Format("{1}\r\n{0}-{2}: [", i, output, node.Key.Data.ToString()); + + foreach (var adjacentNode in node.Value) + adjacents = string.Format("{0}{1},", adjacents, adjacentNode.Data.ToString()); + + if (adjacents.Length > 0) + adjacents = adjacents.TrimEnd(new char[] { ',', ' ' }); + + output = string.Format("{0}{1}]", output, adjacents); + i++; + } + return output; + } + #endregion +} + + +/// +/// 邻接表图实现的顶点; +/// IEnumerable 枚举所有邻接点; +/// +public sealed class GraphVertex : IGraphVertex, IEquatable, IComparable, IComparable +{ + #region 属性 + public Point3d Data { get; set; } + public int Index { get; set; } + #endregion + + #region 构造 + /// + /// 邻接表图实现的顶点 + /// + /// 点 + /// 所在节点索引 + public GraphVertex(Point3d value, int index) + { + Data = value; + Index = index; + } + #endregion + + #region 重载运算符_比较 + public bool Equals(IGraphVertex other) + { + return Index == other.Index; + } + + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not IGraphVertex vertex) + return false; + else + return Equals(vertex); + } + + public override int GetHashCode() + { + return Index; + } + + public int CompareTo(IGraphVertex other) + { + if (Equals(other)) + return 0; + + if (Index < other.Index) + return -1; + else + return 1; + } + + int IComparable.CompareTo(IGraphVertex other) + { + return CompareTo(other); + } + + public int CompareTo(object obj) + { + if (obj is null) + return 1; + + try + { + var other = (GraphVertex)obj; + return CompareTo(other); + } + catch (Exception) + { + throw new ArgumentException("Object is not a IGraphVertex"); + } + } + + public static bool operator ==(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + + public static bool operator !=(GraphVertex person1, GraphVertex person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 无向图中边的定义 +/// +public sealed class GraphEdge : IEdge, IEquatable +{ + #region 属性 + public IGraphVertex TargetVertex { get; set; } + public Curve3d TargetEdge { get; set; } + #endregion + + #region 构造 + /// + /// 无向图中边的定义 + /// + /// 下一点 + /// 下一点之间的曲线 + public GraphEdge(IGraphVertex target, Curve3d edge) + { + TargetVertex = target; + TargetEdge = edge; + } + #endregion + + #region 重载运算符_比较 + public bool Equals(GraphEdge other) + { + if (other is null) + return false; + return TargetVertex == other.TargetVertex && + TargetEdge == other.TargetEdge; + } + public override bool Equals(object obj) + { + if (obj is null) + return false; + if (obj is not GraphEdge personObj) + return false; + else + return Equals(personObj); + } + + public override int GetHashCode() + { + return (TargetVertex.GetHashCode(), TargetEdge.GetHashCode()).GetHashCode(); + } + public static bool operator ==(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return Equals(person1, person2); + + return person1.Equals(person2); + } + public static bool operator !=(GraphEdge person1, GraphEdge person2) + { + if (person1 is null || person2 is null) + return !Equals(person1, person2); + + return !person1.Equals(person2); + } + #endregion +} + + +/// +/// 深度优先搜索; +/// +public sealed class DepthFirst +{ + #region 公共方法 + /// + /// 存储所有的边 + /// +#if true + public List> Curve3ds { get; } = new(); +#else + public List> Curve3ds { get; } = new(); +#endif + private HashSet Curved { get; } = new(); + + + /// + /// 找出所有的路径 + /// + /// 图 + public void FindAll(IGraph graph) + { + var total = new HashSet(); + //var graphtmp = graph.Clone(); + foreach (var item in graph.VerticesAsEnumberable) + { + Dfs(graph, new LinkedHashSet { item }, total); + total.Add(item); + } + } + #endregion + + #region 内部方法 + /// + /// 递归 DFS; + /// + /// 图 + /// 已经遍历的路径 +#if true + void Dfs(IGraph graph, LinkedHashSet visited, HashSet totalVisited) + { + var adjlist = graph.GetAdjacencyList(/*startNode*/ visited.First!.Value); // O(1) + foreach (var nextNode in adjlist) // O(n) + { + if (totalVisited.Contains(nextNode)) + { + continue; + } + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(1) + { + // 将下一点加入路径集合,并进行下一次递归 + var sub = new LinkedHashSet { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub, totalVisited); + } + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited.Last!.Value)) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var curstr = Gethashstring(visited); // O(n) + if (Isnew(curstr)) // O(1) + { + Curve3ds.Add(visited); + Curved.Add(curstr.Item1); + } + } + } + } + + + + +#else + + void Dfs(IGraph graph, List visited) + { + var startNode = visited[0]; + IGraphVertex nextNode; + List sub; + + var adjlist = graph.GetAdjacencyList(startNode).ToList(); // O(n) + for (int i = 0; i < adjlist.Count; i++) // O(n) + { + nextNode = adjlist[i]; + + // 如果下一个点未遍历过 + if (!visited.Contains(nextNode)) // O(n) + { + // 将下一点加入路径集合,并进行下一次递归 + sub = new List { nextNode }; + sub.AddRange(visited); // O(n) + Dfs(graph, sub); + } + + // 如果下一点遍历过,并且路径大于2,说明已经找到起点 + else if (visited.Count > 2 && nextNode.Equals(visited[^1])) + { + // 将重复的路径进行过滤,并把新的路径存入结果 + var cur = RotateToSmallest(visited); // O(n) + var inv = Invert(cur,cur[0]); // O(n) + + var curstr = Gethashstring(cur,inv); + //Env.Print(curstr); + if (Isnew(curstr)) + { + Curve3ds.Add(cur); + Curved.Add(curstr.Item1); + } + } + } + } +#endif + + + + + + + + + /// + /// 将列表旋转到最小的值为列表起点 + /// + /// + /// + static List RotateToSmallest(List lst) + { + var index = lst.IndexOf(lst.Min()); + return lst.Skip(index).Concat(lst.Take(index)).ToList(); + } + + /// + /// 将列表反向,并旋转到起点为最小值 + /// + /// + /// + static List Invert(List lst, IGraphVertex vertex) + { + var tmp = lst.ToList(); + tmp.Reverse(); + var index = tmp.IndexOf(vertex); + return tmp.Skip(index).Concat(lst.Take(index)).ToList(); + } + + static (string, string) Gethashstring(List pathone, List pathtwo) + { + var one = new string[pathone.Count]; + var two = new string[pathtwo.Count]; + for (int i = 0; i < pathone.Count; i++) + { + one[i] = pathone[i].Index.ToString(); + two[i] = pathtwo[i].Index.ToString(); + } + return (string.Join("-", one), string.Join("-", two)); + } + + static (string, string) Gethashstring(LinkedHashSet path) + { + var one = new string[path.Count]; + var two = new string[path.Count]; + path.For(path.MinNode!, (i, ver1, ver2) => { + one[i] = ver1.Index.ToString(); + two[i] = ver2.Index.ToString(); + }); + return (string.Join("-", one), string.Join("-", two)); + } + + + bool Isnew((string, string) path) + { + return !Curved.Contains(path.Item1) && !Curved.Contains(path.Item2); + } + + + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs b/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs new file mode 100644 index 0000000..73547b7 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs @@ -0,0 +1,98 @@ +namespace IFoxCAD.Cad; + +/// +/// 无向图 +/// +public interface IGraph +{ + /// + /// 顶点的数量 + /// + /// + int VerticesCount { get; } + + /// + /// 是否存在顶点 + /// + /// 顶点键 + /// + bool ContainsVertex(IGraphVertex key); + + /// + /// 顶点的迭代器 + /// + /// + IEnumerable VerticesAsEnumberable { get; } + + /// + /// 是否有边 + /// + /// 源顶点 + /// 目的顶点 + /// + bool HasEdge(IGraphVertex source, IGraphVertex destination); + /// + /// 图克隆函数 + /// + /// + IGraph Clone(); + /// + /// 获取边 + /// + /// + /// + /// + IEdge? GetEdge(IGraphVertex source, IGraphVertex dest); + /// + /// 邻接表 + /// + /// + /// + HashSet GetAdjacencyList(IGraphVertex vertex); + /// + /// 邻接边表 + /// + /// + /// + HashSet GetAdjacencyEdge(IGraphVertex vertex); + IGraphVertex? ReferenceVertex { get; } + + void RemoveVertex(Point3d pt); + void RemoveEdge(Curve3d curve); + +} + +/// +/// 无向图顶点 +/// +/// 顶点数据类型 +public interface IGraphVertex : IComparable +{ + /// + /// 顶点的键 + /// + /// + int Index { get; set; } + + /// + /// 顶点的数据 + /// + Point3d Data { get; } +} +/// +/// 无向图边 +/// +public interface IEdge +{ + /// + /// 边 + /// + Curve3d TargetEdge { get; } + /// + /// 目标顶点 + /// + IGraphVertex TargetVertex { get; } +} + + + diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs new file mode 100644 index 0000000..c5f7f11 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs @@ -0,0 +1,25 @@ +namespace IFoxCAD.Cad; + +/* + * 这个类存在的意义是为了不暴露Rect类字段 + * 同时利用了Rect类字段的快速 + * 提供到外面去再继承 + */ + +/// +/// 四叉树图元 +/// +public class QuadEntity : Rect +{ + /// + /// 四叉树图元 + /// + /// 包围盒 + public QuadEntity(Rect box) + { + _X = box._X; + _Y = box._Y; + _Top = box._Top; + _Right = box._Right; + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs new file mode 100644 index 0000000..0eb1bf8 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs @@ -0,0 +1,259 @@ +/* + * 四叉树维基百科 http://en.wikipedia.org/wiki/Quadtree + * 四叉树是一种分区空间的算法,更快找出内部或外部给定区域. + * 通过一个正交矩形边界进行中心点分裂四个正交矩形, + * 插入时候会一直分裂四个正交矩形, + * 当分裂四个节点都无法单独拥有 图元包围盒 就停止分裂,并且你属于这四个节点的父亲. + * (不包含就是面积少了,就这么一句话看代码看半天), + * 还可以通过限制树的深度实现加速. + * + * 第一版: https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=30535 + * + * 第二版: 找邻居 + * https://blog.csdn.net/dive_shallow/article/details/112438050 + * https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/ + * + * 1.根节点:控制根节点从而控制所有节点 + * 2.子节点:包含自身根节点,插入矩形的时候进行递归分裂自身,和实现查找. + * 3.接口:约束都要有正交矩形,否则无法调用"包含"方法 + * 4.选择模式:模仿cad的窗选和框选 + */ +namespace IFoxCAD.Cad; + +/// +/// 根节点控制器 +/// +/// 类型接口约束必须有正交矩形 +public class QuadTree where TEntity : QuadEntity +{ + #region 成员 + /// + /// 根节点 + /// + QuadTreeNode _rootNode; + + /// + /// 四叉树节点的数目 + /// + public int Count { get => _rootNode.CountSubTree; } + + /// + /// 点容器(红黑树) + /// + SortedSet _points; + #endregion + + #region 构造 + /// + /// 四叉树根节点控制器 + /// + /// 四叉树矩形范围 + public QuadTree(Rect rect) + { + _rootNode = new QuadTreeNode(rect, null, 0);//初始化根节点 + _points = new(); + } + #endregion + + #region 方法 + /// + /// 通过根节点插入数据项 + /// + /// + public void Insert(TEntity ent) + { + /* + * 图元点 是不分裂空间的,加入一个红黑树内部. + */ + if (ent.IsPoint) + { + _points.Add(ent); + return; + } + + while (!_rootNode.Contains(ent)) + { + /* + * 四叉树插入时候,如果超出根边界,就需要扩展 + * 扩展时候有一个要求,当前边界要作为扩展边界的一个象限,也就是反向分裂 + * + * 创建新根,计算原根在新根的位置, + * 替换指针:获取新分裂的节点的父节点,判断它哪个儿子是它, + * 替换之后可能仍然不包含图元边界,再循环计算. + */ + var sq_Left = _rootNode._X; + var sq_Botton = _rootNode._Y; + var sq_Right = _rootNode._Right; + var sq_Top = _rootNode._Top; + if (ent._Y >= _rootNode._Y)//上↑增殖 + { + if (ent._X >= _rootNode._X) + { + //右上↗增殖 + sq_Right += _rootNode.Width; + sq_Top += _rootNode.Height; + } + else + { + //左上↖增殖 + sq_Left -= _rootNode.Width; + sq_Top += _rootNode.Height; + } + } + else//在下↓ + { + if (ent._X >= _rootNode._X) + { + //右下↘增殖 + sq_Right += _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + else + { + //左下↙增殖 + sq_Left -= _rootNode.Width; + sq_Botton -= _rootNode.Height; + } + } + //扩大2次方 + var rectSquare = new Rect(sq_Left, sq_Botton, sq_Right, sq_Top); + + //四叉树的旧根要作为四分之一插入 + //新根中计算原根 + //把 旧根节点 连接到 新根节点 上面,然后新根成为根 + var newRoot = new QuadTreeNode(rectSquare, null, 0); + var insert = newRoot.Insert(_rootNode); + if (insert is null) + throw new("四叉树:新根尺寸不对"); + if (!insert.Equals(_rootNode)) + throw new("四叉树:新旧节点大小不一致,无法连接"); + + var insPar = insert.Parent; + _rootNode.Parent = insPar; + if (insPar is null) + return; + + if (_rootNode.Equals(insPar.RightTopTree)) + insPar.RightTopTree = _rootNode; + else if (_rootNode.Equals(insPar.RightBottomTree)) + insPar.RightBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftBottomTree)) + insPar.LeftBottomTree = _rootNode; + else if (_rootNode.Equals(insPar.LeftTopTree)) + insPar.LeftTopTree = _rootNode; + else + throw new("四叉树:新节点不对,无法连接"); + + //其后的子节点层数全部增加层数, + //要加多少层取决于当前根边界属于新根边界的所在层 + var depth = insert.Depth; + if (depth == 0) + throw new("四叉树:插入节点是0,造成错误"); + _rootNode.ForEach(node => { + node.Depth += depth; + return false; + }); + + //交换根控制 + _rootNode = newRoot; + } + + _rootNode.Insert(ent); + } + + + /// + /// 查询四叉树,返回给定区域的数据项 + /// + /// 矩形选区查询 + /// + public List Query(Rect rect, QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith) + { + QuadTreeEvn.SelectMode = selectMode; + + var results = new List(); + //选择图元 + _rootNode.Query(rect, results); + //选择点 + var ptge = _points.GetEnumerator(); + switch (selectMode) + { + case QuadTreeSelectMode.IntersectsWith: + case QuadTreeSelectMode.Contains: + /* 由于红黑树的方法 _points.GetViewBetween() + * 过滤只能过滤X区间,Y区间还是要过滤, + * 那么我就只能用这样的方法加速了 + * + * 而更好的方式是不用红黑树,去加入一个点云数据来进行,可谓是编程无极限.... + */ + while (ptge.MoveNext()) + { + var ptEnt = ptge.Current; + if (rect._X <= ptEnt._X && ptEnt._X <= rect._Right) + { + if (rect._Y <= ptEnt._Y && ptEnt._Y <= rect.Top) + results.Add(ptEnt); + } + else if (ptEnt._X > rect._Right) + break;//超过后面范围就break,因为红黑树已经排序 + } + break; + default: + throw new("四叉树:" + nameof(selectMode)); + } + return results; + } + + /// + /// 删除子节点 + /// + /// 根据范围删除 + public void Remove(Rect rect) + { + _rootNode.Remove(rect); + } + + /// + /// 删除子节点 + /// + /// 根据图元删除 + public void Remove(TEntity ent) + { + _rootNode.Remove(ent); + } + + /// + /// 找到附近节点图元 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect rect, QuadTreeFindMode findMode) + { + return _rootNode.FindNeibor(rect, findMode); + } + + /// + /// 找到附近图元 + /// + /// + /// + public TEntity? FindNearEntity(Rect rect) + { + return _rootNode.FindNearEntity(rect); + } + + /// + /// 执行四叉树中特定的行为 + /// + /// + public void ForEach(QTAction action) + { + _rootNode.ForEach(action); + } + + /// + /// 委托:四叉树节点上执行一个操作 + /// + /// + public delegate bool QTAction(QuadTreeNode obj); + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs new file mode 100644 index 0000000..16d518b --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs @@ -0,0 +1,26 @@ +#pragma warning disable CA2211 // 非常量字段应当不可见 +namespace IFoxCAD.Cad; + +public class QuadTreeEvn +{ + /// + /// 最小的节点有一个面积(一定要大于0) + /// + public static double MinArea = 1e-6; + + /// + /// 选择模式 + /// + public static QuadTreeSelectMode SelectMode; + + /// + /// 最大深度 + /// + public static int QuadTreeMaximumDepth = 2046; + + /// + /// 节点内容超过就分裂 + /// + public static int QuadTreeContentsCountSplit = 20; +} +#pragma warning restore CA2211 // 非常量字段应当不可见 \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs new file mode 100644 index 0000000..86cb5b4 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs @@ -0,0 +1,818 @@ +namespace IFoxCAD.Cad; + +/// +/// 子节点 +/// +/// +public class QuadTreeNode + : Rect + where TEntity : QuadEntity +{ + #region 成员 + /// + /// 子节点:第一象限:右上↗ + /// + public QuadTreeNode? RightTopTree; + /// + /// 子节点:第二象限:左上↖ + /// + public QuadTreeNode? LeftTopTree; + /// + /// 子节点:第三象限:左下↙ + /// + public QuadTreeNode? LeftBottomTree; + /// + /// 子节点:第四象限:右下↘ + /// + public QuadTreeNode? RightBottomTree; + /// + /// 所有子节点 + /// + QuadTreeNode[] Nodes + { + get + { + return new QuadTreeNode[] + { + RightTopTree!, + LeftTopTree!, + LeftBottomTree!, + RightBottomTree!, + }; + } + } + /// + /// 所有子节点是空的 + /// + bool NodesIsEmpty => RightTopTree is null && LeftTopTree is null && LeftBottomTree is null && RightBottomTree is null; + + /// + /// 父节点 + /// + public QuadTreeNode? Parent; + /// + /// 节点的在四叉树的深度 + /// + public int Depth; + + // 注意,内容没有限制:这不是 impement 四叉树的标准方法 + /// (节点图元是交叉线压着的,并不是矩形范围内全部,因为这是四叉树的特性决定) + /// + /// 本节点:内容 + /// + public List Contents; + + /// + /// 本节点和旗下所有子节点:内容群 + /// + public void ContentsSubTree(List results) + { + if (Contents is null) + return; + results.AddRange(Contents); + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.ContentsSubTree(results); + } + + /// + /// 本节点和旗下所有子节点:内容群数量 + /// + public int CountSubTree + { + get + { + if (Contents is null) + return 0; + int count = Contents.Count; + + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + count += node.CountSubTree; + } + return count; + } + } + #endregion + + #region 构造 + /// + /// 四叉树节点 + /// + /// 当前节点边界 + /// 父节点 + /// 节点深度 + public QuadTreeNode(Rect box, QuadTreeNode? parent, int depth) + { + _X = box._X; + _Y = box._Y; + _Right = box._Right; + _Top = box._Top; + + Parent = parent; + Depth = depth; + Contents = new(); + } + #endregion + + #region 增 + /// + /// 将原有节点插入用 + /// + /// + internal QuadTreeNode? Insert(Rect rect) + { + if (!Contains(rect)) + return null; + + //四叉树分裂,将当前节点分为四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + //当前节点边界 包含 图元包围盒 就插入 + //退出递归:4个节点都不完全包含 + //4个节点的上层 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + if (node.Equals(rect)) + { + rect = node; + return node.Insert(rect); + } + } + return this; + } + + /// + /// 将数据项递归插入四叉树 + /// + /// + public QuadTreeNode? Insert(TEntity ent) + { + if (!Contains(ent)) + { + //Debug.WriteLine("不在四叉树边界范围"); + //Trace.WriteLine("不在四叉树边界范围"); + return null; + } + + // if (ent.IsPoint) + // { + // //找到最后一层包含它的节点,然后加入它 + // //因此是跳过分裂矩形的,以免造成无限递归 + // var minNode = GetMinNode(ent); + // minNode.Contents.Add(ent); + // return minNode; + // } + +#if true2 + //方案二: + //内容数超过才分裂,防止树深度过高,但是多选过滤时候慢一点 + if (Contents.Count > QuadTreeEvn.QuadTreeContentsCountSplit) + { + //分裂出四个子节点 + if (_nodesIsEmpty) + { + CreateChildren(); + //分裂之后将当前层的内容扔到四个子节点, + //如果被压着,那么就不会扔到下面 + for (int i = Contents.Count - 1; i >= 0; i--) + { + var minNode = GetMinNode(Contents[i].Box); + minNode.Contents.Add(Contents[i]); + Contents.RemoveAt(i); + } + } + else + { + //没有分裂的话,就递归 + //退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = _Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } + } + } +#else + //方案一:分裂到最细节点 + + //分裂出四个子节点 + if (NodesIsEmpty) + CreateChildren(); + + //4个子节点开始递归 + //退出递归:4个节点都不完全包含,内容就是他们的父亲 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容) + if (node.Contains(ent)) + return node.Insert(ent); + } +#endif + + //为什么要用容器? + //相同包围盒或者四叉树分割线压着多个. + this.Contents.Add(ent); + return this; + } + + /// + /// 创建子节点 + /// + void CreateChildren() + { + // 最小面积控制节点深度,但是这样可能导致树分成高,引起爆栈 + if (Depth > QuadTreeEvn.QuadTreeMaximumDepth) + return; + var recs = RectSplit(this); + var de = Depth + 1; + RightTopTree = new QuadTreeNode(recs[0], this, de); + LeftTopTree = new QuadTreeNode(recs[1], this, de); + LeftBottomTree = new QuadTreeNode(recs[2], this, de); + RightBottomTree = new QuadTreeNode(recs[3], this, de); + } + + /// + /// 矩形分裂为四个 + /// + /// + /// + Rect[] RectSplit(Rect box) + { + var halfWidth = box.Width / 2.0; + var halfHeight = box.Height / 2.0; + + var upperRight = new Rect(box._X + halfWidth, box._Y + halfHeight, box._Right, box._Top); + var upperLeft = new Rect(box._X, box._Y + halfHeight, box._Right - halfWidth, box._Top); + var lowerleft = new Rect(box._X, box._Y, box._Right - halfWidth, box._Top - halfHeight);//基础 + var lowerRight = new Rect(box._X + halfWidth, box._Y, box._Right, box._Top - halfHeight); + + //依照象限顺序输出 + return new Rect[] { upperRight, upperLeft, lowerleft, lowerRight }; + } + #endregion + + #region 删 + /// + /// 删除图元 + /// + /// 根据图元删除 + public bool Remove(TEntity easeEnt) + { + //通过图元id删除无疑是非常低效的, + //1.相当于在所有的容器查找它,但是移除只会移除一次, + // 因此必须要求图元只会加入一次,才能中断检索剩余分支. + //2.这个代价还是太高,因此我们还是要默认条件,图元载入一次之后,不再改动. + //3.不再改动也不太合理,因为cad图元还是可以修改的 + + //1.处理内容 + if (Contents.Remove(easeEnt)) + { + if (CountSubTree == 0) + this.Clear(this); + return true; + } + + //2.递归子节点移除 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.Remove(easeEnt)) //递归进入子节点删除内容 + return true; //删除成功就中断其他节点的搜索 + } + return false; + } + + /// + /// 递归进入最下层节点,然后开始清理 + /// + /// + void Clear(QuadTreeNode node) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + nodes[i]?.Clear(nodes[i]); + + node.Contents.Clear(); + //node.Contents = null;//重复加入时候会出错 + node.RightTopTree = null; + node.LeftTopTree = null; + node.LeftBottomTree = null; + node.RightBottomTree = null; + node.Parent = null; + //node.Box = zoreRect; + } + + /// + /// 删除子节点内容 + /// + /// 根据范围删除 + public void Remove(Rect queryArea) + { + //本节点内容移除 + if (Contents is not null && Contents.Count > 0)//从最上层的根节点开始进入 + { + for (int i = Contents.Count - 1; i >= 0; i--) + { + var ent = Contents[i]; + //移除之后,如果容器是0,那么这里不能直接 Contents=null, + //因为此节点下面可能还有节点, + //需要判断了其后数量0才可以清理. + //否则其后还有内容,那么此节点就是仍然可以用的. + if (queryArea.Contains(ent)) + Contents.Remove(ent); + } + } + + //同插入一样 + //跳到指定节点再搜索这个节点下面的图元 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.NodesIsEmpty) + continue; + + //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Remove(queryArea); + break; + } + //查询区域 完全包含 此节点边界,提取此节点全部内容 + //跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.Clear(node); + continue; + } + //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + //1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Remove(queryArea); + } + + //本节点内容移除之后,旗下还有内容的话, + //会跳过此处,再进入子节点进行递归,直到最后一个节点 + if (CountSubTree == 0) + Clear(this); + } + #endregion + + #region 查 + /// + /// 查询范围内的实体 + /// + /// 查询矩形 + /// + public void Query(Rect queryArea, List results) + { + GetCurrentContents(queryArea, results); + + //遍历子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + //子节点的4个子节点都是空的, + //那么表示元素会在子节点这一层啊... + if (node.NodesIsEmpty) + continue; + + //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环) + if (node.Contains(queryArea)) + { + node.Query(queryArea, results); + break; + } + //查询区域 完全包含 此节点边界,提取此节点全部内容 + //跳过分析碰撞,并继续循环搜索其他节点 + if (queryArea.Contains(node)) + { + node.ContentsSubTree(results); + continue; + } + //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点 + //1,角点碰撞 2,边界碰撞 + if (node.IntersectsWith(queryArea)) + node.Query(queryArea, results); + } + } + + /// + /// 获取本节点内容 + /// + /// + /// + void GetCurrentContents(Rect queryArea, List results) + { + //遍历当前节点内容,加入方式取决于碰撞模式 + if (QuadTreeEvn.SelectMode == QuadTreeSelectMode.IntersectsWith) + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.IntersectsWith(ent)) + results.Add(ent); + } + } + else + { + for (int i = 0; i < Contents.Count; i++) + { + var ent = Contents[i]; + if (queryArea.Contains(ent)) + results.Add(ent); + } + } + } + + /// + /// 找临近图元 + /// + /// 查找矩形 + /// + public TEntity? FindNearEntity(Rect queryArea) + { + TEntity? resultEntity = default; + //1.找到 查找矩形 所在的节点,利用此节点的矩形. + var queryNode = GetMinNode(queryArea); + var queryAreaCenter = queryArea.CenterPoint; + + //2.从根开始搜索 + // 如果搜索父亲的父亲的...内容群,它不是距离最近的,只是节点(亲属关系)最近 + // 储存找过的<图元,距离> + var entDic = new Dictionary(); + + var old = QuadTreeEvn.SelectMode; + QuadTreeEvn.SelectMode = QuadTreeSelectMode.IntersectsWith; + while (true) + { + //循环找父节点大小 + var hw = queryNode.Width / 2.0; + var hh = queryNode.Height / 2.0; + //3.利用选区中心扩展一个节点边界大小的矩形.从而选择图元 + // 再判断图元的与目标的距离,找到最小距离,即为最近 + var minPt = new Point2d(queryAreaCenter.X - hw, queryAreaCenter.Y - hh); + var maxPt = new Point2d(queryAreaCenter.X + hw, queryAreaCenter.Y + hh); + var ents = new List(); + Query(new Rect(minPt, maxPt), ents); + for (int i = 0; i < ents.Count; i++) + { + var ent = ents[i]; + if (entDic.ContainsKey(ent)) + continue; + var dis = ent.CenterPoint.GetDistanceTo(queryAreaCenter); + if (dis > 1e-6)//剔除本身 + entDic.Add(ent, dis); + } + if (entDic.Count > 0) + { + resultEntity = entDic.OrderBy(a => a.Value).First().Key; + break; + } + if (queryNode.Parent is null)//最顶层就退出 + break; + queryNode = queryNode.Parent;//利用父节点矩形进行变大选区 + } + QuadTreeEvn.SelectMode = old; + return resultEntity; + } + + /// + /// 找临近节点的图元 + /// + /// 查找矩形 + /// 查找什么方向 + /// + [Obsolete("找附近节点的并不是最近的图元")] + public TEntity? FindNeibor(Rect queryArea, QuadTreeFindMode findMode) + { + TEntity? resultEntity = default; + //1.找到 查找矩形 所在的节点,利用此节点的矩形. + //2.利用节点矩形是分裂的特点,边和边必然贴合. + //3.找到方向 findMode 拥有的节点,然后查找节点的内容 + var queryNode = GetMinNode(queryArea); + + bool whileFlag = true; + //同一个节点可能包含邻居,因为四叉树的加入是图元压线, + //那么就在这里搜就得了,用中心点决定空间位置 + //但是本空间的图元可能都比它矮,无法满足条件 + if (queryNode.CountSubTree > 1) + { + resultEntity = GetNearestNeighbor(queryNode, findMode, queryArea); + if (resultEntity is null || resultEntity.CenterPoint == queryArea.CenterPoint) + whileFlag = true; + else + whileFlag = false; + } + + while (whileFlag) + { + //同一个父节点是临近的,优先获取 兄弟节点 的内容. + //循环了第二次是北方兄弟的节点, + //但是这不是一个找到临近图元的方法, + //因为临近的可能是父亲的父亲的父亲...另一个函数 FindNearEntity 写 + //本方案也仅仅作为找北方节点 + var parent = queryNode.Parent; + if (parent is not null) + { + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //下格才获取上格,否则导致做了无用功,上格就直接获取邻居了 + if (parent.LeftBottomTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Bottom: + { + if (parent.LeftTopTree == queryNode || parent.RightTopTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Left: + { + if (parent.RightTopTree == queryNode || parent.RightBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + case QuadTreeFindMode.Right: + { + if (parent.LeftTopTree == queryNode || parent.LeftBottomTree == queryNode) + resultEntity = GetNearestNeighbor(parent, findMode, queryArea); + } + break; + } + } + if (resultEntity is not null) + break; + + //通过 所在节点 找 邻居节点, + //拿到 邻居节点 下面的所有内容(图元) + //内容可能是空的,再从邻居那往北找...如果找到了四叉树最外层,仍然没有内容,退出循环 + var neiborNode = FindNeiborNode(queryNode, findMode); + if (neiborNode is null) + continue; + if (neiborNode.CountSubTree > 0) + { + resultEntity = GetNearestNeighbor(neiborNode, findMode, queryArea); + break; + } + if (neiborNode.Parent is null)//如果找到了四叉树最外层,仍然没有内容,退出循环 + break; + queryNode = neiborNode; + } + + return resultEntity; + } + + /// + /// 查找节点的(本内容和子内容)与(查找面积)矩形中点对比,找到最近一个内容 + /// + /// 查找面积 + /// 查找方向 + /// 查找节点 + /// + TEntity? GetNearestNeighbor(QuadTreeNode queryNode, QuadTreeFindMode findMode, Rect queryArea) + { + TEntity? results = default; + + var lst = new List(); + var qcent = queryArea.CenterPoint; + + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //取出Y比queryArea的还大的一个,是最近的那个 + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.Y > qy) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Bottom: + { + var qy = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.Y < qy) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.Y).ToList(); + } + break; + case QuadTreeFindMode.Left: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.X > qx) + lst.Add(ent); + }); + lst = lst.OrderBy(ent => ent.CenterPoint.X).ToList(); + } + break; + case QuadTreeFindMode.Right: + { + var qx = qcent.Y; + queryNode.ContentsSubTree(lst); + lst.ForEach(ent => + { + if (ent.CenterPoint.X < qx) + lst.Add(ent); + }); + lst = lst.OrderByDescending(ent => ent.CenterPoint.X).ToList(); + } + break; + } + + if (lst.Count > 0) + return lst[0];//可能就是本体重叠 + return results; + } + + /// + /// 找包含它的最小分支 + /// + /// 查询的矩形 + /// 节点 + QuadTreeNode GetMinNode(Rect queryArea) + { + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + + //边界包含查询面积,那么再递归查询, + //直到最后四个都不包含,那么上一个就是图元所在节点 + if (node.Contains(queryArea)) + return node.GetMinNode(queryArea);//中断后面的范围,才可以返回正确的 + } + return this; + } + + /// + /// 四叉树找邻居节点(相同或更大) + /// + /// 源节点 + /// 方向 + /// + QuadTreeNode? FindNeiborNode(QuadTreeNode tar, QuadTreeFindMode findMode) + { + var parent = tar.Parent; + if (parent is null) + return null; + switch (findMode) + { + case QuadTreeFindMode.Top: + { + //判断当前节点在父节点的位置,如果是在 下格 就取对应的 上格 + if (tar == parent.LeftBottomTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.RightTopTree; + //否则就是上格 + //找父节点的北方邻居..也就是在爷节点上面找 + //递归,此时必然是 下格,就必然返回 上格,然后退出递归 + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Top); + //父节点的北方邻居 无 子节点 + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor;//返回父节点的北方邻居,比较大 + //父节点的北方邻居 有 子节点,剩下条件就只有这两 + + // 如果直接返回,那么是(相同或更大), + // 而找邻近图元需要的是这个(相同或更大)下面的图元,在外面对这个格子内图元用坐标分析就好了 + if (tar == parent.LeftTopTree) + return parentNeibor.LeftBottomTree; + return parentNeibor.RightBottomTree; + } + case QuadTreeFindMode.Bottom: + { + if (tar == parent.LeftTopTree) + return parent.LeftBottomTree; + if (tar == parent.RightTopTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Bottom); + if (parentNeibor is null || parentNeibor.RightTopTree is null) + return parentNeibor; + if (tar == parent.LeftBottomTree) + return parentNeibor.LeftTopTree; + return parentNeibor.RightTopTree; + } + case QuadTreeFindMode.Right: + { + if (tar == parent.LeftTopTree) + return parent.RightTopTree; + if (tar == parent.LeftBottomTree) + return parent.RightBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Right); + if (tar == parent.RightTopTree) + return parentNeibor?.LeftTopTree; + return parentNeibor?.LeftBottomTree; + } + case QuadTreeFindMode.Left: + { + if (tar == parent.RightTopTree) + return parent.LeftTopTree; + if (tar == parent.RightBottomTree) + return parent.LeftBottomTree; + var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Left); + if (tar == parent.LeftTopTree) + return parentNeibor?.RightTopTree; + return parentNeibor?.RightBottomTree; + } + } + return null; + } + #endregion + + #region 改 + /// + /// 所有的点归类到最小包围它的空间 + /// + //public void PointsToMinNode() + //{ + // ForEach(node => + // { + // for (int i = 0; i < node.Contents.Count; i++) + // { + // var ent = node.Contents[i]; + // if (ent.IsPoint) + // { + // //如果最小包含!=当前,就是没有放在最适合的位置 + // var queryNode = GetMinNode(ent); + // if (queryNode != node) + // { + // node.Remove(ent); + // queryNode.Contents.Add(ent); + // } + // } + // } + // return false; + // }); + //} + #endregion + + #region 方法 + /// + /// 递归全部节点(提供给根用的,所以是全部) + /// + /// QTAction + public bool ForEach(QuadTree.QTAction action) + { + //执行本节点 + if (action(this)) + return true; + + //递归执行本节点的子节点 + var nodes = Nodes; + for (int i = 0; i < nodes.Length; i++) + { + var node = nodes[i]; + if (node is null) + continue; + if (node.ForEach(action)) + break; + } + return false; + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs new file mode 100644 index 0000000..d180236 --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs @@ -0,0 +1,21 @@ +namespace IFoxCAD.Cad; + +/// +/// 四叉树选择模式 +/// +public enum QuadTreeSelectMode +{ + IntersectsWith, //碰撞到就选中 + Contains, //全包含才选中 +} + +/// +/// 四叉树查找方向 +/// +public enum QuadTreeFindMode +{ + Top = 1, //上 + Bottom = 2, //下 + Left = 4, //左 + Right = 8, //右 +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs new file mode 100644 index 0000000..c533ddb --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs @@ -0,0 +1,605 @@ +using System.Diagnostics; + +namespace IFoxCAD.Cad; + +/// +/// Linq Distinct 消重比较两点在容差范围内就去除 +/// +public class TolerancePoint2d : IEqualityComparer +{ + readonly double _tolerance; + public TolerancePoint2d(double tolerance = 1e-6) + { + _tolerance = tolerance; + } + + public bool Equals(Point2d a, Point2d b)//Point3d是struct不会为null + { + /*默认规则是==是0容差,Eq是有容差*/ + // 方形限定 + // 在 0~1e-6 范围实现 圆形限定 则计算部分在浮点数6位后,没有啥意义 + // 在 0~1e-6 范围实现 从时间和CPU消耗来说,圆形限定 都没有 方形限定 的好 + if (_tolerance <= 1e-6) + return Math.Abs(a.X - b.X) <= _tolerance && Math.Abs(a.Y - b.Y) <= _tolerance; + + // 圆形限定 + // DistanceTo 分别对XYZ进行了一次乘法,也是总数3次乘法,然后求了一次平方根 + // (X86.CPU.FSQRT指令用的牛顿迭代法/软件层面可以使用快速平方根....我还以为CPU会采取快速平方根这样的取表操作) + return a.IsEqualTo(b, new Tolerance(_tolerance, _tolerance)); + } + + public int GetHashCode(Point2d obj) + { + //结构体直接返回 obj.GetHashCode(); Point3d ToleranceDistinct3d + //因为结构体是用可值叠加来判断?或者因为结构体兼备了一些享元模式的状态? + //而类是构造的指针,所以取哈希值要改成x+y+z..s给Equals判断用,+是会溢出,所以用^ + return (int)obj.X ^ (int)obj.Y;// ^ (int)obj.Z; + } +} + + +[Serializable] +[StructLayout(LayoutKind.Sequential)] +[DebuggerDisplay("{DebuggerDisplay,nq}")] +[DebuggerTypeProxy(typeof(Rect))] +public class Rect : IEquatable, IComparable +{ + [DebuggerBrowsable(DebuggerBrowsableState.Never)] + private string DebuggerDisplay => ToString("f4"); + +#pragma warning disable CA2211 // 非常量字段应当不可见 + public static TolerancePoint2d RectTolerance = new(1e-6); + public static Tolerance CadTolerance = new(1e-6, 1e-6); +#pragma warning restore CA2211 // 非常量字段应当不可见 + + #region 字段 + //这里的成员不要用{get}封装成属性,否则会导致跳转了一次函数, + //10w图元将会从187毫秒变成400毫秒 + //不用 protected 否则子类传入Rect对象进来无法用 + internal double _X; + internal double _Y; + internal double _Right; + internal double _Top; + #endregion + + #region 成员 + public double X => _X; + public double Y => _Y; + public double Left => _X; + public double Bottom => _Y; + public double Right => _Right; + public double Top => _Top; + + public double Width => _Right - _X; + public double Height => _Top - _Y; + public double Area + { + get + { + var ar = (_Right - _X) * (_Top - _Y); + return ar < 1e-10 ? 0 : ar; + } + } + + public Point2d MinPoint => LeftLower; + public Point2d MaxPoint => RightUpper; + public Point2d CenterPoint => Midst; + + /// + /// 左下Min + /// + public Point2d LeftLower => new(_X, _Y); + + /// + /// 左中 + /// + public Point2d LeftMidst => new(_X, Midst.Y); + + /// + /// 左上 + /// + public Point2d LeftUpper => new(_X, _Top); + + /// + /// 右上Max + /// + public Point2d RightUpper => new(_Right, _Top); + + /// + /// 右中 + /// + public Point2d RightMidst => new(_Right, Midst.Y); + + /// + /// 右下 + /// + public Point2d RightBottom => new(_Right, _Y); + + /// + /// 中间 + /// + public Point2d Midst => new(((_Right - _X) * 0.5) + _X, ((_Top - _Y) * 0.5) + _Y); + + /// + /// 中上 + /// + public Point2d MidstUpper => new(Midst.X, _Top); + + /// + /// 中下 + /// + public Point2d MidstBottom => new(Midst.X, _Y); + + /// + /// 是一个点 + /// 水平或垂直直线包围盒是面积是0,所以面积是0不一定是点 + /// + public bool IsPoint => Math.Abs(_X - _Right) < 1e-10 && Math.Abs(_Y - _Top) < 1e-10; + #endregion + + #region 构造 + public Rect() { } + + /// + /// 矩形类 + /// + /// 左 + /// 下 + /// 右 + /// 上 + public Rect(double left, double bottom, double right, double top) + { + _X = left; + _Y = bottom; + _Right = right; + _Top = top; + } + + /// + /// 构造矩形类 + /// + /// + /// + /// 是否检查大小 + public Rect(Point2d p1, Point2d p3, bool check = false) + { + if (check) + { + _X = Math.Min(p1.X, p3.X); + _Y = Math.Min(p1.Y, p3.Y); + _Right = Math.Max(p1.X, p3.X); + _Top = Math.Max(p1.Y, p3.Y); + } + else + { + _X = p1.X; + _Y = p1.Y; + _Right = p3.X; + _Top = p3.Y; + } + } + #endregion + + #region 重载运算符_比较 + public override bool Equals(object? obj) + { + return this.Equals(obj as Rect); + } + public bool Equals(Rect? b) + { + return this.Equals(b, 1e-6);/*默认规则是==是0容差,Eq是有容差*/ + } + public static bool operator !=(Rect? a, Rect? b) + { + return !(a == b); + } + public static bool operator ==(Rect? a, Rect? b) + { + //此处地方不允许使用==null,因为此处是定义 + if (b is null) + return a is null; + else if (a is null) + return false; + if (ReferenceEquals(a, b))//同一对象 + return true; + + return a.Equals(b, 0); + } + + /// + /// 比较核心 + /// + public bool Equals(Rect? b, double tolerance = 1e-6) + { + if (b is null) + return false; + if (ReferenceEquals(this, b)) //同一对象 + return true; + + return Math.Abs(_X - b._X) < tolerance && + Math.Abs(_Right - b._Right) < tolerance && + Math.Abs(_Top - b._Top) < tolerance && + Math.Abs(_Y - b._Y) < tolerance; + } + + public override int GetHashCode() + { + return (((int)_X ^ (int)_Y).GetHashCode() ^ (int)_Right).GetHashCode() ^ (int)_Top; + } + #endregion + + #region 包含 + public bool Contains(Point2d Point2d) + { + return Contains(Point2d.X, Point2d.Y); + } + public bool Contains(double x, double y) + { + return _X <= x && x <= _Right && + _Y <= y && y <= _Top; + } + + /// + /// 四个点都在内部就是包含 + /// + /// + /// + public bool Contains(Rect rect) + { + return _X <= rect._X && rect._Right <= _Right && + _Y <= rect._Y && rect._Top <= _Top; + } + + /// + /// 一个点在内部就是碰撞 + /// + /// + /// + public bool IntersectsWith(Rect rect) + { + return rect._X <= _Right && _X <= rect._Right && + rect._Top >= _Y && rect._Y <= _Top; + } + #endregion + + #region 方法 + /// + /// 获取共点 + /// + /// + public Point2d[] GetCommonPoint(Rect other) + { + return ToPoints().Intersect(other.ToPoints(), RectTolerance).ToArray(); + } + + public Point2d[] ToPoints() + { + Point2d a = MinPoint;//min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;//max + Point2d d = new(_X, _Top); + return new Point2d[] { a, b, c, d }; + } + + public (Point2d boxMin, Point2d boxRigthDown, Point2d boxMax, Point2d boxLeftUp) ToPoints4() + { + Point2d a = MinPoint;//min + Point2d b = new(_Right, _Y); + Point2d c = MaxPoint;//max + Point2d d = new(_X, _Top); + return (a, b, c, d); + } + + /// + /// 四周膨胀 + /// + /// + public Rect Expand(double d) + { + return new Rect(_X - d, _Y - d, _Right + d, _Top + d); + } + + /// + /// 是否矩形(带角度) + /// + /// + /// + public static bool IsRectAngle(List? ptList, double tolerance = 1e-8) + { + if (ptList == null) + throw new ArgumentNullException(nameof(ptList)); + + var pts = ptList.ToList(); + /* 消重,不这里设置,否则这不是一个正确的单元测试 + * //var ptList = pts.Distinct().ToList(); + * var ptList = pts.DistinctExBy((a, b) => a.DistanceTo(b) < 1e-6).ToList(); + */ + if (ptList.Count == 5) + { + //首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + //最快的方案 + //点乘求值法:(为了处理 正梯形/平行四边形 需要三次) + //这里的容差要在1e-8内,因为点乘的三次浮点数乘法会令精度变低 + var dot = DotProductValue(pts[0], pts[1], pts[3]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[1], pts[2], pts[0]); + if (Math.Abs(dot) < tolerance) + { + dot = DotProductValue(pts[2], pts[3], pts[1]); + return Math.Abs(dot) < tolerance; + } + } + return false; + } + + /// + /// 点积,求值 + /// 1.是两个向量的长度与它们夹角余弦的积 + /// 2.求四个点是否矩形使用 + /// + /// 原点 + /// 点 + /// 点 + /// 0方向相同,夹角0~90度;=0相互垂直;<0方向相反,夹角90~180度]]> + static double DotProductValue(Point2d o, Point2d a, Point2d b) + { + var oa = o.GetVectorTo(a); + var ob = o.GetVectorTo(b); + return (oa.X * ob.X) + (oa.Y * ob.Y); + } + + /// + /// 是否轴向矩形(无角度) + /// + public static bool IsRect(List? ptList, double tolerance = 1e-10) + { + if (ptList == null) + throw new ArgumentNullException(nameof(ptList)); + + var pts = ptList.ToList(); + if (ptList.Count == 5) + { + //首尾点相同移除最后 + if (pts[0].IsEqualTo(pts[^1], CadTolerance)) + pts.RemoveAt(pts.Count - 1); + } + if (pts.Count != 4) + return false; + + return Math.Abs(pts[0].X - pts[3].X) < tolerance && + Math.Abs(pts[0].Y - pts[1].Y) < tolerance && + Math.Abs(pts[1].X - pts[2].X) < tolerance && + Math.Abs(pts[2].Y - pts[3].Y) < tolerance; + } + + /// + /// 获取点集的包围盒的最小点和最大点(无角度) + /// + /// + public static (Point2d boxMin, Point2d boxMax) GetMinMax(IEnumerable pts) + { + var xMin = double.MaxValue; + var xMax = double.MinValue; + var yMin = double.MaxValue; + var yMax = double.MinValue; + //var zMin = double.MaxValue; + //var zMax = double.MinValue; + + pts.ForEach(p => { + xMin = Math.Min(p.X, xMin); + xMax = Math.Max(p.X, xMax); + yMin = Math.Min(p.Y, yMin); + yMax = Math.Max(p.Y, yMax); + //zMin = Math.Min(p.Z, zMin); + //zMax = Math.Max(p.Z, zMax); + }); + return (new Point2d(xMin, yMin), new Point2d(xMax, yMax)); + } + + /// + /// 矩形点序逆时针排列,将min点[0],max点是[3](带角度) + /// + /// + /// + public static bool RectAnglePointOrder(List? pts) + { + if (pts == null) + throw new ArgumentNullException(nameof(pts)); + + if (!Rect.IsRectAngle(pts)) + return false; + + //获取min和max点(非包围盒) + pts = pts.OrderBy(a => a.X).ThenBy(a => a.Y).ToList(); + var minPt = pts.First(); + var maxPt = pts.Last(); + var link = new LoopList(); + link.AddRange(pts); + + pts.Clear(); + //排序这四个点,左下/右下/右上/左上 + var node = link.Find(minPt); + for (int i = 0; i < 4; i++) + { + pts.Add(node!.Value); + node = node.Next; + } + //保证是逆时针 + var isAcw = CrossAclockwise(pts[0], pts[1], pts[2]); + if (!isAcw) + (pts[3], pts[1]) = (pts[1], pts[3]); + return true; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 传参是向量,表示原点是0,0 + /// 传参是向量,表示原点是0,0 + /// 其模为a与b构成的平行四边形面积 + static double Cross(Vector2d a, Vector2d b) + { + return a.X * b.Y - a.Y * b.X; + } + + /// + /// 叉积,二维叉乘计算 + /// + /// 原点 + /// oa向量 + /// ob向量,此为判断点 + /// 返回值有正负,表示绕原点四象限的位置变换,也就是有向面积 + static double Cross(Point2d o, Point2d a, Point2d b) + { + return Cross(o.GetVectorTo(a), o.GetVectorTo(b)); + } + + /// + /// 叉积,逆时针方向为真 + /// + /// 直线点1 + /// 直线点2 + /// 判断点 + /// b点在oa的逆时针 + static bool CrossAclockwise(Point2d o, Point2d a, Point2d b) + { + return Cross(o, a, b) > -1e-6;//浮点数容差考虑 + } + +#if !WinForm + public Entity ToPolyLine() + { + var bv = new List(); + var pts = ToPoints(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((i, vertex) => { + pl.AddVertexAt(i, vertex, 0, 0, 0); + }); + return pl; + } +#endif + + /// + /// 列扫碰撞检测(碰撞算法) + /// 比四叉树还快哦~ + /// + /// + /// 继承Rect的集合 + /// 先处理集合每一个成员;返回true就跳过后续委托 + /// 碰撞,返回两个碰撞的成员;返回true就跳过后续委托 + /// 后处理集合每一个成员 + public static void XCollision(List box, + Func firstProcessing, + Func collisionProcessing, + Action lastProcessing) where T : Rect + { + //先排序X:不需要Y排序,因为Y的上下浮动不共X .ThenBy(a => a.Box.Y) + //因为先排序就可以有序遍历x区间,超过就break,达到更快 + box = box.OrderBy(a => a._X).ToList(); + + //遍历所有图元 + for (int i = 0; i < box.Count; i++) + { + var oneRect = box[i]; + if (firstProcessing(oneRect)) + continue; + + bool actionlast = true; + + //搜索范围要在 one 的头尾中间的部分 + for (int j = i + 1; j < box.Count; j++) + { + var twoRect = box[j]; + //x碰撞:矩形2的Left 在 矩形1[Left-Right]闭区间;穿过的话,也必然有自己的Left因此不需要处理 + if (oneRect._X <= twoRect._X && twoRect._X <= oneRect._Right) + { + //y碰撞,那就是真的碰撞了 + if ((oneRect._Top >= twoRect._Top && twoRect._Top >= oneRect._Y) /*包容上边*/ + || (oneRect._Top >= twoRect._Y && twoRect._Y >= oneRect._Y) /*包容下边*/ + || (twoRect._Top >= oneRect._Top && oneRect._Y >= twoRect._Y)) /*穿过*/ + { + if (collisionProcessing(oneRect, twoRect)) + actionlast = false; + } + //这里想中断y高过它的无意义比较, + //但是必须排序Y,而排序Y必须同X,而这里不是同X(而是同X区间),所以不能中断 + //而做到X区间排序,就必须创造一个集合,再排序这个集合, + //会导致每个图元都拥有一次X区间集合,开销更巨大(因此放弃). + } + else + break;//因为已经排序了,后续的必然超过 x碰撞区间 + } + + if (actionlast) + lastProcessing(oneRect); + } + } + + #endregion + + #region 转换类型 +#if !WinForm + // 隐式转换(相当于是重载赋值运算符) + //public static implicit operator Rect(System.Windows.Rect rect) + //{ + // return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + //} + public static implicit operator Rect(System.Drawing.RectangleF rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } + public static implicit operator Rect(System.Drawing.Rectangle rect) + { + return new Rect(rect.Left, rect.Bottom, rect.Right, rect.Top); + } +#endif + + #region ToString + public sealed override string ToString() + { + return ToString(null, null); + } + public string ToString(IFormatProvider? provider) + { + return ToString(null, provider); + } + public string ToString(string? format = null, IFormatProvider? formatProvider = null) + { + return $"({_X.ToString(format, formatProvider)},{_Y.ToString(format, formatProvider)})," + + $"({_Right.ToString(format, formatProvider)},{_Top.ToString(format, formatProvider)})"; + + // return $"X={_X.ToString(format, formatProvider)}," + + // $"Y={_Y.ToString(format, formatProvider)}," + + // $"Right={_Right.ToString(format, formatProvider)}," + + // $"Top={_Top.ToString(format, formatProvider)}"; + } + + /*为了红黑树,加入这个*/ + public int CompareTo(Rect rect) + { + if (rect == null) + return -1; + if (_X < rect._X) + return -1; + else if (_X > rect._X) + return 1; + else if (_Y < rect._Y)/*x是一样的*/ + return -1; + else if (_Y > rect._Y) + return 1; + return 0;/*全部一样*/ + } + #endregion + + #endregion + + +} diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs b/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs new file mode 100644 index 0000000..2a0be8e --- /dev/null +++ b/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs @@ -0,0 +1,60 @@ +using System; + +namespace IFoxCAD.Cad; + +public static class Utility +{ + /// + /// 带有随机种子的随机数 + /// 为什么这样写随机种子呢 + /// + /// + public static Random GetRandom() + { + var tick = DateTime.Now.Ticks; + + /* + * 知识准备: + * | 高位64位 | 低位32位 | + * Convert.ToString(int.MaxValue, 2)输出二进制 "1111111111111111111111111111111" 31个;最高位是符号位,所以少1位 + * Convert.ToString(long.MaxValue,2)输出二进制,刚好长一倍 "11111111111111111111111111111111 1111111111111111111111111111111" 63个;最高位是符号位,所以少1位 + * Convert.ToString(0xffffffffL, 2)int.MaxValue再按位多1 "1 1111111111111111111111111111111" 32个;前面的0不会打印出来 + * + * Convert.ToString(long.MaxValue>>32, 2)相当于平移高位的到低位范围,也就是上面少打印的二进制 + * 验证右移是不是高位保留,答案是 + * var a = Convert.ToInt64("101111111111111111111111111111111111111111111111111111111111111", 2); + * Convert.ToString(a >> 32,2); + * + * 解释代码: + * 0x01: + * (int)(long.MaxValue & 0xffffffffL) | (int)(long.MaxValue >> 32); + * Convert.ToString(long.MaxValue & 0xffffffffL, 2)//去掉高位:"11111111111111111111111111111111" 32个,再强转int + * 按位与&是保证符号位肯定是1,其他尽可能为0,高位被去掉只是MaxValue&0的原因,强转才是去掉高位..."尽可能"一词带来第一次随机性 + * 0x02: + * Convert.ToString((long.MaxValue >> 32), 2) //去掉低位: "1111111111111111111111111111111" 31个,再强转int + * 按位或|是尽可能为1..."尽可能"一词带来第二次随机性 + * + */ + + var tickSeeds = (int)(tick & 0xffffffffL) | (int)(tick >> 32); + return new Random(tickSeeds); + } + + /// + /// 随机颜色 + /// + /// + public static System.Drawing.Color RandomColor + { + get + { + var ran = GetRandom(); + int R = ran.Next(255); + int G = ran.Next(255); + int B = ran.Next(255); + B = (R + G > 400) ? R + G - 400 : B;//0 : 380 - R - G; + B = (B > 255) ? 255 : B; + return System.Drawing.Color.FromArgb(R, G, B); + } + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs b/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs new file mode 100644 index 0000000..dc7abd3 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs @@ -0,0 +1,85 @@ +namespace IFoxCAD; + +/// +/// 多段线的顶点,凸度,头宽,尾宽 +/// +[Serializable] +public class BulgeVertexWidth +{ + /// + /// 顶点X + /// + public double X; + /// + /// 顶点Y + /// + public double Y; + /// + /// 凸度 + /// + public double Bulge; + /// + /// 头宽 + /// + public double StartWidth; + /// + /// 尾宽 + /// + public double EndWidth; + + public Point2d Vertex => new(X, Y); + + public BulgeVertexWidth() { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(double vertex_X, double vertex_Y, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + { + X = vertex_X; + Y = vertex_Y; + Bulge = bulge; + StartWidth = startWidth; + EndWidth = endWidth; + } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(Point2d vertex, + double bulge = 0, + double startWidth = 0, + double endWidth = 0) + : this(vertex.X, vertex.Y, bulge, startWidth, endWidth) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + public BulgeVertexWidth(BulgeVertex bv) + : this(bv.Vertex.X, bv.Vertex.Y, bv.Bulge) + { } + + /// + /// 多段线的顶点,凸度,头宽,尾宽 + /// + /// 多段线 + /// 子段编号 + public BulgeVertexWidth(Polyline pl, int index) + { + var pt = pl.GetPoint2dAt(index);//这里可以3d + X = pt.X; + Y = pt.Y; + Bulge = pl.GetBulgeAt(index); + StartWidth = pl.GetStartWidthAt(index); + EndWidth = pl.GetEndWidthAt(index); + } + + public BulgeVertex ToBulgeVertex() + { + return new BulgeVertex(Vertex, Bulge); + } +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs index 406a33d..bfbed62 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs @@ -1,135 +1,198 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 集合扩展类 +/// +public static class CollectionEx { /// - /// 集合扩展类 + /// 对象id迭代器转换为集合 /// - public static class CollectionEx + /// 对象id的迭代器 + /// 对象id集合 + public static ObjectIdCollection ToCollection(this IEnumerable ids) { - /// - /// 对象id迭代器转换为集合 - /// - /// 对象id的迭代器 - /// 对象id集合 - public static ObjectIdCollection ToCollection(this IEnumerable ids) - { - return new ObjectIdCollection(ids.ToArray()); - } + return new ObjectIdCollection(ids.ToArray()); + } - /// - /// 实体迭代器转换为集合 - /// - /// 对象类型 - /// 实体对象的迭代器 - /// 实体集合 - public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject - { - DBObjectCollection objCol = new(); - foreach (T obj in objs) - objCol.Add(obj); - return objCol; - } + /// + /// 实体迭代器转换为集合 + /// + /// 对象类型 + /// 实体对象的迭代器 + /// 实体集合 + public static DBObjectCollection ToCollection(this IEnumerable objs) where T : DBObject + { + DBObjectCollection objCol = new(); + foreach (T obj in objs) + objCol.Add(obj); + return objCol; + } - /// - /// double 数值迭代器转换为 double 数值集合 - /// - /// double 数值迭代器 - /// double 数值集合 - public static DoubleCollection ToCollection(this IEnumerable doubles) - { - return new DoubleCollection(doubles.ToArray()); - } + /// + /// double 数值迭代器转换为 double 数值集合 + /// + /// double 数值迭代器 + /// double 数值集合 + public static DoubleCollection ToCollection(this IEnumerable doubles) + { + return new DoubleCollection(doubles.ToArray()); + } - /// - /// 二维点迭代器转换为二维点集合 - /// - /// 二维点迭代器 - /// 二维点集合 - public static Point2dCollection ToCollection(this IEnumerable pts) - { - return new Point2dCollection(pts.ToArray()); - } + /// + /// 二维点迭代器转换为二维点集合 + /// + /// 二维点迭代器 + /// 二维点集合 + public static Point2dCollection ToCollection(this IEnumerable pts) + { + return new Point2dCollection(pts.ToArray()); + } - /// - /// 三维点迭代器转换为三维点集合 - /// - /// 三维点迭代器 - /// 三维点集合 - public static Point3dCollection ToCollection(this IEnumerable pts) - { - return new Point3dCollection(pts.ToArray()); - } + /// + /// 三维点迭代器转换为三维点集合 + /// + /// 三维点迭代器 + /// 三维点集合 + public static Point3dCollection ToCollection(this IEnumerable pts) + { + return new Point3dCollection(pts.ToArray()); + } + + /// + /// 对象id集合转换为对象id列表 + /// + /// 对象id集合 + /// 对象id列表 + public static List ToList(this ObjectIdCollection ids) + { + return ids.Cast().ToList(); + } - /// - /// 对象id集合转换为对象id列表 - /// - /// 对象id集合 - /// 对象id列表 - public static List ToList(this ObjectIdCollection ids) - { - return ids.Cast().ToList(); - } - public static List ToList(this StringCollection strs) + /// + /// 遍历集合的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public static void ForEach(this IEnumerable source, Action action) + { + foreach (var element in source) { - return strs.Cast().ToList(); + action?.Invoke(element); } - - /* Cast不进行过滤,而是直接强转 - var s = new System.Collections.ArrayList(); - s.Add("1"); - s.Add("a"); - s.Add(5666); - - var aaa = s.Cast().ToList(); - System.InvalidCastException: 指定的转换无效。 - + List..ctor(IEnumerable) - + System.Linq.Enumerable.ToList(IEnumerable) - - var aaa = s.Cast().ToList(); - System.InvalidCastException: 无法将类型为“System.Int32”的对象强制转换为类型“System.String”。 - + List..ctor(IEnumerable) - + System.Linq.Enumerable.ToList(IEnumerable) - */ - - /// - /// 遍历集合的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) + } + /// + /// 同时遍历集合索引和值的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public static void ForEach(this IEnumerable source, Action action) + { + int i = 0; + foreach (var item in source) { - if (action is null) - throw new ArgumentNullException(nameof(action)); - foreach (var element in source) - action.Invoke(element); + action?.Invoke(i, item); + i++; } + } + - /// - /// 同时遍历集合索引和值的迭代器,执行action委托 - /// - /// 集合值的类型 - /// 集合 - /// 要运行的委托 - public static void ForEach(this IEnumerable source, Action action) + #region 关键字集合 + public enum KeywordName + { + GlobalName, + LocalName, + DisplayName, + } + + /// + /// 含有关键字 + /// + /// 关键字集合 + /// 关键字 + /// 关键字容器字段名 + /// true含有 + public static bool Contains(this KeywordCollection collection, string name, + KeywordName keywordName = KeywordName.GlobalName) + { + bool contains = false; + switch (keywordName) { - if (action is null) - throw new ArgumentNullException(nameof(action)); - int i = 0; - foreach (var item in source) - { - action.Invoke(i, item); - i++; - } + case KeywordName.GlobalName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].GlobalName == name) + { + contains = true; + break; + } + break; + case KeywordName.LocalName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].LocalName == name) + { + contains = true; + break; + } + break; + case KeywordName.DisplayName: + for (int i = 0; i < collection.Count; i++) + if (collection[i].DisplayName == name) + { + contains = true; + break; + } + break; + default: + break; } + return contains; + } + + /// + /// 获取词典, + /// KeywordCollection是允许重复关键字的,没有哈希索引,在多次判断时候会遍历多次O(n),所以生成一个词典进行O(1) + /// + /// + /// + public static Dictionary GetDict(this KeywordCollection collection) + { + Dictionary map = new(); + for (int i = 0; i < collection.Count; i++) + map.Add(collection[i].GlobalName, collection[i].DisplayName); + return map; + } + #endregion + + #region IdMapping + /// + /// 旧块名 + /// + /// + /// + public static List GetKeys(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Key); + return ids; } + + /// + /// 新块名 + /// + /// + /// + public static List GetValues(this IdMapping idmap) + { + List ids = new(); + foreach (IdPair item in idmap) + ids.Add(item.Value); + return ids; + } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs index 7acb61e..51ff4e2 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs @@ -1,318 +1,312 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 二维解析类曲线转换为二维实体曲线扩展类 +/// +public static class Curve2dEx { + #region Curve2d + /// - /// 二维解析类曲线转换为二维实体曲线扩展类 + /// 按矩阵转换Ge2d曲线为Db曲线 /// - - public static class Curve2dEx + /// Ge2d曲线 + /// 曲线转换矩阵 + /// Db曲线 + public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) { - #region Curve2d - - /// - /// 按矩阵转换Ge2d曲线为Db曲线 - /// - /// Ge2d曲线 - /// 曲线转换矩阵 - /// Db曲线 - public static Curve? ToCurve(this Curve2d curve, Matrix3d mat) + return curve switch { - return curve switch - { - LineSegment2d li => ToCurve(li, mat), - NurbCurve2d nu => ToCurve(nu, mat), - EllipticalArc2d el => ToCurve(el, mat), - CircularArc2d ci => ToCurve(ci, mat), - PolylineCurve2d po => ToCurve(po, mat), - Line2d l2 => ToCurve(l2, mat), - CompositeCurve2d co => ToCurve(co, mat), - _ => null - }; - } + LineSegment2d li => ToCurve(li, mat), + NurbCurve2d nu => ToCurve(nu, mat), + EllipticalArc2d el => ToCurve(el, mat), + CircularArc2d ci => ToCurve(ci, mat), + PolylineCurve2d po => ToCurve(po, mat), + Line2d l2 => ToCurve(l2, mat), + CompositeCurve2d co => ToCurve(co, mat), + _ => null + }; + } + + #endregion Curve2d - #endregion Curve2d + #region CircularArc2d - #region CircularArc2d + /// + /// 判断点是否位于圆内及圆上 + /// + /// 二维解析类圆弧对象 + /// 二维点 + /// 位于圆内及圆上返回 ,反之返回 + public static bool IsIn(this CircularArc2d ca2d, Point2d pnt) + { + return ca2d.IsOn(pnt) || ca2d.IsInside(pnt); + } - /// - /// 判断点是否位于圆内及圆上 - /// - /// 二维解析类圆弧对象 - /// 二维点 - /// 位于圆内及圆上返回 ,反之返回 - public static bool IsIn(this CircularArc2d ca2d, Point2d pnt) + /// + /// 将二维解析类圆弧转换为实体圆或者圆弧,然后进行矩阵变换 + /// + /// 二维解析类圆弧对象 + /// 变换矩阵 + /// 实体圆或者圆弧 + public static Curve ToCurve(this CircularArc2d ca2d, Matrix3d mat) + { + Curve c = ToCurve(ca2d); + c.TransformBy(mat); + return c; + } + + /// + /// 将二维解析类圆弧转换为实体圆或者圆弧 + /// + /// 二维解析类圆弧对象 + /// 实体圆或者圆弧 + public static Curve ToCurve(this CircularArc2d ca2d) + { + if (ca2d.IsClosed()) { - return ca2d.IsOn(pnt) || ca2d.IsInside(pnt); + return ToCircle(ca2d); } - - /// - /// 将二维解析类圆弧转换为实体圆或者圆弧,然后进行矩阵变换 - /// - /// 二维解析类圆弧对象 - /// 变换矩阵 - /// 实体圆或者圆弧 - public static Curve ToCurve(this CircularArc2d ca2d, Matrix3d mat) + else { - Curve c = ToCurve(ca2d); - c.TransformBy(mat); - return c; + return ToArc(ca2d); } + } + + /// + /// 将二维解析类圆弧转换为实体圆 + /// + /// 二维解析类圆弧对象 + /// 实体圆 + public static Circle ToCircle(this CircularArc2d c2d) + { + return + new Circle( + new Point3d(new Plane(), c2d.Center), + new Vector3d(0, 0, 1), + c2d.Radius); + } + + /// + /// 将二维解析类圆弧转换为实体圆弧 + /// + /// 二维解析类圆弧对象 + /// 圆弧 + public static Arc ToArc(this CircularArc2d a2d) + { + double startangle, endangle; + double refangle = a2d.ReferenceVector.Angle; - /// - /// 将二维解析类圆弧转换为实体圆或者圆弧 - /// - /// 二维解析类圆弧对象 - /// 实体圆或者圆弧 - public static Curve ToCurve(this CircularArc2d ca2d) + if (a2d.IsClockWise) { - if (ca2d.IsClosed()) - { - return ToCircle(ca2d); - } - else - { - return ToArc(ca2d); - } + startangle = -a2d.EndAngle - refangle; + endangle = -a2d.StartAngle - refangle; } - - /// - /// 将二维解析类圆弧转换为实体圆 - /// - /// 二维解析类圆弧对象 - /// 实体圆 - public static Circle ToCircle(this CircularArc2d c2d) + else { - return - new Circle( - new Point3d(new Plane(), c2d.Center), - new Vector3d(0, 0, 1), - c2d.Radius); + startangle = a2d.StartAngle + refangle; + endangle = a2d.EndAngle + refangle; } - /// - /// 将二维解析类圆弧转换为实体圆弧 - /// - /// 二维解析类圆弧对象 - /// 圆弧 - public static Arc ToArc(this CircularArc2d a2d) - { - double startangle, endangle; - double refangle = a2d.ReferenceVector.Angle; + return + new Arc( + new Point3d(new Plane(), a2d.Center), + Vector3d.ZAxis, + a2d.Radius, + startangle, + endangle); + } - if (a2d.IsClockWise) + #endregion CircularArc2d + + #region EllipticalArc2d + + //椭圆弧 + /// + /// 将二维解析类椭圆弧转换为实体椭圆弧,然后进行矩阵变换 + /// + /// 二维解析类椭圆弧对象 + /// 变换矩阵 + /// 实体椭圆弧 + public static Ellipse ToCurve(this EllipticalArc2d ea2d, Matrix3d mat) + { + Ellipse e = ToCurve(ea2d); + e.TransformBy(mat); + return e; + } + + /// + /// 将二维解析类椭圆弧转换为实体椭圆弧 + /// + /// 二维解析类椭圆弧对象 + /// 实体椭圆弧 + public static Ellipse ToCurve(this EllipticalArc2d ea2d) + { + Plane plane = new(); + Ellipse ell = + new( + new Point3d(plane, ea2d.Center), + new Vector3d(0, 0, 1), + new Vector3d(plane, ea2d.MajorAxis) * ea2d.MajorRadius, + ea2d.MinorRadius / ea2d.MajorRadius, + 0, + Math.PI * 2); + if (!ea2d.IsClosed()) + { + if (ea2d.IsClockWise) { - startangle = -a2d.EndAngle - refangle; - endangle = -a2d.StartAngle - refangle; + ell.StartAngle = -ell.GetAngleAtParameter(ea2d.EndAngle); + ell.EndAngle = -ell.GetAngleAtParameter(ea2d.StartAngle); } else { - startangle = a2d.StartAngle + refangle; - endangle = a2d.EndAngle + refangle; + ell.StartAngle = ell.GetAngleAtParameter(ea2d.StartAngle); + ell.EndAngle = ell.GetAngleAtParameter(ea2d.EndAngle); } - - return - new Arc( - new Point3d(new Plane(), a2d.Center), - Vector3d.ZAxis, - a2d.Radius, - startangle, - endangle); } + return ell; + } - #endregion CircularArc2d - - #region EllipticalArc2d + #endregion EllipticalArc2d - //椭圆弧 - /// - /// 将二维解析类椭圆弧转换为实体椭圆弧,然后进行矩阵变换 - /// - /// 二维解析类椭圆弧对象 - /// 变换矩阵 - /// 实体椭圆弧 - public static Ellipse ToCurve(this EllipticalArc2d ea2d, Matrix3d mat) - { - Ellipse e = ToCurve(ea2d); - e.TransformBy(mat); - return e; - } + #region Line2d - /// - /// 将二维解析类椭圆弧转换为实体椭圆弧 - /// - /// 二维解析类椭圆弧对象 - /// 实体椭圆弧 - public static Ellipse ToCurve(this EllipticalArc2d ea2d) - { - Plane plane = new Plane(); - Ellipse ell = - new Ellipse( - new Point3d(plane, ea2d.Center), - new Vector3d(0, 0, 1), - new Vector3d(plane, ea2d.MajorAxis) * ea2d.MajorRadius, - ea2d.MinorRadius / ea2d.MajorRadius, - 0, - Math.PI * 2); - if (!ea2d.IsClosed()) + /// + /// 将二维解析类直线转换为实体类构造线 + /// + /// 二维解析类直线 + /// 实体类构造线 + public static Xline ToCurve(this Line2d line2d) + { + Plane plane = new(); + return + new Xline { - if (ea2d.IsClockWise) - { - ell.StartAngle = -ell.GetAngleAtParameter(ea2d.EndAngle); - ell.EndAngle = -ell.GetAngleAtParameter(ea2d.StartAngle); - } - else - { - ell.StartAngle = ell.GetAngleAtParameter(ea2d.StartAngle); - ell.EndAngle = ell.GetAngleAtParameter(ea2d.EndAngle); - } - } - return ell; - } - - #endregion EllipticalArc2d - - #region Line2d + BasePoint = new Point3d(plane, line2d.PointOnLine), + SecondPoint = new Point3d(plane, line2d.PointOnLine + line2d.Direction) + }; + } - /// - /// 将二维解析类直线转换为实体类构造线 - /// - /// 二维解析类直线 - /// 实体类构造线 - public static Xline ToCurve(this Line2d line2d) - { - var plane = new Plane(); - return - new Xline - { - BasePoint = new Point3d(plane, line2d.PointOnLine), - SecondPoint = new Point3d(plane, line2d.PointOnLine + line2d.Direction) - }; - } + /// + /// 将二维解析类直线转换为实体类构造线,然后进行矩阵变换 + /// + /// 二维解析类直线 + /// 变换矩阵 + /// 实体类构造线 + public static Xline ToCurve(this Line2d line2d, Matrix3d mat) + { + Xline xl = ToCurve(line2d); + xl.TransformBy(mat); + return xl; + } - /// - /// 将二维解析类直线转换为实体类构造线,然后进行矩阵变换 - /// - /// 二维解析类直线 - /// 变换矩阵 - /// 实体类构造线 - public static Xline ToCurve(this Line2d line2d, Matrix3d mat) - { - Xline xl = ToCurve(line2d); - xl.TransformBy(mat); - return xl; - } + /// + /// 将二维解析类构造线转换为二维解析类线段 + /// + /// 二维解析类构造线 + /// 起点参数 + /// 终点参数 + /// 二维解析类线段 + public static LineSegment2d ToLineSegment2d(this Line2d line2d, double fromParameter, double toParameter) + { + return + new LineSegment2d + ( + line2d.EvaluatePoint(fromParameter), + line2d.EvaluatePoint(toParameter) + ); + } - /// - /// 将二维解析类构造线转换为二维解析类线段 - /// - /// 二维解析类构造线 - /// 起点参数 - /// 终点参数 - /// 二维解析类线段 - public static LineSegment2d ToLineSegment2d(this Line2d line2d, double fromParameter, double toParameter) - { - return - new LineSegment2d - ( - line2d.EvaluatePoint(fromParameter), - line2d.EvaluatePoint(toParameter) - ); - } + #endregion Line2d - #endregion Line2d + #region LineSegment2d - #region LineSegment2d + /// + /// 将二维解析类线段转换为实体类直线,并进行矩阵变换 + /// + /// 二维解析类线段 + /// 变换矩阵 + /// 实体类直线 + public static Line ToCurve(this LineSegment2d ls2d, Matrix3d mat) + { + Line l = ToCurve(ls2d); + l.TransformBy(mat); + return l; + } - /// - /// 将二维解析类线段转换为实体类直线,并进行矩阵变换 - /// - /// 二维解析类线段 - /// 变换矩阵 - /// 实体类直线 - public static Line ToCurve(this LineSegment2d ls2d, Matrix3d mat) - { - Line l = ToCurve(ls2d); - l.TransformBy(mat); - return l; - } + /// + /// 将二维解析类线段转换为实体类直线 + /// + /// 二维解析类线段 + /// 实体类直线 + public static Line ToCurve(this LineSegment2d ls2d) + { + Plane plane = new(); + return + new Line( + new Point3d(plane, ls2d.StartPoint), + new Point3d(plane, ls2d.EndPoint)); - /// - /// 将二维解析类线段转换为实体类直线 - /// - /// 二维解析类线段 - /// 实体类直线 - public static Line ToCurve(this LineSegment2d ls2d) - { - Plane plane = new Plane(); - return - new Line( - new Point3d(plane, ls2d.StartPoint), - new Point3d(plane, ls2d.EndPoint)); + } - } + #endregion LineSegment2d - #endregion LineSegment2d + #region NurbCurve2d - #region NurbCurve2d + /// + /// 将二维解析类BURB曲线转换为实体类样条曲线,并进行矩阵变换 + /// + /// 二维解析类BURB曲线 + /// 变换矩阵 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve2d nc2d, Matrix3d mat) + { + Spline spl = ToCurve(nc2d); + spl.TransformBy(mat); + return spl; + } - /// - /// 将二维解析类BURB曲线转换为实体类样条曲线,并进行矩阵变换 - /// - /// 二维解析类BURB曲线 - /// 变换矩阵 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve2d nc2d, Matrix3d mat) + /// + /// 将二维解析类BURB曲线转换为实体类样条曲线 + /// + /// 二维解析类BURB曲线 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve2d nc2d) + { + int i; + Plane plane = new(); + Point3dCollection ctlpnts = new(); + for (i = 0; i < nc2d.NumControlPoints; i++) { - Spline spl = ToCurve(nc2d); - spl.TransformBy(mat); - return spl; + ctlpnts.Add(new Point3d(plane, nc2d.GetControlPointAt(i))); } - /// - /// 将二维解析类BURB曲线转换为实体类样条曲线 - /// - /// 二维解析类BURB曲线 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve2d nc2d) + DoubleCollection knots = new(); + foreach (double knot in nc2d.Knots) { - int i; - Plane plane = new Plane(); - Point3dCollection ctlpnts = new Point3dCollection(); - for (i = 0; i < nc2d.NumControlPoints; i++) - { - ctlpnts.Add(new Point3d(plane, nc2d.GetControlPointAt(i))); - } - - DoubleCollection knots = new DoubleCollection(); - foreach (double knot in nc2d.Knots) - { - knots.Add(knot); - } - - DoubleCollection weights = new DoubleCollection(); - for (i = 0; i < nc2d.NumWeights; i++) - { - weights.Add(nc2d.GetWeightAt(i)); - } + knots.Add(knot); + } - NurbCurve2dData ncdata = nc2d.DefinitionData; - - return - new Spline( - ncdata.Degree, - ncdata.Rational, - nc2d.IsClosed(), - ncdata.Periodic, - ctlpnts, - knots, - weights, - 0, - nc2d.Knots.Tolerance); + DoubleCollection weights = new(); + for (i = 0; i < nc2d.NumWeights; i++) + { + weights.Add(nc2d.GetWeightAt(i)); } - #endregion NurbCurve2d + NurbCurve2dData ncdata = nc2d.DefinitionData; + + return + new Spline( + ncdata.Degree, + ncdata.Rational, + nc2d.IsClosed(), + ncdata.Periodic, + ctlpnts, + knots, + weights, + 0, + nc2d.Knots.Tolerance); } + + #endregion NurbCurve2d } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs index 9d477ba..57ca8fd 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs @@ -1,567 +1,557 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 三维解析类曲线转换为三维实体曲线扩展类 +/// +public static class Curve3dEx { /// - /// 三维解析类曲线转换为三维实体曲线扩展类 + /// 判断两个浮点数是否相等 /// - public static class Curve3dEx + /// 容差 + /// 第一个数 + /// 第二个数 + /// 两个数的差值的绝对值小于容差返回 ,反之返回 + public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) { - /// - /// 判断两个浮点数是否相等 - /// - /// 容差 - /// 第一个数 - /// 第二个数 - /// 两个数的差值的绝对值小于容差返回 ,反之返回 - public static bool IsEqualPoint(this Tolerance tol, double d1, double d2) - { - return Math.Abs(d1 - d2) < tol.EqualPoint; - } + return Math.Abs(d1 - d2) < tol.EqualPoint; + } - #region Curve3d + #region Curve3d - /// - /// 获取三维解析类曲线(自交曲线)的交点参数 - /// - /// 三维解析类曲线 - /// 曲线参数的列表 - public static List GetParamsAtIntersectionPoints(this Curve3d c3d) - { - CurveCurveIntersector3d cci = new CurveCurveIntersector3d(c3d, c3d, Vector3d.ZAxis); - List pars = new List(); - for (int i = 0; i < cci.NumberOfIntersectionPoints; i++) - { - pars.AddRange(cci.GetIntersectionParameters(i)); - } - pars.Sort(); - return pars; - } + /// + /// 获取三维解析类曲线(自交曲线)的交点参数 + /// + /// 三维解析类曲线 + /// 曲线参数的列表 + public static List GetParamsAtIntersectionPoints(this Curve3d c3d) + { + CurveCurveIntersector3d cci = new(c3d, c3d, Vector3d.ZAxis); + List pars = new(); + for (int i = 0; i < cci.NumberOfIntersectionPoints; i++) + pars.AddRange(cci.GetIntersectionParameters(i)); - /// - /// 获取三维解析类子曲线 - /// - /// 三维解析类曲线 - /// 子段曲线起点参数 - /// 子段曲线终点参数 - /// 三维解析类曲线 - public static Curve3d GetSubCurve(this Curve3d curve, double from, double to) + pars.Sort(); + return pars; + } + + /// + /// 获取三维解析类子曲线 + /// + /// 三维解析类曲线 + /// 子段曲线起点参数 + /// 子段曲线终点参数 + /// 三维解析类曲线 + public static Curve3d GetSubCurve(this Curve3d curve, double from, double to) + { + Interval inter = curve.GetInterval(); + bool atStart = Tolerance.Global.IsEqualPoint(inter.LowerBound, from); + bool atEnd = Tolerance.Global.IsEqualPoint(inter.UpperBound, to); + if (atStart && atEnd) + return (Curve3d)curve.Clone(); + if (curve is NurbCurve3d) { - Interval inter = curve.GetInterval(); - bool atStart = Tolerance.Global.IsEqualPoint(inter.LowerBound, from); - bool atEnd = Tolerance.Global.IsEqualPoint(inter.UpperBound, to); - if (atStart && atEnd) - return (Curve3d)curve.Clone(); - if (curve is NurbCurve3d) + if (from < to) { - if (from < to) + NurbCurve3d clone = (NurbCurve3d)curve.Clone(); + if (atStart || atEnd) { - NurbCurve3d clone = (NurbCurve3d)curve.Clone(); - if (atStart || atEnd) - { - clone.HardTrimByParams(from, to); - return clone; - } - else - { - clone.HardTrimByParams(inter.LowerBound, to); - clone.HardTrimByParams(from, to); - return clone; - } + clone.HardTrimByParams(from, to); + return clone; } else { - NurbCurve3d clone1 = (NurbCurve3d)curve.Clone(); - clone1.HardTrimByParams(from, inter.UpperBound); - NurbCurve3d clone2 = (NurbCurve3d)curve.Clone(); - clone2.HardTrimByParams(inter.LowerBound, to); - clone1.JoinWith(clone2); - return clone1; + clone.HardTrimByParams(inter.LowerBound, to); + clone.HardTrimByParams(from, to); + return clone; } } else { - Curve3d clone = (Curve3d)curve.Clone(); - clone.SetInterval(new Interval(from, to, Tolerance.Global.EqualPoint)); - return clone; + NurbCurve3d clone1 = (NurbCurve3d)curve.Clone(); + clone1.HardTrimByParams(from, inter.UpperBound); + NurbCurve3d clone2 = (NurbCurve3d)curve.Clone(); + clone2.HardTrimByParams(inter.LowerBound, to); + clone1.JoinWith(clone2); + return clone1; } } - - /// - /// 将三维解析类曲线转换为三维实体类曲线 - /// - /// 三维解析类曲线 - /// 三维实体类曲线 - public static Curve ToCurve(this Curve3d curve) + else { - return curve switch - { - CompositeCurve3d co => ToCurve(co), - LineSegment3d li => ToCurve(li), - EllipticalArc3d el => ToCurve(el), - CircularArc3d ci => ToCurve(ci), - NurbCurve3d nu => ToCurve(nu), - PolylineCurve3d pl => ToCurve(pl), - Line3d l3 => ToCurve(l3), - _ => null - }; + Curve3d clone = (Curve3d)curve.Clone(); + clone.SetInterval(new Interval(from, to, Tolerance.Global.EqualPoint)); + return clone; } + } - /// - /// 将三维解析类曲线转换为三维解析类Nurb曲线 - /// - /// 三维解析类曲线 - /// 三维解析类Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Curve3d curve) + /// + /// 将三维解析类曲线转换为三维实体类曲线 + /// + /// 三维解析类曲线 + /// 三维实体类曲线 + public static Curve? ToCurve(this Curve3d curve) + { + return curve switch { - return curve switch - { - LineSegment3d line => new NurbCurve3d(line), - EllipticalArc3d el => new NurbCurve3d(el), - CircularArc3d cir => new NurbCurve3d(ToEllipticalArc3d(cir)), - NurbCurve3d nur => nur, - PolylineCurve3d pl => new NurbCurve3d(3, pl, false), - _ => null - }; - } + CompositeCurve3d co => ToCurve(co), + LineSegment3d li => ToCurve(li), + EllipticalArc3d el => ToCurve(el), + CircularArc3d ci => ToCurve(ci), + NurbCurve3d nu => ToCurve(nu), + PolylineCurve3d pl => ToCurve(pl), + Line3d l3 => ToCurve(l3), + _ => null + }; + } + + /// + /// 将三维解析类曲线转换为三维解析类Nurb曲线 + /// + /// 三维解析类曲线 + /// 三维解析类Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Curve3d curve) + { + return curve switch + { + LineSegment3d line => new NurbCurve3d(line), + EllipticalArc3d el => new NurbCurve3d(el), + CircularArc3d cir => new NurbCurve3d(ToEllipticalArc3d(cir)), + NurbCurve3d nur => nur, + PolylineCurve3d pl => new NurbCurve3d(3, pl, false), + _ => null + }; + } - #endregion Curve3d + #endregion Curve3d - #region CompositeCurve3d + #region CompositeCurve3d - /// - /// 判断是否为圆和椭圆 - /// - /// 三维解析类曲线 - /// 完整圆及完整的椭圆返回 ,反之返回 - public static bool IsCircular(this Curve3d curve) + /// + /// 判断是否为圆和椭圆 + /// + /// 三维解析类曲线 + /// 完整圆及完整的椭圆返回 ,反之返回 + public static bool IsCircular(this Curve3d curve) + { + return curve switch { - return curve switch - { - CircularArc3d or EllipticalArc3d => curve.IsClosed(), - _ => false - }; - } + CircularArc3d or EllipticalArc3d => curve.IsClosed(), + _ => false + }; + } - /// - /// 将三维复合曲线按曲线参数分割 - /// - /// 三维复合曲线 - /// 曲线参数列表 - /// 三维复合曲线列表 - public static List GetSplitCurves(this CompositeCurve3d c3d, List pars) + /// + /// 将三维复合曲线按曲线参数分割 + /// + /// 三维复合曲线 + /// 曲线参数列表 + /// 三维复合曲线列表 + public static List GetSplitCurves(this CompositeCurve3d c3d, List pars) + { + //曲线参数剔除重复的 + pars.Sort(); + for (int i = pars.Count - 1; i > 0; i--) { - Interval inter = c3d.GetInterval(); - Curve3d[] c3ds = c3d.GetCurves(); - pars.Sort(); - for (int i = pars.Count - 1; i > 0; i--) - { - if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) - pars.RemoveAt(i); - } + if (Tolerance.Global.IsEqualPoint(pars[i], pars[i - 1])) + pars.RemoveAt(i); + } - if (pars.Count == 0) - return new List(); - if (c3ds.Length == 1 && c3ds[0].IsClosed()) + if (pars.Count == 0) + return new List(); + + //这个是曲线参数类 + var inter = c3d.GetInterval(); + //曲线们 + var c3ds = c3d.GetCurves(); + if (c3ds.Length == 1 && c3ds[0].IsClosed()) + { + //闭合曲线不允许打断于一点 + if (pars.Count > 1) { - //闭合曲线不允许打断于一点 - if (pars.Count > 1) + //如果包含起点 + if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) { - //如果包含起点 - if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) - { - pars[0] = inter.LowerBound; - //又包含终点,去除终点 - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - { - pars.RemoveAt(pars.Count - 1); - if (pars.Count == 1) - return new List(); - } - } - else if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) + pars[0] = inter.LowerBound; + //又包含终点,去除终点 + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - pars[pars.Count - 1] = inter.UpperBound; + pars.RemoveAt(pars.Count - 1); + if (pars.Count == 1) + return new List(); } - //加入第一点以支持反向打断 - pars.Add(pars[0]); } - else + else if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) { - return new List(); + pars[^1] = inter.UpperBound; } + //加入第一点以支持反向打断 + pars.Add(pars[0]); } else { - //非闭合曲线加入起点和终点 - if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) - pars[0] = inter.LowerBound; - else - pars.Insert(0, inter.LowerBound); - if (Tolerance.Global.IsEqualPoint(pars[pars.Count - 1], inter.UpperBound)) - pars[pars.Count - 1] = inter.UpperBound; - else - pars.Add(inter.UpperBound); - } - - List curves = new List(); - for (int i = 0; i < pars.Count - 1; i++) - { - List cc3ds = new List(); - //复合曲线参数转换到包含曲线参数 - CompositeParameter cp1 = c3d.GlobalToLocalParameter(pars[i]); - CompositeParameter cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); - if (cp1.SegmentIndex == cp2.SegmentIndex) - { - cc3ds.Add( - c3ds[cp1.SegmentIndex].GetSubCurve( - cp1.LocalParameter, - cp2.LocalParameter)); - } - else - { - inter = c3ds[cp1.SegmentIndex].GetInterval(); - cc3ds.Add( - c3ds[cp1.SegmentIndex].GetSubCurve( - cp1.LocalParameter, - inter.UpperBound)); - for (int j = cp1.SegmentIndex + 1; j < cp2.SegmentIndex; j++) - { - cc3ds.Add((Curve3d)c3ds[j].Clone()); - } - inter = c3ds[cp2.SegmentIndex].GetInterval(); - cc3ds.Add( - c3ds[cp2.SegmentIndex].GetSubCurve( - inter.LowerBound, - cp2.LocalParameter)); - } - curves.Add(new CompositeCurve3d(cc3ds.ToArray())); - } - - if (c3d.IsClosed() && c3ds.Length > 1) - { - var cs = curves[curves.Count - 1].GetCurves().ToList(); - cs.AddRange(curves[0].GetCurves()); - curves[curves.Count - 1] = new CompositeCurve3d(cs.ToArray()); - curves.RemoveAt(0); + return new List(); } - - return curves; + } + else + { + //非闭合曲线加入起点和终点 + if (Tolerance.Global.IsEqualPoint(pars[0], inter.LowerBound)) + pars[0] = inter.LowerBound; + else + pars.Insert(0, inter.LowerBound); + if (Tolerance.Global.IsEqualPoint(pars[^1], inter.UpperBound)) + pars[^1] = inter.UpperBound; + else + pars.Add(inter.UpperBound); } - /// - /// 将复合曲线转换为实体类曲线 - /// - /// 三维复合曲线 - /// 实体曲线 - public static Curve ToCurve(this CompositeCurve3d curve) + List curves = new(); + for (int i = 0; i < pars.Count - 1; i++) { - Curve3d[] cs = curve.GetCurves(); - if (cs.Length == 0) + List cc3ds = new(); + //复合曲线参数转换到包含曲线参数 + var cp1 = c3d.GlobalToLocalParameter(pars[i]); + var cp2 = c3d.GlobalToLocalParameter(pars[i + 1]); + if (cp1.SegmentIndex == cp2.SegmentIndex) { - return null; - } - else if (cs.Length == 1) - { - return ToCurve(cs[0]); + cc3ds.Add( + c3ds[cp1.SegmentIndex].GetSubCurve( + cp1.LocalParameter, + cp2.LocalParameter)); } else { - bool hasNurb = false; - - foreach (var c in cs) - { - if (c is NurbCurve3d || c is EllipticalArc3d) - { - hasNurb = true; - break; - } - } - if (hasNurb) - { - NurbCurve3d nc3d = cs[0].ToNurbCurve3d(); - for (int i = 1; i < cs.Length; i++) - nc3d.JoinWith(cs[i].ToNurbCurve3d()); - return nc3d.ToCurve(); - } - else + inter = c3ds[cp1.SegmentIndex].GetInterval(); + cc3ds.Add( + c3ds[cp1.SegmentIndex].GetSubCurve( + cp1.LocalParameter, + inter.UpperBound)); + for (int j = cp1.SegmentIndex + 1; j < cp2.SegmentIndex; j++) { - return ToPolyline(curve); + cc3ds.Add((Curve3d)c3ds[j].Clone()); } + inter = c3ds[cp2.SegmentIndex].GetInterval(); + cc3ds.Add( + c3ds[cp2.SegmentIndex].GetSubCurve( + inter.LowerBound, + cp2.LocalParameter)); } + curves.Add(new CompositeCurve3d(cc3ds.ToArray())); } - /// - /// 将三维复合曲线转换为实体类多段线 - /// - /// 三维复合曲线 - /// 实体类多段线 - public static Polyline ToPolyline(this CompositeCurve3d cc3d) + if (c3d.IsClosed() && c3ds.Length > 1) { - Polyline pl = new Polyline(); - pl.Elevation = cc3d.StartPoint[2]; - Plane plane = pl.GetPlane(); - Point2d endver = Point2d.Origin; - int i = 0; - foreach (Curve3d c3d in cc3d.GetCurves()) - { - if (c3d is CircularArc3d) - { - CircularArc3d ca3d = (CircularArc3d)c3d; - double b = Math.Tan(0.25 * (ca3d.EndAngle - ca3d.StartAngle)) * ca3d.Normal[2]; - pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), b, 0, 0); - endver = c3d.EndPoint.Convert2d(plane); - } - else - { - pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), 0, 0, 0); - endver = c3d.EndPoint.Convert2d(plane); - } - i++; - } - pl.AddVertexAt(i, endver, 0, 0, 0); - return pl; + var cus1 = curves[curves.Count - 1].GetCurves(); + var cus2 = curves[0].GetCurves(); + var cs = cus1.Combine2(cus2); + curves[^1] = new CompositeCurve3d(cs); + curves.RemoveAt(0); } + return curves; + } - #endregion CompositeCurve3d - - #region Line3d + /// + /// 将复合曲线转换为实体类曲线 + /// + /// 三维复合曲线 + /// 实体曲线 + public static Curve? ToCurve(this CompositeCurve3d curve) + { + Curve3d[] cs = curve.GetCurves(); + if (cs.Length == 0) + return null; + if (cs.Length == 1) + return ToCurve(cs[0]); - /// - /// 将解析类三维构造线转换为实体类构造线 - /// - /// 解析类三维构造线 - /// 实体类构造线 - public static Xline ToCurve(this Line3d line3d) - { - return - new Xline - { - BasePoint = line3d.PointOnLine, - SecondPoint = line3d.PointOnLine + line3d.Direction - }; - } + bool hasNurb = false; - /// - /// 将三维解析类构造线转换为三维解析类线段 - /// - /// 三维解析类构造线 - /// 起点参数 - /// 终点参数 - /// 三维解析类线段 - public static LineSegment3d ToLineSegment3d(this Line3d line3d, double fromParameter, double toParameter) + for (int i = 0; i < cs.Length; i++) { - return - new LineSegment3d - ( - line3d.EvaluatePoint(fromParameter), - line3d.EvaluatePoint(toParameter) - ); + var c = cs[i]; + if (c is NurbCurve3d || c is EllipticalArc3d) + { + hasNurb = true; + break; + } } - - #endregion Line3d - - #region LineSegment3d - - /// - /// 将三维解析类线段转换为实体类直线 - /// - /// 三维解析类线段 - /// 实体类直线 - public static Line ToCurve(this LineSegment3d lineSeg3d) + if (hasNurb) { - return new Line(lineSeg3d.StartPoint, lineSeg3d.EndPoint); + var nc3d = cs[0].ToNurbCurve3d(); + for (int i = 1; i < cs.Length; i++) + nc3d?.JoinWith(cs[i].ToNurbCurve3d()); + return nc3d?.ToCurve(); } - #endregion LineSegment3d - - #region CircularArc3d + return ToPolyline(curve); + } - /// - /// 将三维解析类圆/弧转换为实体圆/弧 - /// - /// 三维解析类圆/弧 - /// 实体圆/弧 - public static Curve ToCurve(this CircularArc3d ca3d) + /// + /// 将三维复合曲线转换为实体类多段线 + /// + /// 三维复合曲线 + /// 实体类多段线 + public static Polyline ToPolyline(this CompositeCurve3d cc3d) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.Elevation = cc3d.StartPoint[2]; + + Plane plane = pl.GetPlane(); + Point2d endver = Point2d.Origin; + int i = 0; + foreach (Curve3d c3d in cc3d.GetCurves()) { - if (ca3d.IsClosed()) + if (c3d is CircularArc3d ca3d) { - return ToCircle(ca3d); + double b = Math.Tan(0.25 * (ca3d.EndAngle - ca3d.StartAngle)) * ca3d.Normal[2]; + pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), b, 0, 0); + endver = c3d.EndPoint.Convert2d(plane); } else { - return ToArc(ca3d); + pl.AddVertexAt(i, c3d.StartPoint.Convert2d(plane), 0, 0, 0); + endver = c3d.EndPoint.Convert2d(plane); } + i++; } + pl.AddVertexAt(i, endver, 0, 0, 0); + return pl; + } - /// - /// 将三维解析类圆/弧转换为实体圆 - /// - /// 三维解析类圆/弧 - /// 实体圆 - public static Circle ToCircle(this CircularArc3d ca3d) => - new Circle(ca3d.Center, ca3d.Normal, ca3d.Radius); - - /// - /// 将三维解析类圆/弧转换为实体圆弧 - /// - /// 三维解析类圆/弧 - /// 实体圆弧 - public static Arc ToArc(this CircularArc3d ca3d) - { - //必须新建,而不能直接使用GetPlane()获取 - double angle = ca3d.ReferenceVector.AngleOnPlane(new Plane(ca3d.Center, ca3d.Normal)); - return new Arc(ca3d.Center, ca3d.Normal, ca3d.Radius, ca3d.StartAngle + angle, ca3d.EndAngle + angle); - } + #endregion CompositeCurve3d + + #region Line3d - /// - /// 将三维解析类圆/弧转换为三维解析类椭圆弧 - /// - /// 三维解析类圆/弧 - /// 三维解析类椭圆弧 - public static EllipticalArc3d ToEllipticalArc3d(this CircularArc3d ca3d) + /// + /// 将解析类三维构造线转换为实体类构造线 + /// + /// 解析类三维构造线 + /// 实体类构造线 + public static Xline ToCurve(this Line3d line3d) + { + return + new Xline + { + BasePoint = line3d.PointOnLine, + SecondPoint = line3d.PointOnLine + line3d.Direction + }; + } + + /// + /// 将三维解析类构造线转换为三维解析类线段 + /// + /// 三维解析类构造线 + /// 起点参数 + /// 终点参数 + /// 三维解析类线段 + public static LineSegment3d ToLineSegment3d(this Line3d line3d, double fromParameter, double toParameter) + { + return + new LineSegment3d + ( + line3d.EvaluatePoint(fromParameter), + line3d.EvaluatePoint(toParameter) + ); + } + + #endregion Line3d + + #region LineSegment3d + + /// + /// 将三维解析类线段转换为实体类直线 + /// + /// 三维解析类线段 + /// 实体类直线 + public static Line ToCurve(this LineSegment3d lineSeg3d) + { + return new Line(lineSeg3d.StartPoint, lineSeg3d.EndPoint); + } + + #endregion LineSegment3d + + #region CircularArc3d + + /// + /// 将三维解析类圆/弧转换为实体圆/弧 + /// + /// 三维解析类圆/弧 + /// 实体圆/弧 + public static Curve ToCurve(this CircularArc3d ca3d) + { + if (ca3d.IsClosed()) { - Vector3d zaxis = ca3d.Normal; - Vector3d xaxis = ca3d.ReferenceVector; - Vector3d yaxis = zaxis.CrossProduct(xaxis); - - return - new EllipticalArc3d( - ca3d.Center, - xaxis, - yaxis, - ca3d.Radius, - ca3d.Radius, - ca3d.StartAngle, - ca3d.EndAngle); + return ToCircle(ca3d); } - - /// - /// 将三维解析类圆/弧转换为三维解析类Nurb曲线 - /// - /// 三维解析类圆/弧 - /// 三维解析类Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this CircularArc3d ca3d) + else { - EllipticalArc3d ea3d = ToEllipticalArc3d(ca3d); - NurbCurve3d nc3d = new NurbCurve3d(ea3d); - return nc3d; + return ToArc(ca3d); } + } + + /// + /// 将三维解析类圆/弧转换为实体圆 + /// + /// 三维解析类圆/弧 + /// 实体圆 + public static Circle ToCircle(this CircularArc3d ca3d) => + new(ca3d.Center, ca3d.Normal, ca3d.Radius); - #endregion CircularArc3d + /// + /// 将三维解析类圆/弧转换为实体圆弧 + /// + /// 三维解析类圆/弧 + /// 实体圆弧 + public static Arc ToArc(this CircularArc3d ca3d) + { + //必须新建,而不能直接使用GetPlane()获取 + double angle = ca3d.ReferenceVector.AngleOnPlane(new Plane(ca3d.Center, ca3d.Normal)); + return new Arc(ca3d.Center, ca3d.Normal, ca3d.Radius, ca3d.StartAngle + angle, ca3d.EndAngle + angle); + } - #region EllipticalArc3d + /// + /// 将三维解析类圆/弧转换为三维解析类椭圆弧 + /// + /// 三维解析类圆/弧 + /// 三维解析类椭圆弧 + public static EllipticalArc3d ToEllipticalArc3d(this CircularArc3d ca3d) + { + Vector3d zaxis = ca3d.Normal; + Vector3d xaxis = ca3d.ReferenceVector; + Vector3d yaxis = zaxis.CrossProduct(xaxis); + + return + new EllipticalArc3d( + ca3d.Center, + xaxis, + yaxis, + ca3d.Radius, + ca3d.Radius, + ca3d.StartAngle, + ca3d.EndAngle); + } - /// - /// 将三维解析类椭圆弧转换为实体类椭圆弧 - /// - /// 三维解析类椭圆弧 - /// 实体类椭圆弧 - public static Ellipse ToCurve(this EllipticalArc3d ea3d) + /// + /// 将三维解析类圆/弧转换为三维解析类Nurb曲线 + /// + /// 三维解析类圆/弧 + /// 三维解析类Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this CircularArc3d ca3d) + { + EllipticalArc3d ea3d = ToEllipticalArc3d(ca3d); + NurbCurve3d nc3d = new(ea3d); + return nc3d; + } + + #endregion CircularArc3d + + #region EllipticalArc3d + + /// + /// 将三维解析类椭圆弧转换为实体类椭圆弧 + /// + /// 三维解析类椭圆弧 + /// 实体类椭圆弧 + public static Ellipse ToCurve(this EllipticalArc3d ea3d) + { + Ellipse ell = + new( + ea3d.Center, + ea3d.Normal, + ea3d.MajorAxis * ea3d.MajorRadius, + ea3d.MinorRadius / ea3d.MajorRadius, + 0, + Math.PI * 2); + //Ge椭圆角度就是Db椭圆的参数 + if (!ea3d.IsClosed()) { - Ellipse ell = - new Ellipse( - ea3d.Center, - ea3d.Normal, - ea3d.MajorAxis * ea3d.MajorRadius, - ea3d.MinorRadius / ea3d.MajorRadius, - 0, - Math.PI * 2); - //Ge椭圆角度就是Db椭圆的参数 - if (!ea3d.IsClosed()) - { - ell.StartAngle = ell.GetAngleAtParameter(ea3d.StartAngle); - ell.EndAngle = ell.GetAngleAtParameter(ea3d.EndAngle); - } - return ell; + ell.StartAngle = ell.GetAngleAtParameter(ea3d.StartAngle); + ell.EndAngle = ell.GetAngleAtParameter(ea3d.EndAngle); } + return ell; + } - #endregion EllipticalArc3d + #endregion EllipticalArc3d - #region NurbCurve3d + #region NurbCurve3d - /// - /// 将三维解析类Nurb曲线转换为实体类样条曲线 - /// - /// 三维解析类Nurb曲线 - /// 实体类样条曲线 - public static Spline ToCurve(this NurbCurve3d nc3d) + /// + /// 将三维解析类Nurb曲线转换为实体类样条曲线 + /// + /// 三维解析类Nurb曲线 + /// 实体类样条曲线 + public static Spline ToCurve(this NurbCurve3d nc3d) + { + Spline spl; + if (nc3d.HasFitData) { - Spline spl = null; - if (nc3d.HasFitData) + NurbCurve3dFitData fdata = nc3d.FitData; + if (fdata.TangentsExist) { - NurbCurve3dFitData fdata = nc3d.FitData; - if (fdata.TangentsExist) - { - spl = new Spline( - fdata.FitPoints, - fdata.StartTangent, - fdata.EndTangent, - nc3d.Order, - fdata.FitTolerance.EqualPoint); - } - else - { - spl = new Spline( - fdata.FitPoints, - nc3d.Order, - fdata.FitTolerance.EqualPoint); - } + spl = new Spline( + fdata.FitPoints, + fdata.StartTangent, + fdata.EndTangent, + nc3d.Order, + fdata.FitTolerance.EqualPoint); } else { - DoubleCollection knots = new DoubleCollection(); - foreach (double knot in nc3d.Knots) - knots.Add(knot); - - NurbCurve3dData ncdata = nc3d.DefinitionData; - spl = new Spline( - ncdata.Degree, - ncdata.Rational, - nc3d.IsClosed(), - ncdata.Periodic, - ncdata.ControlPoints, - knots, - ncdata.Weights, - Tolerance.Global.EqualPoint, - ncdata.Knots.Tolerance); + fdata.FitPoints, + nc3d.Order, + fdata.FitTolerance.EqualPoint); } - return spl; } + else + { + DoubleCollection knots = new(); + foreach (double knot in nc3d.Knots) + knots.Add(knot); + + NurbCurve3dData ncdata = nc3d.DefinitionData; + + spl = new Spline( + ncdata.Degree, + ncdata.Rational, + nc3d.IsClosed(), + ncdata.Periodic, + ncdata.ControlPoints, + knots, + ncdata.Weights, + Tolerance.Global.EqualPoint, + ncdata.Knots.Tolerance); + } + return spl; + } - #endregion NurbCurve3d - - #region PolylineCurve3d + #endregion NurbCurve3d - /// - /// 将三维解析类多段线转换为实体类三维多段线 - /// - /// 三维解析类多段线 - /// 实体类三维多段线 - public static Polyline3d ToCurve(this PolylineCurve3d pl3d) - { - Point3dCollection pnts = new Point3dCollection(); + #region PolylineCurve3d - for (int i = 0; i < pl3d.NumberOfControlPoints; i++) - { - pnts.Add(pl3d.ControlPointAt(i)); - } + /// + /// 将三维解析类多段线转换为实体类三维多段线 + /// + /// 三维解析类多段线 + /// 实体类三维多段线 + public static Polyline3d ToCurve(this PolylineCurve3d pl3d) + { + Point3dCollection pnts = new(); - bool closed = false; - int n = pnts.Count - 1; - if (pnts[0] == pnts[n]) - { - pnts.RemoveAt(n); - closed = true; - } - return new Polyline3d(Poly3dType.SimplePoly, pnts, closed); + for (int i = 0; i < pl3d.NumberOfControlPoints; i++) + { + pnts.Add(pl3d.ControlPointAt(i)); } - #endregion PolylineCurve3d + bool closed = false; + int n = pnts.Count - 1; + if (pnts[0] == pnts[n]) + { + pnts.RemoveAt(n); + closed = true; + } + return new Polyline3d(Poly3dType.SimplePoly, pnts, closed); } + + #endregion PolylineCurve3d } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs index 26fbd18..4056bc5 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs @@ -1,984 +1,662 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using IFoxCAD.Collections; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 实体类曲线扩展类 +/// +public static class CurveEx { /// - /// 实体类曲线扩展类 + /// 曲线长度 /// - public static class CurveEx + /// 曲线 + /// 长度 + public static double GetLength(this Curve curve) { - /// - /// 曲线长度 - /// - /// 曲线 - /// 长度 - public static double GetLength(this Curve curve) - { - return - curve.GetDistanceAtParameter(curve.EndParam); - } + return curve.GetDistanceAtParameter(curve.EndParam); + } - /// - /// 获取分割曲线集合 - /// - /// 曲线 - /// 打断参数表 - /// 打断后曲线的集合 - public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars) + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断参数表 + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable pars) + { + return + curve + .GetSplitCurves(new DoubleCollection(pars.ToArray())) + .Cast(); + } + + /// + /// 获取分割曲线集合 + /// + /// 曲线 + /// 打断点表 + /// 打断后曲线的集合 + public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) + { + return + curve + .GetSplitCurves(new Point3dCollection(points.ToArray())) + .Cast(); + } + + /// + /// 获取曲线集所围成的封闭区域的曲线集,注意此函数不能处理平行边(两个点及两条线组成的闭合环) + /// + /// 曲线集合 + /// 所有的闭合环的曲线集合 + public static IEnumerable GetAllCycle(this IEnumerable curves) + { + // 新建图 + var graph = new Graph(); + foreach (var curve in curves) { - return - curve - .GetSplitCurves(new DoubleCollection(pars.ToArray())) - .Cast(); +#if NET35 + graph.AddEdge(curve.ToCurve3d()!); +#else + graph.AddEdge(curve.GetGeCurve()); +#endif } - - /// - /// 获取分割曲线集合 - /// - /// 曲线 - /// 打断点表 - /// 打断后曲线的集合 - public static IEnumerable GetSplitCurves(this Curve curve, IEnumerable points) + //新建 dfs + var dfs = new DepthFirst(); + // 查询全部的 闭合环 + dfs.FindAll(graph); + // 遍历闭合环的列表,将每个闭合环转换为实体曲线 + var res = new List(); + foreach (var item in dfs.Curve3ds) { - return - curve - .GetSplitCurves(new Point3dCollection(points.ToArray())) - .Cast(); + var curve = graph.GetCurves(item.ToList()).ToArray(); + var comcur = new CompositeCurve3d(curve).ToCurve(); + if (comcur is not null) + res.Add(comcur); } + return res; + } + /// + /// 曲线打断 + /// + /// 曲线列表 + /// 打断后的曲线列表 + public static List BreakCurve(this List curves) + { + var geCurves = new List(); // 存储曲线转换后的复合曲线 + var paramss = new List>(); // 存储每个曲线的交点参数值 - private struct EdgeItem : IEquatable + for (int i = 0; i < curves.Count; i++) { - public Edge Edge; - public bool Forward; - - public EdgeItem(Edge edge, bool forward) + var cc3d = curves[i].ToCompositeCurve3d(); + if (cc3d is not null) { - Edge = edge; - Forward = forward; + geCurves.Add(cc3d); + paramss.Add(new List()); } + } + + //var oldCurves = new List(); + var newCurves = new List(); + var cci3d = new CurveCurveIntersector3d(); - public CompositeCurve3d GetCurve() + for (int i = 0; i < curves.Count; i++) + { + var gc1 = geCurves[i]; + var pars1 = paramss[i]; //引用 + for (int j = i; j < curves.Count; j++) { - var cc3d = Edge.Curve; - if (Forward) - return cc3d; - else - { - cc3d = (CompositeCurve3d)cc3d.Clone(); - return cc3d.GetReverseParameterCurve() as CompositeCurve3d; - } - } + var gc2 = geCurves[j]; + var pars2 = paramss[j]; // 引用 - public bool Equals(EdgeItem other) - { - return - Edge == other.Edge && - Forward == other.Forward; - } + cci3d.Set(gc1, gc2, Vector3d.ZAxis); - public void FindRegion(List edges, List> regions) - { - var region = new LoopList(); - var edgeItem = this; - region.Add(edgeItem); - var edgeItem2 = this.GetNext(edges); - if (edgeItem2.Edge is not null) + for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) { - bool hasList = false; - foreach (var edgeList2 in regions) - { - var node = edgeList2.GetNode(e => e.Equals(edgeItem)); - if (node is not null) - { - if (node.Next.Value.Equals(edgeItem2)) - { - hasList = true; - break; - } - } - } - if (!hasList) - { - while (edgeItem2.Edge is not null) - { - if (edgeItem2.Edge == edgeItem.Edge) - break; - region.Add(edgeItem2); - edgeItem2 = edgeItem2.GetNext(edges); - } - if (edgeItem2.Edge == edgeItem.Edge) - regions.Add(region); - } + var pars = cci3d.GetIntersectionParameters(k); + pars1.Add(pars[0]); // 引用修改会同步到源对象 + pars2.Add(pars[1]); // 引用修改会同步到源对象 } } - public EdgeItem GetNext(List edges) + if (pars1.Count > 0) { - Vector3d vec; - int next; - if (Forward) + var c3ds = gc1.GetSplitCurves(pars1); + if (c3ds.Count > 1) { - vec = Edge.GetEndVector(); - next = Edge.EndIndex; - } - else - { - vec = Edge.GetStartVector(); - next = Edge.StartIndex; - } - - EdgeItem item = new EdgeItem(); - Vector3d vec2, vec3 = new Vector3d(); - double angle = 0; - bool hasNext = false; - bool forward = false; - foreach (var edge in edges) - { - if (edge.IsNext(Edge, next, ref vec3, ref forward)) + foreach (var c3d in c3ds) { - if (hasNext) - { - var angle2 = vec.GetAngleTo(vec3, Vector3d.ZAxis); - if (angle2 < angle) - { - vec2 = vec3; - angle = angle2; - item.Edge = edge; - item.Forward = forward; - } - } - else + var c3dCur = c3d.ToCurve(); + if (c3dCur is not null) { - vec2 = vec3; - angle = vec.GetAngleTo(vec2, Vector3d.ZAxis); - item.Edge = edge; - item.Forward = forward; - hasNext = true; + c3dCur.SetPropertiesFrom(curves[i]); + newCurves.Add(c3dCur); } } + //oldCurves.Add(curves[i]); } - return item; - } - - public override string ToString() - { - return - Forward ? - string.Format("{0}-{1}", Edge.StartIndex, Edge.EndIndex) : - string.Format("{0}-{1}", Edge.EndIndex, Edge.StartIndex); } } - private class Edge - { - public CompositeCurve3d Curve; - public int StartIndex; - public int EndIndex; + return newCurves; + } - public Vector3d GetStartVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new PointOnCurve3d(Curve, inter.LowerBound); - return poc.GetDerivative(1); - } + //转换DBCurve为GeCurved - public Vector3d GetEndVector() - { - var inter = Curve.GetInterval(); - PointOnCurve3d poc = new PointOnCurve3d(Curve, inter.UpperBound); - return -poc.GetDerivative(1); - } + #region Curve - public bool IsNext(Edge edge, int index, ref Vector3d vec, ref bool forward) - { - if (edge != this) - { - if (StartIndex == index) - { - vec = GetStartVector(); - forward = true; - return true; - } - else if (EndIndex == index) - { - vec = GetEndVector(); - forward = false; - return true; - } - } - return false; - } - } - - public static List Topo(List curves) + /// + /// 将曲线转换为ge曲线,此函数将在未来淘汰,二惊加油 + /// + /// 曲线 + /// ge曲线 + public static Curve3d? ToCurve3d(this Curve curve) + { + return curve switch { - //首先按交点分解为Ge曲线集 - List geCurves = new List(); - List> paramss = new List>(); - - foreach (var curve in curves) - { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d is not null) - { - geCurves.Add(cc3d); - paramss.Add(new List()); - } - } - - List edges = new List(); - CurveCurveIntersector3d cci3d = new CurveCurveIntersector3d(); - List newCurves = new List(); - - for (int i = 0; i < curves.Count; i++) - { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; - for (int j = i; j < curves.Count; j++) - { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; + Line li => ToCurve3d(li), + Circle ci => ToCurve3d(ci), + Arc arc => ToCurve3d(arc), + Ellipse el => ToCurve3d(el), + Polyline pl => ToCurve3d(pl), + Polyline2d pl2 => ToCurve3d(pl2), + Polyline3d pl3 => ToCurve3d(pl3), + Spline sp => ToCurve3d(sp), + _ => null + }; + } - cci3d.Set(gc1, gc2, Vector3d.ZAxis); + /// + /// 将曲线转换为复合曲线 + /// + /// 曲线 + /// 复合曲线 + public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) + { + return curve switch + { + Line li => new CompositeCurve3d(new Curve3d[] { ToCurve3d(li) }), + Circle ci => new CompositeCurve3d(new Curve3d[] { ToCurve3d(ci) }), + Arc arc => new CompositeCurve3d(new Curve3d[] { ToCurve3d(arc) }), + Ellipse el => new CompositeCurve3d(new Curve3d[] { ToCurve3d(el) }), + Polyline pl => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl) }), + + Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2)! }), + Polyline3d pl3 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl3) }), + Spline sp => new CompositeCurve3d(new Curve3d[] { ToCurve3d(sp) }), + _ => null + }; + } - for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) - { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); - } - } + /// + /// 将曲线转换为Nurb曲线 + /// + /// 曲线 + /// Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Curve curve) + { + return curve switch + { + Line li => ToNurbCurve3d(li), + Circle ci => ToNurbCurve3d(ci), + Arc arc => ToNurbCurve3d(arc), + Ellipse el => ToNurbCurve3d(el), + Polyline pl => ToNurbCurve3d(pl), + Polyline2d pl2 => ToNurbCurve3d(pl2), + Polyline3d pl3 => ToNurbCurve3d(pl3), + Spline sp => ToNurbCurve3d(sp), + _ => null + }; + } - if (pars1.Count > 0) - { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 0) - { - edges.AddRange( - c3ds.Select(c => new Edge { Curve = c })); - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - else - { - edges.Add(new Edge { Curve = gc1 }); - } - } - else if (gc1.IsClosed()) - { - newCurves.Add(gc1.ToCurve()); - } - } + #region Line - //构建边的邻接表 - var knots = new List(); - var nums = new List(); - var closedEdges = new List(); + /// + /// 将直线转换为ge直线 + /// + /// 直线 + /// ge直线 + public static LineSegment3d ToCurve3d(this Line line) + { + return new LineSegment3d(line.StartPoint, line.EndPoint); + } - foreach (var edge in edges) - { - if (edge.Curve.IsClosed()) - { - closedEdges.Add(edge); - } - else - { - if (knots.Contains(edge.Curve.StartPoint)) - { - edge.StartIndex = - knots.IndexOf(edge.Curve.StartPoint); - nums[edge.StartIndex]++; - } - else - { - knots.Add(edge.Curve.StartPoint); - nums.Add(1); - edge.StartIndex = knots.Count - 1; - } + /// + /// 将直线转换为Nurb曲线 + /// + /// 直线 + /// Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Line line) + { + return new NurbCurve3d(ToCurve3d(line)); + } - if (knots.Contains(edge.Curve.EndPoint)) - { - edge.EndIndex = - knots.IndexOf(edge.Curve.EndPoint); - nums[edge.EndIndex]++; - } - else - { - knots.Add(edge.Curve.EndPoint); - nums.Add(1); - edge.EndIndex = knots.Count - 1; - } - } - } + #endregion Line - newCurves.AddRange(closedEdges.Select(e => e.Curve.ToCurve())); + #region Circle - edges = - edges - .Except(closedEdges) - .Where(e => nums[e.StartIndex] > 1 && nums[e.EndIndex] > 1) - .ToList(); + /// + /// 将圆转换为ge圆弧曲线 + /// + /// 圆 + /// ge圆弧曲线 + public static CircularArc3d ToCurve3d(this Circle cir) + { + return + new CircularArc3d( + cir.Center, + cir.Normal, + cir.Radius); + } - foreach (var edge in edges.Except(closedEdges)) - { - if (nums[edge.StartIndex] == 1 || nums[edge.EndIndex] == 1) - { - if (nums[edge.StartIndex] == 1 && nums[edge.EndIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[edge.EndIndex] = 0; - } - else - { - int next = -1; - if (nums[edge.StartIndex] == 1) - { - nums[edge.StartIndex] = 0; - nums[next = edge.EndIndex]--; - } - else - { - nums[edge.EndIndex] = 0; - nums[next = edge.StartIndex]--; - } - } - } - } + /// + /// 将圆转换为ge椭圆曲线 + /// + /// 圆 + /// ge椭圆曲线 + public static EllipticalArc3d ToEllipticalArc3d(this Circle cir) + { + return ToCurve3d(cir).ToEllipticalArc3d(); + } - List> regions = new List>(); - foreach (var edge in edges) - { - var edgeItem = new EdgeItem(edge, true); - edgeItem.FindRegion(edges, regions); - edgeItem = new EdgeItem(edge, false); - edgeItem.FindRegion(edges, regions); - } + /// + /// 将圆转换为Nurb曲线 + /// + /// 圆 + /// Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Circle cir) + { + return new NurbCurve3d(ToEllipticalArc3d(cir)); + } - for (int i = 0; i < regions.Count; i++) - { - for (int j = i + 1; j < regions.Count;) - { - bool eq = false; - if (regions[i].Count == regions[j].Count) - { - var node = regions[i].First; - var curve = node.Value.Edge.Curve; - var node2 = regions[j].GetNode(e => e.Edge.Curve == curve); - if (eq = node2 is not null) - { - var b = node.Value.Forward; - var b2 = node2.Value.Forward; - for (int k = 1; k < regions[i].Count; k++) - { - node = node.GetNext(b); - node2 = node2.GetNext(b2); - if (node.Value.Edge.Curve != node2.Value.Edge.Curve) - { - eq = false; - break; - } - } - } - } - if (eq) - regions.RemoveAt(j); - else - j++; - } - } + #endregion Circle - foreach (var region in regions) - { - var cs3ds = - region - .Select(e => e.GetCurve()) - .ToArray(); - newCurves.Add(new CompositeCurve3d(cs3ds.ToArray()).ToCurve()); - } + #region Arc - return newCurves; - } + /// + /// 将圆弧转换为ge圆弧曲线 + /// + /// 圆弧 + /// ge圆弧曲线 + public static CircularArc3d ToCurve3d(this Arc arc) + { + Plane plane = new(arc.Center, arc.Normal); + + return + new CircularArc3d( + arc.Center, + arc.Normal, + plane.GetCoordinateSystem().Xaxis, + arc.Radius, + arc.StartAngle, + arc.EndAngle + ); + } - /// - /// 曲线打断 - /// - /// 曲线列表 - /// 打断后的曲线列表 - public static List BreakCurve(List curves) - { - List geCurves = new List(); - List> paramss = new List>(); + /// + /// 将圆弧转换为ge椭圆曲线 + /// + /// 圆弧 + /// ge椭圆曲线 + public static EllipticalArc3d ToEllipticalArc3d(this Arc arc) + { + return ToCurve3d(arc).ToEllipticalArc3d(); + } - foreach (var curve in curves) - { - var cc3d = curve.ToCompositeCurve3d(); - if (cc3d is not null) - { - geCurves.Add(cc3d); - paramss.Add(new List()); - } - } + /// + /// 将圆弧转换为三维Nurb曲线 + /// + /// 圆弧 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Arc arc) + { + return new NurbCurve3d(ToEllipticalArc3d(arc)); + } - List oldCurves = new List(); - List newCurves = new List(); - CurveCurveIntersector3d cci3d = new CurveCurveIntersector3d(); + #endregion Arc - for (int i = 0; i < curves.Count; i++) - { - CompositeCurve3d gc1 = geCurves[i]; - List pars1 = paramss[i]; - for (int j = i; j < curves.Count; j++) - { - CompositeCurve3d gc2 = geCurves[j]; - List pars2 = paramss[j]; + #region Ellipse - cci3d.Set(gc1, gc2, Vector3d.ZAxis); + /// + /// 将椭圆转换为三维ge椭圆曲线 + /// + /// 椭圆 + /// 三维ge椭圆曲线 + public static EllipticalArc3d ToCurve3d(this Ellipse ell) + { + return + new EllipticalArc3d( + ell.Center, + ell.MajorAxis.GetNormal(), + ell.MinorAxis.GetNormal(), + ell.MajorRadius, + ell.MinorRadius, + ell.StartParam, + ell.EndParam); + } - for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) - { - double[] pars = cci3d.GetIntersectionParameters(k); - pars1.Add(pars[0]); - pars2.Add(pars[1]); - } - } + /// + /// 将椭圆转换为三维Nurb曲线 + /// + /// 椭圆 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Ellipse ell) + { + return new NurbCurve3d(ToCurve3d(ell)); + } - if (pars1.Count > 0) - { - List c3ds = gc1.GetSplitCurves(pars1); - if (c3ds.Count > 1) - { - foreach (CompositeCurve3d c3d in c3ds) - { - Curve c = c3d.ToCurve(); - if (c is not null) - { - c.SetPropertiesFrom(curves[i]); - newCurves.Add(c); - } - } - oldCurves.Add(curves[i]); - } - } - } - curves = oldCurves; - return newCurves; - } + #endregion Ellipse - //转换DBCurve为GeCurved + #region Spline - #region Curve + /// + /// 将样条曲线转换为三维Nurb曲线 + /// + /// 样条曲线 + /// 三维Nurb曲线 + public static NurbCurve3d ToCurve3d(this Spline spl) + { + NurbCurve3d nc3d; + NurbsData ndata = spl.NurbsData; + KnotCollection knots = new(); + foreach (Double knot in ndata.GetKnots()) + knots.Add(knot); - /// - /// 将曲线转换为ge曲线,此函数将在未来淘汰,二惊加油 - /// - /// 曲线 - /// ge曲线 - [Obsolete("请使用Cad自带的 GetGeCurve 函数!")] - public static Curve3d? ToCurve3d(this Curve curve) + if (ndata.Rational) { - return curve switch - { - Line li => ToCurve3d(li), - Circle ci => ToCurve3d(ci), - Arc arc => ToCurve3d(arc), - Ellipse el => ToCurve3d(el), - Polyline pl => ToCurve3d(pl), - Polyline2d pl2 => ToCurve3d(pl2), - Polyline3d pl3 => ToCurve3d(pl3), - Spline sp => ToCurve3d(sp), - _ => null - }; + nc3d = + new NurbCurve3d( + ndata.Degree, + knots, + ndata.GetControlPoints(), + ndata.GetWeights(), + ndata.Periodic); } - - /// - /// 将曲线转换为复合曲线 - /// - /// 曲线 - /// 复合曲线 - public static CompositeCurve3d? ToCompositeCurve3d(this Curve curve) + else { - return curve switch - { - Line li => new CompositeCurve3d(new Curve3d[] { ToCurve3d(li) }), - Circle ci => new CompositeCurve3d(new Curve3d[] { ToCurve3d(ci) }), - Arc arc => new CompositeCurve3d(new Curve3d[] { ToCurve3d(arc) }), - Ellipse el => new CompositeCurve3d(new Curve3d[] { ToCurve3d(el) }), - Polyline pl => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl) }), - - Polyline2d pl2 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl2) }), - Polyline3d pl3 => new CompositeCurve3d(new Curve3d[] { ToCurve3d(pl3) }), - Spline sp => new CompositeCurve3d(new Curve3d[] { ToCurve3d(sp) }), - _ => null - }; + nc3d = + new NurbCurve3d( + ndata.Degree, + knots, + ndata.GetControlPoints(), + ndata.Periodic); } - /// - /// 将曲线转换为Nurb曲线 - /// - /// 曲线 - /// Nurb曲线 - public static NurbCurve3d? ToNurbCurve3d(this Curve curve) + if (spl.HasFitData) { - return curve switch - { - Line li => ToNurbCurve3d(li), - Circle ci => ToNurbCurve3d(ci), - Arc arc => ToNurbCurve3d(arc), - Ellipse el => ToNurbCurve3d(el), - Polyline pl => ToNurbCurve3d(pl), - Polyline2d pl2 => ToNurbCurve3d(pl2), - Polyline3d pl3 => ToNurbCurve3d(pl3), - Spline sp => ToNurbCurve3d(sp), - _ => null - }; + var fdata = spl.FitData; + var vec = new Vector3d(); + if (fdata.TangentsExist && (fdata.StartTangent != vec || fdata.EndTangent != vec)) + nc3d.SetFitData(fdata.GetFitPoints(), fdata.StartTangent, fdata.EndTangent); } + return nc3d; + } - #region Line + #endregion Spline - /// - /// 将直线转换为ge直线 - /// - /// 直线 - /// ge直线 - public static LineSegment3d ToCurve3d(this Line line) - { - return new LineSegment3d(line.StartPoint, line.EndPoint); - } + #region Polyline2d - /// - /// 将直线转换为Nurb曲线 - /// - /// 直线 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Line line) + /// + /// 将二维多段线转换为三维ge曲线 + /// + /// 二维多段线 + /// 三维ge曲线 + public static Curve3d? ToCurve3d(this Polyline2d pl2d) + { + switch (pl2d.PolyType) { - return new NurbCurve3d(ToCurve3d(line)); + case Poly2dType.SimplePoly: + case Poly2dType.FitCurvePoly: + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToCurve3d(pl); + default: + return ToNurbCurve3d(pl2d); } - #endregion Line - - #region Circle - - /// - /// 将圆转换为ge圆弧曲线 - /// - /// 圆 - /// ge圆弧曲线 - public static CircularArc3d ToCurve3d(this Circle cir) - { - return - new CircularArc3d( - cir.Center, - cir.Normal, - cir.Radius); - } + //Polyline pl = new Polyline(); + //pl.ConvertFrom(pl2d, false); + //return ToCurve3d(pl); + } - /// - /// 将圆转换为ge椭圆曲线 - /// - /// 圆 - /// ge椭圆曲线 - public static EllipticalArc3d ToEllipticalArc3d(this Circle cir) + /// + /// 将二维多段线转换为三维Nurb曲线 + /// + /// 二维多段线 + /// 三维Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Polyline2d pl2d) + { + switch (pl2d.PolyType) { - return ToCurve3d(cir).ToEllipticalArc3d(); + case Poly2dType.SimplePoly: + case Poly2dType.FitCurvePoly: + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.ConvertFrom(pl2d, false); + return ToNurbCurve3d(pl); + + default: + return ToCurve3d(pl2d.Spline); } + } - /// - /// 将圆转换为Nurb曲线 - /// - /// 圆 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Circle cir) + /// + /// 将二维多段线转换为三维ge多段线 + /// + /// 二维多段线 + /// 三维ge多段线 + public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) + { + Point3dCollection pnts = new(); + foreach (Vertex2d ver in pl) { - return new NurbCurve3d(ToEllipticalArc3d(cir)); + pnts.Add(ver.Position); } + return new PolylineCurve3d(pnts); + } - #endregion Circle - - #region Arc + #endregion Polyline2d - /// - /// 将圆弧转换为ge圆弧曲线 - /// - /// 圆弧 - /// ge圆弧曲线 - public static CircularArc3d ToCurve3d(this Arc arc) - { - Plane plane = new Plane(arc.Center, arc.Normal); - - return - new CircularArc3d( - arc.Center, - arc.Normal, - plane.GetCoordinateSystem().Xaxis, - arc.Radius, - arc.StartAngle, - arc.EndAngle - ); - } + #region Polyline3d - /// - /// 将圆弧转换为ge椭圆曲线 - /// - /// 圆弧 - /// ge椭圆曲线 - public static EllipticalArc3d ToEllipticalArc3d(this Arc arc) - { - return ToCurve3d(arc).ToEllipticalArc3d(); - } - - /// - /// 将圆弧转换为三维Nurb曲线 - /// - /// 圆弧 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Arc arc) + /// + /// 将三维多段线转换为三维曲线 + /// + /// 三维多段线 + /// 三维曲线 + public static Curve3d ToCurve3d(this Polyline3d pl3d) + { + return pl3d.PolyType switch { - return new NurbCurve3d(ToEllipticalArc3d(arc)); - } - - #endregion Arc + Poly3dType.SimplePoly => ToPolylineCurve3d(pl3d), + _ => ToNurbCurve3d(pl3d), + }; + } - #region Ellipse + /// + /// 将三维多段线转换为三维Nurb曲线 + /// + /// 三维多段线 + /// 三维Nurb曲线 + public static NurbCurve3d ToNurbCurve3d(this Polyline3d pl3d) + { + return ToCurve3d(pl3d.Spline); + } - /// - /// 将椭圆转换为三维ge椭圆曲线 - /// - /// 椭圆 - /// 三维ge椭圆曲线 - public static EllipticalArc3d ToCurve3d(this Ellipse ell) + /// + /// 将三维多段线转换为三维ge多段线 + /// + /// 三维多段线 + /// 三维ge多段线 + public static PolylineCurve3d ToPolylineCurve3d(this Polyline3d pl) + { + Point3dCollection pnts = new(); + foreach (ObjectId id in pl) { - return - new EllipticalArc3d( - ell.Center, - ell.MajorAxis.GetNormal(), - ell.MinorAxis.GetNormal(), - ell.MajorRadius, - ell.MinorRadius, - ell.StartParam, - ell.EndParam); + PolylineVertex3d ver = (PolylineVertex3d)id.GetObject(OpenMode.ForRead); + pnts.Add(ver.Position); } + return new PolylineCurve3d(pnts); + } - /// - /// 将椭圆转换为三维Nurb曲线 - /// - /// 椭圆 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Ellipse ell) - { - return new NurbCurve3d(ToCurve3d(ell)); - } + #endregion Polyline3d - #endregion Ellipse + #region Polyline - #region Spline + /// + /// 多段线转换为复合曲线 + /// + /// 多段线对象 + /// 复合曲线对象 + public static CompositeCurve3d ToCurve3d(this Polyline pl) + { + List c3ds = new(); - /// - /// 将样条曲线转换为三维Nurb曲线 - /// - /// 样条曲线 - /// 三维Nurb曲线 - public static NurbCurve3d ToCurve3d(this Spline spl) + for (int i = 0; i < pl.NumberOfVertices; i++) { - NurbCurve3d nc3d; - NurbsData ndata = spl.NurbsData; - KnotCollection knots = new KnotCollection(); - foreach (Double knot in ndata.GetKnots()) - knots.Add(knot); - - if (ndata.Rational) + switch (pl.GetSegmentType(i)) { - nc3d = - new NurbCurve3d( - ndata.Degree, - knots, - ndata.GetControlPoints(), - ndata.GetWeights(), - ndata.Periodic); - } - else - { - nc3d = - new NurbCurve3d( - ndata.Degree, - knots, - ndata.GetControlPoints(), - ndata.Periodic); - } - - if (spl.HasFitData) - { - var fdata = spl.FitData; - var vec = new Vector3d(); - if (fdata.TangentsExist && (fdata.StartTangent != vec || fdata.EndTangent != vec)) - nc3d.SetFitData(fdata.GetFitPoints(), fdata.StartTangent, fdata.EndTangent); - } - return nc3d; - } - - #endregion Spline + case SegmentType.Line: + c3ds.Add(pl.GetLineSegmentAt(i)); + break; - #region Polyline2d - - /// - /// 将二维多段线转换为三维ge曲线 - /// - /// 二维多段线 - /// 三维ge曲线 - public static Curve3d ToCurve3d(this Polyline2d pl2d) - { - switch (pl2d.PolyType) - { - case Poly2dType.SimplePoly: - case Poly2dType.FitCurvePoly: - Polyline pl = new Polyline(); - pl.ConvertFrom(pl2d, false); - return ToCurve3d(pl); + case SegmentType.Arc: + c3ds.Add(pl.GetArcSegmentAt(i)); + break; default: - return ToNurbCurve3d(pl2d); + break; } - - //Polyline pl = new Polyline(); - //pl.ConvertFrom(pl2d, false); - //return ToCurve3d(pl); } + return new CompositeCurve3d(c3ds.ToArray()); + } - /// - /// 将二维多段线转换为三维Nurb曲线 - /// - /// 二维多段线 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline2d pl2d) + /// + /// 多段线转换为Nurb曲线 + /// + /// 多段线 + /// Nurb曲线 + public static NurbCurve3d? ToNurbCurve3d(this Polyline pl) + { + NurbCurve3d? nc3d = null; + for (int i = 0; i < pl.NumberOfVertices; i++) { - switch (pl2d.PolyType) + NurbCurve3d? nc3dtemp = null; + switch (pl.GetSegmentType(i)) { - case Poly2dType.SimplePoly: - case Poly2dType.FitCurvePoly: - Polyline pl = new Polyline(); - pl.ConvertFrom(pl2d, false); - return ToNurbCurve3d(pl); + case SegmentType.Line: + nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); + break; + + case SegmentType.Arc: + nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); + break; default: - return ToCurve3d(pl2d.Spline); + break; } - } - - /// - /// 将二维多段线转换为三维ge多段线 - /// - /// 二维多段线 - /// 三维ge多段线 - public static PolylineCurve3d ToPolylineCurve3d(this Polyline2d pl) - { - var pnts = new Point3dCollection(); - foreach (Vertex2d ver in pl) - pnts.Add(ver.Position); - return new PolylineCurve3d(pnts); - } - #endregion Polyline2d - - #region Polyline3d - - /// - /// 将三维多段线转换为三维曲线 - /// - /// 三维多段线 - /// 三维曲线 - public static Curve3d ToCurve3d(this Polyline3d pl3d) - { - return pl3d.PolyType switch - { - Poly3dType.SimplePoly => ToPolylineCurve3d(pl3d), - _ => ToNurbCurve3d(pl3d), - }; - } - - /// - /// 将三维多段线转换为三维Nurb曲线 - /// - /// 三维多段线 - /// 三维Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline3d pl3d) - { - return ToCurve3d(pl3d.Spline); - } - - /// - /// 将三维多段线转换为三维ge多段线 - /// - /// 三维多段线 - /// 三维ge多段线 - public static PolylineCurve3d ToPolylineCurve3d(this Polyline3d pl) - { - Point3dCollection pnts = new Point3dCollection(); - foreach (ObjectId id in pl) + if (nc3d is null) { - PolylineVertex3d ver = (PolylineVertex3d)id.GetObject(OpenMode.ForRead); - pnts.Add(ver.Position); + nc3d = nc3dtemp; } - return new PolylineCurve3d(pnts); - } - - #endregion Polyline3d - - #region Polyline - - /// - /// 多段线转换为复合曲线 - /// - /// 多段线对象 - /// 复合曲线对象 - public static CompositeCurve3d ToCurve3d(this Polyline pl) - { - List c3ds = new List(); - - for (int i = 0; i < pl.NumberOfVertices; i++) + else if (nc3dtemp is not null) { - switch (pl.GetSegmentType(i)) - { - case SegmentType.Line: - c3ds.Add(pl.GetLineSegmentAt(i)); - break; - - case SegmentType.Arc: - c3ds.Add(pl.GetArcSegmentAt(i)); - break; - - default: - break; - } + nc3d.JoinWith(nc3dtemp); } - return new CompositeCurve3d(c3ds.ToArray()); } + return nc3d; + } - /// - /// 多段线转换为Nurb曲线 - /// - /// 多段线 - /// Nurb曲线 - public static NurbCurve3d ToNurbCurve3d(this Polyline pl) - { - NurbCurve3d nc3d = null; - for (int i = 0; i < pl.NumberOfVertices; i++) - { - NurbCurve3d nc3dtemp = null; - switch (pl.GetSegmentType(i)) - { - case SegmentType.Line: - nc3dtemp = new NurbCurve3d(pl.GetLineSegmentAt(i)); - break; + /// + /// 为优化多段线倒角 + /// + /// 优化多段线 + /// 顶点索引号 + /// 倒角半径 + /// 倒角类型 + public static void ChamferAt(this Polyline polyline, int index, double radius, bool isFillet) + { + if (index < 1 || index > polyline.NumberOfVertices - 2) + throw new System.Exception("错误的索引号"); - case SegmentType.Arc: - nc3dtemp = pl.GetArcSegmentAt(i).ToNurbCurve3d(); - break; + if (SegmentType.Line != polyline.GetSegmentType(index - 1) || + SegmentType.Line != polyline.GetSegmentType(index)) + throw new System.Exception("非直线段不能倒角"); - default: - break; - } - if (nc3d is null) + //获取当前索引号的前后两段直线,并组合为Ge复合曲线 + Curve3d[] c3ds = + new Curve3d[] { - nc3d = nc3dtemp; - } - else if (nc3dtemp is not null) - { - nc3d.JoinWith(nc3dtemp); - } - } - return nc3d; - } - - /// - /// 为优化多段线倒角 - /// - /// 优化多段线 - /// 顶点索引号 - /// 倒角半径 - /// 倒角类型 - public static void ChamferAt(this Polyline polyline, int index, double radius, bool isFillet) - { - if (index < 1 || index > polyline.NumberOfVertices - 2) - throw new System.Exception("错误的索引号"); - - if (polyline.GetSegmentType(index - 1) != SegmentType.Line || - polyline.GetSegmentType(index) != SegmentType.Line) - throw new System.Exception("非直线段不能倒角"); - - //获取当前索引号的前后两段直线,并组合为Ge复合曲线 - var c3ds = new Curve3d[] - { - polyline.GetLineSegmentAt(index - 1), - polyline.GetLineSegmentAt(index) - }; - var cc3d = new CompositeCurve3d(c3ds); - - //试倒直角 - //子曲线的个数有三种情况: - //1、=3时倒角方向正确 - //2、=2时倒角方向相反 - //3、=0或为直线时失败 - c3ds = cc3d.GetTrimmedOffset + polyline.GetLineSegmentAt(index - 1), + polyline.GetLineSegmentAt(index) + }; + CompositeCurve3d cc3d = new(c3ds); + + //试倒直角 + //子曲线的个数有三种情况: + //1、=3时倒角方向正确 + //2、=2时倒角方向相反 + //3、=0或为直线时失败 + c3ds = + cc3d.GetTrimmedOffset ( radius, Vector3d.ZAxis, OffsetCurveExtensionType.Chamfer ); - if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d newcc3d) - { - c3ds = newcc3d.GetCurves(); - if (c3ds.Length == 3) - { - c3ds = cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Chamfer - ); - if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) - throw new System.Exception("倒角半径过大"); - } - else if (c3ds.Length == 2) - { - radius = -radius; - } + if (c3ds.Length > 0 && c3ds[0] is CompositeCurve3d) + { + var newcc3d = c3ds[0] as CompositeCurve3d; + c3ds = newcc3d!.GetCurves(); + if (c3ds.Length == 3) + { + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Chamfer + ); + if (c3ds.Length == 0 || c3ds[0] is LineSegment3d) + throw new System.Exception("倒角半径过大"); } - else + else if (c3ds.Length == 2) { - throw new System.Exception("倒角半径过大"); + radius = -radius; } - - //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 - c3ds = cc3d.GetTrimmedOffset - ( - -radius, - Vector3d.ZAxis, - OffsetCurveExtensionType.Extend - ); - var type = isFillet ? OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; - c3ds = c3ds[0].GetTrimmedOffset - ( - radius, - Vector3d.ZAxis, - type - ); - - //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 - var plTemp = c3ds[0].ToCurve() as Polyline; - polyline.RemoveVertexAt(index); - polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); - polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); + } + else + { + throw new System.Exception("倒角半径过大"); } - #endregion Polyline - - #endregion Curve + //GetTrimmedOffset会生成倒角+偏移,故先反方向倒角,再倒回 + c3ds = cc3d.GetTrimmedOffset + ( + -radius, + Vector3d.ZAxis, + OffsetCurveExtensionType.Extend + ); + OffsetCurveExtensionType type = + isFillet ? + OffsetCurveExtensionType.Fillet : OffsetCurveExtensionType.Chamfer; + c3ds = c3ds[0].GetTrimmedOffset + ( + radius, + Vector3d.ZAxis, + type + ); + + //将结果Ge曲线转为Db曲线,并将相关的数值反映到原曲线 + var plTemp = c3ds[0].ToCurve() as Polyline; + if (plTemp == null) + return; + polyline.RemoveVertexAt(index); + polyline.AddVertexAt(index, plTemp.GetPoint2dAt(1), plTemp.GetBulgeAt(1), 0, 0); + polyline.AddVertexAt(index + 1, plTemp.GetPoint2dAt(2), 0, 0, 0); } -} + + #endregion Polyline + + #endregion Curve +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs index d4fba82..89dc2d5 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs @@ -1,389 +1,371 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 字典扩展类 +/// +public static class DBDictionaryEx { /// - /// 字典扩展类 + /// 获取字典里的全部对象 /// - public static class DBDictionaryEx + /// 对象类型的泛型 + /// 字典 + /// 事务 + /// 对象迭代器 + public static IEnumerable GetAllObjects(this DBDictionary dict, DBTrans? trans = null) where T : DBObject { - /// - /// 获取字典里的全部对象 - /// - /// 对象类型的泛型 - /// 字典 - /// 事务 - /// 对象迭代器 - public static IEnumerable GetAllObjects(this DBDictionary dict, Transaction trans = null) where T : DBObject + trans ??= DBTrans.Top; + foreach (DBDictionaryEntry e in dict) { - trans ??= DBTrans.Top.Transaction; - foreach (DBDictionaryEntry e in dict) - { - yield return trans.GetObject(e.Value, OpenMode.ForRead) as T; - } + var ent = trans.GetObject(e.Value); + if (ent is not null) + yield return ent; } + } - /// - /// 获取字典内指定key的对象 - /// - /// 对象类型的泛型 - /// 字典 - /// 事务 - /// 指定的键值 - /// T 类型的对象 - public static T GetAt(this DBDictionary dict, string key, Transaction trans = null) where T : DBObject + /// + /// 获取字典内指定key的对象 + /// + /// 对象类型的泛型 + /// 字典 + /// 事务 + /// 指定的键值 + /// T 类型的对象 + public static T? GetAt(this DBDictionary dict, string key, DBTrans? trans = null) where T : DBObject + { + trans ??= DBTrans.Top; + if (dict.Contains(key)) { - trans ??= DBTrans.Top.Transaction; - if (dict.Contains(key)) - { - ObjectId id = dict.GetAt(key); - if (!id.IsNull) - { - return trans.GetObject(id, OpenMode.ForRead) as T; - } - } - return null; + ObjectId id = dict.GetAt(key); + if (!id.IsNull) + return trans.GetObject(id); } + return null; + } - /// - /// 添加条目(键值对)到字典 - /// - /// 对象类型 - /// 字典 - /// 事务 - /// 键 - /// 值 - public static void SetAt(this DBDictionary dict, string key, T obj, Transaction tr = null) where T : DBObject + /// + /// 添加条目(键值对)到字典 + /// + /// 对象类型 + /// 字典 + /// 事务 + /// 键 + /// 值 + public static void SetAt(this DBDictionary dict, string key, T obj, Transaction? trans = null) where T : DBObject + { + trans ??= DBTrans.Top.Transaction; + using (dict.ForWrite()) { - tr ??= DBTrans.Top.Transaction; - using (dict.ForWrite()) - { - dict.SetAt(key, obj); - tr.AddNewlyCreatedDBObject(obj, true); - } + dict.SetAt(key, obj); + trans.AddNewlyCreatedDBObject(obj, true); } + } - #region XRecord + #region XRecord - /// - /// 从字典中获取扩展数据 - /// - /// 字典 - /// 键值 - /// 扩展数据 - public static XRecordDataList GetXRecord(this DBDictionary dict, string key) - { - Xrecord rec = dict.GetAt(key); - if (rec is not null) - return rec.Data; - return null; - } + /// + /// 从字典中获取扩展数据 + /// + /// 字典 + /// 键值 + /// 扩展数据 + public static XRecordDataList? GetXRecord(this DBDictionary dict, string key) + { + Xrecord? rec = dict.GetAt(key); + if (rec is not null) + return rec.Data; + return null; + } - /// - /// 保存扩展数据到字典 - /// - /// 扩展数据 - /// 字典 - /// 键值 - public static void SetXRecord(this DBDictionary dict, string key, XRecordDataList rb) - { - using var data = new Xrecord { Data = rb }; - dict.SetAt(key, data); - } - #endregion - - /// - /// 获取扩展字典 - /// - /// 对象 - /// 事务 - /// 扩展字典对象 - public static DBDictionary GetXDictionary(this DBObject obj, Transaction trans = null) + /// + /// 保存扩展数据到字典 + /// + /// 扩展数据 + /// 字典 + /// 键值 + public static void SetXRecord(this DBDictionary dict, string key, XRecordDataList rb) + { + using var data = new Xrecord { Data = rb }; + dict.SetAt(key, data); + } + #endregion + + /// + /// 获取扩展字典 + /// + /// 对象 + /// 事务 + /// 扩展字典对象 + public static DBDictionary? GetXDictionary(this DBObject obj, DBTrans? trans = null) + { + trans ??= DBTrans.Top; + ObjectId id = obj.ExtensionDictionary; + if (id.IsNull) { - trans ??= DBTrans.Top.Transaction; - ObjectId id = obj.ExtensionDictionary; - if (id.IsNull) + using (obj.ForWrite()) { - using (obj.ForWrite()) - { - obj.CreateExtensionDictionary(); - } - - id = obj.ExtensionDictionary; + obj.CreateExtensionDictionary(); } - return id.GetObject(tr: trans); + + id = obj.ExtensionDictionary; } + return id.GetObject(tr: trans); + } - #region 数据表 + #region 数据表 - /// - /// 创建数据表 - /// - /// 原数据类型的字典 - /// 表元素(二维数组) - /// 数据表 - public static DataTable CreateDataTable(Dictionary colTypes, object[,] content) + /// + /// 创建数据表 + /// + /// 原数据类型的字典 + /// 表元素(二维数组) + /// 数据表 + public static DataTable CreateDataTable(Dictionary colTypes, object[,] content) + { + DataTable table = new(); + foreach (var t in colTypes) + table.AppendColumn(t.Value, t.Key); + var ncol = colTypes.Count; + var nrow = content.GetLength(0); + var types = new CellType[ncol]; + colTypes.Values.CopyTo(types, 0); + for (int i = 0; i < nrow; i++) { - DataTable table = new(); - foreach (var t in colTypes) - table.AppendColumn(t.Value, t.Key); - var ncol = colTypes.Count; - var nrow = content.GetLength(0); - var types = new CellType[ncol]; - colTypes.Values.CopyTo(types, 0); - for (int i = 0; i < nrow; i++) + DataCellCollection row = new(); + for (int j = 0; j < ncol; j++) { - DataCellCollection row = new(); - for (int j = 0; j < ncol; j++) - { - var cell = new DataCell(); - cell.SetValue(types[j], content[i, j]); - row.Add(cell); - } - table.AppendRow(row, true); + var cell = new DataCell(); + cell.SetValue(types[j], content[i, j]); + row.Add(cell); } - return table; + table.AppendRow(row, true); } + return table; + } - /// - /// 设定单元格数据 - /// - /// 单元格 - /// 类型 - /// 数据 - public static void SetValue(this DataCell cell, CellType type, object value) - { + /// + /// 设定单元格数据 + /// + /// 单元格 + /// 类型 + /// 数据 + public static void SetValue(this DataCell cell, CellType type, object value) + { - switch (type) - { - case CellType.Bool: - cell.SetBool((bool)value); - break; + switch (type) + { + case CellType.Bool: + cell.SetBool((bool)value); + break; - case CellType.CharPtr: - cell.SetString((string)value); - break; + case CellType.CharPtr: + cell.SetString((string)value); + break; - case CellType.Integer: - cell.SetInteger((int)value); - break; + case CellType.Integer: + cell.SetInteger((int)value); + break; - case CellType.Double: - cell.SetDouble((double)value); - break; + case CellType.Double: + cell.SetDouble((double)value); + break; - case CellType.ObjectId: - cell.SetObjectId((ObjectId)value); - break; + case CellType.ObjectId: + cell.SetObjectId((ObjectId)value); + break; - case CellType.Point: - cell.SetPoint((Point3d)value); - break; + case CellType.Point: + cell.SetPoint((Point3d)value); + break; - case CellType.Vector: - cell.SetVector((Vector3d)value); - break; + case CellType.Vector: + cell.SetVector((Vector3d)value); + break; - case CellType.HardOwnerId: - cell.SetHardOwnershipId((ObjectId)value); - break; + case CellType.HardOwnerId: + cell.SetHardOwnershipId((ObjectId)value); + break; - case CellType.HardPtrId: - cell.SetHardPointerId((ObjectId)value); - break; + case CellType.HardPtrId: + cell.SetHardPointerId((ObjectId)value); + break; - case CellType.SoftOwnerId: - cell.SetSoftOwnershipId((ObjectId)value); - break; + case CellType.SoftOwnerId: + cell.SetSoftOwnershipId((ObjectId)value); + break; - case CellType.SoftPtrId: - cell.SetSoftPointerId((ObjectId)value); - break; - } + case CellType.SoftPtrId: + cell.SetSoftPointerId((ObjectId)value); + break; } - #endregion - - #region 子字典 - /// - /// 获取子字典 - /// - /// 根字典 - /// 事务 - /// 是否创建子字典 - /// 键值列表 - /// 字典 - public static DBDictionary GetSubDictionary(this DBDictionary dict, bool createSubDictionary, IEnumerable dictNames, Transaction trans = null) + } + #endregion + + #region 子字典 + /// + /// 获取子字典 + /// + /// 根字典 + /// 事务 + /// 是否创建子字典 + /// 键值列表 + /// 字典 + public static DBDictionary? GetSubDictionary(this DBDictionary dict, bool createSubDictionary, IEnumerable dictNames, DBTrans? trans = null) + { + DBDictionary? newdict = null; + trans ??= DBTrans.Top; + if (createSubDictionary) { - trans ??= DBTrans.Top.Transaction; - if (createSubDictionary) - { - using (dict.ForWrite()) - dict.TreatElementsAsHard = true; + using (dict.ForWrite()) + dict.TreatElementsAsHard = true; - foreach (string name in dictNames) - { - if (dict.Contains(name)) - { - dict = dict.GetAt(name, trans); - } - else - { - DBDictionary subDict = new(); - dict.SetAt(name, subDict, trans); - dict = subDict; - dict.TreatElementsAsHard = true; - } - } - } - else + foreach (string name in dictNames) { - foreach (string name in dictNames) + if (dict!.Contains(name)) { - if (dict.Contains(name)) - dict = dict.GetAt(name, trans); - else - return null; + newdict = dict.GetAt(name, trans); } - } - - return dict; - } - - - ///// - ///// 获取对象扩展字典的子字典 - ///// - ///// 对象 - ///// 事务 - ///// 是否创建子字典 - ///// 键值列表 - ///// 字典 - //public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) - //{ - // return obj.GetXDictionary().GetSubDictionary(createSubDictionary, dictNames); - //} - - #endregion - - #region 组字典 - /// - /// 添加编组 - /// - /// 组名 - /// 实体Id集合 - /// 编组Id - public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCollection ids) - { - if (dict.Contains(name)) - { - return ObjectId.Null; - } - else - { - using (dict.ForWrite()) + else { - Group g = new(); - g.Append(ids); - dict.SetAt(name, g); - DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); - return g.ObjectId; + DBDictionary subDict = new(); + dict.SetAt(name, subDict, trans); + newdict = subDict; + newdict.TreatElementsAsHard = true; } } } - - /// - /// 添加编组 - /// - /// 组名 - /// 实体Id集合 - /// 编组Id - public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable ids) + else { - if (dict.Contains(name)) + foreach (string name in dictNames) { - return ObjectId.Null; + if (dict.Contains(name)) + newdict = dict.GetAt(name, trans); + else + return null; } - return dict.AddGroup(name, new ObjectIdCollection(ids.ToArray())); } + return newdict; + } - /// - /// 按选择条件获取编组集合 - /// - /// 选择条件,过滤函数 - /// g.NumEntities < 2);]]> - /// 编组集合 - public static IEnumerable GetGroups(this DBDictionary dict, Func func) + + ///// + ///// 获取对象扩展字典的子字典 + ///// + ///// 对象 + ///// 事务 + ///// 是否创建子字典 + ///// 键值列表 + ///// 字典 + //public static DBDictionary GetSubDictionary(this DBObject obj, bool createSubDictionary, params string[] dictNames) + //{ + // return obj.GetXDictionary().GetSubDictionary(createSubDictionary, dictNames); + //} + + #endregion + + #region 组字典 + /// + /// 添加编组 + /// + /// 组名 + /// 实体Id集合 + /// 编组Id + public static ObjectId AddGroup(this DBDictionary dict, string name, ObjectIdCollection ids) + { + if (dict.Contains(name)) + return ObjectId.Null; + + using (dict.ForWrite()) { - return - dict - .GetAllObjects() - .Where(func); + Group g = new(); + g.Append(ids); + dict.SetAt(name, g); + DBTrans.Top.Transaction.AddNewlyCreatedDBObject(g, true); + return g.ObjectId; } + } + + /// + /// 添加编组 + /// + /// 组名 + /// 实体Id集合 + /// 编组Id + public static ObjectId AddGroup(this DBDictionary dict, string name, IEnumerable ids) + { + if (dict.Contains(name)) + return ObjectId.Null; + + return dict.AddGroup(name, new ObjectIdCollection(ids.ToArray())); + } - /// - /// 返回实体的所在编组的集合 - /// - /// 图元实体 - /// 编组集合 - public static IEnumerable GetGroups(this Entity ent) + + /// + /// 按选择条件获取编组集合 + /// + /// 选择条件,过滤函数 + /// g.NumEntities < 2);]]> + /// 编组集合 + public static IEnumerable GetGroups(this DBDictionary dict, Func func) + { + return dict.GetAllObjects() + .Where(func); + } + + /// + /// 返回实体的所在编组的集合 + /// + /// 图元实体 + /// 编组集合 + public static IEnumerable GetGroups(this Entity ent) + { + return ent.GetPersistentReactorIds() + .Cast() + .Select(id => id.GetObject()) + .OfType(); + } + + /// + /// 移除所有的空组 + /// + /// 被移除编组的名称集合 + public static List RemoveNullGroup(this DBDictionary dict) + { + var groups = dict.GetGroups(g => g.NumEntities < 2); + List names = new(); + foreach (Group g in groups) { - return - ent.GetPersistentReactorIds() - .Cast() - .Select(id => id.GetObject()) - .OfType(); + names.Add(g.Name); + using (g.ForWrite()) + g.Erase(); } + return names; + } - /// - /// 移除所有的空组 - /// - /// 被移除编组的名称集合 - public static List RemoveNullGroup(this DBDictionary dict) + /// + /// 移除所有空组 + /// + /// 过滤条件,过滤要删除的组名的规则函数 + /// RemoveNullGroup(g => g.StartsWith("hah")) + /// 被移除编组的名称集合 + public static List RemoveNullGroup(this DBDictionary dict, Func func) + { + var groups = dict.GetGroups(g => g.NumEntities < 2); + List names = new(); + foreach (Group g in groups) { - var groups = dict.GetGroups(g => g.NumEntities < 2); - List names = new(); - foreach (Group g in groups) + if (func(g.Name)) { names.Add(g.Name); using (g.ForWrite()) - { g.Erase(); - } - } - return names; - } - - /// - /// 移除所有空组 - /// - /// 过滤条件,过滤要删除的组名的规则函数 - /// RemoveNullGroup(g => g.StartsWith("hah")) - /// 被移除编组的名称集合 - public static List RemoveNullGroup(this DBDictionary dict, Func func) - { - var groups = dict.GetGroups(g => g.NumEntities < 2); - List names = new(); - foreach (Group g in groups) - { - if (func(g.Name)) - { - names.Add(g.Name); - using (g.ForWrite()) - { - g.Erase(); - } - } } - return names; } - #endregion + return names; + } + #endregion @@ -404,5 +386,4 @@ public static List RemoveNullGroup(this DBDictionary dict, Func +/// 实体对象扩展类 +/// +public static class DBObjectEx { + #region Xdata扩展 /// - /// 实体对象扩展类 + /// 删除扩展数据 /// - public static class DBObjectEx + /// 对象实例 + /// 应用程序名称 + /// 要删除数据的组码 + public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCode) { - #region Xdata扩展 - /// - /// 删除扩展数据 - /// - /// 对象实例 - /// 应用程序名称 - /// 要删除数据的组码 - public static void RemoveXData(this DBObject obj, string appName, DxfCode dxfCode) + XDataList data = obj.XData; + var indexlst = new List(); + bool flag = false; + for (int i = 0; i < data.Count; i++) { - XDataList data = obj.XData; - var indexlst = new List(); - bool flag = false; - for (int i = 0; i < data.Count; i++) + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) - { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - indexlst.Add(i); - } - } + flag = true; } - foreach (var item in indexlst) + if (flag) { - data.RemoveAt(item); + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) + break; + if (data[i].TypeCode == (int)dxfCode) + { + indexlst.Add(i); + } } + } + foreach (var item in indexlst) + { + data.RemoveAt(item); + } - using (obj.ForWrite()) - { - obj.XData = data; - } + using (obj.ForWrite()) + { + obj.XData = data; } - /// - /// 修改扩展数据 - /// - /// 对象实例 - /// 应用程序名称 - /// 要修改数据的组码 - /// 新的数据 - public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCode, object newvalue) + } + /// + /// 修改扩展数据 + /// + /// 对象实例 + /// 应用程序名称 + /// 要修改数据的组码 + /// 新的数据 + public static void ChangeXData(this DBObject obj, string appName, DxfCode dxfCode, object newvalue) + { + XDataList data = obj.XData; + bool flag = false; + for (int i = 0; i < data.Count; i++) { - XDataList data = obj.XData; - bool flag = false; - for (int i = 0; i < data.Count; i++) + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() == appName) - { - flag = true; - } - if (flag) + flag = true; + } + if (flag) + { + if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) + break; + if (data[i].TypeCode == (int)dxfCode) { - if (data[i].TypeCode == (int)DxfCode.ExtendedDataRegAppName && data[i].Value.ToString() != appName) - break; - if (data[i].TypeCode == (int)dxfCode) - { - data[i] = new TypedValue((int)dxfCode, newvalue); - } + data[i] = new TypedValue((int)dxfCode, newvalue); } } + } - using (obj.ForWrite()) - { - obj.XData = data; - } + using (obj.ForWrite()) + { + obj.XData = data; } - #endregion + } + #endregion - #region 读写模式切换 + #region 读写模式切换 - /// - /// 实体自动管理读写函数 - /// - /// 实体类型 - /// 实体对象 - /// 操作委托 - public static void ForWrite(this T obj, Action action) where T : DBObject + /// + /// 实体自动管理读写函数 + /// + /// 实体类型 + /// 实体对象 + /// 操作委托 + public static void ForWrite(this T obj, Action action) where T : DBObject + { + var _isNotifyEnabled = obj.IsNotifyEnabled; + var _isWriteEnabled = obj.IsWriteEnabled; + if (_isNotifyEnabled) { - var _isNotifyEnabled = obj.IsNotifyEnabled; - var _isWriteEnabled = obj.IsWriteEnabled; - if (_isNotifyEnabled) - { - obj.UpgradeFromNotify(); - } - else if (!_isWriteEnabled) - { - obj.UpgradeOpen(); - } - action?.Invoke(obj); - if (_isNotifyEnabled) - { - obj.DowngradeToNotify(_isWriteEnabled); - } - else if (!_isWriteEnabled) - { - obj.DowngradeOpen(); - } + obj.UpgradeFromNotify(); } - - /// - /// 打开模式提权 - /// - /// 实体对象 - /// 提权类对象 - public static UpgradeOpenManager ForWrite(this DBObject obj) + else if (!_isWriteEnabled) { - return new UpgradeOpenManager(obj); + obj.UpgradeOpen(); } - - /// - /// 提权类 - /// - public class UpgradeOpenManager : IDisposable + action?.Invoke(obj); + if (_isNotifyEnabled) + { + obj.DowngradeToNotify(_isWriteEnabled); + } + else if (!_isWriteEnabled) { - private readonly DBObject _obj; - private readonly bool _isNotifyEnabled; - private readonly bool _isWriteEnabled; + obj.DowngradeOpen(); + } + } - internal UpgradeOpenManager(DBObject obj) - { - _obj = obj; - _isNotifyEnabled = _obj.IsNotifyEnabled; - _isWriteEnabled = _obj.IsWriteEnabled; - if (_isNotifyEnabled) - _obj.UpgradeFromNotify(); - else if (!_isWriteEnabled) - _obj.UpgradeOpen(); - } + /// + /// 打开模式提权 + /// + /// 实体对象 + /// 提权类对象 + public static UpgradeOpenManager ForWrite(this DBObject obj) + { + return new UpgradeOpenManager(obj); + } - #region IDisposable 成员 + /// + /// 提权类 + /// + public class UpgradeOpenManager : IDisposable + { + private readonly DBObject _obj; + private readonly bool _isNotifyEnabled; + private readonly bool _isWriteEnabled; - /// - /// 注销函数 - /// - public void Dispose() - { - if (_isNotifyEnabled) - _obj.DowngradeToNotify(_isWriteEnabled); - else if (!_isWriteEnabled) - _obj.DowngradeOpen(); - GC.SuppressFinalize(this); - } + internal UpgradeOpenManager(DBObject obj) + { + _obj = obj; + _isNotifyEnabled = _obj.IsNotifyEnabled; + _isWriteEnabled = _obj.IsWriteEnabled; + if (_isNotifyEnabled) + _obj.UpgradeFromNotify(); + else if (!_isWriteEnabled) + _obj.UpgradeOpen(); + } - #endregion IDisposable 成员 + #region IDisposable 成员 + + /// + /// 注销函数 + /// + public void Dispose() + { + if (_isNotifyEnabled) + _obj.DowngradeToNotify(_isWriteEnabled); + else if (!_isWriteEnabled) + _obj.DowngradeOpen(); + GC.SuppressFinalize(this); } - #endregion + + #endregion IDisposable 成员 } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs new file mode 100644 index 0000000..dff7a9e --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs @@ -0,0 +1,23 @@ +namespace IFoxCAD.Cad; + +public static class DatabaseEx +{ + /// + /// 后台开图文字偏移处理 + /// 0x01 此方案利用前台数据库进行处理 + /// 0x02 当关闭所有前台文档时,会出现无时,不能使用(惊惊没有测试过此状态) + /// 0x03 当关闭所有前台文档时,如何发送命令呢?那就是利用跨进程通讯 + /// + /// 后台打开的数据库 + /// 处理后台的任务 + public static void DBTextDeviation(this Database backstageOpenDwg, Action action) + { + var wdb = HostApplicationServices.WorkingDatabase; + if (wdb != null) + { + HostApplicationServices.WorkingDatabase = backstageOpenDwg; + action?.Invoke(); + HostApplicationServices.WorkingDatabase = wdb; + } + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs index 9745eb9..47f06f0 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs @@ -1,752 +1,1091 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections.Generic; -using System.Runtime.InteropServices; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 命令行扩展类 +/// +public static class EditorEx { + #region 选择集 /// - /// 命令行扩展类 + /// 选择穿过一个点的对象 /// - public static class EditorEx + /// 命令行对象 + /// 点 + /// 过滤器 + /// 选择集结果类 + public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, SelectionFilter? filter = default) { - #region 选择集 - /// - /// 选择穿过一个点的对象 - /// - /// 命令行对象 - /// 点 - /// 过滤器 - /// 选择集结果类 - public static PromptSelectionResult SelectAtPoint(this Editor editor, Point3d point, SelectionFilter filter = default) - { - return editor.SelectCrossingWindow(point, point, filter); - } + return editor.SelectCrossingWindow(point, point, filter); + } - /// - /// 根据线宽创建图层选择集 - /// - /// 命令行对象 - /// 线宽 - /// 图层选择集 - public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lineWeight) - { - OpFilter filter = new OpEqual(370, lineWeight); + /// + /// 根据线宽创建图层选择集 + /// + /// 命令行对象 + /// 线宽 + /// 图层选择集 + public static SelectionSet SelectByLineWeight(this Editor editor, LineWeight lineWeight) + { + OpFilter filter = new OpEqual(370, lineWeight); - var lays = - DBTrans.Top.LayerTable - .GetRecords() - .Where(ltr => ltr.LineWeight == lineWeight) - .Select(ltr => ltr.Name) - .ToArray(); + var lays = + DBTrans.Top.LayerTable + .GetRecords() + .Where(ltr => ltr.LineWeight == lineWeight) + .Select(ltr => ltr.Name) + .ToArray(); - if (lays.Length > 0) - { - filter = - new OpOr - { + if (lays.Length > 0) + { + filter = + new OpOr + { filter, new OpAnd { { 8, string.Join(",", lays) }, { 370, LineWeight.ByLayer } } - }; - } + }; + } + + PromptSelectionResult res = editor.SelectAll(filter); + return res.Value; + } - PromptSelectionResult res = editor.SelectAll(filter); - return res.Value; + public static PromptSelectionResult? SSGet(this Editor editor, string? mode = null, SelectionFilter? filter = null, string[]? messages = null, Dictionary? keywords = null) + { + var pso = new PromptSelectionOptions(); + PromptSelectionResult? ss = null; + if (mode is not null) + { + mode = mode.ToUpper(); + pso.SinglePickInSpace = mode.Contains(":A"); + pso.RejectObjectsFromNonCurrentSpace = mode.Contains(":C"); + pso.AllowDuplicates = mode.Contains(":D"); + pso.SelectEverythingInAperture = mode.Contains(":E"); + pso.RejectObjectsOnLockedLayers = mode.Contains(":L"); + pso.PrepareOptionalDetails = mode.Contains(":N"); + pso.SingleOnly = mode.Contains(":S"); + pso.RejectPaperspaceViewport = mode.Contains(":V"); + pso.AllowSubSelections = mode.Contains("-A"); + pso.ForceSubSelections = mode.Contains("-F"); + + } + if (messages is not null) + { + pso.MessageForAdding = messages[0]; + pso.MessageForRemoval = messages[1]; } - #endregion - #region Info + if (keywords is not null) + { + foreach (var keyword in keywords.Keys) + pso.Keywords.Add(keyword); + if (pso.MessageForRemoval is null) + pso.MessageForAdding = "选择对象"; + pso.MessageForAdding += $"[{string.Join(" / ", keywords.Keys.ToArray())}]"; + pso.KeywordInput += (s, e) => { + if (keywords.ContainsKey(e.Input)) + keywords[e.Input].Invoke(); + }; - /// - /// 带错误提示对话框的打印信息函数 - /// - /// 带格式项的字符串 - /// 指定格式化的对象数组 - public static void StreamMessage(string format, params object[] args) + } + try { - StreamMessage(string.Format(format, args)); - }// + if (filter is not null) + ss = editor.GetSelection(pso, filter); + else + ss = editor.GetSelection(pso); + } + catch (Exception e) + { + editor.WriteMessage($"\nKey is {e.Message}"); + } + return ss; + } + - /// - /// 带错误提示对话框的打印信息函数 - /// - /// 打印信息 - public static void StreamMessage(string message) + /* + * //定义选择集选项 + * var pso = new PromptSelectionOptions + * { + * AllowDuplicates = false, //重复选择 + * }; + * + * //getai遍历全图选择块有用到 + * var dic = new Dictionary() { + * { "Z,全部同名", ()=> { + * getai = BlockHelper.EnumAttIdentical.AllBlockName; + * SendEsc.Esc(); + * }}, + * { "X,动态块显示", ()=> { + * getai = BlockHelper.EnumAttIdentical.Display; + * }}, + * { "V,属性值-默认", ()=> { + * getai = BlockHelper.EnumAttIdentical.DisplayAndTagText; + * }}, + * //允许以下操作,相同的会加入前面的 + * //{ "V,属性值-默认|X,啊啊啊啊", ()=> { + * + * //}}, + * }; + * pso.SsgetAddKeys(dic); + * + * //创建选择集过滤器,只选择块对象 + * var filList = new TypedValue[] { new TypedValue((int)DxfCode.Start, "INSERT") }; + * var filter = new SelectionFilter(filList); + * ssPsr = ed.GetSelection(pso, filter); + */ + + /// + /// 添加选择集关键字和回调 + /// + /// 选择集配置 + /// 关键字,回调委托 + /// + public static void SsgetAddKeys(this PromptSelectionOptions pso, + Dictionary dicActions) + { + Dictionary tmp = new(); + // 后缀名的|号切割,移除掉,组合成新的加入tmp + for (int i = dicActions.Count - 1; i >= 0; i--) { - try + var pair = dicActions.ElementAt(i); + var key = pair.Key; + var keySp = key.Split('|'); + if (keySp.Length < 2) + continue; + + for (int j = 0; j < keySp.Length; j++) { - if (HasEditor()) - WriteMessage(message); + var item = keySp[j]; + // 防止多个后缀通过|符越过词典约束同名 + // 后缀(key)含有,而且Action(value)不同,就把Action(value)累加到后面. + if (dicActions.ContainsKey(item)) + { + if (dicActions[item] != dicActions[key]) + dicActions[item] += dicActions[key]; + } else - InfoMessageBox(message); + tmp.Add(item, dicActions[key]); } - catch (System.Exception ex) - { - Message(ex); - } - }// + dicActions.Remove(key); + } - /// - /// 异常信息对话框 - /// - /// 异常 - public static void Message(System.Exception ex) - { - try - { - System.Windows.Forms.MessageBox.Show( - ex.ToString(), - "Error", - System.Windows.Forms.MessageBoxButtons.OK, - System.Windows.Forms.MessageBoxIcon.Error); - } - catch - { - } - }// + foreach (var item in tmp) + dicActions.Add(item.Key, item.Value); - /// - /// 提示信息对话框 - /// - /// 对话框的标题 - /// 对话框文本 - public static void InfoMessageBox(string caption, string message) + //去除关键字重复的,把重复的执行动作移动到前面 + for (int i = 0; i < dicActions.Count; i++) { - try - { - System.Windows.Forms.MessageBox.Show( - message, - caption, - System.Windows.Forms.MessageBoxButtons.OK, - System.Windows.Forms.MessageBoxIcon.Information); - } - catch (System.Exception ex) + var pair1 = dicActions.ElementAt(i); + var key1 = pair1.Key; + + for (int j = dicActions.Count - 1; j > i; j--) { - Message(ex); + var pair2 = dicActions.ElementAt(j); + var key2 = pair2.Key; + + if (key1.Split(',')[0] == key2.Split(',')[0]) + { + if (dicActions[key1] != dicActions[key2]) + dicActions[key1] += dicActions[key2]; + dicActions.Remove(key2); + } } - }// + } - /// - /// 提示信息对话框 - /// - /// 对话框的标题 - /// 带格式化项的对话框文本 - /// 指定格式化的对象数组 - public static void InfoMessageBox(string caption, string format, params object[] args) + foreach (var item in dicActions) { - InfoMessageBox(caption, string.Format(format, args)); + var keySplitS = item.Key.Split(new string[] { ",", "|" }, StringSplitOptions.RemoveEmptyEntries); + for (int i = 0; i < keySplitS.Length; i += 2) + pso.Keywords.Add(keySplitS[i], keySplitS[i], + keySplitS[i + 1] + "(" + keySplitS[i] + ")"); } - /// - /// 提示信息对话框,默认标题为NFox.Cad - /// - /// 对话框文本 - public static void InfoMessageBox(string message) - { - InfoMessageBox("NFox.Cad", message); - }// + //回调的时候我想用Dict的O(1)索引, + //但是此函数内进行new Dictionary() 在函数栈释放的时候,它被释放掉了. + //因此 dicActions 参数的生命周期 + tmp = new(dicActions); + dicActions.Clear(); + foreach (var item in tmp) + dicActions.Add(item.Key.Split(',')[0], item.Value); + + var keyWords = pso.Keywords; + //从选择集命令中显示关键字 + pso.MessageForAdding = keyWords.GetDisplayString(true); + //关键字回调事件 ssget关键字 + pso.KeywordInput += (sender, e) => { + dicActions[e.Input].Invoke(); + }; + } + - /// - /// 提示信息对话框 - /// - /// 带格式化项的对话框文本 - /// 指定格式化的对象数组 - public static void InfoMessageBox(string format, params object[] args) - { - InfoMessageBox(string.Format(format, args)); - }// - /// - /// 命令行打印字符串 - /// - /// 字符串 - public static void WriteMessage(string message) - { - try - { - if (Acceptable()) - Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); - else - return; - } - catch (System.Exception ex) - { - Message(ex); - } - }// - /// - /// 命令行打印字符串 - /// - /// 带格式化项的文本 - /// 指定格式化的对象数组 - public static void WriteMessage(string format, params object[] args) + + + //#region 即时选择样板 + + ///// + ///// 即时选择,框选更新关键字 + ///// + //public static void SelectTest() + //{ + // Env.Editor.WriteMessage("\n[白嫖工具]--测试"); + // //激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + // //初始化坐标系 + // Env.Editor.CurrentUserCoordinateSystem = Matrix3d.Identity; + + // //创建过滤器 + // var sf = new OpEqual(0, "arc"); + // var pso = new PromptSelectionOptions + // { + // MessageForAdding = "\n请选择对象:" + // }; + + // pso.Keywords.Add("Z"); + // pso.Keywords.Add("X"); + // pso.Keywords.Add("Q"); + // //注册关键字 + // pso.KeywordInput += SelectTest_KeywordInput; + // try + // { + // //用户选择 + // var psr = Env.Editor.GetSelection(pso, sf); + // //处理代码 + + + // } + // catch (Exception ex)//捕获关键字 + // { + // if (ex.Message == "XuError") + // { + // //关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // //重新调用自身 + // ZengLiangYuanJiao(); + // } + // } + // //关闭关键字事件 + // pso.KeywordInput -= SelectTest_KeywordInput; + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + //} + + ///// + ///// 即时选择 + ///// + ///// + ///// + //private static void SelectTest_SelectionAdded(object sender, SelectionAddedEventArgs e) + //{ + // //关闭选中事件 + // Env.Editor.SelectionAdded -= SelectTest_SelectionAdded; + // using (var tr = new DBTrans()) + // { + // //处理代码 + // for (int i = 0; i < e.AddedObjects.Count; i++) + // { + + + // //处理完移除已处理的对象 + // e.Remove(i); + // } + // } + // //激活选中事件 + // Env.Editor.SelectionAdded += SelectTest_SelectionAdded; + //} + + ///// + ///// 关键字响应 + ///// + ///// + ///// + //private static void SelectTest_KeywordInput(object sender, SelectionTextInputEventArgs e) + //{ + // //获取关键字 + // switch (e.Input) + // { + // case "Z": + // { + // break; + // } + // case "X": + // { + // break; + // } + + // case "Q": + // { + // break; + // } + // } + // //抛出异常,用于更新提示信息 + // throw new ArgumentException("XuError"); + //} + + + //#endregion + #endregion + + #region Info + + /// + /// 带错误提示对话框的打印信息函数 + /// + /// 带格式项的字符串 + /// 指定格式化的对象数组 + public static void StreamMessage(string format, params object[] args) + { + StreamMessage(string.Format(format, args)); + } + + /// + /// 带错误提示对话框的打印信息函数 + /// + /// 打印信息 + public static void StreamMessage(string message) + { + try + { + if (HasEditor()) + WriteMessage(message); + else + InfoMessageBox(message); + } + catch (System.Exception ex) { - WriteMessage(string.Format(format, args)); + Message(ex); } + } - /// - /// 判断是否有活动的编辑器对象 - /// - /// 有,没有 - public static bool HasEditor() + /// + /// 异常信息对话框 + /// + /// 异常 + public static void Message(System.Exception ex) + { + try + { + System.Windows.Forms.MessageBox.Show( + ex.ToString(), + "Error", + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Error); + } + catch { - return Application.DocumentManager.MdiActiveDocument is not null - && Application.DocumentManager.Count != 0 - && Application.DocumentManager.MdiActiveDocument.Editor is not null; - }// + } + } - /// - /// 判断是否可以打印字符串 - /// - /// 可以打印,不可以打印 - public static bool Acceptable() + /// + /// 提示信息对话框 + /// + /// 对话框的标题 + /// 对话框文本 + public static void InfoMessageBox(string caption, string message) + { + try { - return HasEditor() - && !Application.DocumentManager.MdiActiveDocument.Editor.IsDragging; - }// + System.Windows.Forms.MessageBox.Show( + message, + caption, + System.Windows.Forms.MessageBoxButtons.OK, + System.Windows.Forms.MessageBoxIcon.Information); + } + catch (System.Exception ex) + { + Message(ex); + } + } + + /// + /// 提示信息对话框 + /// + /// 对话框的标题 + /// 带格式化项的对话框文本 + /// 指定格式化的对象数组 + public static void InfoMessageBox(string caption, string format, params object[] args) + { + InfoMessageBox(caption, string.Format(format, args)); + } - #endregion Info + /// + /// 提示信息对话框,默认标题为NFox.Cad + /// + /// 对话框文本 + public static void InfoMessageBox(string message) + { + InfoMessageBox("NFox.Cad", message); + } - #region 画矢量线 + /// + /// 提示信息对话框 + /// + /// 带格式化项的对话框文本 + /// 指定格式化的对象数组 + public static void InfoMessageBox(string format, params object[] args) + { + InfoMessageBox(string.Format(format, args)); + } - /// - /// 根据点表返回矢量线的列表 - /// - /// 点表 - /// 是否闭合, 为闭合, 为不闭合 - /// - public static List GetLines(IEnumerable pnts, bool isClosed) + /// + /// 命令行打印字符串 + /// + /// 字符串 + public static void WriteMessage(string message) + { + try { + if (Acceptable()) + Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\n" + message); + else + return; + } + catch (System.Exception ex) + { + Message(ex); + } + } - var itor = pnts.GetEnumerator(); - if (!itor.MoveNext()) - return new List(); + /// + /// 命令行打印字符串 + /// + /// 带格式化项的文本 + /// 指定格式化的对象数组 + public static void WriteMessage(string format, params object[] args) + { + WriteMessage(string.Format(format, args)); + } - List values = new(); + /// + /// 判断是否有活动的编辑器对象 + /// + /// 有,没有 + public static bool HasEditor() + { + return Application.DocumentManager.MdiActiveDocument is not null + && Application.DocumentManager.Count != 0 + && Application.DocumentManager.MdiActiveDocument.Editor is not null; + } - TypedValue tvFirst = new((int)LispDataType.Point2d, itor.Current); - TypedValue tv1; - TypedValue tv2 = tvFirst; + /// + /// 判断是否可以打印字符串 + /// + /// 可以打印,不可以打印 + public static bool Acceptable() + { + return HasEditor() + && !Application.DocumentManager.MdiActiveDocument.Editor.IsDragging; + } - while (itor.MoveNext()) - { - tv1 = tv2; - tv2 = new TypedValue((int)LispDataType.Point2d, itor.Current); - values.Add(tv1); - values.Add(tv2); - } + #endregion Info - if (isClosed) - { - values.Add(tv2); - values.Add(tvFirst); - } + #region 画矢量线 - return values; - } + /// + /// 根据点表返回矢量线的列表 + /// + /// 点表 + /// 是否闭合, 为闭合, 为不闭合 + /// + public static List GetLines(IEnumerable pnts, bool isClosed) + { - /// - /// 画矢量线 - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - /// 是否闭合, 为闭合, 为不闭合 - public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex, bool isClosed) - { - var rlst = - new LispList { { LispDataType.Int16, colorIndex } }; - rlst.AddRange(GetLines(pnts, isClosed)); - editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); - } + var itor = pnts.GetEnumerator(); + if (!itor.MoveNext()) + return new List(); - /// - /// 画矢量线 - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex) + List values = new(); + + TypedValue tvFirst = new((int)LispDataType.Point2d, itor.Current); + TypedValue tv1; + TypedValue tv2 = tvFirst; + + while (itor.MoveNext()) { - editor.DrawVectors(pnts, colorIndex, false); + tv1 = tv2; + tv2 = new TypedValue((int)LispDataType.Point2d, itor.Current); + values.Add(tv1); + values.Add(tv2); } - /// - /// 用矢量线画近似圆(正多边形) - /// - /// 编辑器对象 - /// 点表 - /// 颜色码 - /// 半径 - /// 多边形边的个数 - public static void DrawCircles(this Editor editor, IEnumerable pnts, short colorIndex, double radius, int numEdges) + if (isClosed) { - var rlst = - new LispList { { LispDataType.Int16, colorIndex } }; + values.Add(tv2); + values.Add(tvFirst); + } - foreach (Point2d pnt in pnts) - { - Vector2d vec = Vector2d.XAxis * radius; - double angle = Math.PI * 2 / numEdges; + return values; + } - List tpnts = new() - { - pnt + vec - }; - for (int i = 1; i < numEdges; i++) - { - tpnts.Add(pnt + vec.RotateBy(angle * i)); - } + /// + /// 画矢量线 + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + /// 是否闭合, 为闭合, 为不闭合 + public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex, bool isClosed) + { + var rlst = + new LispList { { LispDataType.Int16, colorIndex } }; + rlst.AddRange(GetLines(pnts, isClosed)); + editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); + } - rlst.AddRange(GetLines(tpnts, true)); - } - editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); - } + /// + /// 画矢量线 + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + public static void DrawVectors(this Editor editor, IEnumerable pnts, short colorIndex) + { + editor.DrawVectors(pnts, colorIndex, false); + } - /// - /// 用矢量线画近似圆(正多边形) - /// - /// 编辑器对象 - /// 点 - /// 颜色码 - /// 半径 - /// 多边形边的个数 - public static void DrawCircle(this Editor editor, Point2d pnt, short colorIndex, double radius, int numEdges) + /// + /// 用矢量线画近似圆(正多边形) + /// + /// 编辑器对象 + /// 点表 + /// 颜色码 + /// 半径 + /// 多边形边的个数 + public static void DrawCircles(this Editor editor, IEnumerable pnts, short colorIndex, double radius, int numEdges) + { + var rlst = + new LispList { { LispDataType.Int16, colorIndex } }; + + foreach (Point2d pnt in pnts) { Vector2d vec = Vector2d.XAxis * radius; double angle = Math.PI * 2 / numEdges; - List pnts = new() + List tpnts = new() { pnt + vec }; for (int i = 1; i < numEdges; i++) { - pnts.Add(pnt + vec.RotateBy(angle * i)); + tpnts.Add(pnt + vec.RotateBy(angle * i)); } - editor.DrawVectors(pnts, colorIndex, true); + rlst.AddRange(GetLines(tpnts, true)); } + editor.DrawVectors(rlst, editor.CurrentUserCoordinateSystem); + } - #endregion - - #region 矩阵 + /// + /// 用矢量线画近似圆(正多边形) + /// + /// 编辑器对象 + /// 点 + /// 颜色码 + /// 半径 + /// 多边形边的个数 + public static void DrawCircle(this Editor editor, Point2d pnt, short colorIndex, double radius, int numEdges) + { + Vector2d vec = Vector2d.XAxis * radius; + double angle = Math.PI * 2 / numEdges; - /// - /// 获取UCS到WCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromUcsToWcs(this Editor editor) + List pnts = new() { - return editor.CurrentUserCoordinateSystem; - } - - /// - /// 获取WCS到UCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromWcsToUcs(this Editor editor) + pnt + vec + }; + for (int i = 1; i < numEdges; i++) { - return editor.CurrentUserCoordinateSystem.Inverse(); + pnts.Add(pnt + vec.RotateBy(angle * i)); } - /// - /// 获取MDCS(模型空间)到WCS的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromMDcsToWcs(this Editor editor) - { - Matrix3d mat; - using ViewTableRecord vtr = editor.GetCurrentView(); - mat = Matrix3d.PlaneToWorld(vtr.ViewDirection); - mat = Matrix3d.Displacement(vtr.Target - Point3d.Origin) * mat; - return Matrix3d.Rotation(-vtr.ViewTwist, vtr.ViewDirection, vtr.Target) * mat; - } + editor.DrawVectors(pnts, colorIndex, true); + } - /// - /// 获取WCS到MDCS(模型空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) - { - return editor.GetMatrixFromMDcsToWcs().Inverse(); - } + #endregion - /// - /// 获取MDCS(模型空间)到PDCS(图纸空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) - { - if ((short)Env.GetVar("TILEMODE") == 1) - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Espace papier uniquement"); + #region 矩阵 + + /// + /// 获取UCS到WCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromUcsToWcs(this Editor editor) + { + return editor.CurrentUserCoordinateSystem; + } + + /// + /// 获取WCS到UCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromWcsToUcs(this Editor editor) + { + return editor.CurrentUserCoordinateSystem.Inverse(); + } + + /// + /// 获取MDCS(模型空间)到WCS的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromMDcsToWcs(this Editor editor) + { + Matrix3d mat; + using ViewTableRecord vtr = editor.GetCurrentView(); + mat = Matrix3d.PlaneToWorld(vtr.ViewDirection); + mat = Matrix3d.Displacement(vtr.Target - Point3d.Origin) * mat; + return Matrix3d.Rotation(-vtr.ViewTwist, vtr.ViewDirection, vtr.Target) * mat; + } + + /// + /// 获取WCS到MDCS(模型空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromWcsToMDcs(this Editor editor) + { + return editor.GetMatrixFromMDcsToWcs().Inverse(); + } + + /// + /// 获取MDCS(模型空间)到PDCS(图纸空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromMDcsToPDcs(this Editor editor) + { + if ((short)Env.GetVar("TILEMODE") == 1) + throw new ArgumentException("TILEMODE == 1..Espace papier uniquement"); - Database db = editor.Document.Database; - Matrix3d mat; - using (Transaction tr = db.TransactionManager.StartTransaction()) + Database db = editor.Document.Database; + Matrix3d mat; + using (Transaction tr = db.TransactionManager.StartTransaction()) + { + var vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; + if (vp?.Number == 1) { - Viewport vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - if (vp.Number == 1) + try + { + editor.SwitchToModelSpace(); + vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; + editor.SwitchToPaperSpace(); + } + catch { - try - { - editor.SwitchToModelSpace(); - vp = tr.GetObject(editor.CurrentViewportObjectId, OpenMode.ForRead) as Viewport; - editor.SwitchToPaperSpace(); - } - catch - { - throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput, "Aucun fenêtre active"); - } + throw new Exception("Aucun fenêtre active...ErrorStatus.InvalidInput"); } - Point3d vCtr = new(vp.ViewCenter.X, vp.ViewCenter.Y, 0.0); - mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); - mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; - mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; - tr.Commit(); } - return mat; + Point3d vCtr = new(vp!.ViewCenter.X, vp.ViewCenter.Y, 0.0); + mat = Matrix3d.Displacement(vCtr.GetAsVector().Negate()); + mat = Matrix3d.Displacement(vp.CenterPoint.GetAsVector()) * mat; + mat = Matrix3d.Scaling(vp.CustomScale, vp.CenterPoint) * mat; + tr.Commit(); } + return mat; + } + + /// + /// 获取PDCS(图纸空间)到MDCS(模型空间)的矩阵 + /// + /// 命令行对象 + /// 变换矩阵 + public static Matrix3d GetMatrixFromPDcsToMDcs(this Editor editor) + { + return editor.GetMatrixFromMDcsToPDcs().Inverse(); + } - /// - /// 获取PDCS(图纸空间)到MDCS(模型空间)的矩阵 - /// - /// 命令行对象 - /// 变换矩阵 - public static Matrix3d GetMatrixFromPDcsToMDcs(this Editor editor) + /// + /// 获取变换矩阵 + /// + /// 命令行对象 + /// 源坐标系 + /// 目标坐标系 + /// 变换矩阵 + public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, CoordinateSystemCode to) + { +#if ac2009 + switch (from) { - return editor.GetMatrixFromMDcsToPDcs().Inverse(); - } + case CoordinateSystemCode.Wcs: + switch (to) + { + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.PDcs: + throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"); + } + break; + case CoordinateSystemCode.Ucs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromUcsToWcs(); + + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); - /// - /// 获取变换矩阵 - /// - /// 命令行对象 - /// 源坐标系 - /// 目标坐标系 - /// 变换矩阵 - public static Matrix3d GetMatrix(this Editor editor, CoordinateSystemCode from, CoordinateSystemCode to) + case CoordinateSystemCode.PDcs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + } + break; + case CoordinateSystemCode.MDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + return editor.GetMatrixFromMDcsToWcs(); + + case CoordinateSystemCode.Ucs: + return editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(); + + case CoordinateSystemCode.PDcs: + return editor.GetMatrixFromMDcsToPDcs(); + } + break; + case CoordinateSystemCode.PDcs: + switch (to) + { + case CoordinateSystemCode.Wcs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + case CoordinateSystemCode.Ucs: + throw new Exception("To be used only with DCS... ErrorStatus.InvalidInput"); + case CoordinateSystemCode.MDcs: + return editor.GetMatrixFromPDcsToMDcs(); + } + break; + } + return Matrix3d.Identity; +#else + return (from, to) switch { -#if ac2009 - switch (from) - { - case CoordinateSystemCode.Wcs: - switch (to) - { - case CoordinateSystemCode.Ucs: - return editor.GetMatrixFromWcsToUcs(); - - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromMDcsToWcs(); - - case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - } - break; - case CoordinateSystemCode.Ucs: - switch (to) - { - case CoordinateSystemCode.Wcs: - return editor.GetMatrixFromUcsToWcs(); - - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(); - - case CoordinateSystemCode.PDcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - } - break; - case CoordinateSystemCode.MDcs: - switch (to) - { - case CoordinateSystemCode.Wcs: - return editor.GetMatrixFromMDcsToWcs(); - - case CoordinateSystemCode.Ucs: - return editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(); - - case CoordinateSystemCode.PDcs: - return editor.GetMatrixFromMDcsToPDcs(); - } - break; - case CoordinateSystemCode.PDcs: - switch (to) - { - case CoordinateSystemCode.Wcs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - case CoordinateSystemCode.Ucs: - throw new Autodesk.AutoCAD.Runtime.Exception( - ErrorStatus.InvalidInput, - "To be used only with DCS"); - case CoordinateSystemCode.MDcs: - return editor.GetMatrixFromPDcsToMDcs(); - } - break; - } - return Matrix3d.Identity; -#elif ac2013 - return (from, to) switch - { - (CoordinateSystemCode.Wcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromWcsToUcs(), - (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromMDcsToWcs(), - (CoordinateSystemCode.Ucs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromUcsToWcs(), - (CoordinateSystemCode.Ucs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromMDcsToWcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(), - (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), - (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), - (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) - or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Autodesk.AutoCAD.Runtime.Exception(ErrorStatus.InvalidInput,"To be used only with DCS"), - (_, _) => Matrix3d.Identity - }; + (CoordinateSystemCode.Wcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromWcsToUcs(), + (CoordinateSystemCode.Wcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromWcsToMDcs(), + (CoordinateSystemCode.Ucs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromUcsToWcs(), + (CoordinateSystemCode.Ucs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromUcsToWcs() * editor.GetMatrixFromWcsToMDcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.Wcs) => editor.GetMatrixFromMDcsToWcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.Ucs) => editor.GetMatrixFromMDcsToWcs() * editor.GetMatrixFromWcsToUcs(), + (CoordinateSystemCode.MDcs, CoordinateSystemCode.PDcs) => editor.GetMatrixFromMDcsToPDcs(), + (CoordinateSystemCode.PDcs, CoordinateSystemCode.MDcs) => editor.GetMatrixFromPDcsToMDcs(), + (CoordinateSystemCode.PDcs, CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs) + or (CoordinateSystemCode.Wcs or CoordinateSystemCode.Ucs, CoordinateSystemCode.PDcs) => throw new Exception("To be used only with DCS...ErrorStatus.InvalidInput"), + (_, _) => Matrix3d.Identity + }; #endif - } + } - #endregion + #endregion - #region 缩放 + #region 缩放 - /// - /// 缩放窗口范围 - /// - /// 命令行对象 - /// 窗口左下点 - /// 窗口右上点 - public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint) + /// + /// 缩放窗口范围 + /// + /// 命令行对象 + /// 窗口左下点 + /// 窗口右上点 + public static void ZoomWindow(this Editor ed, Point3d minPoint, Point3d maxPoint) + { + ViewTableRecord cvtr = ed.GetCurrentView(); + ViewTableRecord vtr = new(); + vtr.CopyFrom(cvtr); + + Point3d[] oldpnts = new Point3d[] { minPoint, maxPoint }; + Point3d[] pnts = new Point3d[8]; + Point3d[] dpnts = new Point3d[8]; + for (int i = 0; i < 2; i++) { - ViewTableRecord cvtr = ed.GetCurrentView(); - ViewTableRecord vtr = new(); - vtr.CopyFrom(cvtr); - - Point3d[] oldpnts = new Point3d[] { minPoint, maxPoint }; - Point3d[] pnts = new Point3d[8]; - Point3d[] dpnts = new Point3d[8]; - for (int i = 0; i < 2; i++) + for (int j = 0; j < 2; j++) { - for (int j = 0; j < 2; j++) + for (int k = 0; k < 2; k++) { - for (int k = 0; k < 2; k++) - { - int n = i * 4 + j * 2 + k; - pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); - dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); - } + int n = i * 4 + j * 2 + k; + pnts[n] = new Point3d(oldpnts[i][0], oldpnts[j][1], oldpnts[k][2]); + dpnts[n] = pnts[n].TransformBy(ed.GetMatrixFromWcsToMDcs()); } } - double xmin, xmax, ymin, ymax; - xmin = xmax = dpnts[0][0]; - ymin = ymax = dpnts[0][1]; - for (int i = 1; i < 8; i++) - { - xmin = Math.Min(xmin, dpnts[i][0]); - xmax = Math.Max(xmax, dpnts[i][0]); - ymin = Math.Min(ymin, dpnts[i][1]); - ymax = Math.Max(ymax, dpnts[i][1]); - } - - vtr.Width = xmax - xmin; - vtr.Height = ymax - ymin; - vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2).Convert2d(new Plane()); - - ed.SetCurrentView(vtr); - ed.Regen(); } - - /// - /// 缩放窗口范围 - /// - /// 命令行对象 - /// 窗口范围点 - public static void ZoomWindow(this Editor ed, Extents3d ext) + double xmin, xmax, ymin, ymax; + xmin = xmax = dpnts[0][0]; + ymin = ymax = dpnts[0][1]; + for (int i = 1; i < 8; i++) { - ZoomWindow(ed, ext.MinPoint, ext.MaxPoint); + xmin = Math.Min(xmin, dpnts[i][0]); + xmax = Math.Max(xmax, dpnts[i][0]); + ymin = Math.Min(ymin, dpnts[i][1]); + ymax = Math.Max(ymax, dpnts[i][1]); } - /// - /// 缩放比例 - /// - /// 命令行对象 - /// 中心点 - /// 窗口宽 - /// 窗口高 - public static void Zoom(this Editor ed, Point3d CenPt, double width, double height) - { - using ViewTableRecord view = ed.GetCurrentView(); - view.Width = width; - view.Height = height; - view.CenterPoint = new Point2d(CenPt.X, CenPt.Y); - ed.SetCurrentView(view);//更新当前视图 - } + vtr.Width = xmax - xmin; + vtr.Height = ymax - ymin; + vtr.CenterPoint = (dpnts[0] + (dpnts[7] - dpnts[0]) / 2).Convert2d(new Plane()); - /// - ///缩放窗口范围 - /// - /// 命令行对象 - /// 第一点 - /// 对角点 - /// 偏移距离 - public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double offsetDist = 0.00) - { - Extents3d extents = new(); - extents.AddPoint(lpt); - extents.AddPoint(rpt); - rpt = extents.MaxPoint + new Vector3d(offsetDist, offsetDist, 0); - lpt = extents.MinPoint - new Vector3d(offsetDist, offsetDist, 0); - Vector3d ver = rpt - lpt; - ed.Zoom(lpt + ver / 2, ver.X, ver.Y); - } + ed.SetCurrentView(vtr); + ed.Regen(); + } - /// - /// 动态缩放 - /// - /// 命令行对象 - /// 偏移距离 - public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) - { - var db = ed.Document.Database; - db.UpdateExt(true); - ed.ZoomWindow(db.Extmax, db.Extmin, offsetDist); - } + /// + /// 缩放窗口范围 + /// + /// 命令行对象 + /// 窗口范围点 + public static void ZoomWindow(this Editor ed, Extents3d ext) + { + ZoomWindow(ed, ext.MinPoint, ext.MaxPoint); + } - /// - /// 根据实体对象的范围显示视图 - /// - /// 命令行对象 - /// Entity对象 - /// 偏移距离 - public static void ZoomObject(this Editor ed, Entity ent, double offsetDist = 0.00) - { - Extents3d ext = ent.GeometricExtents; - ed.ZoomWindow(ext.MinPoint, ext.MaxPoint, offsetDist); - } + /// + /// 缩放比例 + /// + /// 命令行对象 + /// 中心点 + /// 窗口宽 + /// 窗口高 + public static void Zoom(this Editor ed, Point3d CenPt, double width, double height) + { + using ViewTableRecord view = ed.GetCurrentView(); + view.Width = width; + view.Height = height; + view.CenterPoint = new Point2d(CenPt.X, CenPt.Y); + ed.SetCurrentView(view);//更新当前视图 + } + + /// + ///缩放窗口范围 + /// + /// 命令行对象 + /// 第一点 + /// 对角点 + /// 偏移距离 + public static void ZoomWindow(this Editor ed, Point3d lpt, Point3d rpt, double offsetDist = 0.00) + { + Extents3d extents = new(); + extents.AddPoint(lpt); + extents.AddPoint(rpt); + rpt = extents.MaxPoint + new Vector3d(offsetDist, offsetDist, 0); + lpt = extents.MinPoint - new Vector3d(offsetDist, offsetDist, 0); + Vector3d ver = rpt - lpt; + ed.Zoom(lpt + ver / 2, ver.X, ver.Y); + } + + + /// + /// 获取有效的数据库范围 + /// + /// 数据库 + /// 容差值:图元包围盒会超过数据库边界,用此参数扩大边界 + /// + public static Extents3d? GetValidExtents3d(this Database db, double extention = 1e-6) + { + db.UpdateExt(true);//更新当前模型空间的范围 + var ve = new Vector3d(extention, extention, extention); + // 数据库没有图元的时候,min是大,max是小,导致新建出错 + // 数据如下: + // min.X == 1E20 && min.Y == 1E20 && min.Z == 1E20 && + // max.X == -1E20 && max.Y == -1E20 && max.Z == -1E20) + var a = db.Extmin; + var b = db.Extmax; + if (a.X < b.X && a.Y < b.Y) + return new Extents3d(db.Extmin - ve, db.Extmax + ve); + + return null; + } + + /// + /// 动态缩放 + /// + /// 命令行对象 + /// 偏移距离 + public static void ZoomExtents(this Editor ed, double offsetDist = 0.00) + { + Database db = ed.Document.Database; + // db.UpdateExt(true); //GetValidExtents3d内提供了 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + ed.ZoomWindow(Point3d.Origin, new Point3d(1, 1, 0), offsetDist); + else + ed.ZoomWindow(db.Extmin, db.Extmax, offsetDist); + } + + /// + /// 根据实体对象的范围显示视图 + /// + /// 命令行对象 + /// Entity对象 + /// 偏移距离 + public static void ZoomObject(this Editor ed, Entity ent, double offsetDist = 0.00) + { + Extents3d ext = ent.GeometricExtents; + ed.ZoomWindow(ext.MinPoint, ext.MaxPoint, offsetDist); + } - #endregion + #endregion - #region Get交互类 + #region Get交互类 - /// - /// 获取Point - /// - /// 命令行对象 - /// 提示信息 - /// 提示使用的基点 - /// - public static PromptPointResult GetPoint(this Editor ed, string Message, Point3d BasePoint) + /// + /// 获取Point + /// + /// 命令行对象 + /// 提示信息 + /// 提示使用的基点 + /// + public static PromptPointResult GetPoint(this Editor ed, string Message, Point3d BasePoint) + { + PromptPointOptions ptOp = new(Message) { - PromptPointOptions ptOp = new(Message) - { - BasePoint = BasePoint, - UseBasePoint = true - }; - return ed.GetPoint(ptOp); - } + BasePoint = BasePoint, + UseBasePoint = true + }; + return ed.GetPoint(ptOp); + } - /// - /// 获取double值 - /// - /// 命令行对象 - /// 提示信息 - /// double默认值 - /// - public static PromptDoubleResult GetDouble(this Editor ed, string Message, double DefaultValue = 1.0) + /// + /// 获取double值 + /// + /// 命令行对象 + /// 提示信息 + /// double默认值 + /// + public static PromptDoubleResult GetDouble(this Editor ed, string Message, double DefaultValue = 1.0) + { + PromptDoubleOptions douOp = new(Message) { - PromptDoubleOptions douOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetDouble(douOp); - } + DefaultValue = DefaultValue + }; + return ed.GetDouble(douOp); + } - /// - /// 获取int值 - /// - /// 命令行对象 - /// 提示信息 - /// double默认值 - /// - public static PromptIntegerResult GetInteger(this Editor ed, string Message, int DefaultValue = 1) + /// + /// 获取int值 + /// + /// 命令行对象 + /// 提示信息 + /// double默认值 + /// + public static PromptIntegerResult GetInteger(this Editor ed, string Message, int DefaultValue = 1) + { + PromptIntegerOptions douOp = new(Message) { - PromptIntegerOptions douOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetInteger(douOp); - } + DefaultValue = DefaultValue + }; + return ed.GetInteger(douOp); + } - /// - /// 获取string值 - /// - /// 命令行对象 - /// 提示信息 - /// string默认值 - /// - public static PromptResult GetString(this Editor ed, string Message, string DefaultValue = "") + /// + /// 获取string值 + /// + /// 命令行对象 + /// 提示信息 + /// string默认值 + /// + public static PromptResult GetString(this Editor ed, string Message, string DefaultValue = "") + { + PromptStringOptions strOp = new(Message) { - PromptStringOptions strOp = new(Message) - { - DefaultValue = DefaultValue - }; - return ed.GetString(strOp); - } + DefaultValue = DefaultValue + }; + return ed.GetString(strOp); + } - #endregion Get交互类 + #endregion - #region 执行lisp + #region 执行lisp +#if NET35 + [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] +#else + [DllImport("accore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] +#endif + static extern int AcedInvoke(IntPtr args, out IntPtr result); -#if ac2009 - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - [DllImport("acad.exe", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPB_WAAPAUresbuf@@@Z")] #else - [System.Security.SuppressUnmanagedCodeSecurity] - [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] - private static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); + // 高版本此接口不能使用lisp(command "xx"),但是可以直接在自动运行接口上 + [DllImport("accore.dll", CharSet = CharSet.Unicode, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "?acedEvaluateLisp@@YAHPEB_WAEAPEAUresbuf@@@Z")] +#endif + [System.Security.SuppressUnmanagedCodeSecurity]//初始化默认值 + static extern int AcedEvaluateLisp(string lispLine, out IntPtr result); - [DllImport("accore.dll", CallingConvention = CallingConvention.Cdecl, EntryPoint = "acedInvoke")] - private static extern int AcedInvoke(IntPtr args, out IntPtr result); +#if NET35 + [DllImport("acad.exe", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "ads_queueexpr")] +#else + [DllImport("accore.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.Cdecl, + EntryPoint = "ads_queueexpr")] #endif - /// - /// 发送lisp语句字符串到cad执行 - /// - /// 编辑器对象 - /// lisp语句 - /// 缓冲结果,返回值 - public static ResultBuffer RunLisp(this Editor ed, string arg) - { - AcedEvaluateLisp(arg, out IntPtr rb); - try - { - if (rb != IntPtr.Zero) - return DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; - } - catch - { } - return null; + static extern int Ads_queueexpr(string strExpr); + + public enum RunLispFlag : byte + { + AdsQueueexpr = 1, + AcedEvaluateLisp = 2, + SendStringToExecute = 4, + } + + /// + /// 发送lisp语句字符串到cad执行 + /// + /// 编辑器对象 + /// lisp语句 + /// 运行方式 + /// 缓冲结果,返回值 +#pragma warning disable IDE0060 // 删除未使用的参数 + public static ResultBuffer? RunLisp(this Editor ed, + string lispCode, + RunLispFlag flag = RunLispFlag.AdsQueueexpr) +#pragma warning restore IDE0060 // 删除未使用的参数 + { + /* + * 测试命令: + * [CommandMethod("CmdTest_RunLisp")] + * public static void CmdTest_RunLisp() + * { + * var res = SendLisp.RunLisp("(setq abc 10)"); + * } + * 调用方式: + * (command "CmdTest_RunLisp1") + * bug说明: + * AcedEvaluateLisp接口在高版本调用时候没有运行成功,使得 !abc 没有值 + * 经过测试,cad08调用成功,此bug与CommandFlags无关 + * 解决方案: + * 0x01 用异步接口,但是这样是显式调用了: + * (setq thisdrawing (vla-get-activedocument (vlax-get-acad-object)))(vla-SendCommand thisdrawing "CmdTest_RunLisp1 ") + * 0x02 使用 Ads_queueexpr 接口 + */ + if ((flag & RunLispFlag.AdsQueueexpr) == RunLispFlag.AdsQueueexpr) + { + // 这个在08/12发送lisp不会出错,但是发送bo命令出错了. + // 0x01 设置 CommandFlags.Session 可以同步, + // 0x02 自执行发送lisp都是异步,(用来发送 含有(command)的lisp的) + _ = Ads_queueexpr(lispCode + "\n"); + } + if ((flag & RunLispFlag.AcedEvaluateLisp) == RunLispFlag.AcedEvaluateLisp) + { + _ = AcedEvaluateLisp(lispCode, out IntPtr rb); + if (rb != IntPtr.Zero) + return DisposableWrapper.Create(typeof(ResultBuffer), rb, true) as ResultBuffer; + } + if ((flag & RunLispFlag.SendStringToExecute) == RunLispFlag.SendStringToExecute) + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + doc.SendStringToExecute(lispCode + "\n", false, false, false); } - #endregion 执行lisp + return null; } -} + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs index c14b566..9f9fc55 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs @@ -1,409 +1,415 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.DatabaseServices.Filters; -using Autodesk.AutoCAD.Geometry; +namespace IFoxCAD.Cad; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; - -namespace IFoxCAD.Cad +/// +/// 实体图元类扩展函数 +/// +public static class EntityEx { + #region 实体刷新 /// - /// 实体图元类扩展函数 + /// 刷新实体显示 /// - public static class EntityEx + /// 实体对象 + public static void Flush(this Entity entity, DBTrans? trans = null) { - #region 实体刷新 - /// - /// 刷新实体显示 - /// - /// 实体对象 - public static void Flush(this Entity entity, Transaction trans = null) - { - if (entity is null) - { - throw new ArgumentNullException(nameof(entity)); - } - if (trans is null) - { - trans = DBTrans.Top.Transaction; - } - entity.RecordGraphicsModified(true); - trans.TransactionManager.QueueForGraphicsFlush(); - } + //if (entity is null) + //{ + // throw new ArgumentNullException(nameof(entity)); + //} + trans ??= DBTrans.Top; + entity.RecordGraphicsModified(true); + trans.Transaction.TransactionManager.QueueForGraphicsFlush(); + trans.Document?.TransactionManager.FlushGraphics(); + } - /// - /// 刷新实体显示 - /// - /// 实体id - public static void Flush(this ObjectId id) => Flush(DBTrans.Top.GetObject(id)); - #endregion - - #region 多段线端点坐标 - /// - /// 获取二维多段线的端点坐标 - /// - /// 二维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline2d pl2d, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl2d) - { - yield return ((Vertex2d)tr.GetObject(id, OpenMode.ForRead)).Position; - } - } + /// + /// 刷新实体显示 + /// + /// 实体id + public static void Flush(this ObjectId id) => Flush(DBTrans.Top.GetObject(id)!); + #endregion - /// - /// 获取三维多段线的端点坐标 - /// - /// 三维多段线 - /// 事务 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline3d pl3d, Transaction tr = null) + #region 多段线端点坐标 + /// + /// 获取二维多段线的端点坐标 + /// + /// 二维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline2d pl2d, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + foreach (ObjectId id in pl2d) { - tr ??= DBTrans.Top.Transaction; - foreach (ObjectId id in pl3d) - { - yield return ((PolylineVertex3d)tr.GetObject(id, OpenMode.ForRead)).Position; - } + yield return tr.GetObject(id)!.Position; } + } - /// - /// 获取多段线的端点坐标 - /// - /// 多段线 - /// 端点坐标集合 - public static IEnumerable GetPoints(this Polyline pl) + /// + /// 获取三维多段线的端点坐标 + /// + /// 三维多段线 + /// 事务 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline3d pl3d, DBTrans? tr = null) + { + tr ??= DBTrans.Top; + foreach (ObjectId id in pl3d) { - return - Enumerable - .Range(0, pl.NumberOfVertices) - .Select(i => pl.GetPoint3dAt(i)); + yield return tr.GetObject(id, OpenMode.ForRead)!.Position; } - #endregion + } - #region TransformBy + /// + /// 获取多段线的端点坐标 + /// + /// 多段线 + /// 端点坐标集合 + public static IEnumerable GetPoints(this Polyline pl) + { + return + Enumerable + .Range(0, pl.NumberOfVertices) + .Select(i => pl.GetPoint3dAt(i)); + } + #endregion - /// - /// 移动实体 - /// - /// 实体 - /// 基点 - /// 目标点 - public static void Move(this Entity ent, Point3d from, Point3d to) - { - ent.TransformBy(Matrix3d.Displacement(to - from)); - } + #region 实体线性变换 - /// - /// 缩放实体 - /// - /// 实体 - /// 缩放基点坐标 - /// 缩放比例 - public static void Scale(this Entity ent, Point3d center, double scaleValue) - { - ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); - } + /// + /// 移动实体 + /// + /// 实体 + /// 基点 + /// 目标点 + public static void Move(this Entity ent, Point3d from, Point3d to) + { + ent.TransformBy(Matrix3d.Displacement(to - from)); + } - /// - /// 旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - /// 旋转平面的法向矢量 - public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) - { - ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); - } + /// + /// 缩放实体 + /// + /// 实体 + /// 缩放基点坐标 + /// 缩放比例 + public static void Scale(this Entity ent, Point3d center, double scaleValue) + { + ent.TransformBy(Matrix3d.Scaling(scaleValue, center)); + } - /// - /// 在XY平面内旋转实体 - /// - /// 实体 - /// 旋转中心 - /// 转角 - public static void Rotation(this Entity ent, Point3d center, double angle) - { - ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); - } + /// + /// 旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + /// 旋转平面的法向矢量 + public static void Rotation(this Entity ent, Point3d center, double angle, Vector3d normal) + { + ent.TransformBy(Matrix3d.Rotation(angle, normal, center)); + } - /// - /// 按对称轴镜像实体 - /// - /// 实体 - /// 对称轴起点 - /// 对称轴终点 - public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) - { - ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); - } + /// + /// 在XY平面内旋转实体 + /// + /// 实体 + /// 旋转中心 + /// 转角,弧度制,正数为顺时针 + public static void Rotation(this Entity ent, Point3d center, double angle) + { + ent.TransformBy(Matrix3d.Rotation(angle, Vector3d.ZAxis.TransformBy(ent.Ecs), center)); + } - /// - /// 按对称面镜像实体 - /// - /// 实体 - /// 对称平面 - public static void Mirror(this Entity ent, Plane plane) - { - ent.TransformBy(Matrix3d.Mirroring(plane)); - } + /// + /// 按对称轴镜像实体 + /// + /// 实体 + /// 对称轴起点 + /// 对称轴终点 + public static void Mirror(this Entity ent, Point3d startPoint, Point3d endPoint) + { + ent.TransformBy(Matrix3d.Mirroring(new Line3d(startPoint, endPoint))); + } - /// - /// 按对称点镜像实体 - /// - /// 实体 - /// 对称点 - public static void Mirror(this Entity ent, Point3d basePoint) - { - ent.TransformBy(Matrix3d.Mirroring(basePoint)); - } + /// + /// 按对称面镜像实体 + /// + /// 实体 + /// 对称平面 + public static void Mirror(this Entity ent, Plane plane) + { + ent.TransformBy(Matrix3d.Mirroring(plane)); + } - #endregion + /// + /// 按对称点镜像实体 + /// + /// 实体 + /// 对称点 + public static void Mirror(this Entity ent, Point3d basePoint) + { + ent.TransformBy(Matrix3d.Mirroring(basePoint)); + } + + #endregion - #region 实体范围 - /// - /// 获取实体集合的范围 - /// - /// 实体迭代器 - /// 实体集合的范围 - public static Extents3d GetExtents(this IEnumerable ents) + #region 实体范围 + /// + /// 获取实体集合的范围 + /// + /// 实体迭代器 + /// 实体集合的范围 + public static Extents3d GetExtents(this IEnumerable ents) + { + var ext = new Extents3d(); + foreach (var item in ents) { - var it = ents.GetEnumerator(); - var ext = it.Current.GeometricExtents; - while (it.MoveNext()) - ext.AddExtents(it.Current.GeometricExtents); - return ext; + ext.AddExtents(item.GeometricExtents); } - #endregion + return ext; + } + #endregion - #region 单行文字 + #region 单行文字 - /// - /// 更正单行文字的镜像属性 - /// - /// 单行文字 - public static void ValidateMirror(this DBText txt) - { - if (!txt.Database.Mirrtext) - { - txt.IsMirroredInX = false; - txt.IsMirroredInY = false; - } - } - #endregion - - #region 多行文字 - - /// - /// 炸散多行文字 - /// - /// 存储多行文字炸散之后的对象的类型 - /// 多行文字 - /// 存储对象变量 - /// 回调函数,用于处理炸散之后的对象 - /// MTextFragment -- 多行文字炸散后的对象 - /// MTextFragmentCallbackStatus -- 回调函数处理的结果 - /// - public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) + /// + /// 更正单行文字的镜像属性 + /// + /// 单行文字 + public static void ValidateMirror(this DBText txt) + { + if (!txt.Database.Mirrtext) { - mt.ExplodeFragments((f, o) => mTextFragmentCallback(f, (T)o), obj); + txt.IsMirroredInX = false; + txt.IsMirroredInY = false; } + } + #endregion - /// - /// 获取多行文字的无格式文本 - /// - /// 多行文字 - /// 文本 - public static string GetUnFormatString(this MText mt) - { - List strs = new(); - mt.ExplodeFragments( - strs, - (f, o) => { - o.Add(f.Text); - return MTextFragmentCallbackStatus.Continue; - }); - return string.Join("", strs.ToArray()); - } - #endregion - - #region 圆弧 - - /// - /// 根据圆心、起点和终点来创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 终点 - /// 圆弧 - public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); - //计算起始和终止角度 - arc.StartAngle = startVector.Angle; - arc.EndAngle = endVector.Angle; - return arc; - } - /// - /// 三点法创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆弧上的点 - /// 终点 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); - //将几何类圆弧对象的圆心和半径赋值给圆弧 -#if ac2009 - return geArc.ToArc(); -#elif ac2013 - return (Arc)Curve.CreateFromGeCurve(geArc); + #region 多行文字 + + /// + /// 炸散多行文字 + /// + /// 存储多行文字炸散之后的对象的类型 + /// 多行文字 + /// 存储对象变量 + /// 回调函数,用于处理炸散之后的对象 + /// 多行文字炸散后的对象 + /// 回调函数处理的结果 + /// + public static void ExplodeFragments(this MText mt, T obj, Func mTextFragmentCallback) + { + mt.ExplodeFragments((f, o) => mTextFragmentCallback(f, (T)o), obj); + } + + /// + /// 获取多行文字的无格式文本 + /// + /// 多行文字 + /// 文本 + public static string GetUnFormatString(this MText mt) + { + List strs = new(); + mt.ExplodeFragments( + strs, + (f, o) => { + o.Add(f.Text); + return MTextFragmentCallbackStatus.Continue; + }); + return string.Join("", strs.ToArray()); + } + #endregion + + #region 圆弧 + + /// + /// 根据圆心、起点、终点来创建圆弧(二维) + /// + /// 起点 + /// 圆心 + /// 终点 + /// 圆弧 + public static Arc CreateArcSCE(Point3d startPoint, Point3d centerPoint, Point3d endPoint) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + Vector2d endVector = new(endPoint.X - centerPoint.X, endPoint.Y - centerPoint.Y); + //计算起始和终止角度 + arc.StartAngle = startVector.Angle; + arc.EndAngle = endVector.Angle; + return arc; + } + /// + /// 三点法创建圆弧(二维) + /// + /// 圆弧对象 + /// 起点 + /// 圆弧上的点 + /// 终点 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d pointOnArc, Point3d endPoint) + { + //创建一个几何类的圆弧对象 + CircularArc3d geArc = new(startPoint, pointOnArc, endPoint); + //将几何类圆弧对象的圆心和半径赋值给圆弧 +#if NET35 + return (Arc)geArc.ToCurve(); +#else + return (Arc)Curve.CreateFromGeCurve(geArc); #endif - } + } - /// - /// 根据起点、圆心和圆弧角度创建圆弧(二维) - /// - /// 圆弧对象 - /// 起点 - /// 圆心 - /// 圆弧角度 - /// 圆弧 - public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) - { - Arc arc = new(); - arc.Center = centerPoint; - arc.Radius = centerPoint.DistanceTo(startPoint); - Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); - arc.StartAngle = startVector.Angle; - arc.EndAngle = startVector.Angle + angle; - return arc; - } + /// + /// 根据起点、圆心和圆弧角度创建圆弧(二维) + /// + /// 圆弧对象 + /// 起点 + /// 圆心 + /// 圆弧角度 + /// 圆弧 + public static Arc CreateArc(Point3d startPoint, Point3d centerPoint, double angle) + { + Arc arc = new(); + arc.SetDatabaseDefaults(); + arc.Center = centerPoint; + arc.Radius = centerPoint.DistanceTo(startPoint); + Vector2d startVector = new(startPoint.X - centerPoint.X, startPoint.Y - centerPoint.Y); + arc.StartAngle = startVector.Angle; + arc.EndAngle = startVector.Angle + angle; + return arc; + } - #endregion + #endregion - #region 圆 + #region 圆 - /// - /// 两点创建圆(两点中点为圆心) - /// - /// 起点 - /// 终点 - /// - public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) - { - Circle circle = new(); - circle.Center = startPoint.GetMidPointTo(endPoint); - circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; - return circle; - } + /// + /// 两点创建圆(两点中点为圆心) + /// + /// 起点 + /// 终点 + /// + public static Circle CreateCircle(Point3d startPoint, Point3d endPoint) + { + Circle circle = new(); + circle.SetDatabaseDefaults(); + circle.Center = startPoint.GetMidPointTo(endPoint); + circle.Radius = startPoint.DistanceTo(endPoint) * 0.5; + return circle; + } - /// - /// 三点法创建圆(失败则返回Null) - /// - /// 第一点 - /// 第二点 - /// 第三点 - /// - public static Circle CreateCircle(Point3d pt1, Point3d PointV, Point3d pt3) - { - //先判断三点是否共线,得到pt1点指向PointV、PointV点的矢量 - Vector3d va = pt1.GetVectorTo(PointV); - Vector3d vb = pt1.GetVectorTo(pt3); - //如两矢量夹角为0或180度(π弧度),则三点共线. - if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) - { - return null; - } - else - { - //创建一个几何类的圆弧对象 - CircularArc3d geArc = new(pt1, PointV, pt3); - geArc.ToCircle(); - return geArc.ToCircle(); - } - } + /// + /// 三点法创建圆(失败则返回Null) + /// + /// 第一点 + /// 第二点 + /// 第三点 + /// + public static Circle? CreateCircle(Point3d pt1, Point3d pt2, Point3d pt3) + { + //先判断三点是否共线,得到pt1点指向pt2、pt2点的矢量 + Vector3d va = pt1.GetVectorTo(pt2); + Vector3d vb = pt1.GetVectorTo(pt3); + //如两矢量夹角为0或180度(π弧度),则三点共线. + if (va.GetAngleTo(vb) == 0 | va.GetAngleTo(vb) == Math.PI) + return null; + + //创建一个几何类的圆弧对象 + CircularArc3d geArc = new(pt1, pt2, pt3); + geArc.ToCircle(); + return geArc.ToCircle(); + } + + /// + /// 通过圆心,半径绘制圆形 + /// + /// 圆心 + /// 半径 + /// 图形的ObjectId + public static Circle? CreateCircle(Point3d center, double radius, double vex = 0, double vey = 0, double vez = 1) + { + return new Circle(center, new Vector3d(vex, vey, vez), radius);//平面法向量XY方向 + } - #endregion + #endregion - #region 块参照 + #region 块参照 - #region 裁剪块参照 + #region 裁剪块参照 - private const string filterDictName = "ACAD_FILTER"; - private const string spatialName = "SPATIAL"; + private const string filterDictName = "ACAD_FILTER"; + private const string spatialName = "SPATIAL"; - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 裁剪多边形点表 - public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 裁剪多边形点表 + public static void ClipBlockRef(this BlockReference bref, IEnumerable pt3ds) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + var pts = + pt3ds + .Select(p => p.TransformBy(mat)) + .Select(p => new Point2d(p.X, p.Y)) + .ToCollection(); + + SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); + using SpatialFilter sf = new() { Definition = sfd }; + var dict = bref.GetXDictionary()!.GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + //SetToDictionary(dict, spatialName, sf); + } + + /// + /// 裁剪块参照 + /// + /// 块参照 + /// 第一角点 + /// 第二角点 + public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d pt2) + { + Matrix3d mat = bref.BlockTransform.Inverse(); + pt1 = pt1.TransformBy(mat); + pt2 = pt2.TransformBy(mat); + + Point2dCollection pts = new() { - if (bref is null) - { - throw new ArgumentNullException(nameof(bref)); - } - if (pt3ds is null) - { - throw new ArgumentNullException(nameof(pt3ds)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - var pts = - pt3ds - .Select(p => p.TransformBy(mat)) - .Select(p => new Point2d(p.X, p.Y)) - .ToCollection(); - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); - } + new Point2d(Math.Min(pt1.X, pt2.X), Math.Min(pt1.Y, pt2.Y)), + new Point2d(Math.Max(pt1.X, pt2.X), Math.Max(pt1.Y, pt2.Y)) + }; + + SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); + using SpatialFilter sf = new() { Definition = sfd }; + var dict = bref.GetXDictionary()!.GetSubDictionary(true, new string[] { filterDictName })!; + dict.SetAt(spatialName, sf); + //SetToDictionary(dict, spatialName, sf); + } + #endregion + + /// + /// 更新动态块属性值 + /// + /// 动态块 + /// 属性值字典 + public static void ChangeBlockProperty(this BlockReference blockReference, + Dictionary propertyNameValues) + { + if (!blockReference.IsDynamicBlock) + return; - /// - /// 裁剪块参照 - /// - /// 块参照 - /// 第一角点 - /// 第二角点 - public static void ClipBlockRef(this BlockReference bref, Point3d pt1, Point3d PointV) + using (blockReference.ForWrite()) { - if (bref is null) - { - throw new ArgumentNullException(nameof(bref)); - } - Matrix3d mat = bref.BlockTransform.Inverse(); - pt1 = pt1.TransformBy(mat); - PointV = PointV.TransformBy(mat); - Point2dCollection pts = new() - { - new Point2d(Math.Min(pt1.X, PointV.X), Math.Min(pt1.Y, PointV.Y)), - new Point2d(Math.Max(pt1.X, PointV.X), Math.Max(pt1.Y, PointV.Y)) - }; - - SpatialFilterDefinition sfd = new(pts, Vector3d.ZAxis, 0.0, 0.0, 0.0, true); - using SpatialFilter sf = new() { Definition = sfd }; - var dict = bref.GetXDictionary().GetSubDictionary(true, new string[] { filterDictName }); - dict.SetAt(spatialName, sf); - //SetToDictionary(dict, spatialName, sf); + foreach (DynamicBlockReferenceProperty item in blockReference.DynamicBlockReferencePropertyCollection) + if (propertyNameValues.ContainsKey(item.PropertyName)) + item.Value = propertyNameValues[item.PropertyName]; } - #endregion - #endregion } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs b/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs index 55b108c..fc0fbaf 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs @@ -1,80 +1,107 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 坐标系类型枚举 +/// +public enum CoordinateSystemCode { /// - /// 坐标系类型枚举 + /// 世界坐标系 /// - public enum CoordinateSystemCode - { - /// - /// 世界坐标系 - /// - Wcs = 0, - - /// - /// 用户坐标系 - /// - Ucs, - - /// - /// 模型空间坐标系 - /// - MDcs, - - /// - /// 图纸空间坐标系 - /// - PDcs - } + Wcs = 0, /// - /// 方向的枚举 + /// 用户坐标系 /// - public enum OrientationType - { - /// - /// 左转或逆时针 - /// - CounterClockWise, - /// - /// 右转或顺时针 - /// - ClockWise, - /// - /// 重合或平行 - /// - Parallel - } + Ucs, /// - /// 点与多边形的关系类型枚举 + /// 模型空间坐标系 /// - public enum PointOnRegionType - { - /// - /// 多边形内部 - /// - Inside, - - /// - /// 多边形上 - /// - On, - - /// - /// 多边形外 - /// - Outside, - - /// - /// 错误 - /// - Error - } + MDcs, + /// + /// 图纸空间坐标系 + /// + PDcs +} +/// +/// 方向的枚举 +/// +public enum OrientationType +{ + /// + /// 左转或逆时针 + /// + CounterClockWise, + /// + /// 右转或顺时针 + /// + ClockWise, + /// + /// 重合或平行 + /// + Parallel } + +/// +/// 点与多边形的关系类型枚举 +/// +public enum PointOnRegionType +{ + /// + /// 多边形内部 + /// + Inside, + + /// + /// 多边形上 + /// + On, + + /// + /// 多边形外 + /// + Outside, + + /// + /// 错误 + /// + Error +} + + + +public enum FontTTF +{ + [Description("宋体.ttf")] + 宋体, + [Description("simfang.ttf")] + 仿宋, + [Description("FSGB2312.ttf")] + 仿宋GB2312, + [Description("Arial.ttf")] + Arial, + [Description("Romans")] + Romans +} + + + +public static class EnumHelper +{ + public static string GetDesc(this Enum val) + { + var type = val.GetType(); + var memberInfo = type.GetMember(val.ToString()); + var attributes = memberInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false); + //如果没有定义描述,就把当前枚举值的对应名称返回 + if (attributes is null || attributes.Length != 1) + { + return val.ToString(); + } + return ((DescriptionAttribute)attributes.Single()).Description; + } +} + diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs index 8f2a7a1..eca3a15 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs @@ -1,665 +1,682 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; +namespace IFoxCAD.Cad; + using System.Drawing; -using System.Linq; -using IFoxCAD.Collections; -using IFoxCAD.Linq; -namespace IFoxCAD.Cad + +/// +/// 图形扩展类 +/// +public static class GeometryEx { + + #region Point&Circle + /// - /// 图形扩展类 + /// 判断点与多边形的关系 /// - public static class GeometryEx + /// 多边形顶点集合 + /// 点 + /// 点与多边形的关系 + public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point2d pt) { - public static double DistanceTo(this Point2d pt1, Point2d PointV) - { - return pt1.GetDistanceTo(PointV); - } - - #region Point&Circle + //遍历点集并生成首尾连接的多边形 + var ptlst = new LoopList(pts); + if (ptlst.Count < 3) + return PointOnRegionType.Error; + + var ls2ds = new List(); + foreach (var node in ptlst.GetNodes()) + { + ls2ds.Add(new LineSegment2d(node.Value, node.Next!.Value)); + } + var cc2d = new CompositeCurve2d(ls2ds.ToArray()); + + //在多边形上? + if (cc2d.IsOn(pt)) + return PointOnRegionType.On; + + //在最小包围矩形外? + var bb2d = cc2d.BoundBlock; + if (!bb2d.Contains(pt)) + return PointOnRegionType.Outside; + + // + bool flag = false; + foreach (var node in ptlst.GetNodes()) + { + var pt1 = node.Value; + var pt2 = node.Next!.Value; + if (pt.Y < pt1.Y && pt.Y < pt2.Y) + continue; + if (pt1.X < pt.X && pt2.X < pt.X) + continue; + Vector2d vec = pt2 - pt1; + double t = (pt.X - pt1.X) / vec.X; + double y = t * vec.Y + pt1.Y; + if (y < pt.Y && t >= 0 && t <= 1) + flag = !flag; + } + return + flag ? + PointOnRegionType.Inside : PointOnRegionType.Outside; + } - /// - /// 判断点与多边形的关系 - /// - /// 多边形顶点集合 - /// 点 - /// 点与多边形的关系 - public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point2d pt) - { - //遍历点集并生成首尾连接的多边形 - var ptlst = new LoopList(pts); - if (ptlst.Count < 3) - return PointOnRegionType.Error; + /// + /// 判断点与多边形的关系 + /// + /// 多边形顶点集合 + /// 点 + /// 点与多边形的关系 + public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point3d pt) + { + //遍历点集并生成首尾连接的多边形 + var ptlst = new LoopList(pts); + if (ptlst.First!.Value == ptlst.Last!.Value) + ptlst.RemoveLast(); + if (ptlst.Count < 3) + return PointOnRegionType.Error; + + var ls3ds = new List(); + foreach (var node in ptlst.GetNodes()) + { + ls3ds.Add(new LineSegment3d(node.Value, node.Next!.Value)); + } + var cc3d = new CompositeCurve3d(ls3ds.ToArray()); + + //在多边形上? + if (cc3d.IsOn(pt)) + return PointOnRegionType.On; + + //在最小包围矩形外? + var bb2d = cc3d.BoundBlock; + if (!bb2d.Contains(pt)) + return PointOnRegionType.Outside; + + // + bool flag = false; + foreach (var node in ptlst.GetNodes()) + { + var pt1 = node.Value; + var pt2 = node.Next!.Value; + if (pt.Y < pt1.Y && pt.Y < pt2.Y) + continue; + if (pt1.X < pt.X && pt2.X < pt.X) + continue; + Vector3d vec = pt2 - pt1; + double t = (pt.X - pt1.X) / vec.X; + double y = t * vec.Y + pt1.Y; + if (y < pt.Y && t >= 0 && t <= 1) + flag = !flag; + } + return + flag ? + PointOnRegionType.Inside : PointOnRegionType.Outside; + } - var ls2ds = new List(); - foreach (var node in ptlst.GetNodes()) - { - ls2ds.Add(new LineSegment2d(node.Value, node.Next.Value)); - } - var cc2d = new CompositeCurve2d(ls2ds.ToArray()); + /// + /// 按两点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, out LoopList ptlst) + { + ptlst = new LoopList { pt1, pt2 }; + return + new CircularArc2d + ( + (pt1 + pt2.GetAsVector()) / 2, + pt1.GetDistanceTo(pt2) / 2 + ); + } - //在多边形上? - if (cc2d.IsOn(pt)) - return PointOnRegionType.On; + /// + /// 按三点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, out LoopList ptlst) + { + ptlst = new LoopList { pt1, pt2, pt3 }; - //在最小包围矩形外? - var bb2d = cc2d.BoundBlock; - if (!bb2d.Contains(pt)) - return PointOnRegionType.Outside; + //遍历各点与下一点的向量长度,找到距离最大的两个点 + LoopListNode maxNode = + ptlst.GetNodes().FindByMax + ( + out double maxLength, + node => node.Value.GetDistanceTo(node.Next!.Value) + ); - // - bool flag = false; - foreach (var node in ptlst.GetNodes()) - { - var pt1 = node.Value; - var PointV = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < PointV.Y) - continue; - if (pt1.X < pt.X && PointV.X < pt.X) - continue; - Vector2d vec = PointV - pt1; - double t = (pt.X - pt1.X) / vec.X; - double y = t * vec.Y + pt1.Y; - if (y < pt.Y && t >= 0 && t <= 1) - flag = !flag; - } - return - flag ? - PointOnRegionType.Inside : PointOnRegionType.Outside; - } + //以两点做最小包围圆 + CircularArc2d ca2d = + GetMinCircle(maxNode.Value, maxNode.Next!.Value, out LoopList tptlst); - /// - /// 判断点与多边形的关系 - /// - /// 多边形顶点集合 - /// 点 - /// 点与多边形的关系 - public static PointOnRegionType PointOnRegion(this IEnumerable pts, Point3d pt) + //如果另一点属于该圆 + if (ca2d.IsIn(maxNode.Previous!.Value)) { - //遍历点集并生成首尾连接的多边形 - var ptlst = new LoopList(pts); - if (ptlst.First.Value == ptlst.Last.Value) - ptlst.RemoveLast(); - if (ptlst.Count < 3) - return PointOnRegionType.Error; - - var ls3ds = new List(); - foreach (var node in ptlst.GetNodes()) - { - ls3ds.Add(new LineSegment3d(node.Value, node.Next.Value)); - } - var cc3d = new CompositeCurve3d(ls3ds.ToArray()); + //返回 + ptlst = tptlst; + return ca2d; + } + //否则按三点做圆 + //ptlst.SetFirst(maxNode); + ptlst = new LoopList { maxNode.Value, maxNode.Next.Value, maxNode.Previous.Value }; + ca2d = new CircularArc2d(pt1, pt2, pt3); + ca2d.SetAngles(0, Math.PI * 2); + return ca2d; + } - //在多边形上? - if (cc3d.IsOn(pt)) - return PointOnRegionType.On; + /// + /// 按四点返回最小包围圆 + /// + /// 基准点 + /// 基准点 + /// 基准点 + /// 基准点 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d? GetMinCircle(Point2d pt1, Point2d pt2, Point2d pt3, Point2d pt4, out LoopList? ptlst) + { + var iniptlst = new LoopList() { pt1, pt2, pt3, pt4 }; + ptlst = null; + CircularArc2d? ca2d = null; - //在最小包围矩形外? - var bb2d = cc3d.BoundBlock; - if (!bb2d.Contains(pt)) - return PointOnRegionType.Outside; + //遍历C43的组合,环链表的优势在这里 + foreach (LoopListNode firstNode in iniptlst.GetNodes()) + { + //获取各组合下三点的最小包围圆 + var secondNode = firstNode.Next; + var thirdNode = secondNode!.Next; + CircularArc2d tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode!.Value, out LoopList tptlst); - // - bool flag = false; - foreach (var node in ptlst.GetNodes()) + //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 + if (tca2d.IsIn(firstNode.Previous!.Value)) { - var pt1 = node.Value; - var PointV = node.Next.Value; - if (pt.Y < pt1.Y && pt.Y < PointV.Y) - continue; - if (pt1.X < pt.X && PointV.X < pt.X) - continue; - Vector3d vec = PointV - pt1; - double t = (pt.X - pt1.X) / vec.X; - double y = t * vec.Y + pt1.Y; - if (y < pt.Y && t >= 0 && t <= 1) - flag = !flag; + if (ca2d is null || tca2d.Radius < ca2d.Radius) + { + ca2d = tca2d; + ptlst = tptlst; + } } - return - flag ? - PointOnRegionType.Inside : PointOnRegionType.Outside; } - /// - /// 按两点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, out LoopList ptlst) - { - ptlst = new LoopList { pt1, PointV }; - return - new CircularArc2d - ( - (pt1 + PointV.GetAsVector()) / 2, - pt1.DistanceTo(PointV) / 2 - ); - } + //返回直径最小的圆 + return ca2d; + } - /// - /// 按三点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, out LoopList ptlst) - { - ptlst = new LoopList { pt1, PointV, pt3 }; - - //遍历各点与下一点的向量长度,找到距离最大的两个点 - double maxLength; - LoopListNode maxNode = - ptlst.GetNodes().FindByMax - ( - out maxLength, - node => node.Value.DistanceTo(node.Next.Value) - ); - - //以两点做最小包围圆 - LoopList tptlst; - CircularArc2d ca2d = - GetMinCircle(maxNode.Value, maxNode.Next.Value, out tptlst); - - //如果另一点属于该圆 - if (ca2d.IsIn(maxNode.Previous.Value)) - { - //返回 - ptlst = tptlst; - return ca2d; - } + /// + /// 计算三点围成的有向面积 + /// + /// 基准点 + /// 第一点 + /// 第二点 + /// 三点围成的三角形的有向面积 + private static double CalArea(Point2d ptBase, Point2d pt1, Point2d pt2) + { + return (pt2 - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; + } + /// + /// 计算三点围成的三角形的真实面积 + /// + /// 基准点 + /// 第一点 + /// 第二点 + /// 三点围成的三角形的真实面积 + public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d pt2) + { + return Math.Abs(CalArea(ptBase, pt1, pt2)); + } - //否则按三点做圆 - ptlst.SetFirst(maxNode); - ca2d = new CircularArc2d(pt1, PointV, pt3); - ca2d.SetAngles(0, Math.PI * 2); - return ca2d; - } + /// + /// 判断三点是否为逆时针,也就是说判断三点是否为左转 + /// + /// 基点 + /// 第一点 + /// 第二点 + /// OrientationType 类型值 + public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d pt2) + { - /// - /// 按四点返回最小包围圆 - /// - /// 基准点 - /// 基准点 - /// 基准点 - /// 基准点 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(Point2d pt1, Point2d PointV, Point2d pt3, Point2d pt4, out LoopList ptlst) + return CalArea(ptBase, pt1, pt2) switch { - LoopList iniptlst = - new LoopList { pt1, PointV, pt3, pt4 }; - ptlst = null; - CircularArc2d ca2d = null; - - //遍历C43的组合,环链表的优势在这里 - foreach (LoopListNode firstNode in iniptlst.GetNodes()) - { - //获取各组合下三点的最小包围圆 - LoopListNode secondNode = firstNode.Next; - LoopListNode thirdNode = secondNode.Next; - LoopList tptlst; - CircularArc2d tca2d = GetMinCircle(firstNode.Value, secondNode.Value, thirdNode.Value, out tptlst); - - //如果另一点属于该圆,并且半径小于当前值就把它做为候选解 - if (tca2d.IsIn(firstNode.Previous.Value)) - { - if (ca2d is null || tca2d.Radius < ca2d.Radius) - { - ca2d = tca2d; - ptlst = tptlst; - } - } - } + > 0 => OrientationType.CounterClockWise, + < 0 => OrientationType.ClockWise, + _ => OrientationType.Parallel + }; + } - //返回直径最小的圆 - return ca2d; - } + /// + /// 计算两个二维向量围成的平行四边形的有向面积 + /// + /// 基向量 + /// 向量 + /// 有向面积 + private static double CalArea(Vector2d vecBase, Vector2d vec) + { + return vec.DotProduct(vecBase.GetPerpendicularVector()) / 2; + } - /// - /// 计算三点围成的有向面积 - /// - /// 基准点 - /// 第一点 - /// 第二点 - /// 三点围成的三角形的有向面积 - private static double CalArea(Point2d ptBase, Point2d pt1, Point2d PointV) - { - return (PointV - ptBase).DotProduct((pt1 - ptBase).GetPerpendicularVector()) / 2; - } - /// - /// 计算三点围成的三角形的真实面积 - /// - /// 基准点 - /// 第一点 - /// 第二点 - /// 三点围成的三角形的真实面积 - public static double GetArea(this Point2d ptBase, Point2d pt1, Point2d PointV) - { - return Math.Abs(CalArea(ptBase, pt1, PointV)); - } + /// + /// 计算两个二维向量围成的平行四边形的真实面积 + /// + /// 基向量 + /// 向量 + /// 真实面积 + public static double GetArea(Vector2d vecBase, Vector2d vec) + { + return Math.Abs(CalArea(vecBase, vec)); + } - /// - /// 判断三点是否为逆时针,也就是说判断三点是否为左转 - /// - /// 基点 - /// 第一点 - /// 第二点 - /// OrientationType 类型值 - public static OrientationType IsClockWise(this Point2d ptBase, Point2d pt1, Point2d PointV) + /// + /// 判断两个二维向量是否左转 + /// + /// 基向量 + /// 向量 + /// OrientationType 类型值 + public static OrientationType IsClockWise(Vector2d vecBase, Vector2d vec) + { + return CalArea(vecBase, vec) switch { + > 0 => OrientationType.CounterClockWise, + < 0 => OrientationType.ClockWise, + _ => OrientationType.Parallel + }; + } - return CalArea(ptBase, pt1, PointV) switch - { + #region PointList - > 0 => OrientationType.CounterClockWise, - < 0 => OrientationType.ClockWise, - _ => OrientationType.Parallel - }; - } + /// + /// 计算点集的有向面积 + /// + /// 点集 + /// 有向面积 + private static double CalArea(IEnumerable pnts) + { + IEnumerator itor = pnts.GetEnumerator(); + if (!itor.MoveNext()) + throw new ArgumentNullException(nameof(pnts)); + Point2d start = itor.Current; + Point2d p1, p2 = start; + double area = 0; - /// - /// 计算两个二维向量围成的平行四边形的有向面积 - /// - /// 基向量 - /// 向量 - /// 有向面积 - private static double CalArea(Vector2d vecBase, Vector2d vec) + while (itor.MoveNext()) { - return vec.DotProduct(vecBase.GetPerpendicularVector()) / 2; + p1 = p2; + p2 = itor.Current; + area += (p1.X * p2.Y - p2.X * p1.Y); } - /// - /// 计算两个二维向量围成的平行四边形的真实面积 - /// - /// 基向量 - /// 向量 - /// 真实面积 - public static double GetArea(Vector2d vecBase, Vector2d vec) + area = (area + (p2.X * start.Y - start.X * p2.Y)) / 2.0; + return area; + } + /// + /// 计算点集的真实面积 + /// + /// 点集 + /// 面积 + public static double GetArea(this IEnumerable pnts) + { + return Math.Abs(CalArea(pnts)); + } + + /// + /// 判断点集的点序 + /// + /// 点集 + /// OrientationType 类型值 + public static OrientationType IsClockWise(this IEnumerable pnts) + { + return CalArea(pnts) switch { - return Math.Abs(CalArea(vecBase, vec)); - } + < 0 => OrientationType.ClockWise, + > 0 => OrientationType.CounterClockWise, + _ => OrientationType.Parallel + }; + } - /// - /// 判断两个二维向量是否左转 - /// - /// 基向量 - /// 向量 - /// OrientationType 类型值 - public static OrientationType IsClockWise(Vector2d vecBase, Vector2d vec) + /// + /// 按点集返回最小包围圆 + /// + /// 点集 + /// 输出圆上的点 + /// 解析类圆对象 + public static CircularArc2d? GetMinCircle(this List pnts, out LoopList? ptlst) + { + //点数较小时直接返回 + switch (pnts.Count) { - return CalArea(vecBase, vec) switch - { - > 0 => OrientationType.CounterClockWise, - < 0 => OrientationType.ClockWise, - _ => OrientationType.Parallel - }; - } + case 0: + ptlst = null; + return null; - #region PointList + case 1: + ptlst = new LoopList { pnts[0] }; + return new CircularArc2d(pnts[0], 0); - /// - /// 计算点集的有向面积 - /// - /// 点集 - /// 有向面积 - private static double CalArea(IEnumerable pnts) - { - IEnumerator itor = pnts.GetEnumerator(); - if (!itor.MoveNext()) - throw new ArgumentNullException(nameof(pnts)); - Point2d start = itor.Current; - Point2d p1, p2 = start; - double area = 0; - - while (itor.MoveNext()) - { - p1 = p2; - p2 = itor.Current; - area += (p1.X * p2.Y - p2.X * p1.Y); - } + case 2: + return GetMinCircle(pnts[0], pnts[1], out ptlst); - area = (area + (p2.X * start.Y - start.X * p2.Y)) / 2.0; - return area; - } - /// - /// 计算点集的真实面积 - /// - /// 点集 - /// 面积 - public static double GetArea(this IEnumerable pnts) - { - return Math.Abs(CalArea(pnts)); - } + case 3: + return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); - /// - /// 判断点集的点序 - /// - /// 点集 - /// OrientationType 类型值 - public static OrientationType IsClockWise(this IEnumerable pnts) - { - return CalArea(pnts) switch - { - < 0 => OrientationType.ClockWise, - > 0 => OrientationType.CounterClockWise, - _ => OrientationType.Parallel - }; + case 4: + return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); } - /// - /// 按点集返回最小包围圆 - /// - /// 点集 - /// 输出圆上的点 - /// 解析类圆对象 - public static CircularArc2d GetMinCircle(this List pnts, out LoopList ptlst) - { - //点数较小时直接返回 - switch (pnts.Count) - { - case 0: - ptlst = null; - return null; + //按前三点计算最小包围圆 + Point2d[] tpnts = new Point2d[4]; + pnts.CopyTo(0, tpnts, 0, 3); + CircularArc2d? ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - case 1: - ptlst = new LoopList { pnts[0] }; - return new CircularArc2d(pnts[0], 0); + //找到点集中距离圆心的最远点为第四点 + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); - case 2: - return GetMinCircle(pnts[0], pnts[1], out ptlst); - - case 3: - return GetMinCircle(pnts[0], pnts[1], pnts[2], out ptlst); + //如果最远点属于圆结束 + while (!ca2d.IsIn(tpnts[3])) + { + //如果最远点不属于圆,按此四点计算最小包围圆 + ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], tpnts[3], out ptlst); - case 4: - return GetMinCircle(pnts[0], pnts[1], pnts[2], pnts[3], out ptlst); + //将结果作为新的前三点 + if (ptlst!.Count == 3) + { + tpnts[2] = ptlst.Last!.Value; } - - //按前三点计算最小包围圆 - Point2d[] tpnts = new Point2d[4]; - pnts.CopyTo(0, tpnts, 0, 3); - CircularArc2d ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - - //找到点集中距离圆心的最远点为第四点 - tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); - - //如果最远点属于圆结束 - while (!ca2d.IsIn(tpnts[3])) + else { - //如果最远点不属于圆,按此四点计算最小包围圆 - ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], tpnts[3], out ptlst); - - //将结果作为新的前三点 - if (ptlst.Count == 3) - { - tpnts[2] = ptlst.Last.Value; - } - else - { - //第三点取另两点中距离圆心较远的点 - //按算法中描述的任选其中一点的话,还是无法收敛...... - tpnts[2] = - tpnts.Except(ptlst) - .FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); - } - tpnts[0] = ptlst.First.Value; - tpnts[1] = ptlst.First.Next.Value; - - //按此三点计算最小包围圆 - ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); - - //找到点集中圆心的最远点为第四点 - tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.DistanceTo(pnt)); + //第三点取另两点中距离圆心较远的点 + //按算法中描述的任选其中一点的话,还是无法收敛...... + tpnts[2] = + tpnts.Except(ptlst) + .FindByMax(pnt => ca2d!.Center.GetDistanceTo(pnt)); } + tpnts[0] = ptlst.First!.Value; + tpnts[1] = ptlst.First.Next!.Value; - return ca2d; + //按此三点计算最小包围圆 + ca2d = GetMinCircle(tpnts[0], tpnts[1], tpnts[2], out ptlst); + + //找到点集中圆心的最远点为第四点 + tpnts[3] = pnts.FindByMax(pnt => ca2d.Center.GetDistanceTo(pnt)); } - /// - /// 获取点集的凸包 - /// - /// 点集 - /// 凸包 - public static List ConvexHull(this List points) - { - if (points is null) - return null; + return ca2d; + } - if (points.Count <= 1) - return points; + /// + /// 获取点集的凸包 + /// + /// 点集 + /// 凸包 + public static List? ConvexHull(this List points) + { + if (points is null) + return null; - int n = points.Count, k = 0; - List H = new(new Point2d[2 * n]); + if (points.Count <= 1) + return points; - points.Sort((a, b) => - a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); + int n = points.Count, k = 0; + List H = new(new Point2d[2 * n]); - // Build lower hull - for (int i = 0; i < n; ++i) - { - while (k >= 2 && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) - k--; - H[k++] = points[i]; - } + points.Sort((a, b) => + a.X == b.X ? a.Y.CompareTo(b.Y) : a.X.CompareTo(b.X)); - // Build upper hull - for (int i = n - 2, t = k + 1; i >= 0; i--) - { - while (k >= t && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) - k--; - H[k++] = points[i]; - } - return H.Take(k - 1).ToList(); + // Build lower hull + for (int i = 0; i < n; ++i) + { + while (k >= 2 && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) + k--; + H[k++] = points[i]; } + // Build upper hull + for (int i = n - 2, t = k + 1; i >= 0; i--) + { + while (k >= t && IsClockWise(H[k - 2], H[k - 1], points[i]) == OrientationType.CounterClockWise) + k--; + H[k++] = points[i]; + } + return H.Take(k - 1).ToList(); + } - #endregion PointList - #endregion Point&Circle + #endregion PointList - #region Ucs + #endregion Point&Circle - /// - /// ucs到wcs的点变换 - /// - /// 点 - /// 变换后的点 - public static Point3d Ucs2Wcs(this Point3d point) - { - return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem); - } + #region Ucs - /// - /// wcs到ucs的点变换 - /// - /// 点 - /// 变换后的点 - public static Point3d Wcs2Ucs(this Point3d point) - { - return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); - } + /// + /// ucs到wcs的点变换 + /// + /// 点 + /// 变换后的点 + public static Point3d Ucs2Wcs(this Point3d point) + { + return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem); + } - /// - /// ucs到wcs的向量变换 - /// - /// 向量 - /// 变换后的向量 - public static Vector3d Ucs2Wcs(this Vector3d vec) - { - return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem); - } + /// + /// wcs到ucs的点变换 + /// + /// 点 + /// 变换后的点 + public static Point3d Wcs2Ucs(this Point3d point) + { + return point.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); + } - /// - /// wcs到ucs的向量变换 - /// - /// 向量 - /// 变换后的向量 - public static Vector3d Wcs2Ucs(this Vector3d vec) - { - return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); - } + /// + /// ucs到wcs的向量变换 + /// + /// 向量 + /// 变换后的向量 + public static Vector3d Ucs2Wcs(this Vector3d vec) + { + return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem); + } - /// - /// 模拟 trans 函数 - /// - /// 点 - /// 源坐标系 - /// 目标坐标系 - /// 变换后的点 - public static Point3d Trans(this Point3d point, CoordinateSystemCode from, CoordinateSystemCode to) - { - return Env.Editor.GetMatrix(from, to) * point; - } + /// + /// wcs到ucs的向量变换 + /// + /// 向量 + /// 变换后的向量 + public static Vector3d Wcs2Ucs(this Vector3d vec) + { + return vec.TransformBy(Env.Editor.CurrentUserCoordinateSystem.Inverse()); + } - /// - /// 模拟 trans 函数 - /// - /// 向量 - /// 源坐标系 - /// 目标坐标系 - /// 变换后的向量 - public static Vector3d Trans(this Vector3d vec, CoordinateSystemCode from, CoordinateSystemCode to) - { - return vec.TransformBy(Env.Editor.GetMatrix(from, to)); - } + /// + /// 模拟 trans 函数 + /// + /// 点 + /// 源坐标系 + /// 目标坐标系 + /// 变换后的点 + public static Point3d Trans(this Point3d point, CoordinateSystemCode from, CoordinateSystemCode to) + { + return Env.Editor.GetMatrix(from, to) * point; + } - /// - /// wcs到dcs的点变换 - /// - /// 点 - /// 是否为图纸空间 - /// 变换后的点 - public static Point3d Wcs2Dcs(this Point3d point, bool atPaperSpace) - { - return - Trans( - point, - CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs - ); - } + /// + /// 模拟 trans 函数 + /// + /// 向量 + /// 源坐标系 + /// 目标坐标系 + /// 变换后的向量 + public static Vector3d Trans(this Vector3d vec, CoordinateSystemCode from, CoordinateSystemCode to) + { + return vec.TransformBy(Env.Editor.GetMatrix(from, to)); + } - /// - /// wcs到dcs的向量变换 - /// - /// 向量 - /// 是否为图纸空间 - /// 变换后的向量 - public static Vector3d Wcs2Dcs(this Vector3d vec, bool atPaperSpace) - { - return - Trans( - vec, - CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs - ); - } + /// + /// wcs到dcs的点变换 + /// + /// 点 + /// 是否为图纸空间 + /// 变换后的点 + public static Point3d Wcs2Dcs(this Point3d point, bool atPaperSpace) + { + return + Trans( + point, + CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs + ); + } + + /// + /// wcs到dcs的向量变换 + /// + /// 向量 + /// 是否为图纸空间 + /// 变换后的向量 + public static Vector3d Wcs2Dcs(this Vector3d vec, bool atPaperSpace) + { + return + Trans( + vec, + CoordinateSystemCode.Wcs, atPaperSpace ? CoordinateSystemCode.PDcs : CoordinateSystemCode.MDcs + ); + } - #endregion Ucs + #endregion Ucs - /// - /// 返回不等比例变换矩阵 - /// - /// 基点 - /// x方向比例 - /// y方向比例 - /// z方向比例 - /// 三维矩阵 - public static Matrix3d GetScaleMatrix(this Point3d point, double x, double y, double z) - { - double[] matdata = new double[16]; - matdata[0] = x; - matdata[3] = point.X * (1 - x); - matdata[5] = y; - matdata[7] = point.Y * (1 - y); - matdata[10] = z; - matdata[11] = point.Z * (1 - z); - matdata[15] = 1; - return new Matrix3d(matdata); - } + /// + /// 返回不等比例变换矩阵 + /// + /// 基点 + /// x方向比例 + /// y方向比例 + /// z方向比例 + /// 三维矩阵 + public static Matrix3d GetScaleMatrix(this Point3d point, double x, double y, double z) + { + double[] matdata = new double[16]; + matdata[0] = x; + matdata[3] = point.X * (1 - x); + matdata[5] = y; + matdata[7] = point.Y * (1 - y); + matdata[10] = z; + matdata[11] = point.Z * (1 - z); + matdata[15] = 1; + return new Matrix3d(matdata); + } - /// - /// 获取坐标范围的大小 - /// - /// 坐标范围 - /// 尺寸对象 - public static Size GetSize(this Extents3d ext) - { - int width = (int)Math.Floor(ext.MaxPoint.X - ext.MinPoint.X); - int height = (int)Math.Ceiling(ext.MaxPoint.Y - ext.MinPoint.Y); - return new Size(width, height); - } + /// + /// 获取坐标范围的大小 + /// + /// 坐标范围 + /// 尺寸对象 + public static Size GetSize(this Extents3d ext) + { + int width = (int)Math.Floor(ext.MaxPoint.X - ext.MinPoint.X); + int height = (int)Math.Ceiling(ext.MaxPoint.Y - ext.MinPoint.Y); + return new Size(width, height); + } - /// - /// 将三维点转换为二维点 - /// - /// 三维点 - /// 二维点 - public static Point2d Point2d(this Point3d pt) - { - return new Point2d(pt.X, pt.Y); - } - /// - /// 将三维点集转换为二维点集 - /// - /// 三维点集 - /// 二维点集 - public static IEnumerable Point2d(this IEnumerable pts) - { - return pts.Select(pt => pt.Point2d()); - } - /// - /// 将二维点转换为三维点 - /// - /// 二维点 - /// 三维点 - public static Point3d Point3d(this Point2d pt) - { - return new Point3d(pt.X, pt.Y, 0); - } + /// + /// 将三维点转换为二维点 + /// + /// 三维点 + /// 二维点 + public static Point2d Point2d(this Point3d pt) + { + return new Point2d(pt.X, pt.Y); + } + /// + /// 将三维点集转换为二维点集 + /// + /// 三维点集 + /// 二维点集 + public static IEnumerable Point2d(this IEnumerable pts) + { + return pts.Select(pt => pt.Point2d()); + } + /// + /// 将二维点转换为三维点 + /// + /// 二维点 + /// 三维点 + public static Point3d Point3d(this Point2d pt) + { + return new Point3d(pt.X, pt.Y, 0); + } + /// + /// 将二维点转换为三维点 + /// + /// 二维点 + /// Z值 + /// 三维点 + public static Point3d Point3d(this Point2d pt,double z) + { + return new Point3d(pt.X, pt.Y, z); + } - /// - /// 获取两个点之间的中点 - /// - /// 第一点 - /// 第二点 - /// 返回两个点之间的中点 - public static Point3d GetMidPointTo(this Point3d pt1, Point3d PointV) - { - return new Point3d((pt1.X + PointV.X) * 0.5, (pt1.Y + PointV.Y) * 0.5, (pt1.Z + PointV.Z) * 0.5); - } + /// + /// 获取两个点之间的中点 + /// + /// 第一点 + /// 第二点 + /// 返回两个点之间的中点 + public static Point3d GetMidPointTo(this Point3d pt1, Point3d pt2) + { + return new Point3d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5, (pt1.Z + pt2.Z) * 0.5); + } + + /// + /// 获取两个点之间的中点 + /// + /// 第一点 + /// 第二点 + /// 返回两个点之间的中点 + public static Point2d GetMidPointTo(this Point2d pt1, Point2d pt2) + { + return new Point2d((pt1.X + pt2.X) * 0.5, (pt1.Y + pt2.Y) * 0.5); + } - /// - /// 根据世界坐标计算用户坐标 - /// - /// 基点世界坐标 - /// 基点用户坐标 - /// 目标世界坐标 - /// 坐标网旋转角,按x轴正向逆时针弧度 - /// 目标用户坐标 - public static Point3d TransPoint(this Point3d basePt, Point3d userPt, Point3d transPt, double ang) - { - Matrix3d transMat = Matrix3d.Displacement(userPt - basePt); - Matrix3d roMat = Matrix3d.Rotation(-ang, Vector3d.ZAxis, userPt); - return transPt.TransformBy(roMat * transMat); - } - /// - /// 计算指定距离和角度的点 - /// - /// 本函数仅适用于x-y平面 - /// 基点 - /// 角度,x轴正向逆时针弧度 - /// 距离 - /// 目标点 - public static Point3d Polar(this Point3d pt, double ang, double len) - { - return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; - } + /// + /// 根据世界坐标计算用户坐标 + /// + /// 基点世界坐标 + /// 基点用户坐标 + /// 目标世界坐标 + /// 坐标网旋转角,按x轴正向逆时针弧度 + /// 目标用户坐标 + public static Point3d TransPoint(this Point3d basePt, Point3d userPt, Point3d transPt, double ang) + { + Matrix3d transMat = Matrix3d.Displacement(userPt - basePt); + Matrix3d roMat = Matrix3d.Rotation(-ang, Vector3d.ZAxis, userPt); + return transPt.TransformBy(roMat * transMat); + } + /// + /// 计算指定距离和角度的点 + /// + /// 本函数仅适用于x-y平面 + /// 基点 + /// 角度,x轴正向逆时针弧度 + /// 距离 + /// 目标点 + public static Point3d Polar(this Point3d pt, double ang, double len) + { + return pt + Vector3d.XAxis.RotateBy(ang, Vector3d.ZAxis) * len; + } + /// + /// 计算指定距离和角度的点 + /// + /// 本函数仅适用于x-y平面 + /// 基点 + /// 角度,x轴正向逆时针弧度 + /// 距离 + /// 目标点 + public static Point2d Polar(this Point2d pt, double ang, double len) + { + return pt + Vector2d.XAxis.RotateBy(ang) * len; } } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs new file mode 100644 index 0000000..b9182ae --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs @@ -0,0 +1,340 @@ +namespace IFoxCAD.Cad; + +/* 封装jig + * 20220726 隐藏事件,利用函数进行数据库图元重绘 + * 20220710 修改SetOption()的空格结束,并添加例子到IFox + * 20220503 cad22需要防止刷新过程中更改队列,是因为允许函数重入导致,08不会有. + * 20220326 重绘图元的函数用错了,现在修正过来 + * 20211216 加入块表时候做一个差集,剔除临时图元 + * 20211209 补充正交变量设置和回收设置 + * 作者: 惊惊⎛⎝◕⏝⏝◕。⎠⎞ ⎛⎝≥⏝⏝0⎠⎞ ⎛⎝⓿⏝⏝⓿。⎠⎞ ⎛⎝≥⏝⏝≤⎠⎞ + * 博客: https://www.cnblogs.com/JJBox/p/15650770.html + */ + +public delegate void WorldDrawEvent(WorldDraw draw); +public class JigEx : DrawJig +{ + #region 成员 + /// + /// 事件:亮显/暗显会被刷新冲刷掉,所以这个事件用于补充非刷新的工作 + /// + event WorldDrawEvent? WorldDrawEvent; + /// + /// 最后的鼠标点,用来确认长度 + /// + public Point3d MousePointWcsLast; + /// + /// 最后的图元,用来生成 + /// + public Entity[] Entitys => _drawEntitys.ToArray(); + + + readonly Action>? _mouseAction; + readonly Tolerance _tolerance;//容差 + + readonly Queue _drawEntitys;//重复生成的图元,放在这里刷新 + JigPromptPointOptions? _options;//jig鼠标配置 + bool _worldDrawFlag = false; // 20220503 + + bool _systemVariables_Orthomode = false; + bool SystemVariables_Orthomode // 正交修改还原 + { + get => _systemVariables_Orthomode; + set + { + //1正交,0非正交 //setvar: https://www.cnblogs.com/JJBox/p/10209541.html + if (Env.OrthoMode != value) + Env.OrthoMode = _systemVariables_Orthomode = value; + } + } + #endregion + + #region 构造 + /// + /// 在界面绘制图元 + /// + private JigEx() + { + _drawEntitys = new(); + } + + /// + /// 在界面绘制图元 + /// + /// + /// 用来频繁执行的回调: + /// 鼠标点; + /// 加入新建的图元,鼠标采样期间会Dispose图元的;所以已经在数据库图元利用事件加入,不要在此加入; + /// + /// 鼠标移动的容差 + public JigEx(Action>? action = null, double tolerance = 1e-6) : this() + { + _mouseAction = action; + _tolerance = new(tolerance, tolerance); + } + #endregion + + #region 方法 + /// + /// 鼠标配置:基点 + /// + /// 基点 + /// 光标绑定 + /// 提示信息 + /// 正交开关 + public JigPromptPointOptions SetOptions(Point3d basePoint, + CursorType cursorType = CursorType.RubberBand, + string msg = "点选第二点", + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + _options.Cursor = cursorType; //光标绑定 + _options.UseBasePoint = true; //基点打开 + _options.BasePoint = basePoint; //基点设定 + return _options; + } + + /// + /// 鼠标配置:提示信息,关键字 + /// + /// 信息 + /// 关键字 + /// 正交开关 + /// + public JigPromptPointOptions SetOptions(string msg, + Dictionary? keywords = null, + bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = JigPointOptions(); + _options.Message = Environment.NewLine + msg; + + //加入关键字,加入时候将空格内容放到最后 + string spaceValue = string.Empty; + const string spaceKey = " "; + + if (keywords != null) + foreach (var item in keywords) + if (item.Key == spaceKey) + spaceValue = item.Value; + else + _options.Keywords.Add(item.Key, item.Key, item.Value); + + ///因为默认配置函数导致此处空格触发是无效的, + ///但是用户如果想触发,就需要在外部减去默认UserInputControls配置 + ///要放最后,才能优先触发其他关键字 + if (spaceValue != string.Empty) + _options.Keywords.Add(spaceKey, spaceKey, spaceValue); + else + _options.Keywords.Add(spaceKey, spaceKey, "<空格退出>"); + + // 外部设置减去配置 + // _options.UserInputControls = + // _options.UserInputControls + // ^ UserInputControls.NullResponseAccepted //输入了鼠标右键,结束jig + // ^ UserInputControls.AnyBlankTerminatesInput; //空格或回车,结束jig; + return _options; + } + + /// + /// 鼠标配置:自定义 + /// + /// + /// 正交开关 + public void SetOptions(Action action, bool orthomode = false) + { + if (orthomode) + SystemVariables_Orthomode = true; + + _options = new JigPromptPointOptions(); + action.Invoke(_options); + } + + /// + /// 执行 + /// + /// + public PromptResult Drag() + { + //jig功能必然是当前前台文档,所以封装内部更好调用 + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + var dr = ed.Drag(this); + + if (SystemVariables_Orthomode) + SystemVariables_Orthomode = !SystemVariables_Orthomode; + return dr; + } + + /// + /// 最后一次的图元加入数据库 + /// + /// 加入此空间 + /// 不生成的图元用于排除,例如刷新时候的提示文字 + /// 加入数据库的id集合 + public IEnumerable? AddEntityToMsPs(BlockTableRecord btrOfAddEntitySpace, + IEnumerable? removeEntity = null) + { + //内部用 _drawEntitys 外部用 Entitys,减少一层转换 + if (_drawEntitys.Count == 0) + return null; + + IEnumerable es = _drawEntitys; + if (removeEntity != null) + es = es.Except(removeEntity);//差集 + + return btrOfAddEntitySpace.AddEntity(es); + } + #endregion + + #region 重写 + /// + /// 鼠标采样器 + /// + /// + /// 返回状态:令频繁刷新结束 + protected override SamplerStatus Sampler(JigPrompts prompts) + { + if (_worldDrawFlag) + return SamplerStatus.NoChange;//OK的时候拖动鼠标与否都不出现图元 + + if (_options is null) + throw new NullReferenceException(nameof(_options)); + + var pro = prompts.AcquirePoint(_options); + if (pro.Status == PromptStatus.Keyword) + return SamplerStatus.OK; + else if (pro.Status != PromptStatus.OK) + return SamplerStatus.Cancel; + + //上次鼠标点不同(一定要这句,不然图元刷新太快会看到奇怪的边线) + var mousePointWcs = pro.Value; + + //== 是比较类字段,但是最好转为哈希比较. + //IsEqualTo 是方形判断(仅加法),但是cad是距离. + //Distance 是圆形判断(会求平方根,使用了牛顿迭代), + //大量数据(十万以上/频繁刷新)面前会显得非常慢. + if (mousePointWcs.IsEqualTo(MousePointWcsLast, _tolerance)) + return SamplerStatus.NoChange; + + //上次循环的缓冲区图元清理,否则将会在vs输出遗忘 Dispose + while (_drawEntitys.Count > 0) + _drawEntitys.Dequeue().Dispose(); + + //委托把容器扔出去接收新创建的图元,然后给重绘更新 + _mouseAction?.Invoke(mousePointWcs, _drawEntitys); + MousePointWcsLast = mousePointWcs; + + return SamplerStatus.OK; + } + + /// + /// 重绘已在数据库的图元 + /// 0x01 此处不加入newEntity的,它们在构造函数的参数回调处加入,它们会进行频繁new和Dispose从而避免遗忘释放 + /// 0x02 此处用于重绘已经在数据的图元 + /// 0x03 此处用于图元亮显暗显,因为会被重绘冲刷掉所以独立出来不重绘,它们也往往已经存在数据库的 + /// + /// + /// newEntity只会存在一个图元队列中,而数据库图元可以分多个集合 + /// 例如: 集合A亮显时 集合B暗显/集合B亮显时 集合A暗显,所以我没有设计多个"数据库图元集合"存放,而是由用户在构造函数外自行创建 + /// + /// + public void DatabaseEntityDraw(WorldDrawEvent action) + { + WorldDrawEvent = action; + } + + /* WorldDraw 封装外的操作说明: + * 0x01 + * 我有一个业务是一次性生成四个方向的箭头,因为cad08缺少瞬时图元, + * 那么可以先提交一次事务,再开一个事务,把Entity传给jig,最后选择删除部分. + * 虽然这个是可行的方案,但是Entity穿越事务本身来说是非必要不使用的. + * 0x02 + * 四个箭头最近鼠标的亮显,其余淡显, + * 在jig使用淡显ent.Unhighlight和亮显ent.Highlight() + * 需要绕过重绘,否则重绘将导致图元频闪,令这两个操作失效, + * 此时需要自定义一个集合 EntityList (不使用本函数的_drawEntitys) + * 再将 EntityList 传给 WorldDrawEvent 事件,事件内实现亮显和淡显(事件已经利用 DatabaseEntityDraw函数进行提供). + * 0x03 + * draw.Geometry.Draw(_drawEntitys[i]); + * 此函数有问题,acad08克隆一份数组也可以用来刷新, + * 而arx上面的jig只能一次改一个,所以可以用此函数. + * 起因是此函数属于异步刷新, + * 同步上下文的刷新是 RawGeometry + * 0x04 + * cad22测试出现,08不会, + * draw.RawGeometry.Draw(ent);会跳到 Sampler(),所以设置 _worldDrawFlag + * 但是禁止重绘重入的话(令图元不频繁重绘),那么鼠标停着的时候就看不见图元, + * 所以只能重绘结束的时候才允许鼠标采集,采集过程的时候不会触发重绘, + * 这样才可以保证容器在重绘中不被更改. + */ + /// + /// 重绘图形 + /// + protected override bool WorldDraw(WorldDraw draw) + { + _worldDrawFlag = true; + WorldDrawEvent?.Invoke(draw); + _drawEntitys.ForEach(ent => { + draw.RawGeometry.Draw(ent); + }); + _worldDrawFlag = false; + return true; + } + #endregion + + /// + /// 用户输入控制默认配置 + /// 令jig.Drag().Status == + /// + /// + static JigPromptPointOptions JigPointOptions() + { + return new JigPromptPointOptions() + { + UserInputControls = + UserInputControls.GovernedByUCSDetect //由UCS探测用 + | UserInputControls.Accept3dCoordinates //接受三维坐标 + | UserInputControls.NullResponseAccepted //输入了鼠标右键,结束jig + | UserInputControls.AnyBlankTerminatesInput //空格或回车,结束jig; + }; + } + + /// + /// 空格默认是, + /// 将它设置为 + /// + public void SetSpaceIsKeyword() + { + var opt = _options; + if (opt == null) + throw new ArgumentNullException(nameof(_options)); + + if ((opt.UserInputControls & UserInputControls.NullResponseAccepted) == UserInputControls.NullResponseAccepted) + opt.UserInputControls ^= UserInputControls.NullResponseAccepted; //输入了鼠标右键,结束jig + if ((opt.UserInputControls & UserInputControls.AnyBlankTerminatesInput) == UserInputControls.AnyBlankTerminatesInput) + opt.UserInputControls ^= UserInputControls.AnyBlankTerminatesInput; //空格或回车,结束jig + } +} + +#if false +| UserInputControls.DoNotEchoCancelForCtrlC //不要取消CtrlC的回音 +| UserInputControls.DoNotUpdateLastPoint //不要更新最后一点 +| UserInputControls.NoDwgLimitsChecking //没有Dwg限制检查 +| UserInputControls.NoZeroResponseAccepted //接受非零响应 +| UserInputControls.NoNegativeResponseAccepted //不否定回复已被接受 +| UserInputControls.Accept3dCoordinates //返回点的三维坐标,是转换坐标系了? +| UserInputControls.AcceptMouseUpAsPoint //接受释放按键时的点而不是按下时 + +| UserInputControls.InitialBlankTerminatesInput //初始 空格或回车,结束jig +| UserInputControls.AcceptOtherInputString //接受其他输入字符串 +| UserInputControls.NoZDirectionOrtho //无方向正射,直接输入数字时以基点到当前点作为方向 +| UserInputControls.UseBasePointElevation //使用基点高程,基点的Z高度探测 +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs new file mode 100644 index 0000000..c831959 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs @@ -0,0 +1,21 @@ +namespace IFoxCAD.Cad; + +public static class ObjEx +{ + /// + /// cad的打印 + /// + /// + public static void Print(this object obj) + { + Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage($"{obj}\n"); + } + /// + /// 系统的打印 + /// + /// + public static void PrintLine(this object obj) + { + Console.WriteLine(obj.ToString()); + } +} diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs index 2a89bd5..52c9261 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs @@ -1,70 +1,89 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Runtime; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 对象id扩展类 +/// +public static class ObjectIdEx { + #region GetObject + /// - /// 对象id扩展类 + /// 获取指定类型对象 /// - public static class ObjectIdEx + /// 指定的泛型 + /// 对象id + /// 事务 + /// 打开模式 + /// 打开删除对象 + /// 指定类型对象 + public static T? GetObject(this ObjectId id, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction? tr = default) where T : DBObject { - #region GetObject - - /// - /// 获取指定类型对象 - /// - /// 指定的泛型 - /// 对象id - /// 事务 - /// 打开模式 - /// 打开删除对象 - /// 指定类型对象 - public static T GetObject(this ObjectId id, - OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject - { - tr ??= DBTrans.Top.Transaction; - return tr.GetObject(id, mode, openErased) as T; - } - - /// - /// 获取指定类型对象集合 - /// - /// 指定的泛型 - /// 对象id集合 - /// 事务 - /// 打开模式 - /// 打开删除对象 - /// 指定类型对象集合 - public static IEnumerable GetObject(this IEnumerable ids, - OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction tr = default) where T : DBObject - { - return ids.Select(id => id.GetObject(mode, openErased, tr)); - } + tr ??= DBTrans.Top.Transaction; + //tr = Env.GetTrans(tr); + return tr.GetObject(id, mode, openErased) as T; + } - /// - /// 返回符合类型的对象id - /// - /// 对象类型 - /// 对象id集合 - /// 对象id集合 - public static IEnumerable OfType(this IEnumerable ids) where T : DBObject - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return ids.Where(id => id.ObjectClass().DxfName == dxfName); - } + /// + /// 获取指定类型对象集合 + /// + /// 指定的泛型 + /// 对象id集合 + /// 事务 + /// 打开模式 + /// 打开删除对象 + /// 指定类型对象集合 + public static IEnumerable GetObject(this IEnumerable ids, OpenMode mode = OpenMode.ForRead, bool openErased = false, Transaction? tr = default) where T : DBObject + { + return ids.Select(id => id.GetObject(mode, openErased, tr)); + } - //Acad08缺少 id.ObjectClass 如何补偿? - public static RXClass ObjectClass(this ObjectId id) - { + /// + /// 返回符合类型的对象id + /// + /// 对象类型 + /// 对象id集合 + /// 对象id集合 + public static IEnumerable OfType(this IEnumerable ids) where T : DBObject + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return ids.Where(id => id.ObjectClass().DxfName == dxfName); + } + #endregion GetObject + + //Acad08缺少 id.ObjectClass 如何补偿? + public static RXClass ObjectClass(this ObjectId id) + { #if NET35 - return RXClass.GetClass(id.GetType()); + return RXClass.GetClass(id.GetType()); #else - return id.ObjectClass; + return id.ObjectClass; #endif - } - #endregion GetObject + } + + /// + /// id是否有效,未被删除 + /// + /// 对象id + /// id有效返回 ,反之返回 + public static bool IsOk(this ObjectId id) + { + return !id.IsNull && id.IsValid && !id.IsErased && !id.IsEffectivelyErased && id.IsResident; + } + /// + /// 删除id代表的对象 + /// + /// 对象id + public static void Erase(this ObjectId id) + { + if (id.IsOk()) + { + var ent = id.GetObject()!; + using (ent.ForWrite()) + { + ent.Erase(); + }// 第一种读写权限自动转换写法 + //Env.Editor.Regen(); + } } } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs new file mode 100644 index 0000000..4898d50 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs @@ -0,0 +1,134 @@ +namespace IFoxCAD.Cad; + +public static class PointEx +{ + + /// + /// 获取点的hash字符串,同时可以作为pt的字符串表示 + /// + /// 点 + /// 指示计算几维坐标的标志,1为计算x,2为计算x,y,其他为计算x,y,z + /// 保留的小数位数 + /// hash字符串 + public static string GetHashString(this Point3d pt, int xyz = 3, int decimalRetain = 6) + { + var de = $"f{decimalRetain}"; + return xyz switch + { + 1 => $"({pt.X.ToString(de)})", + 2 => $"({pt.X.ToString(de)},{pt.Y.ToString(de)})", + _ => $"({pt.X.ToString(de)},{pt.Y.ToString(de)},{pt.Z.ToString(de)})" + }; + } + + //为了频繁触发所以弄个全局变量 + static Plane? _Plane; + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 方向 + /// 弧度值 + public static double GetAngle(this Point3d startPoint, Point3d endPoint, Vector3d? direction = null) + { + if (direction != null) + _Plane = new Plane(Point3d.Origin, direction.Value); + if (_Plane == null) + _Plane = new Plane(Point3d.Origin, Vector3d.ZAxis); + return startPoint.GetVectorTo(endPoint).AngleOnPlane(_Plane); + } + /// + /// 两点计算弧度范围0到2Pi + /// + /// 起点 + /// 终点 + /// 弧度值 + public static double GetAngle(this Point2d startPoint, Point2d endPoint) + { + return startPoint.GetVectorTo(endPoint).Angle; + } + + /// + /// 获取中点 + /// + /// + /// + /// + public static Point2d GetCenter(this Point2d a, Point2d b) + { + // (p1 + p2) / 2; //溢出风险 + return new Point2d(a.X * 0.5 + b.X * 0.5, + a.Y * 0.5 + b.Y * 0.5); + } + + /// http://www.lee-mac.com/bulgeconversion.html + /// + /// 求凸度,判断三点是否一条直线上 + /// + /// 圆弧起点 + /// 圆弧腰点 + /// 圆弧尾点 + /// 逆时针为正,顺时针为负 + public static double GetArcBulge(this Point2d arc1, Point2d arc2, Point2d arc3, double tol = 1e-10) + { + double dStartAngle = arc2.GetAngle(arc1); + double dEndAngle = arc2.GetAngle(arc3); + //求的P1P2与P1P3夹角 + var talAngle = (Math.PI - dStartAngle + dEndAngle) / 2; + //凸度==拱高/半弦长==拱高比值/半弦长比值 + //有了比值就不需要拿到拱高值和半弦长值了,因为接下来是相除得凸度 + double bulge = Math.Sin(talAngle) / Math.Cos(talAngle); + + //处理精度 + if (bulge > 0.9999 && bulge < 1.0001) + bulge = 1; + else if (bulge < -0.9999 && bulge > -1.0001) + bulge = -1; + else if (Math.Abs(bulge) < tol) + bulge = 0; + return bulge; + } + + + #region 首尾相连 + /// + /// 首尾相连 + /// + public static Point2dCollection End2End(this Point2dCollection ptcol) + { + if (ptcol == null) + throw new ArgumentNullException(nameof(ptcol)); + + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 + return ptcol; + + //首尾不同,去加一个到最后 + var lst = new Point2d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + return new(lst); + } + /// + /// 首尾相连 + /// + public static Point3dCollection End2End(this Point3dCollection ptcol) + { + if (ptcol == null) + throw new ArgumentNullException(nameof(ptcol)); + + if (ptcol.Count == 0 || ptcol[0].Equals(ptcol[^1]))//首尾相同直接返回 + return ptcol; + + //首尾不同,去加一个到最后 + var lst = new Point3d[ptcol.Count + 1]; + for (int i = 0; i < lst.Length; i++) + lst[i] = ptcol[i]; + lst[^1] = lst[0]; + + return new(lst); + } + #endregion +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs index 9ede888..dbde0ac 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs @@ -1,106 +1,101 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 选择集扩展类 +/// +public static class SelectionSetEx { - + #region 获取对象id /// - /// 选择集扩展类 + /// 获取已选择的对象 /// - public static class SelectionSetEx + /// 选择集 + /// 已选择的对象集合 + public static IEnumerable GetSelectedObjects(this SelectionSet ss) { - #region 获取对象id - /// - /// 获取已选择的对象 - /// - /// 选择集 - /// 已选择的对象集合 - public static IEnumerable GetSelectedObjects(this SelectionSet ss) - { - return ss.Cast(); - } + return ss.Cast(); + } - /// - /// 获取已选择的对象 - /// - /// 已选择的对象泛型 - /// 选择集 - /// 已选择的对象集合 - public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : SelectedObject - { - return ss.Cast().OfType(); - } + /// + /// 获取已选择的对象 + /// + /// 已选择的对象泛型 + /// 选择集 + /// 已选择的对象集合 + public static IEnumerable GetSelectObjects(this SelectionSet ss) where T : SelectedObject + { + return ss.Cast().OfType(); + } - /// - /// 从选择集中获取对象id - /// - /// 图元类型 - /// 选择集 - /// 已选择的对象id集合 - public static IEnumerable GetObjectIds(this SelectionSet ss) where T : Entity - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return - ss - .GetObjectIds() - .Where(id => id.ObjectClass().DxfName == dxfName); - } + /// + /// 从选择集中获取对象id + /// + /// 图元类型 + /// 选择集 + /// 已选择的对象id集合 + public static IEnumerable GetObjectIds(this SelectionSet ss) where T : Entity + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return + ss + .GetObjectIds() + .Where(id => id.ObjectClass().DxfName == dxfName); + } - /// - /// 将选择集的对象按类型分组 - /// - /// 选择集 - /// 分组后的类型/对象id集合 - public static IEnumerable> GetObjectIdGroup(this SelectionSet ss) - { - return - ss - .GetObjectIds() - .GroupBy(id => id.ObjectClass().DxfName); - } - #endregion + /// + /// 将选择集的对象按类型分组 + /// + /// 选择集 + /// 分组后的类型/对象id集合 + public static IEnumerable> GetObjectIdGroup(this SelectionSet ss) + { + return + ss + .GetObjectIds() + .GroupBy(id => id.ObjectClass().DxfName); + } + #endregion - #region 获取实体对象 + #region 获取实体对象 - /// - /// 获取指定类型图元 - /// - /// 指定类型 - /// 选择集 - /// 事务 - /// 打开模式 - /// 图元集合 - public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity - { - return - ss - .GetObjectIds() - .Select(id => tr.GetObject(id, openMode) as T); - } + /// + /// 获取指定类型图元 + /// + /// 指定类型 + /// 选择集 + /// 事务 + /// 打开模式 + /// 图元集合 + public static IEnumerable GetEntities(this SelectionSet ss, OpenMode openMode = OpenMode.ForRead, DBTrans? tr = default) where T : Entity + { + if (ss is null) + throw new ArgumentNullException(nameof(ss)); - #endregion + tr ??= DBTrans.Top; + return + ss + .GetObjectIds() + .Select(id => tr.GetObject(id, openMode)) + .Where(ent => ent != null); + } - #region ForEach + #endregion - /// - /// 遍历选择集 - /// - /// 指定图元类型 - /// 选择集 - /// 事务 - /// 打开模式 - /// 处理函数 - public static void ForEach(this SelectionSet ss, Action action, OpenMode openMode = OpenMode.ForRead, Transaction tr = default) where T : Entity + #region ForEach + /// + /// 遍历选择集 + /// + /// 指定图元类型 + /// 选择集 + /// 事务 + /// 打开模式 + /// 处理函数 + public static void ForEach(this SelectionSet ss, Action action, OpenMode openMode = OpenMode.ForRead, DBTrans? tr = default) where T : Entity + { + foreach (T? ent in ss.GetEntities(openMode, tr)) { - foreach (T ent in ss.GetEntities(openMode, tr)) - { - action?.Invoke(ent); - } + action?.Invoke(ent); } - #endregion } + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs index 327f8ba..3c0ace3 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs @@ -1,81 +1,73 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; - -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; - -namespace IFoxCAD.Cad +/// +/// 符号表类扩展函数 +/// +public static class SymbolTableEx { + #region 图层表 + /// + /// 添加图层 + /// + /// 图层符号表 + /// 图层名 + /// 图层颜色 + /// 图层id + public static ObjectId Add(this SymbolTable table, string name, Color color) + { + return table.Add(name, lt => lt.Color = color); + } /// - /// 符号表类扩展函数 + /// 添加图层 /// - public static class SymbolTableEx + /// 图层符号表 + /// 图层名 + /// 图层颜色索引值 + /// 图层id + public static ObjectId Add(this SymbolTable table, string name, int colorIndex) { - #region 图层表 - /// - /// 添加图层 - /// - /// 图层符号表 - /// 图层名 - /// 图层颜色 - /// 图层id - public static ObjectId Add(this SymbolTable table, string name, Color color) + colorIndex %= 256;//防止输入的颜色超出256 + colorIndex = Math.Abs(colorIndex);//防止负数 + return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); + } + /// + /// 更改图层名 + /// + /// 图层符号表 + /// 旧图层名 + /// 新图层名 + public static ObjectId Rename(this SymbolTable table, string Oldname, string NewName) + { + if (table.Has(Oldname)) { - return table.Add(name, lt => lt.Color = color); + table.Change(Oldname, ly => + { + ly.Name = NewName; + } + ); + return table[NewName]; } - /// - /// 添加图层 - /// - /// 图层符号表 - /// 图层名 - /// 图层颜色索引值 - /// 图层id - public static ObjectId Add(this SymbolTable table, string name, int colorIndex) + else { - colorIndex %= 256; //防止输入的颜色超出256 - colorIndex = Math.Abs(colorIndex);//防止负数 - return table.Add(name, lt => lt.Color = Color.FromColorIndex(ColorMethod.ByColor, (short)colorIndex)); + return ObjectId.Null; } - /// - /// 更改图层名 - /// - /// 图层符号表 - /// 旧图层名 - /// 新图层名 - public static ObjectId Rename(this SymbolTable table, string Oldname, string NewName) + } + /// + /// 删除图层 + /// + /// 层表 + /// 图层名 + /// 成功返回 ,失败返回 + public static bool Delete(this SymbolTable table, string name) + { + if (name == "0" || name == "Defpoints" || !table.Has(name) || table[name] == DBTrans.Top.Database.Clayer) { - if (table.Has(Oldname)) - { - table.Change(Oldname, ly => { - ly.Name = NewName; - } - ); - return table[NewName]; - } - else - { - return ObjectId.Null; - } + return false; } - /// - /// 删除图层 - /// - /// 层表 - /// 图层名 - /// 成功返回 ,失败返回 - public static bool Delete(this SymbolTable table, string name) + table.CurrentSymbolTable.GenerateUsageData(); + var ltr = table.GetRecord(name); + if (ltr is not null) { - if (name == "0" || name == "Defpoints" || !table.Has(name) || table[name] == DBTrans.Top.Database.Clayer) - { - return false; - } - table.CurrentSymbolTable.GenerateUsageData(); - var ltr = table.GetRecord(name); if (ltr.IsUsed) { return false; @@ -86,191 +78,303 @@ public static bool Delete(this SymbolTable table, } return true; } - #endregion - - #region 块表 - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 对所添加块表的委托n - /// 添加图元的委托 - /// 添加属性定义的委托 - /// 块定义id - /// TODO: 需要测试匿名块等特殊的块是否能定义 - public static ObjectId Add(this SymbolTable table, string name, Action action = null, Func> ents = null, Func> attdef = null) - { - return table.Add(name, btr => { - action?.Invoke(btr); - var entsres = ents?.Invoke(); - if (entsres is not null) - { - btr.AddEntity(entsres); - } - var adddefres = attdef?.Invoke(); - if (adddefres is not null) - { - btr.AddEntity(adddefres); - } - //if (ents is not null) - //{ - // btr.AddEntity(ents?.Invoke()); - //} - //if (attdef is not null) - //{ - // btr.AddEntity(attdef?.Invoke()); - //} - }); - } - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 图元 - /// 属性定义 - /// - public static ObjectId Add(this SymbolTable table, string name, IEnumerable ents = null, IEnumerable attdef = null) - { - return table.Add(name, null, () => { return ents; }, () => { return attdef; }); - } + return false; + } + #endregion - /// - /// 添加块定义 - /// - /// 块表 - /// 块名 - /// 图元(包括属性) - /// - public static ObjectId Add(this SymbolTable table, string name, params Entity[] ents) + #region 块表 + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 对所添加块表的委托n + /// 添加图元的委托 + /// 添加属性定义的委托 + /// 块定义id + /// TODO: 需要测试匿名块等特殊的块是否能定义 + public static ObjectId Add(this SymbolTable table, string name, Action? action = null, Func>? ents = null, Func>? attdef = null) + { + return table.Add(name, btr => { - return table.Add(name, null, () => { return ents; }); - } + action?.Invoke(btr); + var entsres = ents?.Invoke(); + if (entsres is not null) + { + btr.AddEntity(entsres); + } + var adddefres = attdef?.Invoke(); + if (adddefres is not null) + { + btr.AddEntity(adddefres); + } - /// - /// 从文件中获取块定义 - /// - /// 块表 - /// 文件名 - /// 是否覆盖 - /// 块定义Id - public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, bool over) + }); + } + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 图元 + /// 属性定义 + /// + public static ObjectId Add(this SymbolTable table, string name, IEnumerable? ents = null, IEnumerable? attdef = null) + { + return table.Add(name, btr => { - //FileInfo fi = new(fileName); - //string blkdefname = fi.Name; - //if (blkdefname.Contains(".")) - //{ - // blkdefname = blkdefname.Substring(0, blkdefname.LastIndexOf('.')); - //} + if (ents is not null) + { + btr.AddEntity(ents); + } + if (attdef is not null) + { + btr.AddEntity(attdef); + } + }); + } - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); + /// + /// 添加块定义 + /// + /// 块表 + /// 块名 + /// 图元(包括属性) + /// + public static ObjectId Add(this SymbolTable table, string name, params Entity[] ents) + { + return table.Add(name, null, () => { return ents; }); + } - ObjectId id = table[blkdefname]; - bool has = id != ObjectId.Null; - if ((has && over) || !has) + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义id + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, ObjectId id, List atts) + { + table.Change(id, btr => + { + var attTags = new List(); + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + foreach (AttributeDefinition att in atts) { - Database db = new(); - db.ReadDwgFile(fileName, FileShare.Read, true, null); - id = table.Database.Insert(BlockTableRecord.ModelSpace, blkdefname, db, false); + if (!attTags.Contains(att.Tag.ToUpper())) + { + btr.AddEntity(att); + } } + }); + } + /// + /// 添加属性到块定义 + /// + /// 块表 + /// 块定义名字 + /// 属性列表 + public static void AddAttsToBlocks(this SymbolTable table, string name, List atts) + { + table.Change(name, btr => + { + var attTags = new List(); + btr.GetEntities() + .ForEach(def => attTags.Add(def.Tag.ToUpper())); + foreach (AttributeDefinition att in atts) + { + if (!attTags.Contains(att.Tag.ToUpper())) + { + btr.AddEntity(att); + } + } + }); + } - return id; - } - + /// + /// 从文件中获取块定义 + /// + /// 块表 + /// 文件名 + /// 是否覆盖 + /// 块定义Id + public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, bool over) + { + //FileInfo fi = new(fileName); + //string blkdefname = fi.Name; + //if (blkdefname.Contains(".")) + //{ + // blkdefname = blkdefname.Substring(0, blkdefname.LastIndexOf('.')); + //} + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(fileName, "dwg"), false); - /// - /// 从文件中获取块定义 - /// - /// 块表 - /// 文件名 - /// 块定义名 - /// 是否覆盖 - /// 块定义Id - public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, string blockName, bool over) + ObjectId id = table[blkdefname]; + bool has = id != ObjectId.Null; + if ((has && over) || !has) { - return - table.GetRecordFrom( - t => t.BlockTable, - fileName, - blockName, - over); + Database db = new(false, true); + db.ReadDwgFile(fileName, FileShare.Read, true, null); + db.CloseInput(true); + id = table.Database.Insert(BlockTableRecord.ModelSpace, blkdefname, db, false); } - #endregion + + return id; + } - #region 线型表 - /// - /// 添加线型 - /// - /// 线型表 - /// 线型名 - /// 线型说明 - /// 线型长度 - /// 笔画长度数组 - /// 线型id - public static ObjectId Add(this SymbolTable table, string name, string description, double length, double[] dash) - { - return table.Add( - name, - ltt => { - ltt.AsciiDescription = description; - ltt.PatternLength = length; //线型的总长度 + + /// + /// 从文件中获取块定义 + /// + /// 块表 + /// 文件名 + /// 块定义名 + /// 是否覆盖 + /// 块定义Id + public static ObjectId GetBlockFrom(this SymbolTable table, string fileName, string blockName, bool over) + { + return + table.GetRecordFrom( + t => t.BlockTable, + fileName, + blockName, + over); + } + #endregion + + + #region 线型表 + /// + /// 添加线型 + /// + /// 线型表 + /// 线型名 + /// 线型说明 + /// 线型长度 + /// 笔画长度数组 + /// 线型id + public static ObjectId Add(this SymbolTable table, string name, string description, double length, double[] dash) + { + return table.Add( + name, + ltt => + { + ltt.AsciiDescription = description; + ltt.PatternLength = length; //线型的总长度 ltt.NumDashes = dash.Length; //组成线型的笔画数目 for (int i = 0; i < dash.Length; i++) - { - ltt.SetDashLengthAt(i, dash[i]); - } + { + ltt.SetDashLengthAt(i, dash[i]); + } //ltt.SetDashLengthAt(0, 0.5); //0.5个单位的划线 //ltt.SetDashLengthAt(1, -0.25); //0.25个单位的空格 //ltt.SetDashLengthAt(2, 0); // 一个点 //ltt.SetDashLengthAt(3, -0.25); //0.25个单位的空格 } - ); - } - #endregion + ); + } + #endregion - #region 文字样式表 - /// - /// 添加文字样式记录 - /// - /// 文字样式表 - /// 文字样式名 - /// 字体名 - /// 宽度比例 - /// 文字样式Id - public static ObjectId Add(this SymbolTable table, string textStyleName, string font, double xscale) + #region 文字样式表 + /// + /// 添加文字样式记录 + /// + /// 文字样式表 + /// 文字样式名 + /// 字体名 + /// 宽度比例 + /// 文字样式Id + public static ObjectId Add(this SymbolTable table, + string textStyleName, + string font, + double xscale = 1.0) + { + return + table.Add( + textStyleName, + tstr => + { + tstr.Name = textStyleName; + tstr.FileName = font; + tstr.XScale = xscale; + }); + } + /// + /// 添加文字样式记录 + /// + /// 文字样式表 + /// 文字样式名 + /// 字体名枚举 + /// 宽度比例 + /// 文字样式Id + public static ObjectId Add(this SymbolTable table, string textStyleName, FontTTF fontTTF, double xscale = 1.0) + { + return table.Add(textStyleName, fontTTF.GetDesc(), xscale); + } + + /// + ///

添加文字样式记录,如果存在就默认强制替换

+ /// 此函数为了 而设 + ///
+ /// 文字样式表 + /// 文字样式名 + /// 字体名 + /// 大字体名 + /// 宽度比例 + /// 高度 + /// 是否强制替换 + /// 文字样式Id + public static ObjectId AddWithChange(this SymbolTable table, + string textStyleName, + string smallFont, + string bigFont = "", + double xScale = 1, + double height = 0, + bool forceChange = true) + { + if (forceChange && table.Has(textStyleName)) { - return - table.Add( - textStyleName, - tstr => { - tstr.Name = textStyleName; - tstr.FileName = font; - tstr.XScale = xscale; - }); + table.Change(textStyleName, ttr => + { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + if (bigFont != "") + { + ttr.BigFontFileName = bigFont; + } + }); + return table[textStyleName]; } - #endregion + return table.Add(textStyleName, ttr => + { + ttr.FileName = smallFont; + ttr.XScale = xScale; + ttr.TextSize = height; + }); + } - #region 注册应用程序表 - #endregion + #endregion - #region 标注样式表 + #region 注册应用程序表 - #endregion + #endregion - #region 用户坐标系表 + #region 标注样式表 - #endregion + #endregion - #region 视图表 + #region 用户坐标系表 - #endregion + #endregion - #region 视口表 + #region 视图表 - #endregion - } + #endregion + + #region 视口表 + + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs index c66b2e2..bceeac0 100644 --- a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs +++ b/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs @@ -1,353 +1,460 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Text; +namespace IFoxCAD.Cad; - -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; - -namespace IFoxCAD.Cad +/// +/// 符号表记录扩展类 +/// +public static class SymbolTableRecordEx { + #region 块表记录 + + #region 克隆实体id /// - /// 符号表记录扩展类 + /// 深度克隆id到块表记录 + /// 0x01 此方法不允许是未添加数据库的图元,因此它是id + /// 0x02 若为未添加数据库图元,则利用entity.Clone();同时不需要考虑动态块属性,可以使用entity.GetTransformedCopy /// - public static class SymbolTableRecordEx + /// + /// 克隆到当前块表记录,相当于原地克隆 + /// 克隆到目标块表记录内,相当于制作新块 + /// + /// 图元id集合,注意所有成员都要在同一个空间中 + /// 克隆后的id词典 + public static IdMapping DeepClone(this BlockTableRecord btr, ObjectIdCollection objIds) { - - - #region 块表记录 - - #region 添加实体 - /// - /// 添加实体对象 - /// - /// 块表记录 - /// 实体 - /// 事务管理器 - /// 对象 id - public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, Transaction tr = null) + if (objIds is null || objIds.Count == 0) + throw new ArgumentNullException(nameof(objIds)); + + var db = objIds[0].Database; + IdMapping mapping = new(); + using (btr.ForWrite()) { - if (entity is null) - throw new ArgumentNullException(nameof(entity), "对象为 null"); + try + { + db.DeepCloneObjects(objIds, btr.ObjectId, mapping, false); - ObjectId id; - tr ??= DBTrans.Top.Transaction; - using (btr.ForWrite()) + // 不在此提取,为了此函数被高频调用 + // 获取克隆键值对(旧块名,新块名) + // foreach (ObjectId item in blockIds) + // result.Add(mapping[item].Value); + } + catch (System.Exception e) { - id = btr.AppendEntity(entity); - tr.AddNewlyCreatedDBObject(entity, true); + LogHelper.FlagOutVsOutput = true; + e.WriteLog("深度克隆出错了"); } - return id; } + return mapping; + } - /// - /// 添加实体集合 - /// - /// 实体类型 - /// 块表记录 - /// 事务 - /// 实体集合 - /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, Transaction tr = null) where T : Entity - { - if (ents.Any(ent => ent is null)) - throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); + /// + /// 克隆图元实体(这个函数有问题,会出现偶尔成功,偶尔失败,拖动过变成匿名块) + /// 若为块则进行设置属性,因此控制动态块属性丢失; + /// + /// 图元 + /// 矩阵 + //public static void EntityTransformedCopy(this Entity ent, Matrix3d matrix) + //{ + // var entNew = ent.GetTransformedCopy(matrix); + // if (ent is BlockReference blockReference) + // entNew.SetPropertiesFrom(blockReference); + //} - tr ??= DBTrans.Top.Transaction; - using (btr.ForWrite()) - { - return ents - .Select( - ent => { - ObjectId id = btr.AppendEntity(ent); - tr.AddNewlyCreatedDBObject(ent, true); - return id; - }) - .ToList(); - } - } - /// - /// 添加多个实体 - /// - /// 块表记录 - /// 实体集合 - /// 对象 id 列表 - public static IEnumerable AddEntity(this BlockTableRecord btr, params Entity[] ents) - { - return btr.AddEntity(ents, null); - } - #endregion + #endregion - #region 添加图元 - /// - /// 在指定绘图空间添加图元 - /// - /// 图元类型 - /// 绘图空间 - /// 图元对象 - /// 图元属性设置委托 - /// 事务管理器 - /// 图元id - private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action action, Transaction trans) where T : Entity - { - trans ??= DBTrans.Top.Transaction; - action?.Invoke(ent); - return btr.AddEntity(ent, trans); - } + #region 添加实体 + /// + /// 添加实体对象 + /// + /// 块表记录 + /// 实体 + /// 事务管理器 + /// 对象 id + public static ObjectId AddEntity(this BlockTableRecord btr, Entity entity, + Transaction? trans = null) + { + //if (entity is null) + // throw new ArgumentNullException(nameof(entity), "对象为 null"); - /// - /// 在指定绘图空间添加直线 - /// - /// 事务管理器 - /// 起点 - /// 终点 - /// 绘图空间 - /// 直线属性设置委托 - /// 直线的id - public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, Action action = default, Transaction trans = default) + ObjectId id; + trans ??= DBTrans.Top.Transaction; + using (btr.ForWrite()) { - var line = new Line(start, end); - return btr.AddEnt(line, action, trans); + id = btr.AppendEntity(entity); + trans.AddNewlyCreatedDBObject(entity, true); } - /// - /// 在指定绘图空间X-Y平面添加圆 - /// - /// 绘图空间 - /// 圆心 - /// 半径 - /// 圆属性设置委托 - /// 事务管理器 - /// 圆的id - public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, Action action = default, Transaction trans = default) + return id; + } + + /// + /// 添加实体集合 + /// + /// 实体类型 + /// 块表记录 + /// 实体集合 + /// 事务 + /// 对象 id 列表 + public static IEnumerable AddEntity(this BlockTableRecord btr, IEnumerable ents, + Transaction? trans = null) where T : Entity + { + //if (ents.Any(ent => ent is null)) + // throw new ArgumentNullException(nameof(ents), "实体集合内存在 null 对象"); + + trans ??= DBTrans.Top.Transaction; + using (btr.ForWrite()) { - var circle = new Circle(center, Vector3d.ZAxis, radius); - return btr.AddEnt(circle, action, trans); + return ents + .Select( + ent => { + ObjectId id = btr.AppendEntity(ent); + trans.AddNewlyCreatedDBObject(ent, true); + return id; + }) + .ToList(); } + } - /// - /// 在指定绘图空间X-Y平面3点画外接圆 - /// - /// 绘图空间 - /// 第一点 - /// 第二点 - /// 第三点 - /// 圆属性设置委托 - /// 事务管理器 - /// 三点有外接圆则返回圆的id,否则返回ObjectId.Null - public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d p1, Point3d p2, - Action action = default, Transaction trans = default) + /// + /// 添加多个实体 + /// + /// 块表记录 + /// 实体集合 + /// 对象 id 列表 + public static IEnumerable AddEntity(this BlockTableRecord btr, params Entity[] ents) + { + return btr.AddEntity(ents, null); + } + #endregion + + #region 添加图元 + /// + /// 在指定绘图空间添加图元 + /// + /// 图元类型 + /// 绘图空间 + /// 图元对象 + /// 图元属性设置委托 + /// 事务管理器 + /// 图元id + private static ObjectId AddEnt(this BlockTableRecord btr, T ent, Action? action, Transaction? trans) where T : Entity + { + //trans ??= DBTrans.Top.Transaction; + action?.Invoke(ent); + return btr.AddEntity(ent, trans); + } + /// + /// 委托式的添加图元 + /// + /// 块表 + /// 返回图元的委托 + /// 事务 + /// 图元id,如果委托返回 null,则为 ObjectId.Null + public static ObjectId AddEnt(this BlockTableRecord btr, Func action, Transaction? transaction) + { + //transaction ??= DBTrans.Top.Transaction; + var ent = action.Invoke(); + if (ent is null) + return ObjectId.Null; + + return btr.AddEntity(ent, transaction); + } + + /// + /// 在指定绘图空间添加直线 + /// + /// 事务管理器 + /// 起点 + /// 终点 + /// 绘图空间 + /// 直线属性设置委托 + /// 直线的id + public static ObjectId AddLine(this BlockTableRecord btr, Point3d start, Point3d end, + Action? action = default, Transaction? trans = default) + { + var line = new Line(start, end); + return btr.AddEnt(line, action, trans); + } + /// + /// 在指定绘图空间X-Y平面添加圆 + /// + /// 绘图空间 + /// 圆心 + /// 半径 + /// 圆属性设置委托 + /// 事务管理器 + /// 圆的id + public static ObjectId AddCircle(this BlockTableRecord btr, Point3d center, double radius, + Action? action = default, Transaction? trans = default) + { + var circle = new Circle(center, Vector3d.ZAxis, radius); + return btr.AddEnt(circle, action, trans); + } + + /// + /// 在指定绘图空间X-Y平面3点画外接圆 + /// + /// 绘图空间 + /// 第一点 + /// 第二点 + /// 第三点 + /// 圆属性设置委托 + /// 事务管理器 + /// 三点有外接圆则返回圆的id,否则返回ObjectId.Null + public static ObjectId AddCircle(this BlockTableRecord btr, Point3d p0, Point3d p1, Point3d p2, + Action? action = default, Transaction? trans = default) + { + var circle = EntityEx.CreateCircle(p0, p1, p2); + //return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + if (circle is null) + throw new ArgumentNullException(nameof(circle), "对象为 null"); + + return btr.AddEnt(circle, action, trans); + } + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 多段线信息 + /// 线宽 + /// 是否闭合 + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List bvws, + double? constantWidth = null, + bool isClosed = true, + Action? action = default, + Transaction? trans = default) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + if (constantWidth is not null) { - Circle circle = EntityEx.CreateCircle(p0, p1, p2); - return circle is not null ? btr.AddEnt(circle, action, trans) : throw new ArgumentNullException(nameof(circle), "对象为 null"); + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, constantWidth.Value, constantWidth.Value); } - /// - /// 在指定的绘图空间添加轻多段线 - /// - /// 绘图空间 - /// 端点表 - /// 凸度表 - /// 端点的起始宽度 - /// 端点的终止宽度 - /// 轻多段线属性设置委托 - /// 事务管理器 - /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List pts, List bulges = default, List startWidths = default, List endWidths = default, Action action = default, Transaction trans = default) + else { - bulges ??= new List(new double[pts.Count]); - startWidths ??= new List(new double[pts.Count]); - endWidths ??= new List(new double[pts.Count]); - Polyline pl = new(); - for (int i = 0; i < pts.Count; i++) - { - pl.AddVertexAt(i, pts[i].Point2d(), bulges[i], startWidths[i], endWidths[i]); - } - return btr.AddEnt(pl, action, trans); + for (int i = 0; i < bvws.Count; i++) + pl.AddVertexAt(i, bvws[i].Vertex, bvws[i].Bulge, bvws[i].StartWidth, bvws[i].EndWidth); } + pl.Closed = isClosed;//闭合 + return btr.AddEnt(pl, action, trans); + } + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 端点表 + /// 凸度表 + /// 端点的起始宽度 + /// 端点的终止宽度 + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List pts, + List? bulges = default, + List? startWidths = default, + List? endWidths = default, + Action? action = default, + Transaction? trans = default) + { + bulges ??= new(new double[pts.Count]); + startWidths ??= new(new double[pts.Count]); + endWidths ??= new(new double[pts.Count]); -#if ac2013 - /// - /// 在指定的绘图空间添加轻多段线 - /// - /// 绘图空间 - /// 端点表,利用元组(Point3d pt, double bulge, double startWidth, double endWidth) - /// 轻多段线属性设置委托 - /// 事务管理器 - /// 轻多段线id - public static ObjectId AddPline(this BlockTableRecord btr, List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, Action action = default, Transaction trans = default) - { + Polyline pl = new(); + pl.SetDatabaseDefaults(); - Polyline pl = new(); - pts.ForEach((i, vertex) => - { - pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); - }); + for (int i = 0; i < pts.Count; i++) + pl.AddVertexAt(i, pts[i].Point2d(), bulges[i], startWidths[i], endWidths[i]); + return btr.AddEnt(pl, action, trans); + } - return btr.AddEnt(pl, action, trans); - } -#endif + /// + /// 在指定的绘图空间添加轻多段线 + /// + /// 绘图空间 + /// 端点表,利用元组(Point3d pt, double bulge, double startWidth, double endWidth) + /// 轻多段线属性设置委托 + /// 事务管理器 + /// 轻多段线id + public static ObjectId AddPline(this BlockTableRecord btr, + List<(Point3d pt, double bulge, double startWidth, double endWidth)> pts, + Action? action = default, + Transaction? trans = default) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pts.ForEach((i, vertex) => { + pl.AddVertexAt(i, vertex.pt.Point2d(), vertex.bulge, vertex.startWidth, vertex.endWidth); + }); - /// - /// 在指定绘图空间X-Y平面3点画圆弧 - /// - /// 绘图空间 - /// 圆弧起点 - /// 圆弧上的点 - /// 圆弧终点 - /// 圆弧属性设置委托 - /// 事务管理器 - /// 圆弧id - public static ObjectId AddArc(this BlockTableRecord btr, Point3d startPoint, Point3d pointOnArc, Point3d endPoint, Action action = default, Transaction trans = default) - { - var arc = EntityEx.CreateArc(startPoint, pointOnArc, endPoint); - return btr.AddEnt(arc, action, trans); - } - #endregion + return btr.AddEnt(pl, action, trans); + } - #region 获取实体/实体id - /// - /// 获取块表记录内的指定类型的实体 - /// - /// 实体类型 - /// 块表记录 - /// 事务 - /// 打开模式 - /// 实体集合 - public static IEnumerable GetEntities(this BlockTableRecord btr, OpenMode mode = OpenMode.ForRead, Transaction trans = null) where T : Entity - { - trans ??= DBTrans.Top.Transaction; - return - btr - .Cast() - .Select(id => trans.GetObject(id, mode)) - .OfType(); - } + /// + /// 在指定绘图空间X-Y平面3点画圆弧 + /// + /// 绘图空间 + /// 圆弧起点 + /// 圆弧上的点 + /// 圆弧终点 + /// 圆弧属性设置委托 + /// 事务管理器 + /// 圆弧id + public static ObjectId AddArc(this BlockTableRecord btr, + Point3d startPoint, Point3d pointOnArc, Point3d endPoint, + Action? action = default, Transaction? trans = default) + { + var arc = EntityEx.CreateArc(startPoint, pointOnArc, endPoint); + return btr.AddEnt(arc, action, trans); + } - /// - /// 按类型获取实体Id,AutoCad2010以上版本支持 - /// - /// 实体类型 - /// 块表记录 - /// 实体Id集合 - public static IEnumerable GetObjectIds(this BlockTableRecord btr) where T : Entity - { - string dxfName = RXClass.GetClass(typeof(T)).DxfName; - return btr.Cast() - .Where(id => id.ObjectClass().DxfName == dxfName); - } + // todo: 所有涉及默认无参构造的实体类型,都需要调用SetDatabaseDefaults(); + #endregion - /// - /// 按类型获取实体Id的分组 - /// - /// 块表记录 - /// 实体Id分组 - public static IEnumerable> GetObjectIds(this BlockTableRecord btr) - { - return - btr - .Cast() - .GroupBy(id => id.ObjectClass().DxfName); - } + #region 获取实体/实体id + /// + /// 获取块表记录内的指定类型的实体 + /// + /// 实体类型 + /// 块表记录 + /// 打开模式 + /// 事务 + /// 实体集合 + public static IEnumerable GetEntities(this BlockTableRecord btr, + OpenMode mode = OpenMode.ForRead, + Transaction? trans = default) where T : Entity + { + trans ??= DBTrans.Top.Transaction; + return + btr + .Cast() + .Select(id => trans.GetObject(id, mode)) + .OfType(); + } - /// - /// 获取绘制顺序表 - /// - /// 块表 - /// 事务 - /// 绘制顺序表 - public static DrawOrderTable GetDrawOrderTable(this BlockTableRecord btr, Transaction tr = null) - { - tr ??= DBTrans.Top.Transaction; - return tr.GetObject(btr.DrawOrderTableId, OpenMode.ForRead) as DrawOrderTable; - } + /// + /// 按类型获取实体Id,AutoCad2010以上版本支持 + /// + /// 实体类型 + /// 块表记录 + /// 实体Id集合 + public static IEnumerable GetObjectIds(this BlockTableRecord btr) where T : Entity + { + string dxfName = RXClass.GetClass(typeof(T)).DxfName; + return btr.Cast() + .Where(id => id.ObjectClass().DxfName == dxfName); + } - #endregion + /// + /// 按类型获取实体Id的分组 + /// + /// 块表记录 + /// 实体Id分组 + public static IEnumerable> GetObjectIds(this BlockTableRecord btr) + { + return + btr + .Cast() + .GroupBy(id => id.ObjectClass().DxfName); + } - #region 插入块参照 + /// + /// 获取绘制顺序表 + /// + /// 块表 + /// 事务 + /// 绘制顺序表 + public static DrawOrderTable? GetDrawOrderTable(this BlockTableRecord btr, + Transaction? trans = default) + { + trans ??= DBTrans.Top.Transaction; + return trans.GetObject(btr.DrawOrderTableId, OpenMode.ForRead) as DrawOrderTable; + } + #endregion - /// - /// 插入块参照 - /// - /// 插入点 - /// 块名 - /// 块插入比例,默认为1 - /// 块插入旋转角(弧度),默认为0 - /// 属性字典{Tag,Value},默认为null - /// 块参照对象id - public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - string blockName, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction trans = null) + #region 插入块参照 + /// + /// 插入块参照 + /// + /// 块表记录 + /// 插入点 + /// 块名 + /// 块插入比例,默认为1 + /// 块插入旋转角(弧度),默认为0 + /// 属性字典{Tag,Value},默认为null + /// 事务 + /// 块参照对象id + public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, + string blockName, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = default, Transaction? trans = null) + { + trans ??= DBTrans.Top.Transaction; + if (!DBTrans.Top.BlockTable.Has(blockName)) { - trans ??= DBTrans.Top.Transaction; - if (!DBTrans.Top.BlockTable.Has(blockName)) - { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{blockName}的块定义。"); - return ObjectId.Null; - } - return blockTableRecord.InsertBlock(position, DBTrans.Top.BlockTable[blockName], scale, rotation, atts, trans); + DBTrans.Top.Editor?.WriteMessage($"\n不存在名字为{blockName}的块定义。"); + return ObjectId.Null; } - /// - /// 插入块参照 - /// - /// 插入点 - /// 块定义id - /// 块插入比例,默认为1 - /// 块插入旋转角(弧度),默认为0 - /// 属性字典{Tag,Value},默认为null - /// 块参照对象id - public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, - ObjectId blockId, - Scale3d scale = default, - double rotation = default, - Dictionary atts = default, Transaction tr = null) + return blockTableRecord.InsertBlock(position, DBTrans.Top.BlockTable[blockName], scale, rotation, atts, trans); + } + /// + /// 插入块参照 + /// + /// 插入点 + /// 块定义id + /// 块插入比例,默认为1 + /// 块插入旋转角(弧度),默认为0 + /// 属性字典{Tag,Value},默认为null + /// 块参照对象id + public static ObjectId InsertBlock(this BlockTableRecord blockTableRecord, Point3d position, + ObjectId blockId, + Scale3d scale = default, + double rotation = default, + Dictionary? atts = default, Transaction? trans = null) + { + trans ??= DBTrans.Top.Transaction; + if (!DBTrans.Top.BlockTable.Has(blockId)) { - tr ??= DBTrans.Top.Transaction; - if (!DBTrans.Top.BlockTable.Has(blockId)) - { - DBTrans.Top.Editor.WriteMessage($"\n不存在名字为{DBTrans.Top.GetObject(blockId).Name}的块定义。"); - return ObjectId.Null; - } - using var blockref = new BlockReference(position, blockId) - { - ScaleFactors = scale, - Rotation = rotation - }; - var objid = blockTableRecord.AddEntity(blockref); - if (atts != default) + DBTrans.Top.Editor?.WriteMessage($"\n不存在块定义。"); + return ObjectId.Null; + } + using var blockref = new BlockReference(position, blockId) + { + ScaleFactors = scale, + Rotation = rotation + }; + var objid = blockTableRecord.AddEntity(blockref); + if (atts != default) + { + var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord)!; + if (btr.HasAttributeDefinitions) { - var btr = DBTrans.Top.GetObject(blockref.BlockTableRecord); - if (btr.HasAttributeDefinitions) + var attdefs = btr.GetEntities(); + foreach (var attdef in attdefs) { - var attdefs = btr - .GetEntities() - .Where(attdef => !(attdef.Constant || attdef.Invisible)); - foreach (var attdef in attdefs) - { - using AttributeReference attref = new(); - attref.SetAttributeFromBlock(attdef, blockref.BlockTransform); - attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); - attref.AdjustAlignment(DBTrans.Top.Database); - if (atts.ContainsKey(attdef.Tag)) - attref.TextString = atts[attdef.Tag]; + using AttributeReference attref = new(); + attref.SetDatabaseDefaults(); + attref.SetAttributeFromBlock(attdef, blockref.BlockTransform); + attref.Position = attdef.Position.TransformBy(blockref.BlockTransform); + attref.AdjustAlignment(DBTrans.Top.Database); + + if (atts.ContainsKey(attdef.Tag)) + attref.TextString = atts[attdef.Tag]; - blockref.AttributeCollection.AppendAttribute(attref); - tr.AddNewlyCreatedDBObject(attref, true); - } + blockref.AttributeCollection.AppendAttribute(attref); + trans.AddNewlyCreatedDBObject(attref, true); } } - return objid; } - - #endregion - #endregion - - - + return objid; } + #endregion + + #endregion } diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs b/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs new file mode 100644 index 0000000..0e875c5 --- /dev/null +++ b/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs @@ -0,0 +1,188 @@ +namespace IFoxCAD.Cad; + +public static class Tools +{ + public static void TestTimes2(int count, string message, Action action) + { + System.Diagnostics.Stopwatch watch = new(); + watch.Start(); //开始监视代码运行时间 + for (int i = 0; i < count; i++) + action.Invoke();//需要测试的代码 + watch.Stop(); //停止监视 + TimeSpan timespan = watch.Elapsed; //获取当前实例测量得出的总时间 + double time = timespan.TotalMilliseconds; + string name = "毫秒"; + if (timespan.TotalMilliseconds > 1000) + { + time = timespan.TotalSeconds; + name = "秒"; + } + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({name})"); //总毫秒数 + } + + /// + /// 纳秒计时器 + /// + public static void TestTimes(int count, string message, Action action, + Timer.TimeEnum timeEnum = Timer.TimeEnum.Millisecond) + { + double time = Timer.RunTime(() => { + for (int i = 0; i < count; i++) + action(); + }, timeEnum); + + string timeNameZn = ""; + switch (timeEnum) + { + case Timer.TimeEnum.Second: + timeNameZn = " 秒"; + break; + case Timer.TimeEnum.Millisecond: + timeNameZn = " 毫秒"; + break; + case Timer.TimeEnum.Microsecond: + timeNameZn = " 微秒"; + break; + case Timer.TimeEnum.Nanosecond: + timeNameZn = " 纳秒"; + break; + } + + Env.Print($"{message} 代码执行 {count} 次的时间:{time} ({timeNameZn})"); + } +} + +/* +//测试例子,同时验证两个计时器 +var stopwatch = new Stopwatch(); +Timer.RunTime(() => { + + stopwatch.Start(); + for (int i = 0; i < 10000000; i++) + i++; + stopwatch.Stop(); + +}, Timer.TimeEnum.Millisecond, "运行:"); +Console.WriteLine("运行毫秒:" + stopwatch.ElapsedMilliseconds); + */ + +public class Timer +{ + [Flags] + public enum TimeEnum + { + /// + /// 秒 + /// + Second, + /// + /// 毫秒 + /// + Millisecond, + /// + /// 微秒 + /// + Microsecond, + /// + /// 纳秒 + /// + Nanosecond, + } + + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceCounter(out long lpPerformanceCount); + + /// + /// 这个函数会检索性能计数器的频率. + /// 性能计数器的频率在系统启动时是固定的,并且在所有处理器上都是一致的 + /// 因此,只需在应用初始化时查询频率,即可缓存结果 + /// 在运行 Windows XP 或更高版本的系统上,该函数将始终成功,因此永远不会返回零 + /// + /// + /// + [DllImport("Kernel32.dll")] + static extern bool QueryPerformanceFrequency(out long lpFrequency); + + long _startTime, _stopTime; + long _freq; + + public Timer() + { + _startTime = 0; + _stopTime = 0; + + if (!QueryPerformanceFrequency(out _freq)) + throw new Win32Exception("不支持高性能计数器"); + } + + /// + /// 开始计时器 + /// + public void Start() + { + System.Threading.Thread.Sleep(0); + QueryPerformanceCounter(out _startTime); + } + + /// + /// 停止计时器 + /// + public void Stop() + { + QueryPerformanceCounter(out _stopTime); + _Second = (double)(_stopTime - _startTime) / _freq; + } + double _Second = 0; + + // 返回计时器经过时间 + public double Second => _Second; + public double Millisecond => _Second * 1000.0; + public double Microsecond => _Second * 1000000.0; + public double Nanosecond => _Second * 1000000000.0; + + public static double RunTime(Action action, TimeEnum timeEnum = TimeEnum.Millisecond, string? msg = null) + { + var nanoSecond = new Timer(); + nanoSecond.Start(); + action(); + nanoSecond.Stop(); + + double time = 0; + switch (timeEnum) + { + case TimeEnum.Second: + time = nanoSecond.Second; + break; + case TimeEnum.Millisecond: + time = nanoSecond.Millisecond; + break; + case TimeEnum.Microsecond: + time = nanoSecond.Microsecond; + break; + case TimeEnum.Nanosecond: + time = nanoSecond.Nanosecond; + break; + } + if (msg != null) + { + string timeNameZn = ""; + switch (timeEnum) + { + case TimeEnum.Second: + timeNameZn = " 秒"; + break; + case TimeEnum.Millisecond: + timeNameZn = " 毫秒"; + break; + case TimeEnum.Microsecond: + timeNameZn = " 微秒"; + break; + case TimeEnum.Nanosecond: + timeNameZn = " 纳秒"; + break; + } + Env.Print(msg + " " + time + timeNameZn); + } + return time; + } +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" new file mode 100644 index 0000000..7868bee --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" @@ -0,0 +1,358 @@ +namespace IFoxCAD.Cad; +using PointV = Point2d; + +/// +/// 填充边界转换器 +/// +public class HatchConverter +{ + #region 辅助类 + /// + /// 生成圆形数据 + /// + class CircleData + { + public PointV Center; + public double Radius; + + /// + /// 生成圆形数据 + /// + /// 对称点1 + /// 对称点2 + public CircleData(PointV symmetryAxisPoint1, PointV symmetryAxisPoint2) + { + Center = symmetryAxisPoint1.GetCenter(symmetryAxisPoint2); + Radius = symmetryAxisPoint1.GetDistanceTo(symmetryAxisPoint2) * 0.5; + } + } + + /// + /// 填充转换器的数据 + /// + class HatchConverterData + { + public List PolyLineData; + public List CircleData; + public List SplineData; + + /// + /// 填充转换器的数据 + /// + public HatchConverterData() + { + PolyLineData = new(); + CircleData = new(); + SplineData = new(); + } + } + #endregion + + #region 成员 + /// + /// 外部只能调用id,否则跨事务造成错误 + /// + public ObjectId OldHatchId + { + get + { + if (_oldHatch is null) + return ObjectId.Null; + return _oldHatch.ObjectId; + } + } + readonly Hatch? _oldHatch; + + readonly List _hcDatas; + /// + /// 生成的填充边界id + /// + public List BoundaryIds; + #endregion + + #region 构造 + /// + /// 填充边界转换器 + /// + HatchConverter() + { + _hcDatas = new(); + BoundaryIds = new(); + } + + /// + /// 填充边界转换器 + /// + /// 需要转化的Hatch对象 + public HatchConverter(Hatch hatch) : this() + { + _oldHatch = hatch; + + //不能在提取信息的时候进行新建cad图元, + //否则cad将会提示遗忘释放 + hatch.ForEach(loop => { + var hcData = new HatchConverterData(); + + bool isCurve2d = true; + if (loop.IsPolyline) + { + //边界是多段线 + HatchLoopIsPolyline(loop, hcData); + isCurve2d = false; + } + else + { + if (loop.Curves.Count == 2)//1是不可能的,大于2的是曲线 + { + //边界是曲线,过滤可能是圆形的情况 + var cir = TwoArcFormOneCircle(loop); + if (cir is not null) + { + hcData.CircleData.Add(cir); + isCurve2d = false; + } + } + } + + //边界是曲线 + if (isCurve2d) + HatchLoopIsCurve2d(loop, hcData); + + _hcDatas.Add(hcData); + }); + } + #endregion + + #region 方法 + /// + /// 多段线处理 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsPolyline(HatchLoop loop, HatchConverterData hcData) + { + if (loop is null) + throw new ArgumentNullException(nameof(loop)); + + if (hcData is null) + throw new ArgumentNullException(nameof(hcData)); + + //判断为圆形: + //上下两个圆弧,然后填充,就会生成此种填充 + //顶点数是3,凸度是半圆,两个半圆就是一个圆形 + if (loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == 1 && loop.Polyline[1].Bulge == 1 || + loop.Polyline.Count == 3 && loop.Polyline[0].Bulge == -1 && loop.Polyline[1].Bulge == -1) + { + hcData.CircleData.Add(new CircleData(loop.Polyline[0].Vertex, loop.Polyline[1].Vertex)); + } + else + { + //遍历多段线信息 + var bvc = loop.Polyline; + for (int i = 0; i < bvc.Count; i++) + hcData.PolyLineData.Add(new BulgeVertexWidth(bvc[i])); + } + } + + /// + /// 两个圆弧组成圆形 + /// + /// + /// + static CircleData? TwoArcFormOneCircle(HatchLoop loop) + { + if (loop is null) + throw new ArgumentNullException(nameof(loop)); + + if (loop.Curves.Count != 2) + throw new ArgumentException( + "边界非多段线,而且点数!=2,点数为:" + nameof(loop.Curves.Count) + ";两个矩形交集的时候会出现此情况."); + + CircleData? circular = null; + + //判断为圆形: + //用一条(不是两条)多段线画出两条圆弧为正圆,就会生成此种填充 + //边界为曲线,数量为2,可能是两个半圆曲线,如果是,就加入圆形数据中 + + //第一段 + var getCurves1Pts = loop.Curves[0].GetSamplePoints(3); //曲线取样点分两份(3点) + var mid1Pt = getCurves1Pts[1]; //腰点 + double bulge1 = loop.Curves[0].StartPoint.GetArcBulge(mid1Pt, loop.Curves[0].EndPoint); + + //第二段 + var getCurves2Pts = loop.Curves[1].GetSamplePoints(3); + var mid2Pt = getCurves2Pts[1]; + double bulge2 = loop.Curves[1].StartPoint.GetArcBulge(mid2Pt, loop.Curves[1].EndPoint); + + //第一段上弧&&第二段反弧 || 第一段反弧&&第二段上弧 + if (bulge1 == -1 && bulge2 == -1 || bulge1 == 1 && bulge2 == 1) + circular = new CircleData(loop.Curves[0].StartPoint, loop.Curves[1].StartPoint); //两个起点就是对称点 + + return circular; + } + + /// + /// 处理边界曲线 + /// + /// 填充边界 + /// 收集图元信息 + static void HatchLoopIsCurve2d(HatchLoop loop, HatchConverterData hcData) + { + //取每一段曲线,曲线可能是直线来的,但是圆弧会按照顶点来分段 + int curveIsClosed = 0; + + //遍历边界的多个子段 + foreach (Curve2d curve in loop.Curves) + { + //计数用于实现闭合 + curveIsClosed++; + if (curve is NurbCurve2d spl) + { + //判断为样条曲线: + hcData.SplineData.Add(spl); + continue; + } + + var pts = curve.GetSamplePoints(3); + var midPt = pts[1]; + if (curve.StartPoint.IsEqualTo(curve.EndPoint, new Tolerance(1e-6, 1e-6)))//首尾相同,就是圆形 + { + //判断为圆形: + //获取起点,然后采样三点,中间就是对称点(直径点) + hcData.CircleData.Add(new CircleData(curve.StartPoint, midPt)); + continue; + } + + //判断为多段线,圆弧: + double bulge = curve.StartPoint.GetArcBulge(midPt, curve.EndPoint); + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.StartPoint, bulge)); + + //末尾点,不闭合的情况下就要获取这个 + if (curveIsClosed == loop.Curves.Count) + hcData.PolyLineData.Add(new BulgeVertexWidth(curve.EndPoint, 0)); + } + } + + /// + /// 创建边界图元 + /// + /// 返回图元 + public void CreateBoundaryEntitys(List outEnts) + { + for (int i = 0; i < _hcDatas.Count; i++) + { + var data = _hcDatas[i]; + + //生成边界:多段线 + if (data.PolyLineData.Count > 0) + { + Polyline pl = new(); + pl.SetDatabaseDefaults(); + for (int j = 0; j < data.PolyLineData.Count; j++) + { + pl.AddVertexAt(j, + data.PolyLineData[j].Vertex, + data.PolyLineData[j].Bulge, + data.PolyLineData[j].StartWidth, + data.PolyLineData[j].EndWidth); + } + outEnts.Add(pl); + } + + //生成边界:圆 + data.CircleData.ForEach(item => { + outEnts.Add(new Circle(item.Center.Point3d(), Vector3d.ZAxis, item.Radius)); + }); + + //生成边界:样条曲线 + data.SplineData.ForEach(item => { + outEnts.Add(item.ToCurve()); + }); + } + + if (_oldHatch is not null) + { + outEnts.ForEach(ent => { + ent.Color = _oldHatch.Color; + ent.Layer = _oldHatch.Layer; + }); + } + } + + + /// + /// 创建边界图元和新填充到当前空间 + /// + /// 事务 + /// 数据库 + /// 边界关联 + /// 是否创建填充,false则只创建边界 + /// 新填充id,边界在获取 + public ObjectId CreateBoundarysAndHatchToMsPs(BlockTableRecord btrOfAddEntitySpace, + bool boundaryAssociative = true, + bool createHatchFlag = true, + Transaction? trans = null) + { + //重设边界之前肯定是有边界才可以 + if (BoundaryIds.Count == 0) + { + List boundaryEntitys = new(); + CreateBoundaryEntitys(boundaryEntitys); + boundaryEntitys.ForEach(ent => { + BoundaryIds.Add(btrOfAddEntitySpace.AddEntity(ent)); + }); + } + + if (!createHatchFlag) + return ObjectId.Null; + /* + * 此处为什么要克隆填充,而不是新建填充? + * 因为填充如果是新建的,那么将会丢失基点,概念如下: + * 两个一样的填充,平移其中一个,那么再提取他们的基点会是一样的! + * 所以生成时候就不等同于画面相同. + * 也因为我不知道什么新建方式可以新建一模一样的填充,因此使用了克隆 + * 那么它的平移后的基点在哪里呢? + */ + + var newHatchId = btrOfAddEntitySpace.DeepClone(new ObjectIdCollection(new ObjectId[] { OldHatchId })).GetValues()[0]; + trans ??= DBTrans.Top.Transaction; + var hatchEnt = trans.GetObject(newHatchId, OpenMode.ForWrite) as Hatch; + if (hatchEnt != null) + { + ResetBoundary(hatchEnt, boundaryAssociative); + hatchEnt.DowngradeOpen(); + } + return newHatchId; + } + + + /// + /// 重设边界 + /// + /// + /// 边界关联 + void ResetBoundary(Hatch hatch, + bool boundaryAssociative = true) + { + //删除原有边界 + while (hatch.NumberOfLoops != 0) + hatch.RemoveLoopAt(0); + + hatch.Associative = boundaryAssociative; + + var obIds = new ObjectIdCollection(); + for (int i = 0; i < BoundaryIds.Count; i++) + { + obIds.Clear(); + obIds.Add(BoundaryIds[i]); + //要先添加最外面的边界 + if (i == 0) + hatch.AppendLoop(HatchLoopTypes.Outermost, obIds); + else + hatch.AppendLoop(HatchLoopTypes.Default, obIds); + } + //计算填充并显示 + hatch.EvaluateHatch(true); + } + #endregion +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" new file mode 100644 index 0000000..8bfd043 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" @@ -0,0 +1,15 @@ +namespace IFoxCAD.Cad; + +public static class HatchEx +{ + /// + /// 遍历填充每条边 + /// + /// + /// + public static void ForEach(this Hatch hatch, Action action) + { + for (int i = 0; i < hatch.NumberOfLoops; i++) + action.Invoke(hatch.GetLoopAt(i)); + } +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" new file mode 100644 index 0000000..1226479 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" @@ -0,0 +1,354 @@ +namespace IFoxCAD.Cad; + +/* + * ӵĵһ߽߽,ڶͼı߽硣 + * Ҫⲿ߽,ʹӻΪ HatchLoopTypes.Outermost AppendLoop , + * һ߽类,ͿԼı߽硣 + * ڲ߽ʹô HatchLoopTypes.Default AppendLoop + * + * ߽ʱ,ӵ(߽,߽,߽,߽ͨ....) + * ߽ʱ,ӵ(߽,߽ͨ.....߽,߽ͨ....) + */ + +/// +/// ͼ +/// +public class HatchInfo +{ + #region Ա + /// + /// ߽id(ŵһ) + /// + readonly List _boundaryIds; + /// + /// ͼԪ + /// + readonly Hatch _hatch; + /// + /// ߽(˴ֱ=>Ա,Ϊ뷴Ӧ) + /// + readonly bool _boundaryAssociative; + /// + /// :û(̶)//ݶļ + /// + string? _hatchName; + /// + /// ģʽ(Ԥ/û/Զ) + /// + HatchPatternType _patternTypeHatch; + /// + /// ģʽ + /// + GradientPatternType _patternTypeGradient; + /// + /// / + /// + double Scale => _hatch.PatternScale; + /// + /// Ƕ + /// + double Angle => _hatch.PatternAngle; + #endregion + + #region + HatchInfo() + { + _hatch = new Hatch(); + _hatch.SetDatabaseDefaults(); + _boundaryIds = new(); + } + + /// + /// ͼ + /// + /// ߽ + /// ԭ + /// + /// Ƕ + public HatchInfo(bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) : this() + { + if (hatchScale <= 0) + throw new ArgumentException("Сڵ0"); + + _hatch.PatternScale = hatchScale;// + _hatch.PatternAngle = hatchAngle;//Ƕ + _boundaryAssociative = boundaryAssociative; + + hatchOrigin ??= Point2d.Origin; + _hatch.Origin = hatchOrigin.Value; //ԭ + } + + /// + /// ͼ + /// + /// ߽ + /// ߽ + /// ԭ + /// + /// Ƕ + public HatchInfo(IEnumerable boundaryIds, + bool boundaryAssociative = true, + Point2d? hatchOrigin = null, + double hatchScale = 1, + double hatchAngle = 0) + : this(boundaryAssociative, hatchOrigin, hatchScale, hatchAngle) + { + _boundaryIds.AddRange(boundaryIds); + } + + #endregion + + #region + /// + /// ģʽ1:Ԥ + /// + public HatchInfo Mode1PreDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.PreDefined; + return this; + } + + /// + /// ģʽ2:û + /// + /// Ƿ˫ + public HatchInfo Mode2UserDefined(bool patternDouble = true) + { + _hatchName = "_USER"; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.UserDefined; + + _hatch.PatternDouble = patternDouble; //Ƿ˫򣨱д SetHatchPattern ֮ǰ + _hatch.PatternSpace = Scale; //ࣨд SetHatchPattern ֮ǰ + return this; + } + + /// + /// ģʽ3:Զ + /// + /// + public HatchInfo Mode3UserDefined(string name) + { + _hatchName = name; + _hatch.HatchObjectType = HatchObjectType.HatchObject; //(/) + _patternTypeHatch = HatchPatternType.CustomDefined; + return this; + } + + /// + /// ģʽ4: + /// + /// + /// ɫʼɫ + /// ɫɫ + /// ƶ + /// ɫֵ + /// ɫ˫ɫ + public HatchInfo Mode4Gradient(GradientName name, Color colorStart, Color colorEnd, + float gradientShift = 0, + float shadeTintValue = 0, + bool gradientOneColorMode = false) + { + //entgetֱȻ"SOLID",Ϊ"","" + _hatchName = name.ToString(); + _hatch.HatchObjectType = HatchObjectType.GradientObject; //(/) + _patternTypeGradient = GradientPatternType.PreDefinedGradient;//ģʽ4: + //_patternTypeGradient = GradientPatternType.UserDefinedGradient;//ģʽ5:..ģʽɶ + + //ýɫʼͽɫ + var gColor1 = new GradientColor(colorStart, 0); + var gColor2 = new GradientColor(colorEnd, 1); + _hatch.SetGradientColors(new GradientColor[] { gColor1, gColor2 }); + + _hatch.GradientShift = gradientShift; //ݶλ + _hatch.ShadeTintValue = shadeTintValue; //Ӱɫֵ + _hatch.GradientOneColorMode = gradientOneColorMode;//䵥ɫ/˫ɫ + _hatch.GradientAngle = Angle; //Ƕ + + return this; + } + + /// + /// + /// + /// ˿ռ + public ObjectId Build(BlockTableRecord btrOfAddEntitySpace) + { + //ݿ + var hatchId = btrOfAddEntitySpace.AddEntity(_hatch); + + //ģʽ:/ + if (_hatch.HatchObjectType == HatchObjectType.GradientObject) + _hatch.SetGradient(_patternTypeGradient, _hatchName); + else + _hatch.SetHatchPattern(_patternTypeHatch, _hatchName); + + //߽,ݿռھͻ + //Ϊ true 뷴Ӧ,˱Ƚ(ά뽫ʮɺ),. + _hatch.Associative = _boundaryAssociative; + + // AppendLoop ؼ,Ͳ + if (_boundaryIds.Count > 0) + AppendLoop(_boundaryIds, HatchLoopTypes.Default); + + //䲢ʾ(߽,쳣) + _hatch.EvaluateHatch(true); + + return hatchId; + } + + /// + /// ִͼԪ޸ + /// + /// ӳʵ + public HatchInfo Action(Action action) + { + action(_hatch); + return this; + } + + /// + /// ձ߽缯 + /// + public HatchInfo ClearBoundary() + { + _boundaryIds.Clear(); + return this; + } + + /// + /// ɾ߽ͼԪ + /// + public HatchInfo EraseBoundary() + { + for (int i = 0; i < _boundaryIds.Count; i++) + _boundaryIds[i].Erase(); + return this; + } + + /// + /// ߽ + /// + /// ߽id + /// 뷽ʽ + void AppendLoop(IEnumerable boundaryIds, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + var obIds = new ObjectIdCollection(); + //߽DZպϵ,Ѿݿ + //պϻ. + foreach (var border in boundaryIds) + { + obIds.Clear(); + obIds.Add(border); + _hatch.AppendLoop(hatchLoopTypes, obIds); + } + obIds.Dispose(); + } + + /// + /// ߽(¸߰汾亯) + /// + /// 㼯 + /// ͹ȼ + /// ˿ռ + /// 뷽ʽ + /// + public HatchInfo AppendLoop(Point2dCollection pts, + DoubleCollection bluges, + BlockTableRecord btrOfAddEntitySpace, + HatchLoopTypes hatchLoopTypes = HatchLoopTypes.Default) + { + if (pts == null) + throw new ArgumentNullException(nameof(pts)); + + var ptsEnd2End = pts.End2End(); +#if NET35 + _boundaryIds.Add(CreateAddBoundary(ptsEnd2End, bluges, btrOfAddEntitySpace)); +#else + //2011API,ԲͼԪ¼߽, + //ͨĻ,߽ _boundaryIds ǿյ,ô Build() ʱҪ˿յ + _hatch.AppendLoop(hatchLoopTypes, ptsEnd2End, bluges); +#endif + return this; + } + +#if NET35 + /// + /// ͨ㼯͹ɱ߽Ķ + /// + /// 㼯 + /// ͹ȼ + /// ˿ռ + /// id + static ObjectId CreateAddBoundary(Point2dCollection? pts, + DoubleCollection? bluges, + BlockTableRecord btrOfAddEntitySpace) + { + if (pts is null) + throw new ArgumentException(null, nameof(pts)); + if (bluges is null) + throw new ArgumentException(null, nameof(bluges)); + + var bvws = new List(); + + var itor1 = pts.GetEnumerator(); + var itor2 = bluges.GetEnumerator(); + while (itor1.MoveNext() && itor2.MoveNext()) + bvws.Add(new BulgeVertexWidth(itor1.Current, itor2.Current)); + + return btrOfAddEntitySpace.AddPline(bvws); + } +#endif + #endregion + + #region ö + /// + /// ɫͼ + /// + public enum GradientName + { + /// + /// ״ + /// + Linear, + /// + /// Բ״ + /// + Cylinder, + /// + /// Բ״ + /// + Invcylinder, + /// + /// ״ + /// + Spherical, + /// + /// ״ + /// + Invspherical, + /// + /// ״ + /// + Hemisperical, + /// + /// ״ + /// + InvHemisperical, + /// + /// ״ + /// + Curved, + /// + /// ״ + /// + Incurved + } + #endregion +} \ No newline at end of file diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" new file mode 100644 index 0000000..989721c --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" @@ -0,0 +1,95 @@ +namespace IFoxCAD.Cad; + +public static class AttachmentPointHelper +{ + static readonly Dictionary _alignment = new() + { + { "左上", AttachmentPoint.TopLeft }, + { "中上", AttachmentPoint.TopCenter },//单行的对齐 + { "右上", AttachmentPoint.TopRight }, + + { "左中", AttachmentPoint.MiddleLeft }, + { "正中", AttachmentPoint.MiddleCenter },//多行的正中 + { "右中", AttachmentPoint.MiddleRight }, + + { "左对齐", AttachmentPoint.BaseLeft },//※优先(放在前面优先获取) + { "左", AttachmentPoint.BaseLeft }, + + { "中间", AttachmentPoint.BaseMid }, + + { "右对齐", AttachmentPoint.BaseRight },//※优先(放在前面优先获取) + { "右", AttachmentPoint.BaseRight }, + + { "左下", AttachmentPoint.BottomLeft }, + { "中下", AttachmentPoint.BottomCenter }, + { "右下", AttachmentPoint.BottomRight }, + + { "对齐", AttachmentPoint.BaseAlign },//※优先(放在前面优先获取) + { "调整", AttachmentPoint.BaseAlign }, + + { "居中", AttachmentPoint.BaseCenter },//单行的中 + { "铺满", AttachmentPoint.BaseFit }, + }; + + /// + /// 输入文字获得对齐方式 + /// + /// + /// + public static AttachmentPoint Get(string key) + { + return _alignment[key]; + } + + /// + /// 输入对齐方式获得文字 + /// + /// + /// + public static string Get(AttachmentPoint value) + { + return _alignment.FirstOrDefault(q => q.Value == value).Key; + } +} + +#if false +//反射描述 +//这些东西cad没有用到啊...所以不纳入了 +public enum AttachmentPoint2 +{ + [Description("下对齐")] + BottomAlign = 14, + [Description("中对齐")] + MiddleAlign = 15,//0xF + [Description("上对齐")] + TopAlign = 16,//0x10 + [Description("下铺满")] + BottomFit = 18, + [Description("中铺满")] + MiddleFit = 19, + [Description("上铺满")] + TopFit = 20, + [Description("下居中")] + BottomMid = 22, + [Description("中居中")] + MiddleMid = 23, + [Description("下居中")] + TopMid = 24, +} + +public static Dictionary GetEnumDic(Type enumType) +{ + Dictionary dic = new(); + var fieldinfos = enumType.GetFields(); + for (int i = 0; i < fieldinfos.Length; i++) + { + var field = fieldinfos[i]; + if (field.FieldType.IsEnum) + { + var objs = field.GetCustomAttributes(typeof(DescriptionAttribute), false); + dic.Add(field.Name, ((DescriptionAttribute)objs[0]).Description); + } + } + return dic; +} +#endif diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" new file mode 100644 index 0000000..5b37a31 --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" @@ -0,0 +1,62 @@ +namespace IFoxCAD.Cad; + +public static partial class EntityAdd +{ + /// + /// 创建单行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// 对齐点,因样式 可能无效 + /// + public static Entity AddDBTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft, + Point3d? justifyPoint = null) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + justifyPoint, + textStyleId, + textHigh, + db); + return TextInfo.AddDBTextToEntity(); + } + + /// + /// 新建多行文字 + /// + /// 数据库 + /// 内容 + /// 插入点 + /// 字体高度 + /// 文字样式 + /// 对齐方式 + /// + public static Entity AddMTextToEntity(this Database db, + string textContents, + Point3d position, + double textHigh = 2.5, + ObjectId? textStyleId = null, + AttachmentPoint justify = AttachmentPoint.BaseLeft) + { + var TextInfo = new TextInfo( + textContents, + position, + justify, + null, + textStyleId, + textHigh, + db); + return TextInfo.AddMTextToEntity(); + } +} diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" new file mode 100644 index 0000000..573963b --- /dev/null +++ "b/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" @@ -0,0 +1,178 @@ +namespace IFoxCAD.Cad; + +/// +/// 文字信息类 +/// +public class TextInfo +{ + readonly Database? Database; + readonly string? Contents; + readonly Point3d Position; + + public string TextJustifyCn => AttachmentPointHelper.Get(TextJustify); + readonly AttachmentPoint TextJustify; + readonly Point3d? AlignmentPoint; + + readonly double TextHeight; + readonly ObjectId? TextStyleId; + + /// + /// 文字信息类 + /// + /// 内容 + /// 基点 + /// 对齐方式 + /// 对齐点(对齐方式是左,此参数无效,为null不为左就报错) + /// 文字样式id + /// 文字高度 + /// 数据库 + public TextInfo(string? contents, + Point3d position, + AttachmentPoint justify, + Point3d? justifyPoint = null, + ObjectId? textStyleId = null, + double textHeight = 2.5, + Database? database = null) + { + Contents = contents; + Position = position; + TextJustify = justify; + + if (justifyPoint is null && TextJustify != AttachmentPoint.BaseLeft) + throw new ArgumentNullException(nameof(justifyPoint)); + + AlignmentPoint = justifyPoint; + TextHeight = textHeight; + TextStyleId = textStyleId; + Database = database; + } + + /// + /// 创建单行文字 + /// + public DBText AddDBTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var acText = new DBText(); + acText.SetDatabaseDefaults(); + + if (Database is not null) + acText.SetDatabaseDefaults(Database);//我的默认值是填满的,所以可以不需要 + + if (TextStyleId is not null) + acText.SetTextStyleId(TextStyleId.Value); + + acText.Height = TextHeight; //高度 + acText.TextString = Contents; //内容 + acText.Position = Position; //插入点(一定要先设置) + acText.Justify = TextJustify; //使他们对齐 + //acText.HorizontalMode + + if (AlignmentPoint is not null) + acText.AlignmentPoint = AlignmentPoint.Value; + else if (acText.Justify != AttachmentPoint.BaseLeft) + acText.AlignmentPoint = Position; + + if (Database is not null) + acText.AdjustAlignment(Database); + return acText; + } + + /// + /// 创建多行文字 + /// + /// + public MText AddMTextToEntity() + { + if (string.IsNullOrEmpty(Contents)) + throw new ArgumentNullException(nameof(Contents) + "创建文字无内容"); + + var mText = new MText(); + mText.SetDatabaseDefaults(); + + if (Database is not null) + mText.SetDatabaseDefaults(Database); + + if (TextStyleId is not null) + mText.SetTextStyleId(TextStyleId.Value); + + mText.TextHeight = TextHeight; //高度 + mText.Contents = Contents; //内容 + mText.Location = Position; //插入点(一定要先设置) + + //mText.SetAttachmentMovingLocation(TextJustify); + mText.Attachment = TextJustify;//使他们对齐 + + return mText; + } +} + +//反射设定对象的文字样式id +public static partial class TextInfoHelper +{ + /// + /// 设置文字样式id + /// + /// 单行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this DBText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + /// + /// 设置文字样式id + /// + /// 多行文字 + /// 文字样式表记录id + public static void SetTextStyleId(this MText acText, ObjectId ltrObjectId) + { + SetEntityTxtStyleId(acText, ltrObjectId); + } + + static void SetEntityTxtStyleId(Entity acText, ObjectId ltrObjectId) + { + GetTextStyleIdType(acText)?.SetValue(acText, ltrObjectId, null); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this DBText acText) + { + return GetEntityTxtStyleId(acText); + } + + /// + /// 获取文字样式id + /// + public static ObjectId GetTextStyleId(this MText acText) + { + return GetEntityTxtStyleId(acText); + } + + static ObjectId GetEntityTxtStyleId(Entity acText) + { + var result = ObjectId.Null; + var id = GetTextStyleIdType(acText)?.GetValue(acText, null); + if (id != null) + result = (ObjectId)id; + return result; + } + + static PropertyInfo? _textStyleId = null; + static PropertyInfo GetTextStyleIdType(Entity acText) + { + if (_textStyleId == null) + { + var entType = acText.GetType(); + var prs = entType.GetProperties(); + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyle");//反射获取属性 + if (_textStyleId == null) + _textStyleId = prs.FirstOrDefault(a => a.Name == "TextStyleId");//反射获取属性 + } + return _textStyleId; + } +} diff --git a/src/IFoxCAD.Cad/GlobalUsings.cs b/src/IFoxCAD.Cad/GlobalUsings.cs new file mode 100644 index 0000000..a125299 --- /dev/null +++ b/src/IFoxCAD.Cad/GlobalUsings.cs @@ -0,0 +1,41 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.InteropServices; + +global using Exception = System.Exception; + +/// autocad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; + +global using Autodesk.AutoCAD.DatabaseServices.Filters; +global using Autodesk.AutoCAD; + +//jig此命名空间容易引起Polyline等等重义,因此不放入全局空间 +//using Autodesk.AutoCAD.GraphicsInterface; +global using WorldDraw = Autodesk.AutoCAD.GraphicsInterface.WorldDraw; +global using Manager = Autodesk.AutoCAD.GraphicsSystem.Manager; + +global using Group = Autodesk.AutoCAD.DatabaseServices.Group; +global using Viewport = Autodesk.AutoCAD.DatabaseServices.Viewport; + +global using System.Collections.Specialized; +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// ifoxcad.basal 引用 +global using IFoxCAD.Basal; \ No newline at end of file diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index d742bef..ac9bf4e 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -1,66 +1,90 @@ - - - - preview - enable + - net35;net40 - true - 0.1.3 - InspireFunction - xsfhlzh;vicwjb - 基于.NET的Cad二次开发类库 - InspireFunction - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - git - IFoxCAD;CAD;AutoCad;C#;NET - Optimize and add multiple functions. - true - true - true - LICENSE - true - + + preview + enable - - - - - - runtime - - - + net35;net40;net45 + true + true + true + 0.3.6.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + 增加四叉树测试. + true + true + true + LICENSE + true + x64 + - - DEBUG - - - $(Configuration);ac2009 - - - $(Configuration);ac2013 - + + + + - - - True - - - + + + + - - - + + + + - - - + + DEBUG + + + $(Configuration);ac2008;ac2009 + + + $(Configuration);ac2013 + + + $(Configuration);ac2015 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + - - - + + + True + + + + + + + + diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data deleted file mode 100644 index 5f28270..0000000 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs index 0aa2f69..009d13e 100644 --- a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs +++ b/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs @@ -1,72 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Runtime; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// lisp点对表的数据封装类 +/// +public class LispDottedPair : LispList { + #region 构造函数 /// - /// lisp 点对表的数据封装类 + /// 默认无参构造函数 /// - public class LispDottedPair : LispList + public LispDottedPair() { - #region 构造函数 - /// - /// 默认无参构造函数 - /// - public LispDottedPair() - { - } + } - /// - /// 构造函数 - /// - /// TypedValue 迭代器 - public LispDottedPair(IEnumerable values) : base(values) - { - } - /// - /// 构造函数 - /// - /// 点对表左数 - /// 点对表右数 - public LispDottedPair(TypedValue left, TypedValue right) - { - Add(left); - Add(right); - } - #endregion + /// + /// 构造函数 + /// + /// TypedValue 迭代器 + public LispDottedPair(IEnumerable values) : base(values) + { + } + /// + /// 构造函数 + /// + /// 点对表左数 + /// 点对表右数 + public LispDottedPair(TypedValue left, TypedValue right) + { + Add(left); + Add(right); + } + #endregion - /// - /// 点对表的值 - /// - public override List Value + #region 重写 + /// + /// 点对表的值 + /// + public override List Value + { + get { - get - { - var value = new List + var value = new List { new TypedValue((int)LispDataType.ListBegin,-1), new TypedValue((int)LispDataType.DottedPair,-1) }; - value.InsertRange(1, this); - return value; - } + value.InsertRange(1, this); + return value; } + } + #endregion - #region 转换器 + #region 转换器 - /// - /// LispDottedPair 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](LispDottedPair values) => values.Value.ToArray(); - /// - /// LispDottedPair 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(LispDottedPair values) => new(values.Value.ToArray()); + /// + /// LispDottedPair 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](LispDottedPair values) => values.Value.ToArray(); + /// + /// LispDottedPair 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(LispDottedPair values) => new(values.Value.ToArray()); - #endregion - } + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/LispList.cs b/src/IFoxCAD.Cad/ResultData/LispList.cs index 4742d38..8c72669 100644 --- a/src/IFoxCAD.Cad/ResultData/LispList.cs +++ b/src/IFoxCAD.Cad/ResultData/LispList.cs @@ -1,202 +1,195 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// lisp数据封装类 +/// +public class LispList : TypedValueList { + #region 构造函数 /// - /// lisp数据封装类 + /// 默认构造函数 /// - public class LispList : TypedValueList - { - #region 构造函数 - /// - /// 默认构造函数 - /// - public LispList() { } + public LispList() { } + + /// + /// 构造函数 + /// + /// TypedValue 迭代器 + public LispList(IEnumerable values) : base(values) { } + #endregion - /// - /// 构造函数 - /// - /// TypedValue 迭代器 - public LispList(IEnumerable values) : base(values) { } - #endregion - /// - /// lisp 列表的值 - /// - public virtual List Value + #region 重写 + /// + /// lisp 列表的值 + /// + public virtual List Value + { + get { - get - { - var value = new List + var value = new List { new TypedValue((int)LispDataType.ListBegin,-1), new TypedValue((int)LispDataType.ListEnd,-1) }; - value.InsertRange(1, this); - return value; - } + value.InsertRange(1, this); + return value; } + } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object? obj) + { + if (code < 5000) { - if (code < 5000) - { - throw new System.Exception("传入的组码值不是 lisp数据 有效范围!"); - } - Add(new TypedValue(code, obj)); + throw new System.Exception("传入的组码值不是 lisp数据 有效范围!"); } + Add(new TypedValue(code, obj)); + } - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(LispDataType code, object obj) - { - Add((int)code, obj); - } - /// - /// 添加数据,参数为true时添加 lisp 中的 T,false时添加 lisp 中的 nil - /// - /// bool 型的数据 - public void Add(bool value) - { - if (value) - { - Add(LispDataType.T_atom, true); - } - else - { - Add(LispDataType.Nil, null); - } - } - /// - /// 添加字符串 - /// - /// 字符串 - public void Add(string value) - { - Add(LispDataType.Text, value); - } - /// - /// 添加短整型数 - /// - /// 短整型数 - public void Add(short value) - { - Add(LispDataType.Int16, value); - } - /// - /// 添加整型数 - /// - /// 整型数 - public void Add(int value) - { - Add(LispDataType.Int32, value); - } - /// - /// 添加浮点数 - /// - /// 浮点数 - public void Add(double value) - { - Add(LispDataType.Double, value); - } - /// - /// 添加对象id - /// - /// 对象id - public void Add(ObjectId value) - { - Add(LispDataType.ObjectId, value); - } - /// - /// 添加选择集 - /// - /// 选择集 - public void Add(SelectionSet value) - { - Add(LispDataType.SelectionSet, value); - } - /// - /// 添加二维点 - /// - /// 二维点 - public void Add(Point2d value) - { - Add(LispDataType.Point2d, value); - } - /// - /// 添加三维点 - /// - /// 三维点 - public void Add(Point3d value) - { - Add(LispDataType.Point3d, value); - } - /// - /// 添加二维点 - /// - /// X - /// Y - public void Add(double x, double y) - { - Add(LispDataType.Point2d, new Point2d(x, y)); - } - /// - /// 添加三维点 - /// - /// X - /// Y - /// Z - public void Add(double x, double y, double z) + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(LispDataType code, object? obj) + { + Add((int)code, obj); + } + /// + /// 添加数据,参数为true时添加 lisp 中的 T,false时添加 lisp 中的 nil + /// + /// bool 型的数据 + public void Add(bool value) + { + if (value) { - Add(LispDataType.Point3d, new Point3d(x, y, z)); + Add(LispDataType.T_atom, true); } - /// - /// 添加列表 - /// - /// lisp 列表 - public void Add(LispList value) + else { - this.AddRange(value.Value); + Add(LispDataType.Nil, null); } + } + /// + /// 添加字符串 + /// + /// 字符串 + public void Add(string value) + { + Add(LispDataType.Text, value); + } + /// + /// 添加短整型数 + /// + /// 短整型数 + public void Add(short value) + { + Add(LispDataType.Int16, value); + } + /// + /// 添加整型数 + /// + /// 整型数 + public void Add(int value) + { + Add(LispDataType.Int32, value); + } + /// + /// 添加浮点数 + /// + /// 浮点数 + public void Add(double value) + { + Add(LispDataType.Double, value); + } + /// + /// 添加对象id + /// + /// 对象id + public void Add(ObjectId value) + { + Add(LispDataType.ObjectId, value); + } + /// + /// 添加选择集 + /// + /// 选择集 + public void Add(SelectionSet value) + { + Add(LispDataType.SelectionSet, value); + } + /// + /// 添加二维点 + /// + /// 二维点 + public void Add(Point2d value) + { + Add(LispDataType.Point2d, value); + } + /// + /// 添加三维点 + /// + /// 三维点 + public void Add(Point3d value) + { + Add(LispDataType.Point3d, value); + } + /// + /// 添加二维点 + /// + /// X + /// Y + public void Add(double x, double y) + { + Add(LispDataType.Point2d, new Point2d(x, y)); + } + /// + /// 添加三维点 + /// + /// X + /// Y + /// Z + public void Add(double x, double y, double z) + { + Add(LispDataType.Point3d, new Point3d(x, y, z)); + } + /// + /// 添加列表 + /// + /// lisp 列表 + public void Add(LispList value) + { + this.AddRange(value.Value); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 LispList - /// - /// ResultBuffer 实例 - public static implicit operator LispList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// LispList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](LispList values) => values.Value.ToArray(); - /// - /// LispList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(LispList values) => new(values.Value.ToArray()); - /// - /// TypedValue 数组隐式转换到 LispList - /// - /// TypedValue 数组 - public static implicit operator LispList(TypedValue[] values) => new(values); - #endregion - } + #region 转换器 + /// + /// ResultBuffer 隐式转换到 LispList + /// + /// ResultBuffer 实例 + public static implicit operator LispList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// LispList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](LispList values) => values.Value.ToArray(); + /// + /// LispList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(LispList values) => new(values.Value.ToArray()); + /// + /// TypedValue 数组隐式转换到 LispList + /// + /// TypedValue 数组 + public static implicit operator LispList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs index 2fccce5..896a226 100644 --- a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs +++ b/src/IFoxCAD.Cad/ResultData/TypedValueList.cs @@ -1,69 +1,63 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// 用于集中管理扩展数据/扩展字典/resultbuffer的类 +/// +public class TypedValueList : List { + #region 构造函数 /// - /// 用于集中管理扩展数据/扩展字典/resultbuffer的类 + /// 默认无参构造函数 /// - public class TypedValueList : List - { - #region 构造函数 - /// - /// 默认无参构造函数 - /// - public TypedValueList() { } - /// - /// 采用 TypedValue 迭代器构造 TypedValueList - /// - /// - public TypedValueList(IEnumerable values) : base(values) { } - - #endregion + public TypedValueList() { } + /// + /// 采用 TypedValue 迭代器构造 TypedValueList + /// + /// + public TypedValueList(IEnumerable values) : base(values) { } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public virtual void Add(int code, object obj) - { - Add(new TypedValue(code, obj)); - } + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public virtual void Add(int code, object obj) + { + Add(new TypedValue(code, obj)); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 TypedValueList - /// - /// ResultBuffer 实例 - public static implicit operator TypedValueList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// TypedValueList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](TypedValueList values) => values.ToArray(); - /// - /// TypedValueList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(TypedValueList values) => new(values); - /// - /// TypedValue 数组隐式转换到 TypedValueList - /// - /// TypedValue 数组 - public static implicit operator TypedValueList(TypedValue[] values) => new(values); - /// - /// 转换为字符串 - /// - /// ResultBuffer 字符串 - public override string ToString() - { - return new ResultBuffer(this).ToString(); - } - #endregion + #region 转换器 + /// + /// ResultBuffer 隐式转换到 TypedValueList + /// + /// ResultBuffer 实例 + public static implicit operator TypedValueList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// TypedValueList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](TypedValueList values) => values.ToArray(); + /// + /// TypedValueList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(TypedValueList values) => new(values); + /// + /// TypedValue 数组隐式转换到 TypedValueList + /// + /// TypedValue 数组 + public static implicit operator TypedValueList(TypedValue[] values) => new(values); + /// + /// 转换为字符串 + /// + /// ResultBuffer 字符串 + public override string ToString() + { + return new ResultBuffer(this).ToString(); } + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs index 0364f9a..1fb19d8 100644 --- a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs @@ -1,65 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; - -namespace IFoxCAD.Cad +/// +/// 扩展字典数据封装类 +/// +public class XRecordDataList : TypedValueList { + #region 构造函数 /// /// 扩展字典数据封装类 /// - public class XRecordDataList : TypedValueList - { - public XRecordDataList() - { - } - public XRecordDataList(IEnumerable values) : base(values) { } - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) - { - if (code >= 1000) - { - throw new System.Exception("传入的组码值不是 XRecordData 有效范围!"); - } - Add(new TypedValue(code, obj)); - } + public XRecordDataList() { } - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(DxfCode code, object obj) + /// + /// 扩展字典数据封装类 + /// + public XRecordDataList(IEnumerable values) : base(values) { } + #endregion + + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object obj) + { + if (code >= 1000) { - Add((int)code, obj); + throw new System.Exception("传入的组码值不是 XRecordData 有效范围!"); } - #endregion + Add(new TypedValue(code, obj)); + } - #region 转换器 - /// - /// ResultBuffer 隐式转换到 XRecordDataList - /// - /// ResultBuffer 实例 - public static implicit operator XRecordDataList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// XRecordDataList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](XRecordDataList values) => values.ToArray(); - /// - /// XRecordDataList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例s - public static implicit operator ResultBuffer(XRecordDataList values) => new(values); - /// - /// TypedValue 数组隐式转换到 XRecordDataList - /// - /// TypedValue 数组 - public static implicit operator XRecordDataList(TypedValue[] values) => new(values); - #endregion + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(DxfCode code, object obj) + { + Add((int)code, obj); } + #endregion + + #region 转换器 + /// + /// ResultBuffer 隐式转换到 XRecordDataList + /// + /// ResultBuffer 实例 + public static implicit operator XRecordDataList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// XRecordDataList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](XRecordDataList values) => values.ToArray(); + /// + /// XRecordDataList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例s + public static implicit operator ResultBuffer(XRecordDataList values) => new(values); + /// + /// TypedValue 数组隐式转换到 XRecordDataList + /// + /// TypedValue 数组 + public static implicit operator XRecordDataList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/ResultData/XdataList.cs b/src/IFoxCAD.Cad/ResultData/XdataList.cs index e22ffe2..a666100 100644 --- a/src/IFoxCAD.Cad/ResultData/XdataList.cs +++ b/src/IFoxCAD.Cad/ResultData/XdataList.cs @@ -1,71 +1,68 @@ -using Autodesk.AutoCAD.DatabaseServices; +namespace IFoxCAD.Cad; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +/// +/// 扩展数据封装类 +/// +public class XDataList : TypedValueList { - + #region 构造函数 /// /// 扩展数据封装类 /// - public class XDataList : TypedValueList - { - public XDataList() - { - } + public XDataList() { } - public XDataList(IEnumerable values) : base(values) { } + /// + /// 扩展数据封装类 + /// + public XDataList(IEnumerable values) : base(values) { } + #endregion - #region 添加数据 - /// - /// 添加数据 - /// - /// 组码 - /// 组码值 - public override void Add(int code, object obj) - { - if (code < 1000 || code > 1071) - { - throw new System.Exception("传入的组码值不是XData有效范围!"); - } - Add(new TypedValue(code, obj)); - } + #region 添加数据 + /// + /// 添加数据 + /// + /// 组码 + /// 组码值 + public override void Add(int code, object obj) + { + if (code < 1000 || code > 1071) + throw new System.Exception("传入的组码值不是XData有效范围!"); - /// - /// 添加数据 - /// - /// dxfcode枚举值 - /// 组码值 - public void Add(DxfCode code, object obj) - { + Add(new TypedValue(code, obj)); + } - Add((int)code, obj); - } + /// + /// 添加数据 + /// + /// dxfcode枚举值 + /// 组码值 + public void Add(DxfCode code, object obj) + { + Add((int)code, obj); + } - #endregion + #endregion - #region 转换器 - /// - /// ResultBuffer 隐式转换到 XDataList - /// - /// ResultBuffer 实例 - public static implicit operator XDataList(ResultBuffer buffer) => new(buffer.AsArray()); - /// - /// XDataList 隐式转换到 TypedValue 数组 - /// - /// TypedValueList 实例 - public static implicit operator TypedValue[](XDataList values) => values.ToArray(); - /// - /// XDataList 隐式转换到 ResultBuffer - /// - /// TypedValueList 实例 - public static implicit operator ResultBuffer(XDataList values) => new(values); - /// - /// TypedValue 数组隐式转换到 XDataList - /// - /// TypedValue 数组 - public static implicit operator XDataList(TypedValue[] values) => new(values); - #endregion - } + #region 转换器 + /// + /// ResultBuffer 隐式转换到 XDataList + /// + /// ResultBuffer 实例 + public static implicit operator XDataList(ResultBuffer buffer) => new(buffer.AsArray()); + /// + /// XDataList 隐式转换到 TypedValue 数组 + /// + /// TypedValueList 实例 + public static implicit operator TypedValue[](XDataList values) => values.ToArray(); + /// + /// XDataList 隐式转换到 ResultBuffer + /// + /// TypedValueList 实例 + public static implicit operator ResultBuffer(XDataList values) => new(values); + /// + /// TypedValue 数组隐式转换到 XDataList + /// + /// TypedValue 数组 + public static implicit operator XDataList(TypedValue[] values) => new(values); + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/AOP.cs b/src/IFoxCAD.Cad/Runtime/AOP.cs new file mode 100644 index 0000000..ac8270b --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/AOP.cs @@ -0,0 +1,99 @@ +//namespace IFoxCAD.Cad; +//using HarmonyLib; + +//public class IFoxRefuseInjectionTransaction : Attribute +//{ +// /// +// /// 拒绝注入事务 +// /// +// public IFoxRefuseInjectionTransaction() +// { +// } +//} + +//public class AOP +//{ +// /// +// /// 在此命名空间下的命令末尾注入清空事务栈函数 +// /// +// public static void Run(string nameSpace) +// { +// Dictionary cmdDic = new(); +// AutoClass.AppDomainGetTypes(type => { +// if (type.Namespace != nameSpace) +// return; +// //类上面特性 +// if (type.IsClass) +// { +// var attr = type.GetCustomAttributes(true); +// if (RefuseInjectionTransaction(attr)) +// return; +// } + +// //函数上面特性 +// var mets = type.GetMethods();//获得它的成员函数 +// for (int ii = 0; ii < mets.Length; ii++) +// { +// var method = mets[ii]; +// //找到特性,特性下面的方法要是Public,否则就被编译器优化掉了. +// var attr = method.GetCustomAttributes(true); +// for (int jj = 0; jj < attr.Length; jj++) +// if (attr[jj] is CommandMethodAttribute cmdAtt) +// { +// if (!RefuseInjectionTransaction(attr)) +// cmdDic.Add(cmdAtt.GlobalName, (cmdAtt, type, method)); +// } +// } +// }); + +// //运行的命令写在了Test.dll,当然不是ifox.cad类库内了.... +// if (cmdDic.Count == 0) +// return; + +// var harmony = new Harmony(nameSpace); +// var mPrefix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddFirst());//进入函数前 +// var mPostfix = SymbolExtensions.GetMethodInfo(() => IFoxCmdAddLast());//进入函数后 +// var mp1 = new HarmonyMethod(mPrefix); +// var mp2 = new HarmonyMethod(mPostfix); + +// foreach (var item in cmdDic) +// { +// //原函数执行(空间type,函数名) +// var mOriginal = AccessTools.Method(item.Value.MetType, item.Value.MetInfo.Name); +// //mOriginal.Invoke(); +// //新函数执行:创造两个函数加入里面 +// var newMet = harmony.Patch(mOriginal, mp1, mp2); +// //newMet.Invoke(); +// } +// } + +// /// +// /// 拒绝注入事务 +// /// +// /// 属性 +// /// +// private static bool RefuseInjectionTransaction(object[] attr) +// { +// bool refuseInjectionTransaction = false; +// for (int kk = 0; kk < attr.Length; kk++) +// { +// if (attr[kk] is IFoxRefuseInjectionTransaction) +// { +// refuseInjectionTransaction = true; +// break; +// } +// } +// return refuseInjectionTransaction; +// } + +// public static void IFoxCmdAddFirst() +// { +// //此生命周期会在静态事务栈上面,被无限延长 +// var _ = DBTrans.Top; +// } + +// public static void IFoxCmdAddLast() +// { +// DBTrans.FinishDatabase(); +// } +//} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs index 7be301f..f8f5273 100644 --- a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs +++ b/src/IFoxCAD.Cad/Runtime/AcadVersion.cs @@ -1,130 +1,72 @@ -using Microsoft.Win32; -using System; -using System.Collections.Generic; -using System.Reflection; -using System.Text.RegularExpressions; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// cad版本号类 +/// +public static class AcadVersion { + private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; + /// - /// cad版本号类 + /// 所有安装的cad的版本号 /// - public class AcadVersion + public static List Versions { - /// - /// 主版本 - /// - public int Major - { private set; get; } - - /// - /// 次版本 - /// - public int Minor - { private set; get; } - - /// - /// 版本号 - /// - public double ProgId => double.Parse($"{Major}.{Minor}"); - - /// - /// 注册表名称 - /// - public string ProductName - { private set; get; } - - /// - /// 注册表位置 - /// - public string ProductRootKey - { private set; get; } - - private static readonly string _pattern = @"Autodesk\\AutoCAD\\R(\d+)\.(\d+)\\.*?"; - - private static List _versions; - - /// - /// 所有安装的cad的版本号 - /// - public static List Versions + get { - get + string[] copys = Registry.LocalMachine + .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") + .GetValueNames(); + + var _versions = new List(); + foreach (var rootkey in copys) { - if (_versions is null) - { - string[] copys = - Registry.LocalMachine - .OpenSubKey(@"SOFTWARE\Autodesk\Hardcopy") - .GetValueNames(); - _versions = new List(); - foreach (var rootkey in copys) - { - if (Regex.IsMatch(rootkey, _pattern)) - { - var gs = Regex.Match(rootkey, _pattern).Groups; - var ver = - new AcadVersion - { - ProductRootKey = rootkey, - ProductName = - Registry.LocalMachine - .OpenSubKey("SOFTWARE") - .OpenSubKey(rootkey) - .GetValue("ProductName") - .ToString(), + if (!Regex.IsMatch(rootkey, _pattern)) + continue; - Major = int.Parse(gs[1].Value), - Minor = int.Parse(gs[2].Value), - }; + var gs = Regex.Match(rootkey, _pattern).Groups; + var ver = new CadVersion + { + ProductRootKey = rootkey, + ProductName = Registry.LocalMachine + .OpenSubKey("SOFTWARE") + .OpenSubKey(rootkey) + .GetValue("ProductName") + .ToString(), - _versions.Add(ver); - } - } - } - return _versions; + Major = int.Parse(gs[1].Value), + Minor = int.Parse(gs[2].Value), + }; + _versions.Add(ver); } + return _versions; } + } - /// 已打开的cad的版本号 - /// 已打开cad的application对象 - /// cad版本号对象 - public static AcadVersion FromApp(object app) - { - if (app is null) - { - throw new ArgumentNullException(nameof(app)); - } - - string acver = - app.GetType() - .InvokeMember( - "Version", - BindingFlags.GetProperty, - null, - app, - new object[0]).ToString(); - - var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; - int major = int.Parse(gs[1].Value); - int minor = int.Parse(gs[2].Value); - foreach (var ver in Versions) - { - if (ver.Major == major && ver.Minor == minor) - return ver; - } + /// 已打开的cad的版本号 + /// 已打开cad的application对象 + /// cad版本号对象 + public static CadVersion? FromApp(object app) + { + if (app == null) + throw new ArgumentNullException(nameof(app)); - return null; - } + string acver = app.GetType() + .InvokeMember( + "Version", + BindingFlags.GetProperty, + null, + app, + new object[0]).ToString(); - /// - /// 转换为字符串 - /// - /// 表示版本号的字符串 - public override string ToString() + var gs = Regex.Match(acver, @"(\d+)\.(\d+).*?").Groups; + int major = int.Parse(gs[1].Value); + int minor = int.Parse(gs[2].Value); + foreach (var ver in Versions) { - return - $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + if (ver.Major == major && ver.Minor == minor) + return ver; } + return null; } } diff --git a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs index 62b70f8..c13cc26 100644 --- a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs +++ b/src/IFoxCAD.Cad/Runtime/AssemInfo.cs @@ -1,36 +1,77 @@ -using System; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 程序集信息 +/// +[Serializable] +public struct AssemInfo { /// - /// 程序集信息 - /// - [Serializable] - public struct AssemInfo - { - /// - /// 注册名 - /// - public string Name { get; set; } - - /// - /// 程序集全名 - /// - public string Fullname { get; set; } - - /// - /// 程序集路径 - /// - public string Loader { get; set; } - - /// - /// 加载方式 - /// - public AssemLoadType LoadType { get; set; } - - /// - /// 程序集说明 - /// - public string Description { get; set; } - } + /// 注册名 + ///
+ public string Name; + + /// + /// 程序集全名 + /// + public string Fullname; + + /// + /// 程序集路径 + /// + public string Loader; + + /// + /// 加载方式 + /// + public AssemLoadType LoadType; + + /// + /// 程序集说明 + /// + public string Description; +} + + +/// +/// 程序集加载类型 +/// +public enum AssemLoadType +{ + /// + /// 启动 + /// + Startting = 2, + + /// + /// 随命令 + /// + ByCommand = 12, + + /// + /// 无效 + /// + Disabled = 20 +} + + +/// +/// 注册中心配置信息 +/// +public enum AutoRegConfig +{ + /// + /// 注册表 + /// + Regedit = 1, + /// + /// 反射特性 + /// + ReflectionAttribute = 2, + /// + /// 反射接口 + /// + ReflectionInterface = 4, + + All = Regedit | ReflectionAttribute | ReflectionInterface, } diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs index 111be63..540762c 100644 --- a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs +++ b/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs @@ -1,131 +1,169 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Microsoft.Win32; -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using CadRuntime = Autodesk.AutoCAD.Runtime; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + + +/// +/// 注册中心 +/// 初始化程序集信息写入注册表并反射特性和接口 +/// 启动cad后的执行顺序为: +/// 1:程序集配置中心构造函数 +/// 2:特性..(多个) +/// 3:接口..(多个) +/// +public abstract class AutoRegAssem : IExtensionApplication { + #region 字段 + readonly AutoReflection _autoRef; + readonly AssemInfo _info; + #endregion + + #region 静态方法 + /// + /// 程序集的路径 + /// + public static FileInfo Location => new(Assembly.GetCallingAssembly().Location); + + /// + /// 程序集的目录 + /// + public static DirectoryInfo CurrDirectory => Location.Directory; + /// - /// 程序集加载类型 + /// 获取程序集的目录 /// - public enum AssemLoadType + /// 程序集 + /// 路径对象 + public static DirectoryInfo GetDirectory(Assembly? assem) { - /// - /// 启动 - /// - Startting = 2, - - /// - /// 随命令 - /// - ByCommand = 12, - - /// - /// 无效 - /// - Disabled = 20 + if (assem is null) + throw new(nameof(assem)); + + return new FileInfo(assem.Location).Directory; } + #endregion + #region 构造函数 /// - /// 自动加载程序集的抽象类,继承自 IExtensionApplication 接口 + /// 注册中心 /// - public abstract class AutoRegAssem : CadRuntime.IExtensionApplication + /// 配置项目 + public AutoRegAssem(AutoRegConfig autoRegConfig) { - private AssemInfo _info = new(); - - /// - /// 程序集的路径 - /// - public static FileInfo Location => new(Assembly.GetCallingAssembly().Location); - - /// - /// 程序集的目录 - /// - public static DirectoryInfo CurrDirectory => Location.Directory; - - /// - /// 获取程序集的目录 - /// - /// 程序集 - /// 路径对象 - public static DirectoryInfo GetDirectory(Assembly assem) + var assem = Assembly.GetCallingAssembly(); + _info = new() { - if (assem is null) - { - throw new(nameof(assem)); - } - return new FileInfo(assem.Location).Directory; - } + Loader = assem.Location, + Fullname = assem.FullName, + Name = assem.GetName().Name, + LoadType = AssemLoadType.Startting + }; - /// - /// 初始化程序集信息 - /// - public AutoRegAssem() + if ((autoRegConfig & AutoRegConfig.Regedit) == AutoRegConfig.Regedit) { - Assembly assem = Assembly.GetCallingAssembly(); - _info.Loader = assem.Location; - _info.Fullname = assem.FullName; - _info.Name = assem.GetName().Name; - _info.LoadType = AssemLoadType.Startting; - if (!SearchForReg()) - { RegApp(); - } } - #region RegApp + //实例化了 AutoClass 之后会自动执行 IFoxAutoGo 接口下面的类, + //以及自动执行特性 [IFoxInitialize] + //类库用户不在此处进行其他代码,而是实现特性 + _autoRef = new AutoReflection(_info.Name, autoRegConfig); + _autoRef.Initialize(); + } + #endregion - private static RegistryKey GetAcAppKey() - { -#if ac2009 - string key = HostApplicationServices.Current.RegistryProductRootKey; -#elif ac2013 - string key = HostApplicationServices.Current.MachineRegistryProductRootKey; + #region RegApp + + /// + /// 获取当前cad注册表位置 + /// + /// 打开权限 + /// + public static RegistryKey GetAcAppKey(bool writable = true) + { + RegistryKey? ackey = null; + +#if NET35 + string key = HostApplicationServices.Current.RegistryProductRootKey; //这里浩辰读出来是"" +#elif !HC2020 + string key = HostApplicationServices.Current.UserRegistryProductRootKey; #endif - RegistryKey ackey = - Registry.CurrentUser.OpenSubKey(key, true); - return ackey.CreateSubKey("Applications"); - } - private bool SearchForReg() - { - RegistryKey appkey = GetAcAppKey(); - var regApps = appkey.GetSubKeyNames(); - return regApps.Contains(_info.Name); - } +#if !HC2020 + ackey = Registry.CurrentUser.OpenSubKey(key, writable); +#else + //浩辰 + var s = GrxCAD.DatabaseServices.HostApplicationServices.Current.RegistryProductRootKey;//浩辰奇怪的空值 + string str = CadSystem.Getvar("ACADVER"); + str = Regex.Replace(str, @"[^\d.\d]", ""); + double.TryParse(str, out double a); + // string regedit = @"Software\Gstarsoft\GstarCAD\R" + a.ToString() + @"\zh-CN"; //2019 + // string regedit = @"Software\Gstarsoft\GstarCAD\B" + a.ToString() + @"\zh-CN";//2020 这里是 + string regedit = @"Software\Gstarsoft\GstarCAD\B20\zh-CN";//2020 这里是 + + ackey = Registry.CurrentUser.OpenSubKey(regedit, writable); +#endif + return ackey.CreateSubKey("Applications"); + } + + /// + /// 卸载注册表信息 + /// + public bool UnRegApp() + { + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; - /// - /// 在注册表写入自动加载的程序集信息 - /// - public void RegApp() + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) { - RegistryKey appkey = GetAcAppKey(); - RegistryKey rk = appkey.CreateSubKey(_info.Name); - rk.SetValue("DESCRIPTION", _info.Fullname, RegistryValueKind.String); - rk.SetValue("LOADCTRLS", _info.LoadType, RegistryValueKind.DWord); - rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); - rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); - appkey.Close(); + appkey.DeleteSubKey(_info.Name, false); + return true; } + return false; + } - #endregion RegApp + /// + /// 是否已经存在注册表 + /// + /// + bool SearchForReg() + { + var appkey = GetAcAppKey(); + if (appkey.SubKeyCount == 0) + return false; - #region IExtensionApplication 成员 + var regApps = appkey.GetSubKeyNames(); + if (regApps.Contains(_info.Name)) + { + //20220409 bug:文件名相同,路径不同,需要判断路径 + var info = appkey.OpenSubKey(_info.Name); + return info.GetValue("LOADER")?.ToString().ToLower() == _info.Loader.ToLower(); + } + return false; + } - /// - /// 初始化函数 - /// - public abstract void Initialize(); + /// + /// 在注册表写入自动加载的程序集信息 + /// + public void RegApp() + { + var appkey = GetAcAppKey(); + var rk = appkey.CreateSubKey(_info.Name); + rk.SetValue("DESCRIPTION", _info.Fullname, RegistryValueKind.String); + rk.SetValue("LOADCTRLS", _info.LoadType, RegistryValueKind.DWord); + rk.SetValue("LOADER", _info.Loader, RegistryValueKind.String); + rk.SetValue("MANAGED", 1, RegistryValueKind.DWord); + appkey.Close(); + } - /// - /// 结束函数 - /// - public abstract void Terminate(); + //这里的是不会自动执行的 + public void Initialize() { } + public void Terminate() { } - #endregion IExtensionApplication 成员 + ~AutoRegAssem() + { + _autoRef.Terminate(); } + #endregion RegApp } diff --git a/src/IFoxCAD.Cad/Runtime/CadVersion.cs b/src/IFoxCAD.Cad/Runtime/CadVersion.cs new file mode 100644 index 0000000..2e32c74 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/CadVersion.cs @@ -0,0 +1,92 @@ +namespace IFoxCAD.Cad; + +#if ac2009 + +public class CadVersion +{ + /// + /// 主版本 + /// + public int Major { get; set; } + + /// + /// 次版本 + /// + public int Minor { get; set; } + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName { get; set; } + + /// + /// 注册表位置 + /// + public string? ProductRootKey { get; set; } + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } + // public override bool Equals(object obj) + // { + // return base.Equals(obj); + // } + + // public override int GetHashCode() + // { + // return base.GetHashCode(); + // } + + // //public override string ToString() + // //{ + // // return base.ToString(); + // //} +} +#else +public record CadVersion +{ + /// + /// 主版本 + /// + public int Major; + + /// + /// 次版本 + /// + public int Minor; + + /// + /// 版本号 + /// + public double ProgId => double.Parse($"{Major}.{Minor}"); + + /// + /// 注册表名称 + /// + public string? ProductName; + + /// + /// 注册表位置 + /// + public string? ProductRootKey; + + /// + /// 转换为字符串 + /// + /// 表示版本号的字符串 + public override string ToString() + { + return $"名称:{ProductName}\n版本号:{ProgId}\n注册表位置:{ProductRootKey}"; + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/DBTrans.cs b/src/IFoxCAD.Cad/Runtime/DBTrans.cs index 9507f17..d207edf 100644 --- a/src/IFoxCAD.Cad/Runtime/DBTrans.cs +++ b/src/IFoxCAD.Cad/Runtime/DBTrans.cs @@ -1,319 +1,440 @@ -using System; -using System.Collections.Generic; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.ApplicationServices; -using System.IO; - -namespace IFoxCAD.Cad -{ - public class DBTrans : IDisposable - { - #region 私有字段 - /// - /// 文档锁 - /// - private DocumentLock documentLock = default; - /// - /// 是否释放资源 - /// - private bool disposedValue; - /// - /// 是否提交事务 - /// - private bool _commit; - /// - /// 事务栈 - /// - private static readonly Stack dBTrans = new(); - #endregion - - #region 公开属性 - /// - /// 返回当前事务 - /// - public static DBTrans Top => dBTrans.Peek(); - /// - /// 数据库 - /// - public Database Database { get; private set; } - /// - /// 文档 - /// - public Document Document { get; private set; } - /// - /// 命令行 - /// - public Editor Editor { get; private set; } - /// - /// 事务管理器 - /// - public Transaction Transaction { get; private set; } - - #endregion - - #region 构造函数 - /// - /// 默认构造函数,默认为打开当前文档,默认提交事务 - /// - /// 要打开的文档 - /// 事务是否提交 - public DBTrans(Document doc = null, bool commit = true, bool doclock = false) - { - doc ??= Application.DocumentManager.MdiActiveDocument; - Document = doc; - Database = Document.Database; - Editor = Document.Editor; - Init(commit, doclock); - } +namespace IFoxCAD.Cad; - /// - /// 构造函数,打开数据库,默认提交事务 - /// - /// 要打开的数据库 - /// 事务是否提交 - public DBTrans(Database database, bool commit = true) - { - Database = database; - Init(commit, false); - } - /// - /// 构造函数,打开文件,默认提交事务 - /// - /// 要打开的文件名 - /// 事务是否提交 - public DBTrans(string fileName, bool commit = true) - { - Database = new Database(false, true); - Database.ReadDwgFile(fileName, FileShare.Read, true, null); - Database.CloseInput(true); - Init(commit, false); - } - /// - /// 初始化事务及事务队列、提交模式 - /// - /// 提交模式 - private void Init(bool commit, bool doclock) - { - if (doclock) - { - documentLock = Document.LockDocument(); - } - Transaction = Database.TransactionManager.StartTransaction(); - _commit = commit; - dBTrans.Push(this); - } +/// +/// 事务栈,隐匿事务在数据库其中担任的角色 +/// +public class DBTrans : IDisposable +{ + #region 私有字段 + /// + /// 文档锁 + /// + private readonly DocumentLock? documentLock; + /// + /// 是否释放资源 + /// + private bool disposedValue; + /// + /// 是否提交事务 + /// + private readonly bool _commit; + /// + /// 事务栈 + /// + private static readonly Stack dBTrans = new(); + #endregion - #endregion - - #region 符号表 - - /// - /// 块表 - /// - public SymbolTable BlockTable => new(this, Database.BlockTableId); - /// - /// 当前绘图空间 - /// - public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId); - /// - /// 模型空间 - /// - public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace]); - /// - /// 图纸空间 - /// - public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace]); - /// - /// 层表 - /// - public SymbolTable LayerTable => new(this, Database.LayerTableId); - /// - /// 文字样式表 - /// - public SymbolTable TextStyleTable => new(this, Database.TextStyleTableId); - - /// - /// 注册应用程序表 - /// - public SymbolTable RegAppTable => new(this, Database.RegAppTableId); - - /// - /// 标注样式表 - /// - public SymbolTable DimStyleTable => new(this, Database.DimStyleTableId); - - /// - /// 线型表 - /// - public SymbolTable LinetypeTable => new(this, Database.LinetypeTableId); - - /// - /// 用户坐标系表 - /// - public SymbolTable UcsTable => new(this, Database.UcsTableId); - - /// - /// 视图表 - /// - public SymbolTable ViewTable => new(this, Database.ViewTableId); - - /// - /// 视口表 - /// - public SymbolTable ViewportTable => new(this, Database.ViewportTableId); - #endregion - - #region 字典 - //TODO: 补充关于扩展字典,命名对象字典,组字典,多线样式字典等对象字典的属性 - /// - /// 命名对象字典 - /// - public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId); - /// - /// 组字典 - /// - public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId); - /// - /// 多重引线样式字典 - /// - public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId); - /// - /// 多线样式字典 - /// - public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId); - /// - /// 材质字典 - /// - public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId); - /// - /// 表格样式字典 - /// - public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId); - /// - /// 视觉样式字典 - /// - public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId); - /// - /// 颜色字典 - /// - public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId); - /// - /// 打印设置字典 - /// - public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId); - /// - /// 打印样式表名字典 - /// - public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId); - /// - /// 布局字典 - /// - public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId); - /// - /// 数据链接字典 - /// - public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId); -#if ac2013 - /// - /// 详细视图样式字典 - /// - public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId); - /// - /// 剖面视图样式字典 - /// - public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId); -#endif - #endregion - - #region 获取对象 - /// - /// 根据对象id获取图元对象 - /// - /// 要获取的图元对象的类型 - /// 对象id - /// 打开模式,默认为只读 - /// 是否打开已删除对象,默认为不打开 - /// 是否打开锁定图层对象,默认为不打开 - /// 图元对象,类型不匹配时返回 - public T GetObject(ObjectId id, - OpenMode mode = OpenMode.ForRead, - bool openErased = false, - bool forceOpenOnLockedLayer = false) where T : DBObject + #region 公开属性 + /// + /// 返回当前事务 + /// + public static DBTrans Top + { + get { - return Transaction.GetObject(id, mode, openErased, forceOpenOnLockedLayer) as T; - } + /* + * 0x01 + * 事务栈上面有事务,这个事务属于当前文档, + * 那么直接提交原本事务然后再开一个(一直把栈前面的同数据库提交清空) + * 那不就发生跨事务读取图元了吗?....否决 + * + * 0x02 + * 跨文档事务出错 Autodesk.AutoCAD.Runtime.Exception:“eNotFromThisDocument” + * Curves.GetEntities()会从Top获取事务(Top会new一个),此时会是当前文档; + * 然后命令文中发生了 using var tr = new DBTrans(); + * 当退出命令此事务释放,但是从来不释放Top, + * 然后我新建了一个文档,再进行命令=>又进入Top,Top返回了前一个文档的事务 + * 因此所以无法清理栈,所以Dispose不触发,导致无法刷新图元和Ctrl+Z出错 + * 所以用AOP方式修复 + * + * 0x03 + * 经过艰苦卓绝的测试,aop模式由于不能断点调试,所以暂时放弃。 + */ - /// - /// 根据对象句柄字符串获取对象Id - /// - /// 句柄字符串 - /// 对象id - public ObjectId GetObjectId(string handleString) - { - var hanle = new Handle(Convert.ToInt64(handleString, 16)); - return Database.GetObjectId(false, hanle, 0); + // 由于大量的函数依赖本属性,强迫用户先开启事务 + if (dBTrans.Count == 0) + throw new ArgumentNullException("事务栈没有任何事务,请在调用前创建:" + nameof(DBTrans)); + var trans = dBTrans.Peek(); + return trans; } + } - #endregion + /// + /// 文档 + /// + public Document? Document { get; private set; } + /// + /// 命令行 + /// + public Editor? Editor { get; private set; } + /// + /// 事务管理器 + /// + public Transaction Transaction { get; private set; } + /// + /// 数据库 + /// + public Database Database { get; private set; } + #endregion + #region 构造函数 + /// + /// 事务栈 + /// 默认构造函数,默认为打开当前文档,默认提交事务 + /// + /// 要打开的文档 + /// 事务是否提交 + /// 是否锁文档 + public DBTrans(Document? doc = null, bool commit = true, bool doclock = false) + { + Document = doc ?? Application.DocumentManager.MdiActiveDocument; + Database = Document.Database; + Editor = Document.Editor; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + if (doclock) + documentLock = Document.LockDocument(); + dBTrans.Push(this); + } - #region idispose接口相关函数 + /// + /// 事务栈 + /// 打开数据库,默认提交事务 + /// + /// 要打开的数据库 + /// 事务是否提交 + public DBTrans(Database database, bool commit = true) + { + Database = database; + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + dBTrans.Push(this); + } - public void Abort() - { - Transaction.Abort(); - } + /// + /// 事务栈 + /// 打开文件,默认提交事务 + /// + /// 要打开的文件名 + /// 事务是否提交 + /// 开图模式 + /// 密码 + public DBTrans(string fileName, + bool commit = true, +#pragma warning disable CS0436 // 类型与导入类型冲突 + FileOpenMode openMode = FileOpenMode.OpenForReadAndWriteNoShare, +#pragma warning restore CS0436 // 类型与导入类型冲突 + string? password = null) + { + if (string.IsNullOrEmpty(fileName?.Trim())) + throw new ArgumentNullException(nameof(fileName)); - public void Commit() + if (!File.Exists(fileName)) + Database = new Database(true, false); + else { - if (_commit) + var doc = Acap.DocumentManager + .Cast() + .FirstOrDefault(doc => doc.Name == fileName); + if (doc is not null) { - Transaction.Commit(); + Database = doc.Database; + Document = doc; + Editor = doc.Editor; } else { - Abort(); + Database = new Database(false, true); + if (Path.GetExtension(fileName).ToLower().Contains("dxf")) + Database.DxfIn(fileName, null); + else + { +#if ac2008 + //此处没有一一对应的关系 +#pragma warning disable CS0436 // 类型与导入类型冲突 + var sf = openMode switch + { + FileOpenMode.OpenTryForReadShare => FileShare.Read, + FileOpenMode.OpenForReadAndAllShare => FileShare.ReadWrite, + FileOpenMode.OpenForReadAndWriteNoShare => FileShare.None, + FileOpenMode.OpenForReadAndReadShare => FileShare.ReadWrite, + _ => FileShare.ReadWrite, + }; +#pragma warning restore CS0436 // 类型与导入类型冲突 + Database.ReadDwgFile(fileName, sf, true/*控制读入一个与系统编码不相同的文件时的转换操作*/, password); +#else + Database.ReadDwgFile(fileName, openMode, true/*控制读入一个与系统编码不相同的文件时的转换操作*/, password); +#endif + } + Database.CloseInput(true); } - } - protected virtual void Dispose(bool disposing) + Transaction = Database.TransactionManager.StartTransaction(); + _commit = commit; + dBTrans.Push(this); + } + #endregion + + #region 类型转换 + /// + /// 隐式转换为Transaction + /// + /// 事务管理器 + /// 事务管理器 + public static implicit operator Transaction(DBTrans tr) + { + return tr.Transaction; + } + #endregion + + #region 符号表 + + /// + /// 块表 + /// + public SymbolTable BlockTable => new(this, Database.BlockTableId); + /// + /// 当前绘图空间 + /// + public BlockTableRecord CurrentSpace => BlockTable.GetRecord(Database.CurrentSpaceId)!; + /// + /// 模型空间 + /// + public BlockTableRecord ModelSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.ModelSpace])!; + /// + /// 图纸空间 + /// + public BlockTableRecord PaperSpace => BlockTable.GetRecord(BlockTable.CurrentSymbolTable[BlockTableRecord.PaperSpace])!; + /// + /// 层表 + /// + public SymbolTable LayerTable => new(this, Database.LayerTableId); + /// + /// 文字样式表 + /// + public SymbolTable TextStyleTable => new(this, Database.TextStyleTableId); + + /// + /// 注册应用程序表 + /// + public SymbolTable RegAppTable => new(this, Database.RegAppTableId); + + /// + /// 标注样式表 + /// + public SymbolTable DimStyleTable => new(this, Database.DimStyleTableId); + + /// + /// 线型表 + /// + public SymbolTable LinetypeTable => new(this, Database.LinetypeTableId); + + /// + /// 用户坐标系表 + /// + public SymbolTable UcsTable => new(this, Database.UcsTableId); + + /// + /// 视图表 + /// + public SymbolTable ViewTable => new(this, Database.ViewTableId); + + /// + /// 视口表 + /// + public SymbolTable ViewportTable => new(this, Database.ViewportTableId); + #endregion + + #region 字典 + /// + /// 命名对象字典 + /// + public DBDictionary NamedObjectsDict => GetObject(Database.NamedObjectsDictionaryId)!; + /// + /// 组字典 + /// + public DBDictionary GroupDict => GetObject(Database.GroupDictionaryId)!; + /// + /// 多重引线样式字典 + /// + public DBDictionary MLeaderStyleDict => GetObject(Database.MLeaderStyleDictionaryId)!; + /// + /// 多线样式字典 + /// + public DBDictionary MLStyleDict => GetObject(Database.MLStyleDictionaryId)!; + /// + /// 材质字典 + /// + public DBDictionary MaterialDict => GetObject(Database.MaterialDictionaryId)!; + /// + /// 表格样式字典 + /// + public DBDictionary TableStyleDict => GetObject(Database.TableStyleDictionaryId)!; + /// + /// 视觉样式字典 + /// + public DBDictionary VisualStyleDict => GetObject(Database.VisualStyleDictionaryId)!; + /// + /// 颜色字典 + /// + public DBDictionary ColorDict => GetObject(Database.ColorDictionaryId)!; + /// + /// 打印设置字典 + /// + public DBDictionary PlotSettingsDict => GetObject(Database.PlotSettingsDictionaryId)!; + /// + /// 打印样式表名字典 + /// + public DBDictionary PlotStyleNameDict => GetObject(Database.PlotStyleNameDictionaryId)!; + /// + /// 布局字典 + /// + public DBDictionary LayoutDict => GetObject(Database.LayoutDictionaryId)!; + /// + /// 数据链接字典 + /// + public DBDictionary DataLinkDict => GetObject(Database.DataLinkDictionaryId)!; +#if !ac2009 + /// + /// 详细视图样式字典 + /// + public DBDictionary DetailViewStyleDict => GetObject(Database.DetailViewStyleDictionaryId)!; + /// + /// 剖面视图样式字典 + /// + public DBDictionary SectionViewStyleDict => GetObject(Database.SectionViewStyleDictionaryId)!; +#endif + #endregion + + #region 获取对象 + /// + /// 根据对象id获取图元对象 + /// + /// 要获取的图元对象的类型 + /// 对象id + /// 打开模式,默认为只读 + /// 是否打开已删除对象,默认为不打开 + /// 是否打开锁定图层对象,默认为不打开 + /// 图元对象,类型不匹配时返回 + public T? GetObject(ObjectId id, + OpenMode mode = OpenMode.ForRead, + bool openErased = false, + bool forceOpenOnLockedLayer = false) where T : DBObject + { + return Transaction.GetObject(id, mode, openErased, forceOpenOnLockedLayer) as T; + } + + /// + /// 根据对象句柄字符串获取对象Id + /// + /// 句柄字符串 + /// 对象id + public ObjectId GetObjectId(string handleString) + { + var hanle = new Handle(Convert.ToInt64(handleString, 16)); + //return Database.GetObjectId(false, hanle, 0); + return Helper.TryGetObjectId(Database, hanle); + } + + + + #endregion + + #region 保存文件 + /// + /// 保存当前数据库的dwg文件,如果前台打开则按dwg默认版本保存,否则按version参数的版本保存 + /// + /// dwg版本,默认为2004 + public void SaveDwgFile(DwgVersion version = DwgVersion.AC1800) + { + bool flag = true; + foreach (Document doc in Application.DocumentManager) { - if (!disposedValue) + // 前台开图,使用命令保存 + if (doc.Database.Filename == this.Database.Filename) { - if (disposing) - { - // 释放托管状态(托管对象) - Commit(); - dBTrans.Pop(); - if (!Transaction.IsDisposed) - { - Transaction.Dispose(); - } - documentLock?.Dispose(); - } - - // 释放未托管的资源(未托管的对象)并替代终结器 - // 将大型字段设置为 null - disposedValue = true; + doc.SendStringToExecute("_qsave\n", false, true, true); //不需要切换文档 + flag = false; + break; } } - - // 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 - ~DBTrans() + if (flag) { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: false); + // 后台开图,用数据库保存 + Database.SaveAs(Database.Filename, version); } + } + #endregion - public void Dispose() + #region idispose接口相关函数 + /// + /// 取消事务 + /// + public void Abort() + { + Dispose(false); + } + /// + /// 提交事务 + /// + public void Commit() + { + Dispose(true); + } + + protected virtual void Dispose(bool disposing) + { + /* 事务dispose流程: + * 1. 根据传入的参数确定是否提交,true为提交,false为不提交 + * 2. 根据disposedValue的值确定是否重复dispose,false为首次dispose + * 3. 如果锁文档就将文档锁dispose + * 4. 不管是否提交,既然进入dispose,就要将事务栈的当前事务弹出 + * 注意这里的事务栈不是cad的事务管理器,而是dbtrans的事务 + * 5. 清理非托管的字段 + */ + + if (disposedValue) + return; + + // 释放未托管的资源(未托管的对象)并替代终结器 + // 将大型字段设置为 null + disposedValue = true; + + if (disposing) + { + // 调用cad的事务进行提交,释放托管状态(托管对象) + Transaction.Commit(); + } + else { - // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 - Dispose(disposing: true); - GC.SuppressFinalize(this); + // 否则取消所有的修改 + Transaction.Abort(); } - #endregion + // 调用 cad事务的dispose进行销毁 + if (!Transaction.IsDisposed) + Transaction.Dispose(); + + // 调用文档锁dispose + documentLock?.Dispose(); + + // 将事务栈的当前dbtrans弹栈 + dBTrans.Pop(); + } + + // 仅当“Dispose(bool disposing)”拥有用于释放未托管资源的代码时才替代终结器 + ~DBTrans() + { + // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 + Dispose(disposing: false); + } + + public void Dispose() + { + // 不要更改此代码。请将清理代码放入“Dispose(bool disposing)”方法中 + Dispose(disposing: true); + GC.SuppressFinalize(this); } + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/IFoxCAD.Cad/Runtime/Env.cs index 0d490e1..379504b 100644 --- a/src/IFoxCAD.Cad/Runtime/Env.cs +++ b/src/IFoxCAD.Cad/Runtime/Env.cs @@ -1,453 +1,490 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.GraphicsSystem; -using System; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 系统管理类 +/// 封装了一些系统 osmode、cmdecho、dimblk 系统变量 +/// 封装了常用的 文档 编辑器 数据库等对象为静态变量 +/// 封装了配置页面的注册表信息获取函数 +/// +public static class Env { + #region Goal + /// - /// 系统管理类 - /// 封装了一些系统 osmode、cmdecho、dimblk 系统变量 - /// 封装了常用的 文档 编辑器 数据库等对象为静态变量 - /// 封装了配置页面的注册表信息获取函数 + /// 当前的数据库 /// - public static class Env + public static Database Database => HostApplicationServices.WorkingDatabase; + + /// + /// 当前文档 + /// + public static Document Document => Application.DocumentManager.MdiActiveDocument; + + /// + /// 编辑器对象 + /// + public static Editor Editor => Document.Editor; + + /// + /// 图形管理器 + /// + public static Manager GsManager => Document.GraphicsManager; + + #endregion Goal + + #region Preferences + + /// + /// 获取当前配置的数据 + /// + /// 小节名 + /// 数据名 + /// 对象 + public static object GetCurrentProfileProperty(string subSectionName, string propertyName) + { + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection cpf = ucm.OpenCurrentProfile(); + IConfigurationSection ss = cpf.OpenSubsection(subSectionName); + return ss.ReadProperty(propertyName, ""); + } + + /// + /// 获取对话框配置的数据 + /// + /// 对话框对象 + /// 配置项 + public static IConfigurationSection GetDialogSection(object dialog) { - #region Goal + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection ds = ucm.OpenDialogSection(dialog); + return ds; + } + /// + /// 获取公共配置的数据 + /// + /// 数据名 + /// 配置项 + public static IConfigurationSection GetGlobalSection(string propertyName) + { + UserConfigurationManager ucm = Application.UserConfigurationManager; + IConfigurationSection gs = ucm.OpenGlobalSection(); + IConfigurationSection ss = gs.OpenSubsection(propertyName); + return ss; + } + + #endregion Preferences + + #region Enum + + /// + /// 控制在AutoLISP的command函数运行时AutoCAD是否回显提示和输入, 为显示, 为不显示 + /// + public static bool CmdEcho + { + get => Convert.ToInt16(Application.GetSystemVariable("cmdecho")) == 1; + set => Application.SetSystemVariable("cmdecho", Convert.ToInt16(value)); + } + + /// + /// 控制在光标是否为正交模式, 为打开正交, 为关闭正交 + /// + public static bool OrthoMode + { + get => Convert.ToInt16(Application.GetSystemVariable("ORTHOMODE")) == 1; + set => Application.SetSystemVariable("ORTHOMODE", Convert.ToInt16(value)); + } + + #region Dimblk + + /// + /// 标注箭头类型 + /// + public enum DimblkType + { /// - /// 当前的数据库 + /// 实心闭合 /// - public static Database CurrentDatabase - { - get - { - return HostApplicationServices.WorkingDatabase; - } - } + Defult, /// - /// 当前文档 + /// 点 /// - public static Document ActiveDocument - { - get - { - return Application.DocumentManager.MdiActiveDocument; - } - } + Dot, /// - /// 编辑器对象 + /// 小点 /// - public static Editor Editor - { - get - { - return ActiveDocument.Editor; - } - } + DotSmall, /// - /// 图形管理器 + /// 空心点 /// - public static Manager GsManager - { - get - { - return ActiveDocument.GraphicsManager; - } - } + DotBlank, - #endregion Goal + /// + /// 原点标记 + /// + Origin, - #region Preferences + /// + /// 原点标记2 + /// + Origin2, /// - /// 获取当前配置的数据 + /// 打开 /// - /// 小节名 - /// 数据名 - /// 对象 - public static object GetCurrentProfileProperty(string subSectionName, string propertyName) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection cpf = ucm.OpenCurrentProfile(); - IConfigurationSection ss = cpf.OpenSubsection(subSectionName); - return ss.ReadProperty(propertyName, ""); - } + Open, /// - /// 获取对话框配置的数据 + /// 直角 /// - /// 对话框对象 - /// 配置项 - public static IConfigurationSection GetDialogSection(object dialog) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection ds = ucm.OpenDialogSection(dialog); - return ds; - } + Open90, /// - /// 获取公共配置的数据 + /// 30度角 /// - /// 数据名 - /// 配置项 - public static IConfigurationSection GetGlobalSection(string propertyName) - { - UserConfigurationManager ucm = Application.UserConfigurationManager; - IConfigurationSection gs = ucm.OpenGlobalSection(); - IConfigurationSection ss = gs.OpenSubsection(propertyName); - return ss; - } + Open30, - #endregion Preferences + /// + /// 闭合 + /// + Closed, - #region Enum + /// + /// 空心小点 + /// + Small, /// - /// 控制在AutoLISP的command函数运行时AutoCAD是否回显提示和输入, 为显示, 为不显示 + /// 无 /// - public static bool CmdEcho - { - get => Convert.ToInt16(Application.GetSystemVariable("cmdecho")) == 1; - set => Application.SetSystemVariable("cmdecho", Convert.ToInt16(value)); - } + None, - #region Dimblk + /// + /// 倾斜 + /// + Oblique, /// - /// 标注箭头类型 + /// 实心框 /// - public enum DimblkType - { - /// - /// 实心闭合 - /// - Defult, - - /// - /// 点 - /// - Dot, - - /// - /// 小点 - /// - DotSmall, - - /// - /// 空心点 - /// - DotBlank, - - /// - /// 原点标记 - /// - Origin, - - /// - /// 原点标记2 - /// - Origin2, - - /// - /// 打开 - /// - Open, - - /// - /// 直角 - /// - Open90, - - /// - /// 30度角 - /// - Open30, - - /// - /// 闭合 - /// - Closed, - - /// - /// 空心小点 - /// - Small, - - /// - /// 无 - /// - None, - - /// - /// 倾斜 - /// - Oblique, - - /// - /// 实心框 - /// - BoxFilled, - - /// - /// 方框 - /// - BoxBlank, - - /// - /// 空心闭合 - /// - ClosedBlank, - - /// - /// 实心基准三角形 - /// - DatumFilled, - - /// - /// 基准三角形 - /// - DatumBlank, - - /// - /// 完整标记 - /// - Integral, - - /// - /// 建筑标记 - /// - ArchTick, - } + BoxFilled, /// - /// 标注箭头属性 + /// 方框 /// - public static DimblkType Dimblk - { - get - { - string s = (string)Application.GetSystemVariable("dimblk"); - if (string.IsNullOrEmpty(s)) - { - return DimblkType.Defult; - } - else - { - return s.ToEnum(); - } - } - set - { - string s = GetDimblkName(value); - Application.SetSystemVariable("dimblk", s); - } - } + BoxBlank, /// - /// 获取标注箭头名 + /// 空心闭合 /// - /// 标注箭头类型 - /// 箭头名 - public static string GetDimblkName(DimblkType dimblk) - { - return - dimblk == DimblkType.Defult - ? - "." - : - "_" + dimblk.GetName(); - } + ClosedBlank, /// - /// 获取标注箭头ID + /// 实心基准三角形 /// - /// 标注箭头类型 - /// 箭头ID - public static ObjectId GetDimblkId(DimblkType dimblk) - { - DimblkType oldDimblk = Dimblk; - Dimblk = dimblk; - ObjectId id = HostApplicationServices.WorkingDatabase.Dimblk; - Dimblk = oldDimblk; - return id; - } + DatumFilled, - #endregion Dimblk + /// + /// 基准三角形 + /// + DatumBlank, - #region OsMode + /// + /// 完整标记 + /// + Integral, /// - /// 捕捉模式系统变量类型 + /// 建筑标记 /// - public enum OSModeType + ArchTick + } + + private static readonly Dictionary dimdescdict = new() + { + { "实心闭合", DimblkType.Defult }, + { "点", DimblkType.Dot }, + { "小点", DimblkType.DotSmall }, + { "空心点", DimblkType.DotBlank }, + { "原点标记", DimblkType.Origin }, + { "原点标记 2", DimblkType.Origin2 }, + { "打开", DimblkType.Open }, + { "直角", DimblkType.Open90 }, + { "30 度角", DimblkType.Open30 }, + { "闭合", DimblkType.Closed }, + { "空心小点", DimblkType.Small }, + { "无", DimblkType.None }, + { "倾斜", DimblkType.Oblique }, + { "实心框", DimblkType.BoxFilled }, + { "方框", DimblkType.BoxBlank }, + { "空心闭合", DimblkType.ClosedBlank }, + { "实心基准三角形", DimblkType.DatumFilled }, + { "基准三角形", DimblkType.DatumBlank }, + { "完整标记", DimblkType.Integral }, + { "建筑标记", DimblkType.ArchTick }, + + { "", DimblkType.Defult }, + { "_DOT", DimblkType.Dot }, + { "_DOTSMALL", DimblkType.DotSmall }, + { "_DOTBLANK", DimblkType.DotBlank }, + { "_ORIGIN", DimblkType.Origin }, + { "_ORIGIN2", DimblkType.Origin2 }, + { "_OPEN", DimblkType.Open }, + { "_OPEN90", DimblkType.Open90 }, + { "_OPEN30", DimblkType.Open30 }, + { "_CLOSED", DimblkType.Closed }, + { "_SMALL", DimblkType.Small }, + { "_NONE", DimblkType.None }, + { "_OBLIQUE", DimblkType.Oblique }, + { "_BOXFILLED", DimblkType.BoxFilled }, + { "_BOXBLANK", DimblkType.BoxBlank }, + { "_CLOSEDBLANK", DimblkType.ClosedBlank }, + { "_DATUMFILLED", DimblkType.DatumFilled }, + { "_DATUMBLANK", DimblkType.DatumBlank }, + { "_INTEGRAL", DimblkType.Integral }, + { "_ARCHTICK", DimblkType.ArchTick }, + }; + + + + /// + /// 标注箭头属性 + /// + public static DimblkType Dimblk + { + get + { + string s = ((string)Application.GetSystemVariable("dimblk")).ToUpper(); + //if (string.IsNullOrEmpty(s)) + //{ + // return DimblkType.Defult; + //} + //else + //{ + // if (dimdescdict.TryGetValue(s, out DimblkType value)) + // { + // return value; + // } + // return s.ToEnum(); + // //return s.FromDescName(); + //} + return dimdescdict[s]; + } + set { - /// - /// 无 - /// - None = 0, - - /// - /// 端点 - /// - End = 1, - - /// - /// 中点 - /// - Middle = 2, - - /// - /// 圆心 - /// - Center = 4, - - /// - /// 节点 - /// - Node = 8, - - /// - /// 象限点 - /// - Quadrant = 16, - - /// - /// 交点 - /// - Intersection = 32, - - /// - /// 插入点 - /// - Insert = 64, - - /// - /// 垂足 - /// - Pedal = 128, - - /// - /// 切点 - /// - Tangent = 256, - - /// - /// 最近点 - /// - Nearest = 512, - - /// - /// 几何中心 - /// - Quick = 1024, - - /// - /// 外观交点 - /// - Appearance = 2048, - - /// - /// 延伸 - /// - Extension = 4096, - - /// - /// 平行 - /// - Parallel = 8192 + string s = GetDimblkName(value); + Application.SetSystemVariable("dimblk", s); } + } + + /// + /// 获取标注箭头名 + /// + /// 标注箭头类型 + /// 箭头名 + public static string GetDimblkName(DimblkType dimblk) + { + return + dimblk == DimblkType.Defult + ? + "." + : + "_" + dimblk.GetName(); + } + /// + /// 获取标注箭头ID + /// + /// 标注箭头类型 + /// 箭头ID + public static ObjectId GetDimblkId(DimblkType dimblk) + { + DimblkType oldDimblk = Dimblk; + Dimblk = dimblk; + ObjectId id = HostApplicationServices.WorkingDatabase.Dimblk; + Dimblk = oldDimblk; + return id; + } + + #endregion Dimblk + + #region OsMode + + /// + /// 捕捉模式系统变量类型 + /// + public enum OSModeType + { /// - /// 捕捉模式系统变量 + /// 无 /// - public static OSModeType OSMode - { - get - { - return (OSModeType)Convert.ToInt16(Application.GetSystemVariable("osmode")); - } - set - { - Application.SetSystemVariable("osmode", (int)value); - } - } + None = 0, + /// - /// 捕捉模式osm1是否包含osm2 + /// 端点 /// - /// 原模式 - /// 要比较的模式 - /// 包含时返回 ,不包含时返回 - public static bool Include(this OSModeType osm1, OSModeType osm2) - { - return (osm1 & osm2) == osm2; - } - #endregion OsMode + End = 1, - private static T ToEnum(this string value) - { - return (T)Enum.Parse(typeof(T), value, true); - } + /// + /// 中点 + /// + Middle = 2, - private static string GetName(this T value) - { - return Enum.GetName(typeof(T), value); - } + /// + /// 圆心 + /// + Center = 4, - #endregion Enum + /// + /// 节点 + /// + Node = 8, - #region 环境变量 /// - /// 获取cad变量 + /// 象限点 /// - /// 变量名 - /// 变量值 - public static object GetVar(string varName) - { - return Application.GetSystemVariable(varName); - } + Quadrant = 16, + /// - /// 设置cad变量 + /// 交点 /// - /// 变量名 - /// 变量值 - public static void SetVar(string varName, object value) - { - Application.SetSystemVariable(varName, value); - } -#nullable enable + Intersection = 32, + /// - /// 获取系统环境变量 + /// 插入点 /// - /// 变量名 - /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null - public static string? GetEnv(string var) - { - //从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 - return Environment.GetEnvironmentVariable(var); - } + Insert = 64, + /// - /// 设置系统环境变量 + /// 垂足 /// - /// 变量名 - /// 变量值 - public static void SetEnv(string var, string? value) - { - //创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 - Environment.SetEnvironmentVariable(var, value); - } -#nullable disable - #endregion + Pedal = 128, + /// + /// 切点 + /// + Tangent = 256, + + /// + /// 最近点 + /// + Nearest = 512, + + /// + /// 几何中心 + /// + Quick = 1024, + + /// + /// 外观交点 + /// + Appearance = 2048, /// - /// 命令行打印,会自动调用对象的toString函数 + /// 延伸 /// - /// 要打印的对象 - public static void Print(object message) => Editor.WriteMessage($"{message}\n"); + Extension = 4096, + + /// + /// 平行 + /// + Parallel = 8192 + } + + /// + /// 捕捉模式系统变量 + /// + public static OSModeType OSMode + { + get + { + return (OSModeType)Convert.ToInt16(Application.GetSystemVariable("osmode")); + } + set + { + Application.SetSystemVariable("osmode", (int)value); + } + } + /// + /// 捕捉模式osm1是否包含osm2 + /// + /// 原模式 + /// 要比较的模式 + /// 包含时返回 ,不包含时返回 + public static bool Include(this OSModeType osm1, OSModeType osm2) + { + return (osm1 & osm2) == osm2; + } + #endregion OsMode + + + private static string GetName(this T value) + { + return Enum.GetName(typeof(T), value); + } + + #endregion Enum + + #region 环境变量 + /// + /// 获取cad变量 + /// + /// 变量名 + /// 变量值 + public static object GetVar(string varName) + { + return Application.GetSystemVariable(varName); + } + /// + /// 设置cad变量 + /// + /// 变量名 + /// 变量值 + public static void SetVar(string varName, object value) + { + try + { + Application.SetSystemVariable(varName, value); + } + catch (System.Exception) + { + Env.Print($"{varName} 是不存在的变量!"); + } + } + /// + /// 获取系统环境变量 + /// + /// 变量名 + /// 指定的环境变量的值;或者如果找不到环境变量,则返回 null + public static string? GetEnv(string var) + { + //从当前进程或者从当前用户或本地计算机的 Windows 操作系统注册表项检索环境变量的值 + return Environment.GetEnvironmentVariable(var); + } + /// + /// 设置系统环境变量 + /// + /// 变量名 + /// 变量值 + public static void SetEnv(string var, string? value) + { + //创建、修改或删除当前进程中或者为当前用户或本地计算机保留的 Windows 操作系统注册表项中存储的环境变量 + Environment.SetEnvironmentVariable(var, value); } + #endregion + + + /// + /// 命令行打印,会自动调用对象的toString函数 + /// + /// 要打印的对象 + public static void Print(object message) => Editor.WriteMessage($"{message}\n"); + /// + /// 判断当前是否在UCS坐标下 + /// + /// Bool + public static bool IsUcs() => (short)GetVar("WORLDUCS") == 0; } diff --git a/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs b/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs new file mode 100644 index 0000000..5108dd5 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs @@ -0,0 +1,13 @@ +#if ac2008 //NET35 +namespace Autodesk.AutoCAD.DatabaseServices +{ + [Wrapper("AcDbDatabase::OpenMode")] + public enum FileOpenMode + { + OpenTryForReadShare = 4, + OpenForReadAndAllShare = 3, + OpenForReadAndWriteNoShare = 2, + OpenForReadAndReadShare = 1 + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/IAutoGo.cs b/src/IFoxCAD.Cad/Runtime/IAutoGo.cs new file mode 100644 index 0000000..27ce550 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/IAutoGo.cs @@ -0,0 +1,307 @@ +namespace IFoxCAD.Cad; + +using System.Diagnostics; + +/// +/// 加载时优先级 +/// +[Flags] +public enum Sequence : byte +{ + First,// 最先 + Last, // 最后 +} + +/// +/// 加载时自动执行接口 +/// +public interface IFoxAutoGo +{ + // 控制加载顺序 + Sequence SequenceId(); + // 关闭cad的时候会自动执行 + void Terminate(); + // 打开cad的时候会自动执行 + void Initialize(); +} + +/// +/// 加载时自动执行特性 +/// +[AttributeUsage(AttributeTargets.Method)] +public class IFoxInitialize : Attribute +{ + /// + /// 优先级 + /// + internal Sequence SequenceId; + /// + /// 用于初始化;用于结束回收 + /// + internal bool IsInitialize; + /// + /// 用于初始化和结束回收 + /// + /// 优先级 + /// 用于初始化;用于结束回收 + public IFoxInitialize(Sequence sequence = Sequence.Last, bool isInitialize = true) + { + SequenceId = sequence; + IsInitialize = isInitialize; + } +} + +//为了解决IExtensionApplication在一个dll内无法多次实现接口的关系 +//所以在这里反射加载所有的 IAutoGo ,以达到能分开写"启动运行"函数的目的 +class RunClass +{ + public Sequence Sequence { get; } + readonly MethodInfo _methodInfo; + + public RunClass(MethodInfo method, Sequence sequence) + { + _methodInfo = method; + Sequence = sequence; + } + + /// + /// 运行方法 + /// + public void Run() + { + _methodInfo.Invoke(); + } +} + +/// +/// 此类作为加载后cad自动运行接口的一部分,用于反射特性和接口 +/// 启动cad后的执行顺序为: +/// 1:特性..(多个) +/// 2:接口..(多个) +/// +public class AutoReflection +{ + static List _InitializeList = new(); //储存方法用于初始化 + static List _TerminateList = new(); //储存方法用于结束释放 + + readonly string _dllName; + readonly AutoRegConfig _autoRegConfig; + + /// + /// 反射执行 + /// 1.特性: + /// 2.接口: + /// + /// 约束在此dll进行加速 + public AutoReflection(string dllName, AutoRegConfig configInfo) + { + _dllName = dllName; + _autoRegConfig = configInfo; + } + + //启动cad的时候会自动执行 + public void Initialize() + { + try + { + //收集特性,包括启动时和关闭时 + if ((_autoRegConfig & AutoRegConfig.ReflectionAttribute) == AutoRegConfig.ReflectionAttribute) + GetAttributeFunctions(_InitializeList, _TerminateList); + + if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + GetInterfaceFunctions(_InitializeList, nameof(Initialize)); + + if (_InitializeList.Count > 0) + { + //按照 SequenceId 排序_升序 + _InitializeList = _InitializeList.OrderBy(runClass => runClass.Sequence).ToList(); + RunFunctions(_InitializeList); + } + } + catch (System.Exception) + { + Debugger.Break(); + } + } + + //关闭cad的时候会自动执行 + public void Terminate() + { + try + { + if ((_autoRegConfig & AutoRegConfig.ReflectionInterface) == AutoRegConfig.ReflectionInterface) + GetInterfaceFunctions(_TerminateList, nameof(Terminate)); + + if (_TerminateList.Count > 0) + { + //按照 SequenceId 排序_降序 + _TerminateList = _TerminateList.OrderByDescending(runClass => runClass.Sequence).ToList(); + RunFunctions(_TerminateList); + } + } + catch (System.Exception) + { + Debugger.Break(); + } + } + + /// + /// 遍历程序域下所有类型 + /// + /// 输出每个成员执行 + /// 过滤此dll,不含后缀 + public static void AppDomainGetTypes(Action action, string? dllNameWithoutExtension = null) + { +#if DEBUG + int error = 0; +#endif + try + { + var assemblies = AppDomain.CurrentDomain.GetAssemblies(); +#if !NET35 + //cad2021出现如下报错 + //System.NotSupportedException:动态程序集中不支持已调用的成员 + //assemblies = assemblies.Where(p => !p.IsDynamic).ToArray();//这个要容器类型转换 + assemblies = Array.FindAll(assemblies, p => !p.IsDynamic); +#endif + //主程序域 + for (int ii = 0; ii < assemblies.Length; ii++) + { + var assembly = assemblies[ii]; + + //获取类型集合,反射时候还依赖其他的dll就会这个错误 + //此通讯库要跳过,否则会报错. + var location = Path.GetFileNameWithoutExtension(assembly.Location); + if (dllNameWithoutExtension != null && location != dllNameWithoutExtension) + continue; + if (location == "AcInfoCenterConn")//通讯库 + continue; + + Type[]? types = null; + try + { + types = assembly.GetTypes(); + } + catch (ReflectionTypeLoadException) { continue; } + + if (types is null) + continue; + + for (int jj = 0; jj < types.Length; jj++) + { + var type = types[jj]; + if (type is not null) + { +#if DEBUG + ++error; +#endif + action(type); + } + } + } + } + catch (System.Exception e) + { +#if DEBUG + Debug.WriteLine($"出错:{nameof(AppDomainGetTypes)};计数{error};错误信息:{e.Message}"); + Debugger.Break(); +#endif + } + } + + /// + /// 收集接口下的函数 + /// + /// 储存要运行的方法 + /// 查找方法名 + /// + void GetInterfaceFunctions(List runClassList, string methodName) + { + const string sqid = nameof(Sequence) + "Id"; + + AppDomainGetTypes(type => { + if (type.IsAbstract) + return; + + var ints = type.GetInterfaces(); + for (int sss = 0; sss < ints.Length; sss++) + { + var inters = ints[sss]; + if (inters.Name != nameof(IFoxAutoGo)) + continue; + + Sequence? sequence = null; + MethodInfo? initialize = null; + + var mets = type.GetMethods(); + for (int jj = 0; jj < mets.Length; jj++) + { + var method = mets[jj]; + if (method.IsAbstract) + continue; + + if (method.Name == sqid) + { + var obj = method.Invoke(); + if (obj is not null) + sequence = (Sequence)obj; + continue; + } + else if (method.Name == methodName) + initialize = method; + + if (initialize is not null && sequence is not null) + break; + } + + if (initialize is null) + continue; + + var seq = sequence is null ? Sequence.Last : sequence.Value; + runClassList.Add(new RunClass(initialize, seq)); + break; + } + }, _dllName); + + } + + /// + /// 收集特性下的函数 + /// + void GetAttributeFunctions(List initialize, List terminate) + { + AppDomainGetTypes(type => { + if (type.IsAbstract) + return; + + var mets = type.GetMethods(); + for (int ii = 0; ii < mets.Length; ii++) + { + var method = mets[ii]; + var attr = method.GetCustomAttributes(true); + for (int jj = 0; jj < attr.Length; jj++) + { + if (attr[jj] is IFoxInitialize jjAtt) + { + var runc = new RunClass(method, jjAtt.SequenceId); + if (jjAtt.IsInitialize) + initialize.Add(runc); + else + terminate.Add(runc); + break; + } + } + } + }, _dllName); + } + + /// + /// 执行收集到的函数 + /// + static void RunFunctions(List runClassList) + { + for (int i = 0; i < runClassList.Count; i++) + runClassList[i].Run(); + runClassList.Clear(); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/Log.cs b/src/IFoxCAD.Cad/Runtime/Log.cs new file mode 100644 index 0000000..02293e2 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/Log.cs @@ -0,0 +1,412 @@ +namespace IFoxCAD.Cad; + +using System; +using System.Diagnostics; +using System.Threading; + +#region 写入日志到不同的环境中 +//https://zhuanlan.zhihu.com/p/338492989 +public abstract class LogBase +{ + public abstract void ReadLog(string message); + public abstract void WriteLog(string message); + public abstract void DeleteLog(string message); +} + +/// +/// 日志输出环境 +/// +public enum LogTarget +{ + /// + /// 文件 + /// + File = 1, + /// + /// 数据库 + /// + Database = 2, + /// + /// windows日志 + /// + EventLog = 4, +} + +/// +/// 写入到文件中 +/// +public class FileLogger : LogBase +{ + public override void DeleteLog(string message) + { + throw new NotImplementedException(); + } + + public override void ReadLog(string message) + { + throw new NotImplementedException(); + } + + public override void WriteLog(string? message) + { + //把异常信息输出到文件 + var sw = new StreamWriter(LogHelper.LogAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(message); + sw.Flush(); + sw.Close(); + sw.Dispose(); + } +} + +/// +/// 写入到数据库(暂时不支持) +/// +public class DBLogger : LogBase +{ + public override void DeleteLog(string message) + { + throw new NotImplementedException(); + } + + public override void ReadLog(string message) + { + throw new NotImplementedException(); + } + + public override void WriteLog(string? message) + { + throw new NotImplementedException(); + } +} + +/// +/// 写入到win日志 +/// +public class EventLogger : LogBase +{ + // https://docs.microsoft.com/en-us/answers/questions/526018/windows-event-log-with-net-5.html + // net50要加 + // 需要win权限 + + public string LogName = "IFoxCadLog"; + public override void DeleteLog(string message) + { +#if !NET5_0 && !NET6_0 + if (EventLog.Exists(LogName)) + EventLog.Delete(LogName); +#endif + } + + public override void ReadLog(string message) + { +#if !NET5_0 && !NET6_0 + EventLog eventLog = new(); + eventLog.Log = LogName; + foreach (EventLogEntry entry in eventLog.Entries) + { + //Write your custom code here + } +#endif + } + + public override void WriteLog(string? message) + { +#if !NET5_0 && !NET6_0 + try + { + EventLog eventLog = new() + { + Source = LogName + }; + eventLog.WriteEntry(message, EventLogEntryType.Information); + } + catch (System.Security.SecurityException e) + { + throw new Exception("您没有权限写入win日志中" + e.Message); + } +#endif + } +} + +#endregion + +#region 静态方法 +public static class LogHelper +{ +#pragma warning disable CA2211 // 非常量字段应当不可见 + /// + /// 日志文件完整路径 + /// + public static string? LogAddress; + /// + /// 输出错误信息到日志文件的开关 + /// + public static bool FlagOutFile = false; + /// + /// 输出错误信息到vs输出窗口的开关 + /// + public static bool FlagOutVsOutput = true; + +#pragma warning restore CA2211 // 非常量字段应当不可见 + + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 提供给外部设置log文件保存路径 + /// + /// 空的话就为运行的dll旁边的一个文件夹上 + public static void OptionFile(string? newlogAddress = null) + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + try + { + LogAddress = newlogAddress; + if (string.IsNullOrEmpty(LogAddress)) + { + //微软回复:静态构造函数只会被调用一次, + //并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + //https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + //新建文件夹 + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; //设置文件夹属性为普通 + } + + sb.Append('\\'); + sb.Append(DateTime.Now.ToString("yy-MM-dd")); + sb.Append(".log"); + LogAddress = sb.ToString(); + } + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } + + public static string WriteLog(this string? message, + LogTarget target = LogTarget.File) + { + if (message == null) + return string.Empty; + return LogAction(null, message, target); + } + + public static string WriteLog(this Exception? exception, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, null, target); + } + + public static string WriteLog(this Exception? exception, string? message, + LogTarget target = LogTarget.File) + { + if (exception == null) + return string.Empty; + return LogAction(exception, message, target); + } + + + + static string LogAction(Exception? ex, string? message, LogTarget target) + { + if (ex == null && message == null) + return string.Empty; + + if (target == LogTarget.File && LogAddress == null) + OptionFile(); + + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, message); + //var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt?.ToString(); + if (logtxtJson == null) + return string.Empty; + + if (FlagOutFile) + { + LogBase? logger; + switch (target) + { + case LogTarget.File: + logger = new FileLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.Database: + logger = new DBLogger(); + logger.WriteLog(logtxtJson); + break; + case LogTarget.EventLog: + logger = new EventLogger(); + logger.WriteLog(logtxtJson); + break; + } + } + + if (FlagOutVsOutput) + { + Debug.WriteLine("错误日志: " + LogAddress); + Debug.Write(logtxtJson); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endregion + +#region 序列化 +[Serializable] +public class LogTxt +{ + public string 当前时间; + public string? 备注信息; + public string? 异常信息; + public string? 异常对象; + public string? 触发方法; + public string? 调用堆栈; + + LogTxt() + { + // 以不同语言显示日期 + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("es-ES")) + // DateTime.Now.ToString("f", new System.Globalization.CultureInfo("zh-cn")) + // 为了最小信息熵,所以用这样的格式,并且我喜欢补0 + 当前时间 = DateTime.Now.ToString("yy-MM-dd hh:mm:ss"); + } + + public LogTxt(Exception? ex, string? message) : this() + { + if (ex == null && message == null) + throw new ArgumentNullException(nameof(ex)); + + if (ex != null) + { + 异常信息 = ex.Message; + 异常对象 = ex.Source; + 触发方法 = ex.TargetSite == null ? string.Empty : ex.TargetSite.ToString(); + 调用堆栈 = ex.StackTrace == null ? string.Empty : ex.StackTrace.Trim(); + } + if (message != null) + 备注信息 = message; + } + + /// 为了不引入json的dll,所以这里自己构造 + public override string? ToString() + { + var sb = new StringBuilder(); + sb.Append('{'); + sb.Append(Environment.NewLine); + sb.AppendLine($" \"{nameof(当前时间)}\": \"{当前时间}\""); + sb.AppendLine($" \"{nameof(备注信息)}\": \"{备注信息}\""); + sb.AppendLine($" \"{nameof(异常信息)}\": \"{异常信息}\""); + sb.AppendLine($" \"{nameof(异常对象)}\": \"{异常对象}\""); + sb.AppendLine($" \"{nameof(触发方法)}\": \"{触发方法}\""); + sb.AppendLine($" \"{nameof(调用堆栈)}\": \"{调用堆栈}\""); + sb.Append('}'); + return sb.ToString(); + } +} +#endregion + + +#if false //最简单的实现 +public static class Log +{ + /// + /// 读写锁 + /// 当资源处于写入模式时,其他线程写入需要等待本次写入结束之后才能继续写入 + /// + static readonly ReaderWriterLockSlim _logWriteLock = new(); + + /// + /// 日志文件完整路径 + /// + static readonly string _logAddress; + + static Log() + { + //微软回复:静态构造函数只会被调用一次, + //并且在它执行完成之前,任何其它线程都不能创建这个类的实例或使用这个类的静态成员 + //https://blog.csdn.net/weixin_34204722/article/details/90095812 + var sb = new StringBuilder(); + sb.Append(Environment.CurrentDirectory); + sb.Append("\\ErrorLog"); + + //新建文件夹 + var path = sb.ToString(); + if (!Directory.Exists(path)) + { + Directory.CreateDirectory(path) + .Attributes = FileAttributes.Normal; //设置文件夹属性为普通 + } + + sb.Append('\\'); + sb.Append(DateTime.Now.ToString("yy-MM-dd")); + sb.Append(".log"); + _logAddress = sb.ToString(); + } + + + /// + /// 将异常打印到日志文件 + /// + /// 异常 + /// 备注 + /// DEBUG模式打印到vs输出窗口 + public static string? WriteLog(this Exception? ex, + string? remarks = null, + bool printDebugWindow = true) + { + try + { + _logWriteLock.EnterWriteLock();// 写模式锁定 读写锁 + + var logtxt = new LogTxt(ex, remarks); + //var logtxtJson = Newtonsoft.Json.JsonConvert.SerializeObject(logtxt, Formatting.Indented); + var logtxtJson = logtxt.ToString(); + + if (logtxtJson == null) + return string.Empty; + + //把异常信息输出到文件 + var sw = new StreamWriter(_logAddress, true/*当天日志文件存在就追加,否则就创建*/); + sw.Write(logtxtJson); + sw.Flush(); + sw.Close(); + sw.Dispose(); + + if (printDebugWindow) + { + Debug.WriteLine("错误日志: " + _logAddress); + Debug.Write(logtxtJson); + //Debugger.Break(); + //Debug.Assert(false, "终止进程"); + } + return logtxtJson; + } + finally + { + _logWriteLock.ExitWriteLock();// 解锁 读写锁 + } + } +} +#endif \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs b/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs new file mode 100644 index 0000000..c64a5f8 --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs @@ -0,0 +1,53 @@ +namespace IFoxCAD.Cad; + +internal static class MethodInfoHelper +{ + private static readonly Dictionary methodDic = new(); + + /// + /// 执行函数 + /// + /// 函数 + /// 已经外部创建的对象,为空则此处创建 + public static object? Invoke(this MethodInfo methodInfo, object? instance = null) + { + if (methodInfo == null) + throw new ArgumentNullException(nameof(methodInfo)); + + object? result = null; + if (methodInfo.IsStatic) + { + //新函数指针进入此处 + //参数数量一定要匹配,为null则参数个数不同导致报错, + //参数为stirng[],则可以传入object[]代替,其他参数是否还可以实现默认构造? + var paramInfos = methodInfo.GetParameters(); + var args = new List { }; + for (int i = 0; i < paramInfos.Length; i++) + args.Add(null!); + result = methodInfo.Invoke(null, args.ToArray());//静态调用 + } + else + { + //原命令的函数指针进入此处 + //object instance; + if (methodDic.ContainsKey(methodInfo)) + instance = methodDic[methodInfo]; + + if (instance == null) + { + var reftype = methodInfo.ReflectedType; + if (reftype == null) return null; + var fullName = reftype.FullName; //命名空间+类 + if (fullName == null) return null; + var type = reftype.Assembly.GetType(fullName);//通过程序集反射创建类+ + if (type == null) return null; + instance = Activator.CreateInstance(type); + if (!type.IsAbstract)//无法创建抽象类成员 + methodDic.Add(methodInfo, instance); + } + if (instance != null) + result = methodInfo.Invoke(instance, null); //非静态,调用实例化方法 + } + return result; + } +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs index c319b5e..00bba7b 100644 --- a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs +++ b/src/IFoxCAD.Cad/Runtime/SymbolTable.cs @@ -1,317 +1,327 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using Autodesk.AutoCAD.DatabaseServices; -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +public class SymbolTable : IEnumerable + where TTable : SymbolTable + where TRecord : SymbolTableRecord, new() { - public class SymbolTable : IEnumerable - where TTable : SymbolTable - where TRecord : SymbolTableRecord, new() - { - #region 程序集内部属性 - /// - /// 事务管理器 - /// - internal DBTrans DTrans { get; private set; } - /// - /// 数据库 - /// - internal Database Database { get; private set; } + #region 程序集内部属性 + /// + /// 事务管理器 + /// + internal DBTrans DTrans { get; private set; } + /// + /// 数据库 + /// + internal Database Database { get; private set; } - #endregion + #endregion - #region 公开属性 - /// - /// 当前符号表 - /// - public TTable CurrentSymbolTable { get; private set; } - #endregion + #region 公开属性 + /// + /// 当前符号表 + /// + public TTable CurrentSymbolTable { get; private set; } + #endregion - #region 构造函数 - /// - /// 构造函数,初始化Trans和CurrentSymbolTable属性 - /// - /// 事务管理器 - /// 符号表id - internal SymbolTable(DBTrans tr, ObjectId tableId) - { - DTrans = tr; - CurrentSymbolTable = DTrans.GetObject(tableId); - } + #region 构造函数 + /// + /// 构造函数,初始化Trans和CurrentSymbolTable属性 + /// + /// 事务管理器 + /// 符号表id + internal SymbolTable(DBTrans tr, ObjectId tableId) + { + DTrans = tr; + Database = tr.Database; + CurrentSymbolTable = DTrans.GetObject(tableId)!; + } - #endregion + #endregion - #region 索引器 - /// - /// 索引器 - /// - /// 对象名称 - /// 对象的id - public ObjectId this[string key] + #region 索引器 + /// + /// 索引器 + /// + /// 对象名称 + /// 对象的id + public ObjectId this[string key] + { + get { - get - { - if (Has(key)) - { - return CurrentSymbolTable[key]; - } - return ObjectId.Null; - } + if (Has(key)) + return CurrentSymbolTable[key]; + return ObjectId.Null; } - #endregion + } + #endregion - #region Has - /// - /// 判断是否存在符号表记录 - /// - /// 记录名 - /// 存在返回 , 不存在返回 - public bool Has(string key) - { - return CurrentSymbolTable.Has(key); - } - /// - /// 判断是否存在符号表记录 - /// - /// 记录id - /// 存在返回 , 不存在返回 - public bool Has(ObjectId objectId) - { - return CurrentSymbolTable.Has(objectId); - } - #endregion + #region Has + /// + /// 判断是否存在符号表记录 + /// + /// 记录名 + /// 存在返回 , 不存在返回 + public bool Has(string key) + { + return CurrentSymbolTable.Has(key); + } + /// + /// 判断是否存在符号表记录 + /// + /// 记录id + /// 存在返回 , 不存在返回 + public bool Has(ObjectId objectId) + { + return CurrentSymbolTable.Has(objectId); + } + #endregion - #region 添加符号表记录 - /// - /// 添加符号表记录 - /// - /// 符号表记录 - /// 对象id - private ObjectId Add(TRecord record) + #region 添加符号表记录 + /// + /// 添加符号表记录 + /// + /// 符号表记录 + /// 对象id + private ObjectId Add(TRecord record) + { + ObjectId id; + using (CurrentSymbolTable.ForWrite()) { - ObjectId id; - using (CurrentSymbolTable.ForWrite()) - { - id = CurrentSymbolTable.Add(record); - DTrans.Transaction.AddNewlyCreatedDBObject(record, true); - } - return id; + id = CurrentSymbolTable.Add(record); + DTrans.Transaction.AddNewlyCreatedDBObject(record, true); } - /// - /// 添加符号表记录 - /// - /// 符号表记录名 - /// 符号表记录处理函数的无返回值委托 - /// 对象id - public ObjectId Add(string name, Action action = null) - { - ObjectId id = this[name]; - if (id.IsNull) - { - TRecord record = new() - { - Name = name - }; - id = Add(record); - using (record.ForWrite()) - { - action?.Invoke(record); - } - } + return id; + } + /// + /// 添加符号表记录 + /// + /// 符号表记录名 + /// 符号表记录处理函数的无返回值委托 + /// 对象id + public ObjectId Add(string name, Action? action = null) + { + ObjectId id = this[name]; + if (id.IsNull) return id; - } - #endregion - #region 删除符号表记录 - /// - /// 删除符号表记录 - /// - /// 符号表记录对象 - private static void Remove(TRecord record) + var record = new TRecord() { - using (record.ForWrite()) - { - record.Erase(); - } - } - /// - /// 删除符号表记录 - /// - /// 符号表记录名 - public void Remove(string name) - { - TRecord record = GetRecord(name); - if (record is not null) - { - Remove(record); - } + Name = name + }; + id = Add(record); + using (record.ForWrite()) + action?.Invoke(record); + return id; + } + #endregion - } - /// - /// 删除符号表记录 - /// - /// 符号表记录对象id - public void Remove(ObjectId id) - { - TRecord record = GetRecord(id); - if (record is not null) - { - Remove(record); - } - } + #region 删除符号表记录 + /// + /// 删除符号表记录 + /// + /// 符号表记录对象 + private static void Remove(TRecord record) + { + using (record.ForWrite()) + record.Erase(); + } + /// + /// 删除符号表记录 + /// + /// 符号表记录名 + public void Remove(string name) + { + var record = GetRecord(name); + if (record is not null) + Remove(record); + } - #endregion + /// + /// 删除符号表记录 + /// + /// 符号表记录对象id + public void Remove(ObjectId id) + { + var record = GetRecord(id); + if (record is not null) + Remove(record); + } + #endregion - #region 修改符号表记录 - /// - /// 修改符号表 - /// - /// 符号表记录 - /// 修改委托 - private static void Change(TRecord record, Action action) + #region 修改符号表记录 + /// + /// 修改符号表 + /// + /// 符号表记录 + /// 修改委托 + private static void Change(TRecord record, Action action) + { + using (record.ForWrite()) { - using (record.ForWrite()) - { - action?.Invoke(record); - } + action.Invoke(record); } - /// - /// 修改符号表 - /// - /// 符号表记录名 - /// 修改委托 - public void Change(string name, Action action) + // 调用regen()函数可能会导致卡顿 + //Env.Editor.Regen(); + } + /// + /// 修改符号表 + /// + /// 符号表记录名 + /// 修改委托 + public void Change(string name, Action action) + { + var record = GetRecord(name); + if (record is not null) { - var record = GetRecord(name); - if (record is not null) - { - Change(record, action); - } + Change(record, action); } - /// - /// 修改符号表 - /// - /// 符号表记录id - /// 修改委托 - public void Change(ObjectId id, Action action) + } + /// + /// 修改符号表 + /// + /// 符号表记录id + /// 修改委托 + public void Change(ObjectId id, Action action) + { + var record = GetRecord(id); + if (record is not null) { - var record = GetRecord(id); - if (record is not null) - { - Change(record, action); - } + Change(record, action); } - #endregion + } + #endregion - #region 获取符号表记录 - /// - /// 获取符号表记录 - /// - /// 符号表记录的id - /// 打开模式,默认为只读 - /// 符号表记录 - public TRecord GetRecord(ObjectId id, OpenMode openMode = OpenMode.ForRead) => id.IsNull ? null : DTrans.GetObject(id, openMode); + #region 获取符号表记录 + /// + /// 获取符号表记录 + /// + /// 符号表记录的id + /// 打开模式,默认为只读 + /// 符号表记录 + public TRecord? GetRecord(ObjectId id, OpenMode openMode = OpenMode.ForRead) => /*id.IsNull ? null : */DTrans.GetObject(id, openMode); - /// - /// 获取符号表记录 - /// - /// 符号表记录名 - /// 打开模式,默认为只读 - /// 符号表记录 - public TRecord GetRecord(string name, OpenMode openMode = OpenMode.ForRead) => GetRecord(this[name], openMode); + /// + /// 获取符号表记录 + /// + /// 符号表记录名 + /// 打开模式,默认为只读 + /// 符号表记录 + public TRecord? GetRecord(string name, OpenMode openMode = OpenMode.ForRead) => GetRecord(this[name], openMode); - /// - /// 获取符号表记录 - /// - /// 符号表记录集合 - public IEnumerable GetRecords() - { - return this.Select(id => GetRecord(id)); - } - - /// - /// 获取符号表记录的名字集合 - /// - /// 记录的名字集合 - public IEnumerable GetRecordNames() => this.Select(id => GetRecord(id).Name); - /// - /// 获取符合过滤条件的符号表记录名字集合 - /// - /// 过滤器委托 - /// 记录的名字集合 - public IEnumerable GetRecordNames(Func filter) + /// + /// 获取符号表记录 + /// + /// 符号表记录集合 + public IEnumerable GetRecords() + { + foreach (var item in this) { - foreach (var id in this) + var record = GetRecord(item); + if (record is not null) { - var record = GetRecord(id); - if (filter.Invoke(record)) - { - yield return record.Name; - } + yield return record; } } + } - /// - /// 从源数据库拷贝符号表记录 - /// - /// 符号表 - /// 符号表记录名 - /// 是否覆盖, 为覆盖, 为不覆盖 - /// 对象id - public ObjectId GetRecordFrom(SymbolTable table, string name, bool over) + /// + /// 获取符号表记录的名字集合 + /// + /// 记录的名字集合 + public IEnumerable GetRecordNames() => GetRecords().Select(record => record.Name); + /// + /// 获取符合过滤条件的符号表记录名字集合 + /// + /// 过滤器委托 + /// 记录的名字集合 + public IEnumerable GetRecordNames(Func filter) + { + foreach (var item in this) { - if (table is null) - { - throw new ArgumentNullException(nameof(table)); - } - - ObjectId rid = this[name]; - bool has = rid != ObjectId.Null; - if ((has && over) || !has) + var record = GetRecord(item); + if (record is not null && filter.Invoke(record)) { - ObjectId id = table[name]; - using IdMapping idm = new(); - using (ObjectIdCollection ids = new() { id }) - { - table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); - } - rid = idm[id].Value; + yield return record.Name; } - return rid; } + } - /// - /// 从文件拷贝符号表记录 - /// - /// 符号表过滤器 - /// 文件名 - /// 符号表记录名 - /// 是否覆盖, 为覆盖, 为不覆盖 - /// 对象id - internal ObjectId GetRecordFrom(Func> tableSelector, string fileName, string name, bool over) + /// + /// 从源数据库拷贝符号表记录 + /// + /// 符号表 + /// 符号表记录名 + /// 是否覆盖, 为覆盖, 为不覆盖 + /// 对象id + public ObjectId GetRecordFrom(SymbolTable table, string name, bool over) + { + if (table is null) { - using var tr = new DBTrans(fileName); - return GetRecordFrom(tableSelector(tr), name, over); + throw new ArgumentNullException(nameof(table), "对象为null"); } - #endregion - #region IEnumerable 成员 - - public IEnumerator GetEnumerator() + ObjectId rid = this[name]; + bool has = rid != ObjectId.Null; + if ((has && over) || !has) { - - foreach (var id in CurrentSymbolTable) + ObjectId id = table[name]; + using IdMapping idm = new(); + using (ObjectIdCollection ids = new() { id }) { - yield return id; + table.Database.WblockCloneObjects(ids, CurrentSymbolTable.Id, idm, DuplicateRecordCloning.Replace, false); } + rid = idm[id].Value; } + return rid; + } + + /// + /// 从文件拷贝符号表记录 + /// + /// 符号表过滤器 + /// 文件名 + /// 符号表记录名 + /// 是否覆盖, 为覆盖, 为不覆盖 + /// 对象id + internal ObjectId GetRecordFrom(Func> tableSelector, string fileName, string name, bool over) + { + using var tr = new DBTrans(fileName); + return GetRecordFrom(tableSelector(tr), name, over); + } + #endregion + + #region 遍历 + /// + /// 遍历集合的迭代器,执行action委托 + /// + /// 集合值的类型 + /// 集合 + /// 要运行的委托 + public void ForEach(Action action, OpenMode openMode = OpenMode.ForRead) + { + //GetRecords().ForEach(re => action(re)); - IEnumerator IEnumerable.GetEnumerator() + foreach (var item in this) { - return GetEnumerator(); + var record = GetRecord(item, openMode); + if (record is not null) + action(record); } - #endregion } + #endregion + + #region IEnumerable 成员 + + public IEnumerator GetEnumerator() + { + foreach (var id in CurrentSymbolTable) + yield return id; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + #endregion } diff --git a/src/IFoxCAD.Cad/Runtime/Utils.cs b/src/IFoxCAD.Cad/Runtime/Utils.cs new file mode 100644 index 0000000..eb372ab --- /dev/null +++ b/src/IFoxCAD.Cad/Runtime/Utils.cs @@ -0,0 +1,98 @@ +using System; + +namespace IFoxCAD.Cad; + +public class Helper +{ + /* + * id = db.GetObjectId(false, handle, 0); + * 参数意义: db.GetObjectId(如果没有找到就创建,句柄号,标记..将来备用) + * 在vs的输出会一直抛出: + * 引发的异常:“Autodesk.AutoCAD.Runtime.Exception”(位于 AcdbMgd.dll 中) + * "eUnknownHandle" + * 这就是为什么慢的原因,所以直接运行就好了!而Debug还是需要用arx的API替代. + */ + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb17.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId17x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QAE?AW4ErrorStatus@Acad@@AAVAcDbObjectId@@_NABVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x32(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + [System.Security.SuppressUnmanagedCodeSecurity] + [DllImport("acdb18.dll", CallingConvention = CallingConvention.ThisCall/*08的调用约定 高版本是__cdecl*/, + EntryPoint = "?getAcDbObjectId@AcDbDatabase@@QEAA?AW4ErrorStatus@Acad@@AEAVAcDbObjectId@@_NAEBVAcDbHandle@@K@Z")] + extern static int getAcDbObjectId18x64(IntPtr db, out ObjectId id, [MarshalAs(UnmanagedType.U1)] bool createnew, ref Handle h, uint reserved); + + /// + /// 句柄转id,NET35(08~12)专用的 + /// + /// 数据库 + /// 句柄 + /// 返回的id + /// 不存在则创建 + /// 保留,用于未来 + /// 成功0,其他值都是错误.可以强转ErrorStatus + static int GetAcDbObjectId(IntPtr db, Handle handle, out ObjectId id, bool createIfNotFound = false, uint reserved = 0) + { + id = ObjectId.Null; + switch (Application.Version.Major) + { + case 17: + { + if (IntPtr.Size == 4) + return getAcDbObjectId17x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId17x64(db, out id, createIfNotFound, ref handle, reserved); + } + case 18: + { + if (IntPtr.Size == 4) + return getAcDbObjectId18x32(db, out id, createIfNotFound, ref handle, reserved); + else + return getAcDbObjectId18x64(db, out id, createIfNotFound, ref handle, reserved); + } + } + return -1; + } + + /// + /// 句柄转id + /// + /// 数据库 + /// 句柄 + /// id + public static ObjectId TryGetObjectId(Database db, Handle handle) + { +#if !NET35 + //高版本直接利用 + var es = db.TryGetObjectId(handle, out ObjectId id); + //if (!es) +#else + var es = GetAcDbObjectId(db.UnmanagedObject, handle, out ObjectId id); + //if (ErrorStatus.OK != (ErrorStatus)es) +#endif + return id; + } + + //public static int GetCadFileVersion(string filename) + //{ + // var bytes = File.ReadAllBytes(filename); + // var headstr = Encoding.Default.GetString(bytes)[0..6]; + // if (!headstr.StartsWith("AC")) return 0; + // var vernum = int.Parse(headstr.Replace("AC", "")); + // var a = Enum.Parse(typeof(DwgVersion), "AC1800"); + // Enum.TryParse() + // return vernum + 986; + + //} +} \ No newline at end of file diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs index 567be46..9727b72 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs @@ -1,83 +1,79 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 比较运算符类 +/// +public class OpComp : OpEqual { /// - /// 比较运算符类 + /// 比较运算符,如: + /// "<=" + /// 以及合并比较运算符: + /// "<=,<=,=" /// - public class OpComp : OpEqual - { - /// - /// 比较运算符,如: - /// "<=" - /// 以及合并比较运算符: - /// "<=,<=,=" - /// - public string Content { get; } + public string Content { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Comp"; } - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Comp"; } + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 数据 - public OpComp(string content, TypedValue value) - : base(value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 数据 + public OpComp(string content, TypedValue value) + : base(value) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - public OpComp(string content, int code) - : base(code) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + public OpComp(string content, int code) + : base(code) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - /// 组码值 - public OpComp(string content, int code, object value) - : base(code, value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + /// 组码值 + public OpComp(string content, int code, object value) + : base(code, value) + { + Content = content; + } - /// - /// 比较运算符类构造函数 - /// - /// 运算符 - /// 组码 - /// 组码值 - public OpComp(string content, DxfCode code, object value) - : base(code, value) - { - Content = content; - } + /// + /// 比较运算符类构造函数 + /// + /// 运算符 + /// 组码 + /// 组码值 + public OpComp(string content, DxfCode code, object value) + : base(code, value) + { + Content = content; + } - /// - /// 获取过滤器数据迭代器 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return new TypedValue(-4, Content); - yield return Value; - } + /// + /// 获取过滤器数据迭代器 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return new TypedValue(-4, Content); + yield return Value; } } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs index 76dc738..370a0c1 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs @@ -1,90 +1,86 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 相等运算符类 +/// +public class OpEqual : OpFilter { /// - /// 相等运算符类 + /// 组码与匹配值的TypedValue类型值 /// - public class OpEqual : OpFilter - { - /// - /// 组码与匹配值的TypedValue类型值 - /// - public TypedValue Value { get; private set; } + public TypedValue Value { get; private set; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Equal"; } - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Equal"; } + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - public OpEqual(int code) - { - Value = new TypedValue(code); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + public OpEqual(int code) + { + Value = new TypedValue(code); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - /// 组码值 - public OpEqual(int code, object value) - { - Value = new TypedValue(code, value); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + /// 组码值 + public OpEqual(int code, object value) + { + Value = new TypedValue(code, value); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码 - /// 组码值 - public OpEqual(DxfCode code, object value) - { - Value = new TypedValue((int)code, value); - } + /// + /// 相等运算符类构造函数 + /// + /// 组码 + /// 组码值 + public OpEqual(DxfCode code, object value) + { + Value = new TypedValue((int)code, value); + } - /// - /// 相等运算符类构造函数 - /// - /// 组码与组码值的TypedValue类型值 - internal OpEqual(TypedValue value) - { - Value = value; - } + /// + /// 相等运算符类构造函数 + /// + /// 组码与组码值的TypedValue类型值 + internal OpEqual(TypedValue value) + { + Value = value; + } - /// - /// 过滤器数据迭代器 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return Value; - } + /// + /// 过滤器数据迭代器 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return Value; + } - /// - /// 设置数据 - /// - /// 组码值 - public void SetValue(object value) - { - Value = new TypedValue(Value.TypeCode, value); - } + /// + /// 设置数据 + /// + /// 组码值 + public void SetValue(object value) + { + Value = new TypedValue(Value.TypeCode, value); + } - /// - /// 设置数据 - /// - /// 组码 - /// 组码值 - public void SetValue(int code, object value) - { - Value = new TypedValue(code, value); - } + /// + /// 设置数据 + /// + /// 组码 + /// 组码值 + public void SetValue(int code, object value) + { + Value = new TypedValue(code, value); } } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs index cd73129..a698aeb 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs @@ -1,342 +1,346 @@ -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using System; -using System.Collections.Generic; -using System.Linq; - -namespace IFoxCAD.Cad +namespace IFoxCAD.Cad; + +/// +/// 选择集过滤器抽象类 +/// +public abstract class OpFilter { /// - /// 选择集过滤器抽象类 + /// 过滤器的名字 + /// + public abstract string Name { get; } + + /// + /// 获取TypedValue类型的值的迭代器的抽象方法,子类必须重写 + /// + /// TypedValue迭代器 + public abstract IEnumerable GetValues(); + + /// + /// 非操作符,返回的是OpFilter类型变量的 属性 + /// + /// OpFilter类型变量 + /// OpFilter对象 + public static OpFilter operator !(OpFilter item) + { + return item.Not; + } + + /// + /// 只读属性,表示这个过滤器取反 + /// + public OpFilter Not + { + get { return new OpNot(this); } + } + + /// + /// 过滤器值转换为 TypedValue 类型数组 + /// + /// TypedValue数组 + public TypedValue[] ToArray() + { + return GetValues().ToArray(); + } + + /// + /// 隐式类型转换,将自定义的过滤器转换为 Autocad 认识的选择集过滤器 + /// + /// 过滤器对象 + /// + /// 选择集过滤器. + /// + public static implicit operator SelectionFilter(OpFilter item) + { + return new SelectionFilter(item.ToArray()); + } + + /// + /// 转换为字符串 + /// + /// 字符串 + public override string ToString() + { + string s = ""; + foreach (var value in GetValues()) + s += value.ToString(); + return s; + } + + /// + /// 构建过滤器 + /// + /// + /// 举两个利用构建函数创建选择集过滤的例子 + /// + /// !(e.Dxf(0) == "line" & e.Dxf(8) == "0") + /// | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); + /// + /// 例子2: + /// var f2 = OpFilter.Bulid( + /// e => e.Or( + /// !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), + /// e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", + /// e.Dxf(10) <= new Point3d(10, 10, 0)))); + /// ]]> + /// + /// 构建过滤器的函数委托 + /// 过滤器 + public static OpFilter Bulid(Func func) + { + return func(new Op()).Filter!; + } + + #region Operator + + /// + /// 过滤器操作符类 /// - public abstract class OpFilter + public class Op { /// - /// 过滤器的名字 + /// 过滤器属性 /// - public abstract string Name { get; } + internal OpFilter? Filter { get; private set; } + + internal Op() + { + } + + private Op(OpFilter filter) + { + Filter = filter; + } /// - /// 获取TypedValue类型的值的迭代器的抽象方法,子类必须重写 + /// AND 操作符 /// - /// TypedValue迭代器 - public abstract IEnumerable GetValues(); + /// 操作符类型的可变参数 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op And(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static + { + var filter = new OpAnd(); + foreach (var op in args) + filter.Add(op.Filter!); + return new Op(filter); + } /// - /// 非操作符,返回的是OpFilter类型变量的 属性 + /// or 操作符 /// - /// OpFilter类型变量 - /// OpFilter对象 - public static OpFilter operator !(OpFilter item) + /// 操作符类型的可变参数 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Or(params Op[] args) +#pragma warning restore CA1822 // 将成员标记为 static { - return item.Not; + var filter = new OpOr(); + foreach (var op in args) + filter.Add(op.Filter!); + return new Op(filter); } /// - /// 只读属性,表示这个过滤器取反 + /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 /// - public OpFilter Not + /// 组码 + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code) +#pragma warning restore CA1822 // 将成员标记为 static { - get { return new OpNot(this); } + return new Op(new OpEqual(code)); } /// - /// 过滤器值转换为 TypedValue 类型数组 + /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 /// - /// TypedValue数组 - public TypedValue[] ToArray() + /// 组码 + /// 关系运算符的值,比如">,>,=" + /// Op对象 +#pragma warning disable CA1822 // 将成员标记为 static + public Op Dxf(int code, string content) +#pragma warning restore CA1822 // 将成员标记为 static { - return GetValues().ToArray(); + return new Op(new OpComp(content, code)); } /// - /// 隐式类型转换,将自定义的过滤器转换为 Autocad 认识的选择集过滤器 + /// 非操作符 /// - /// 过滤器对象 - /// - /// 选择集过滤器. - /// - public static implicit operator SelectionFilter(OpFilter item) + /// 过滤器操作符对象 + /// Op对象 + public static Op operator !(Op right) { - return new SelectionFilter(item.ToArray()); + right.Filter = !right.Filter!; + return right; } /// - /// 转换为字符串 + /// 相等操作符 /// - /// 字符串 - public override string ToString() + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator ==(Op left, object right) { - string s = ""; - foreach (var value in GetValues()) - s += value.ToString(); - return s; + var eq = (OpEqual)left.Filter!; + eq.SetValue(right); + return left; } /// - /// 构建过滤器 + /// 不等操作符 /// - /// - /// 举两个利用构建函数创建选择集过滤的例子 - /// - /// !(e.Dxf(0) == "line" & e.Dxf(8) == "0") - /// | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); - /// - /// 例子2: - /// var f2 = OpFilter.Bulid( - /// e => e.Or( - /// !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), - /// e.And(e.Dxf(0) != "circle", e.Dxf(8) == "2", - /// e.Dxf(10) <= new Point3d(10, 10, 0)))); - /// ]]> - /// - /// 构建过滤器的函数委托 - /// 过滤器 - public static OpFilter Bulid(Func func) + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator !=(Op left, object right) { - return func(new OpFilter.Op()).Filter; + var eq = (OpEqual)left.Filter!; + eq.SetValue(right); + left.Filter = eq.Not; + return left; } - #region Operator + private static Op GetCompOp(string content, Op left, object right) + { + var eq = (OpEqual)left.Filter!; + var comp = new OpComp(content, eq.Value.TypeCode, right); + return new Op(comp); + } /// - /// 过滤器操作符类 + /// 大于操作符 /// - public class Op + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator >(Op left, object right) { - /// - /// 过滤器属性 - /// - internal OpFilter Filter { get; private set; } + return GetCompOp(">", left, right); + } - internal Op() - { - } + /// + /// 小于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator <(Op left, object right) + { + return GetCompOp("<", left, right); + } - private Op(OpFilter filter) - { - Filter = filter; - } - - /// - /// AND 操作符 - /// - /// 操作符类型的可变参数 - /// Op对象 - public Op And(params Op[] args) - { - var filter = new OpAnd(); - foreach (var op in args) - filter.Add(op.Filter); - return new Op(filter); - } - - /// - /// or 操作符 - /// - /// 操作符类型的可变参数 - /// Op对象 - public Op Or(params Op[] args) - { - var filter = new OpOr(); - foreach (var op in args) - filter.Add(op.Filter); - return new Op(filter); - } - - /// - /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 - /// - /// 组码 - /// Op对象 - public Op Dxf(int code) - { - return new Op(new OpEqual(code)); - } - - /// - /// dxf 操作符,此函数只能用于过滤器中,不是组码操作函数 - /// - /// 组码 - /// 关系运算符的值,比如">,>,=" - /// Op对象 - public Op Dxf(int code, string content) - { - return new Op(new OpComp(content, code)); - } - - /// - /// 非操作符 - /// - /// 过滤器操作符对象 - /// Op对象 - public static Op operator !(Op right) - { - right.Filter = !right.Filter; - return right; - } - - /// - /// 相等操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator ==(Op left, object right) - { - var eq = (OpEqual)left.Filter; - eq.SetValue(right); - return left; - } - - /// - /// 不等操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator !=(Op left, object right) - { - var eq = (OpEqual)left.Filter; - eq.SetValue(right); - left.Filter = eq.Not; - return left; - } + /// + /// 大于等于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator >=(Op left, object right) + { + return GetCompOp(">=", left, right); + } - private static Op GetCompOp(string content, Op left, object right) - { - var eq = (OpEqual)left.Filter; - var comp = new OpComp(content, eq.Value.TypeCode, right); - return new Op(comp); - } - - /// - /// 大于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator >(Op left, object right) - { - return GetCompOp(">", left, right); - } - - /// - /// 小于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator <(Op left, object right) - { - return GetCompOp("<", left, right); - } - - /// - /// 大于等于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator >=(Op left, object right) - { - return GetCompOp(">=", left, right); - } - - /// - /// 小于等于操作符 - /// - /// 过滤器操作符对象 - /// 数据 - /// Op对象 - public static Op operator <=(Op left, object right) - { - return GetCompOp("<=", left, right); - } - - /// - /// 大于等于操作符 - /// - /// 过滤器操作符对象 - /// 点 - /// Op对象 - public static Op operator >=(Op left, Point3d right) - { - return GetCompOp(">,>,*", left, right); - } - - /// - /// 小于等于操作符 - /// - /// 过滤器操作符对象 - /// 点 - /// Op对象 - public static Op operator <=(Op left, Point3d right) - { - return GetCompOp("<,<,*", left, right); - } - - /// - /// 并操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator &(Op left, Op right) - { - var filter = new OpAnd(); - filter.Add(left.Filter); - filter.Add(right.Filter); - return new Op(filter); - } - - /// - /// 或操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator |(Op left, Op right) + /// + /// 小于等于操作符 + /// + /// 过滤器操作符对象 + /// 数据 + /// Op对象 + public static Op operator <=(Op left, object right) + { + return GetCompOp("<=", left, right); + } + + /// + /// 大于等于操作符 + /// + /// 过滤器操作符对象 + /// 点 + /// Op对象 + public static Op operator >=(Op left, Point3d right) + { + return GetCompOp(">,>,*", left, right); + } + + /// + /// 小于等于操作符 + /// + /// 过滤器操作符对象 + /// 点 + /// Op对象 + public static Op operator <=(Op left, Point3d right) + { + return GetCompOp("<,<,*", left, right); + } + + /// + /// 并操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator &(Op left, Op right) + { + var filter = new OpAnd { - var filter = new OpOr(); - filter.Add(left.Filter); - filter.Add(right.Filter); - return new Op(filter); - } - - /// - /// 异或操作符 - /// - /// 过滤器操作符对象 - /// 过滤器操作符对象 - /// Op对象 - public static Op operator ^(Op left, Op right) + left.Filter!, + right.Filter! + }; + return new Op(filter); + } + + /// + /// 或操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator |(Op left, Op right) + { + var filter = new OpOr { - var filter = new OpXor(left.Filter, right.Filter); - return new Op(filter); - } - - /// - /// 比较函数 - /// - /// 对象 - /// - /// 是否相等 - /// - public override bool Equals(object obj) => base.Equals(obj); - - /// - /// 获取HashCode - /// - /// HashCode - public override int GetHashCode() => base.GetHashCode(); + left.Filter!, + right.Filter! + }; + return new Op(filter); } - #endregion Operator + /// + /// 异或操作符 + /// + /// 过滤器操作符对象 + /// 过滤器操作符对象 + /// Op对象 + public static Op operator ^(Op left, Op right) + { + var filter = new OpXor(left.Filter!, right.Filter!); + return new Op(filter); + } + + /// + /// 比较函数 + /// + /// 对象 + /// + /// 是否相等 + /// + public override bool Equals(object obj) => base.Equals(obj); + + /// + /// 获取HashCode + /// + /// HashCode + public override int GetHashCode() => base.GetHashCode(); } + + #endregion Operator } diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs index 06a5408..3a02513 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpList.cs @@ -1,170 +1,166 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 逻辑操作符的列表抽象类 +/// +public abstract class OpList : OpLogi { /// - /// 逻辑操作符的列表抽象类 + /// 过滤器列表 /// - public abstract class OpList : OpLogi + protected List _lst = new(); + + /// + /// 添加过滤器条件的虚函数,子类可以重写 + /// + /// 举个利用这个类及其子类创建选择集过滤的例子 + /// + /// ,>,*" } + /// }, + /// }; + /// ]]> + /// + /// 过滤器对象 + public virtual void Add(OpFilter value) { - /// - /// 过滤器列表 - /// - protected List _lst = new(); + _lst.Add(value); + } - /// - /// 添加过滤器条件的虚函数,子类可以重写 - /// - /// 举个利用这个类及其子类创建选择集过滤的例子 - /// - /// ,>,*" } - /// }, - /// }; - /// ]]> - /// - /// 过滤器对象 - public virtual void Add(OpFilter value) - { - _lst.Add(value); - } + /// + /// 添加过滤条件 + /// + /// 逻辑非~ + /// 组码 + /// 组码值 + public void Add(string speccode, int code, object value) + { + if (speccode == "~") + _lst.Add(new OpEqual(code, value).Not); + } - /// - /// 添加过滤条件 - /// - /// 逻辑非~ - /// 组码 - /// 组码值 - public void Add(string speccode, int code, object value) - { - if (speccode == "~") - _lst.Add(new OpEqual(code, value).Not); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + public void Add(int code, object value) + { + _lst.Add(new OpEqual(code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - public void Add(int code, object value) - { - _lst.Add(new OpEqual(code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + public void Add(DxfCode code, object value) + { + _lst.Add(new OpEqual(code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - public void Add(DxfCode code, object value) - { - _lst.Add(new OpEqual(code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + /// 比较运算符 + public void Add(int code, object value, string comp) + { + _lst.Add(new OpComp(comp, code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - /// 比较运算符 - public void Add(int code, object value, string comp) - { - _lst.Add(new OpComp(comp, code, value)); - } + /// + /// 添加过滤条件 + /// + /// 组码 + /// 组码值 + /// 比较运算符 + public void Add(DxfCode code, object value, string comp) + { + _lst.Add(new OpComp(comp, code, value)); + } - /// - /// 添加过滤条件 - /// - /// 组码 - /// 组码值 - /// 比较运算符 - public void Add(DxfCode code, object value, string comp) - { - _lst.Add(new OpComp(comp, code, value)); - } + /// + /// 过滤器迭代器 + /// + /// OpFilter迭代器 + public override IEnumerator GetEnumerator() + { + foreach (var value in _lst) + yield return value; + } +} - /// - /// 过滤器迭代器 - /// - /// OpFilter迭代器 - public override IEnumerator GetEnumerator() - { - foreach (var value in _lst) - yield return value; - } +/// +/// 逻辑与类 +/// +public class OpAnd : OpList +{ + /// + /// 符号名 + /// + public override string Name + { + get { return "And"; } } /// - /// 逻辑与类 + /// 添加过滤条件 /// - public class OpAnd : OpList + /// 过滤器对象 + public override void Add(OpFilter value) { - /// - /// 符号名 - /// - public override string Name + if (value is OpAnd opand) { - get { return "And"; } + foreach (var item in opand) + _lst.Add(item); } - - /// - /// 添加过滤条件 - /// - /// 过滤器对象 - public override void Add(OpFilter value) + else { - if (value is OpAnd opand) - { - foreach (var item in opand) - _lst.Add(item); - } - else - { - _lst.Add(value); - } + _lst.Add(value); } } +} + +/// +/// 逻辑或类 +/// +public class OpOr : OpList +{ + /// + /// 符号名 + /// + public override string Name + { + get { return "Or"; } + } /// - /// 逻辑或类 + /// 添加过滤条件 /// - public class OpOr : OpList + /// 过滤器对象 + public override void Add(OpFilter value) { - /// - /// 符号名 - /// - public override string Name + if (value is OpOr opor) { - get { return "Or"; } + foreach (var item in opor) + _lst.Add(item); } - - /// - /// 添加过滤条件 - /// - /// 过滤器对象 - public override void Add(OpFilter value) + else { - if (value is OpOr opor) - { - foreach (var item in opor) - _lst.Add(item); - } - else - { - _lst.Add(value); - } + _lst.Add(value); } } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs index 3fa0785..4227608 100644 --- a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs +++ b/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs @@ -1,133 +1,128 @@ -using Autodesk.AutoCAD.DatabaseServices; -using System.Collections; -using System.Collections.Generic; +namespace IFoxCAD.Cad; -namespace IFoxCAD.Cad +/// +/// 过滤器逻辑运算符抽象类 +/// +public abstract class OpLogi : OpFilter, IEnumerable { /// - /// 过滤器逻辑运算符抽象类 + /// 返回-4组码的开始内容 /// - public abstract class OpLogi : OpFilter, IEnumerable + public TypedValue First { - /// - /// 返回-4组码的开始内容 - /// - public TypedValue First - { - get { return new TypedValue(-4, $"<{Name}"); } - } - - /// - /// 返回-4组码的结束内容 - /// - public TypedValue Last - { - get { return new TypedValue(-4, $"{Name}>"); } - } - - /// - /// 获取过滤条件 - /// - /// TypedValue迭代器 - public override IEnumerable GetValues() - { - yield return First; - foreach (var item in this) - { - foreach (var value in item.GetValues()) - yield return value; - } - yield return Last; - } + get { return new TypedValue(-4, $"<{Name}"); } + } - /// - /// 获取迭代器 - /// - /// OpFilter迭代器 - public abstract IEnumerator GetEnumerator(); + /// + /// 返回-4组码的结束内容 + /// + public TypedValue Last + { + get { return new TypedValue(-4, $"{Name}>"); } + } - IEnumerator IEnumerable.GetEnumerator() + /// + /// 获取过滤条件 + /// + /// TypedValue迭代器 + public override IEnumerable GetValues() + { + yield return First; + foreach (var item in this) { - return GetEnumerator(); + foreach (var value in item.GetValues()) + yield return value; } + yield return Last; } /// - /// 逻辑非类 + /// 获取迭代器 /// - public class OpNot : OpLogi + /// OpFilter迭代器 + public abstract IEnumerator GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() { - private OpFilter Value { get; } + return GetEnumerator(); + } +} - /// - /// 逻辑非类构造函数 - /// - /// OpFilter数据 - public OpNot(OpFilter value) - { - Value = value; - } +/// +/// 逻辑非类 +/// +public class OpNot : OpLogi +{ + private OpFilter Value { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Not"; } - } + /// + /// 逻辑非类构造函数 + /// + /// OpFilter数据 + public OpNot(OpFilter value) + { + Value = value; + } - /// - /// 获取迭代器 - /// - /// OpFilter迭代器 - public override IEnumerator GetEnumerator() - { - yield return Value; - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Not"; } } /// - /// 逻辑异或类 + /// 获取迭代器 /// - public class OpXor : OpLogi + /// OpFilter迭代器 + public override IEnumerator GetEnumerator() { - /// - /// 左操作数 - /// - public OpFilter Left { get; } + yield return Value; + } +} - /// - /// 右操作数 - /// - public OpFilter Right { get; } +/// +/// 逻辑异或类 +/// +public class OpXor : OpLogi +{ + /// + /// 左操作数 + /// + public OpFilter Left { get; } - /// - /// 逻辑异或类构造函数 - /// - /// 左操作数 - /// 右操作数 - public OpXor(OpFilter left, OpFilter right) - { - Left = left; - Right = right; - } + /// + /// 右操作数 + /// + public OpFilter Right { get; } - /// - /// 符号名 - /// - public override string Name - { - get { return "Xor"; } - } + /// + /// 逻辑异或类构造函数 + /// + /// 左操作数 + /// 右操作数 + public OpXor(OpFilter left, OpFilter right) + { + Left = left; + Right = right; + } - /// - /// 获取迭代器 - /// - /// 选择集过滤器类型迭代器 - public override IEnumerator GetEnumerator() - { - yield return Left; - yield return Right; - } + /// + /// 符号名 + /// + public override string Name + { + get { return "Xor"; } + } + + /// + /// 获取迭代器 + /// + /// 选择集过滤器类型迭代器 + public override IEnumerator GetEnumerator() + { + yield return Left; + yield return Right; } -} \ No newline at end of file +} diff --git a/src/IFoxCAD.WPF/Converter.cs b/src/IFoxCAD.WPF/Converter.cs index 7682212..828ece9 100644 --- a/src/IFoxCAD.WPF/Converter.cs +++ b/src/IFoxCAD.WPF/Converter.cs @@ -1,108 +1,98 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; +namespace IFoxCAD.WPF; -using System.Windows.Data; - - -namespace IFoxCAD.WPF +/// +/// 字符串到整数的转换器 +/// +public class StringToIntConverter : IValueConverter +{ + /// + /// 字符串转换到整数 + /// + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + string? a = value as string; + _ = int.TryParse(a, out int b); + return b; + } + /// + /// 整数转换到字符串 + /// + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } +} +/// +/// 字符串到实数的转换器 +/// +public class StringToDoubleConverter : IValueConverter { /// - /// 字符串到整数的转换器 + /// 字符串转换到实数 /// - public class StringToIntConverter : IValueConverter + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 字符串转换到整数 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = int.TryParse(a, out int b); - return b; - } - /// - /// 整数转换到字符串 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } + string? a = value as string; + _ = double.TryParse(a, out double b); + return b; } /// - /// 字符串到实数的转换器 + /// 实数转换到字符串 + /// + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return value.ToString(); + } +} +/// +/// 整数到字符串的转换器 +/// +public class IntToStringConverter : IValueConverter +{ + /// + /// 整数转换到字符串 /// - public class StringToDoubleConverter : IValueConverter + /// 绑定源生成的值 + /// 绑定目标属性的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 字符串转换到实数 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = double.TryParse(a, out double b); - return b; - } - /// - /// 实数转换到字符串 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } + return value.ToString(); } /// - /// 整数到字符串的转换器 + /// 字符串转换到整数 /// - public class IntToStringConverter : IValueConverter + /// 绑定目标生成的值 + /// 要转换为的类型 + /// 要使用的转换器参数 + /// 要用在转换器中的区域性 + /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { - /// - /// 整数转换到字符串 - /// - /// 绑定源生成的值 - /// 绑定目标属性的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object Convert(object value, Type targetType, object parameter, CultureInfo culture) - { - return value.ToString(); - } - /// - /// 字符串转换到整数 - /// - /// 绑定目标生成的值 - /// 要转换为的类型 - /// 要使用的转换器参数 - /// 要用在转换器中的区域性 - /// 转换后的值。 如果该方法返回 null,则使用有效的 null 值。 - public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) - { - string a = value as string; - _ = int.TryParse(a, out int b); - return b; - } + string? a = value as string; + _ = int.TryParse(a, out int b); + return b; } } diff --git a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs index 39cd184..596961d 100644 --- a/src/IFoxCAD.WPF/DependencyObjectExtensions.cs +++ b/src/IFoxCAD.WPF/DependencyObjectExtensions.cs @@ -1,40 +1,34 @@ -using System.Windows; -using System.Windows.Media; +namespace IFoxCAD.WPF; -namespace IFoxCAD.WPF +/// +/// 依赖属性扩展类 +/// +public static class DependencyObjectExtensions { /// - /// 依赖属性扩展类 + /// 获取父对象依赖属性 /// - public static class DependencyObjectExtensions + /// 子对象 + /// 依赖属性 + public static DependencyObject? GetParentObject(this DependencyObject child) { - /// - /// 获取父对象依赖属性 - /// - /// 子对象 - /// 依赖属性 - public static DependencyObject? GetParentObject(this DependencyObject child) - { - if (child is null) - return null; - - if (child is ContentElement contentElement) - { - var parent = ContentOperations.GetParent(contentElement); - if (parent is not null) return parent; + if (child is null) return null; - var fce = contentElement as FrameworkContentElement; - return fce?.Parent; - } + if (child is ContentElement contentElement) + { + DependencyObject parent = ContentOperations.GetParent(contentElement); + if (parent is not null) return parent; - if (child is FrameworkElement frameworkElement) - { - var parent = frameworkElement.Parent; - if (parent is not null) - return parent; - } + FrameworkContentElement? fce = contentElement as FrameworkContentElement; + return fce?.Parent; + } - return VisualTreeHelper.GetParent(child); + if (child is FrameworkElement frameworkElement) + { + DependencyObject parent = frameworkElement.Parent; + if (parent is not null) return parent; } + + return VisualTreeHelper.GetParent(child); } } diff --git a/src/IFoxCAD.WPF/EnumSelection.cs b/src/IFoxCAD.WPF/EnumSelection.cs new file mode 100644 index 0000000..d964351 --- /dev/null +++ b/src/IFoxCAD.WPF/EnumSelection.cs @@ -0,0 +1,72 @@ +namespace IFoxCAD.WPF; +public class EnumSelection : INotifyPropertyChanged where T : struct, IComparable, IFormattable, IConvertible +{ + private T value; // stored value of the Enum + private readonly bool isFlagged; // Enum uses flags? + private readonly bool canDeselect; // Can be deselected? (Radio buttons cannot deselect, checkboxes can) + private readonly T blankValue; // what is considered the "blank" value if it can be deselected? + + public EnumSelection(T value) : this(value, false, default) { } + public EnumSelection(T value, bool canDeselect) : this(value, canDeselect, default) { } + public EnumSelection(T value, T blankValue) : this(value, true, blankValue) { } + public EnumSelection(T value, bool canDeselect, T blankValue) + { + if (!typeof(T).IsEnum) throw new ArgumentException($"{nameof(T)} must be an enum type"); // I really wish there was a way to constrain generic types to enums... + isFlagged = typeof(T).IsDefined(typeof(FlagsAttribute), false); + + this.value = value; + this.canDeselect = canDeselect; + this.blankValue = blankValue; + } + + public T Value + { + get { return value; } + set + { + if (this.value.Equals(value)) return; + this.value = value; + OnPropertyChanged(); + OnPropertyChanged("Item[]"); // Notify that the indexer property has changed + } + } + + [IndexerName("Item")] + public bool this[T key] + { + get + { + int iKey = (int)(object)key; + return isFlagged ? ((int)(object)value & iKey) == iKey : value.Equals(key); + } + set + { + if (isFlagged) + { + int iValue = (int)(object)this.value; + int iKey = (int)(object)key; + + if (((iValue & iKey) == iKey) == value) return; + + if (value) + Value = (T)(object)(iValue | iKey); + else + Value = (T)(object)(iValue & ~iKey); + } + else + { + if (this.value.Equals(key) == value) return; + if (!value && !canDeselect) return; + + Value = value ? key : blankValue; + } + } + } + + public event PropertyChangedEventHandler? PropertyChanged; + + private void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} \ No newline at end of file diff --git a/src/IFoxCAD.WPF/EventBindingExtension.cs b/src/IFoxCAD.WPF/EventBindingExtension.cs index 9298c07..eb58c75 100644 --- a/src/IFoxCAD.WPF/EventBindingExtension.cs +++ b/src/IFoxCAD.WPF/EventBindingExtension.cs @@ -1,221 +1,210 @@ -using System; -using System.ComponentModel; -using System.Reflection.Emit; -using System.Windows.Input; -using System.Windows.Markup; -using System.Windows; -using System.Reflection; +namespace IFoxCAD.WPF; - -namespace IFoxCAD.WPF +/// +/// 事件绑定标签类 +/// +/// +public class EventBindingExtension : MarkupExtension { /// - /// 事件绑定标签类 + /// 命令属性 + /// + public string? Command { get; set; } + /// + /// 命令参数属性 + /// + public string? CommandParameter { get; set; } + /// + /// 当在派生类中实现时,返回用作此标记扩展的目标属性值的对象。 /// - /// - public class EventBindingExtension : MarkupExtension + /// 可为标记扩展提供服务的服务提供程序帮助程序。 + /// + /// 要在应用了扩展的属性上设置的对象值。 + /// + /// + /// + public override object? ProvideValue(IServiceProvider serviceProvider) { - /// - /// 命令属性 - /// - public string Command { get; set; } - /// - /// 命令参数属性 - /// - public string CommandParameter { get; set; } - /// - /// 当在派生类中实现时,返回用作此标记扩展的目标属性值的对象。 - /// - /// 可为标记扩展提供服务的服务提供程序帮助程序。 - /// - /// 要在应用了扩展的属性上设置的对象值。 - /// - /// - /// - public override object ProvideValue(IServiceProvider serviceProvider) + if (serviceProvider is null) { - if (serviceProvider is null) - { - throw new ArgumentNullException(nameof(serviceProvider)); - } - if (!(serviceProvider.GetService(typeof(IProvideValueTarget)) is IProvideValueTarget targetProvider)) - { - throw new InvalidOperationException(); - } - - if (!(targetProvider.TargetObject is FrameworkElement targetObject)) - { - throw new InvalidOperationException(); - } - - var memberInfo = targetProvider.TargetProperty as MemberInfo; - if (memberInfo is null) - { - throw new InvalidOperationException(); - } + throw new ArgumentNullException(nameof(serviceProvider)); + } + if (serviceProvider.GetService(typeof(IProvideValueTarget)) is not IProvideValueTarget targetProvider) + { + throw new InvalidOperationException(); + } - if (string.IsNullOrWhiteSpace(Command)) - { - Command = memberInfo.Name.Replace("Add", ""); - if (Command.Contains("Handler")) - { - Command = Command.Replace("Handler", "Command"); - } - else - { - Command += "Command"; - } - } + if (targetProvider.TargetObject is not FrameworkElement targetObject) + { + throw new InvalidOperationException(); + } - return CreateHandler(memberInfo, Command, targetObject.GetType()); + if (targetProvider.TargetProperty is not MemberInfo memberInfo) + { + throw new InvalidOperationException(); } - private Type GetEventHandlerType(MemberInfo memberInfo) + if (string.IsNullOrWhiteSpace(Command)) { - Type eventHandlerType = null; - if (memberInfo is EventInfo) + Command = memberInfo.Name.Replace("Add", ""); + if (Command.Contains("Handler")) { - var info = memberInfo as EventInfo; - var eventInfo = info; - eventHandlerType = eventInfo.EventHandlerType; + Command = Command.Replace("Handler", "Command"); } - else if (memberInfo is MethodInfo) + else { - var info = memberInfo as MethodInfo; - var methodInfo = info; - ParameterInfo[] pars = methodInfo.GetParameters(); - eventHandlerType = pars[1].ParameterType; + Command += "Command"; } - - return eventHandlerType; } - private object CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) + return CreateHandler(memberInfo, Command!, targetObject.GetType()); + } + + private Type? GetEventHandlerType(MemberInfo memberInfo) + { + Type? eventHandlerType = null; + if (memberInfo is EventInfo eventInfo) + { + //var info = memberInfo as EventInfo; + //var eventInfo = info; + eventHandlerType = eventInfo.EventHandlerType; + } + else if (memberInfo is MethodInfo methodInfo) { - Type eventHandlerType = GetEventHandlerType(memberInfo); + //var info = memberInfo as MethodInfo; + //var methodInfo = info; + ParameterInfo[] pars = methodInfo.GetParameters(); + eventHandlerType = pars[1].ParameterType; + } - if (eventHandlerType is null) return null; + return eventHandlerType; + } - var handlerInfo = eventHandlerType.GetMethod("Invoke"); - var method = new DynamicMethod("", handlerInfo.ReturnType, - new Type[] - { - handlerInfo.GetParameters()[0].ParameterType, - handlerInfo.GetParameters()[1].ParameterType, - }); +#pragma warning disable IDE0060 // 删除未使用的参数 + private object? CreateHandler(MemberInfo memberInfo, string cmdName, Type targetType) +#pragma warning restore IDE0060 // 删除未使用的参数 + { + Type? eventHandlerType = GetEventHandlerType(memberInfo); - var gen = method.GetILGenerator(); - gen.Emit(OpCodes.Ldarg, 0); - gen.Emit(OpCodes.Ldarg, 1); - gen.Emit(OpCodes.Ldstr, cmdName); - if (CommandParameter is null) - { - gen.Emit(OpCodes.Ldnull); - } - else + if (eventHandlerType is null) return null; + + var handlerInfo = eventHandlerType.GetMethod("Invoke"); + var method = new DynamicMethod("", handlerInfo.ReturnType, + new Type[] { - gen.Emit(OpCodes.Ldstr, CommandParameter); - } - gen.Emit(OpCodes.Call, getMethod); - gen.Emit(OpCodes.Ret); + handlerInfo.GetParameters()[0].ParameterType, + handlerInfo.GetParameters()[1].ParameterType, + }); - return method.CreateDelegate(eventHandlerType); + var gen = method.GetILGenerator(); + gen.Emit(OpCodes.Ldarg, 0); + gen.Emit(OpCodes.Ldarg, 1); + gen.Emit(OpCodes.Ldstr, cmdName); + if (CommandParameter is null) + { + gen.Emit(OpCodes.Ldnull); } - - static readonly MethodInfo getMethod = typeof(EventBindingExtension).GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); - - static void Handler(object sender, object args) + else { - HandlerIntern(sender, args, "cmd", null); + gen.Emit(OpCodes.Ldstr, CommandParameter); } - /// - /// Handlers the intern. - /// - /// The sender. - /// The arguments. - /// Name of the command. - /// The command parameter. - public static void HandlerIntern(object sender, object args, string cmdName, string commandParameter) + gen.Emit(OpCodes.Call, getMethod); + gen.Emit(OpCodes.Ret); + + return method.CreateDelegate(eventHandlerType); + } + + static readonly MethodInfo getMethod = typeof(EventBindingExtension).GetMethod("HandlerIntern", new Type[] { typeof(object), typeof(object), typeof(string), typeof(string) }); + +#pragma warning disable IDE0051 // 删除未使用的私有成员 + static void Handler(object sender, object args) +#pragma warning restore IDE0051 // 删除未使用的私有成员 + { + HandlerIntern(sender, args, "cmd", null); + } + /// + /// Handlers the intern. + /// + /// The sender. + /// The arguments. + /// Name of the command. + /// The command parameter. + public static void HandlerIntern(object sender, object args, string cmdName, string? commandParameter) + { + if (sender is FrameworkElement fe) { - var fe = sender as FrameworkElement; - if (fe is not null) + var cmd = GetCommand(fe, cmdName); + object? commandParam = null; + if (!string.IsNullOrWhiteSpace(commandParameter)) + { + commandParam = GetCommandParameter(fe, args, commandParameter!); + } + if ((cmd is not null) && cmd.CanExecute(commandParam)) { - ICommand cmd = GetCommand(fe, cmdName); - object commandParam = null; - if (!string.IsNullOrWhiteSpace(commandParameter)) - { - commandParam = GetCommandParameter(fe, args, commandParameter); - } - if ((cmd is not null) && cmd.CanExecute(commandParam)) - { - cmd.Execute(commandParam); - } + cmd.Execute(commandParam); } } + } - internal static ICommand GetCommand(FrameworkElement target, string cmdName) - { - var vm = FindViewModel(target); - if (vm is null) return null; + internal static ICommand? GetCommand(FrameworkElement target, string cmdName) + { + var vm = FindViewModel(target); + if (vm is null) return null; - var vmType = vm.GetType(); - var cmdProp = vmType.GetProperty(cmdName); - if (cmdProp is not null) - return cmdProp.GetValue(vm) as ICommand; + var vmType = vm.GetType(); + var cmdProp = vmType.GetProperty(cmdName); + if (cmdProp is not null) + { + return cmdProp.GetValue(vm) as ICommand; + } #if DEBUG - throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); + throw new Exception("EventBinding path error: '" + cmdName + "' property not found on '" + vmType + "' 'DelegateCommand'"); #endif - return null; - } - internal static object GetCommandParameter(FrameworkElement target, object args, string commandParameter) - { - var classify = commandParameter.Split('.'); - object ret; - switch (classify[0]) - { - case "$e": - ret = args; - break; - case "$this": - ret = classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target; - break; - default: - ret = commandParameter; - break; - } - return ret; - } +#pragma warning disable CS0162 // 检测到无法访问的代码 + return null; +#pragma warning restore CS0162 // 检测到无法访问的代码 + } - internal static ViewModelBase? FindViewModel(FrameworkElement? target) + internal static object GetCommandParameter(FrameworkElement target, object args, string commandParameter) + { + var classify = commandParameter.Split('.'); + object ret = classify[0] switch { - if (target is null) - return null; + "$e" => args, + "$this" => classify.Length > 1 ? FollowPropertyPath(target, commandParameter.Replace("$this.", ""), target.GetType()) : target, + _ => commandParameter, + }; + return ret; + } - if (target.DataContext is ViewModelBase vm) - return vm; + internal static ViewModelBase? FindViewModel(FrameworkElement? target) + { + if (target is null) return null; - var parent = target.GetParentObject() as FrameworkElement; - return FindViewModel(parent); - } + if (target.DataContext is ViewModelBase vm) return vm; - internal static object FollowPropertyPath(object target, string path, Type valueType = null) - { - if (target is null) throw new ArgumentNullException(nameof(target)); - if (path is null) throw new ArgumentNullException(nameof(path)); + var parent = target.GetParentObject() as FrameworkElement; - Type currentType = valueType ?? target.GetType(); + return FindViewModel(parent); + } - foreach (string propertyName in path.Split('.')) - { - PropertyInfo property = currentType.GetProperty(propertyName); - if (property is null) throw new NullReferenceException("property"); + internal static object FollowPropertyPath(object target, string path, Type? valueType = null) + { + if (target is null) throw new ArgumentNullException(nameof(target)); + if (path is null) throw new ArgumentNullException(nameof(path)); - target = property.GetValue(target); - currentType = property.PropertyType; - } - return target; + Type currentType = valueType ?? target.GetType(); + + foreach (string propertyName in path.Split('.')) + { + PropertyInfo property = currentType.GetProperty(propertyName); + if (property is null) throw new NullReferenceException("property"); + + target = property.GetValue(target); + currentType = property.PropertyType; } + return target; } } diff --git a/src/IFoxCAD.WPF/GlobalUsings.cs b/src/IFoxCAD.WPF/GlobalUsings.cs new file mode 100644 index 0000000..8ab21a7 --- /dev/null +++ b/src/IFoxCAD.WPF/GlobalUsings.cs @@ -0,0 +1,18 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.ComponentModel; +global using System.Runtime.CompilerServices; +global using System.Diagnostics; +global using System.Windows; +global using System.Windows.Input; +global using System.Reflection.Emit; +global using System.Windows.Markup; +global using System.Reflection; +global using System.Windows.Media; +global using System.Globalization; +global using System.Windows.Data; + diff --git a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj index 297d6ce..9fa74ff 100644 --- a/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj +++ b/src/IFoxCAD.WPF/IFoxCAD.WPF.csproj @@ -1,43 +1,40 @@  - - - preview - enable + + preview + enable - - NET45;NET46;NET47;NET48; + net45 + true + true + true + true + 0.3.0 + xsfhlzh;vicwjb + InspireFunction + WPF的简单MVVM模式开发类库 + InspireFunction + LICENSE + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + IFoxCAD;C#;NET;WPF;MVVM + git + 开启可空类型. + - true - true - true - true - 0.1.0 - xsfhlzh;vicwjb - InspireFunction - WPF的简单MVVM模式开发类库 - InspireFunction - LICENSE - https://gitee.com/inspirefunction/ifoxcad - https://gitee.com/inspirefunction/ifoxcad.git - IFoxCAD;C#;NET;WPF;MVVM - git - 基于NFOX类库的重构版本 - + + DEBUG;TRACE + - - DEBUG;TRACE - + + + - - - - - - - True - - - + + + True + + + diff --git a/src/IFoxCAD.WPF/RelayCommand.cs b/src/IFoxCAD.WPF/RelayCommand.cs index cb4f118..72fabca 100644 --- a/src/IFoxCAD.WPF/RelayCommand.cs +++ b/src/IFoxCAD.WPF/RelayCommand.cs @@ -1,205 +1,199 @@ using Microsoft.Xaml.Behaviors; -using System; -using System.Diagnostics; -using System.Windows; -using System.Windows.Input; -namespace IFoxCAD.WPF +namespace IFoxCAD.WPF; + +/// +/// 命令基类 +/// +/// +public class RelayCommand : ICommand { + readonly Func? _canExecute; + readonly Action _execute; /// - /// 命令基类 + /// 初始化 类. /// - /// - public class RelayCommand : ICommand + /// 执行函数 + public RelayCommand(Action execute) : this(execute, null) { - readonly Func _canExecute; - readonly Action _execute; - /// - /// 初始化 类. - /// - /// 执行函数 - public RelayCommand(Action execute):this(execute,null) - { - } - /// - /// 初始化 类. - /// - /// 执行函数委托 - /// 是否可执行函数委托 - /// execute - public RelayCommand(Action execute,Func canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } + } + /// + /// 初始化 类. + /// + /// 执行函数委托 + /// 是否可执行函数委托 + /// execute + public RelayCommand(Action execute, Func? canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } - /// - /// 当出现影响是否应执行该命令的更改时发生。 - /// - public event EventHandler CanExecuteChanged + /// + /// 当出现影响是否应执行该命令的更改时发生。 + /// + public event EventHandler CanExecuteChanged + { + add { - add - { - if (_canExecute is not null) - { - CommandManager.RequerySuggested += value; - } - } - remove + if (_canExecute is not null) { - if (_canExecute is not null) - { - CommandManager.RequerySuggested -= value; - } + CommandManager.RequerySuggested += value; } } - /// - /// 定义确定此命令是否可在其当前状态下执行的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - /// - /// 如果可执行此命令,则为 ;否则为 。 - /// - [DebuggerStepThrough] - public bool CanExecute(object parameter) + remove { - return _canExecute is null ? true : _canExecute(parameter); - } - /// - /// 定义在调用此命令时要调用的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - public void Execute(object parameter) - { - _execute(parameter); + if (_canExecute is not null) + { + CommandManager.RequerySuggested -= value; + } } } + /// + /// 定义确定此命令是否可在其当前状态下执行的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + /// + /// 如果可执行此命令,则为 ;否则为 。 + /// + [DebuggerStepThrough] + public bool CanExecute(object parameter) + { + return _canExecute is null || _canExecute(parameter); + } + /// + /// 定义在调用此命令时要调用的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + public void Execute(object parameter) + { + _execute(parameter); + } +} +/// +/// 命令泛型基类 +/// +/// 事件类型 +/// +public class RelayCommand : ICommand +{ + readonly Func _canExecute; + readonly Action _execute; /// - /// 命令泛型基类 + /// 初始化 类。 /// - /// 事件类型 - /// - public class RelayCommand : ICommand + /// 执行函数 + public RelayCommand(Action execute) : this(execute, (o) => true) { - readonly Func _canExecute; - readonly Action _execute; - /// - /// 初始化 类。 - /// - /// 执行函数 - public RelayCommand(Action execute) : this(execute, (o)=>true) - { - } + } - /// - /// 初始化 类。 - /// - /// 执行函数委托 - /// 是否可执行函数委托 - /// execute - public RelayCommand(Action execute, Func canExecute) - { - _execute = execute ?? throw new ArgumentNullException(nameof(execute)); - _canExecute = canExecute; - } - /// - /// 当出现影响是否应执行该命令的更改时发生。 - /// - public event EventHandler CanExecuteChanged + /// + /// 初始化 类。 + /// + /// 执行函数委托 + /// 是否可执行函数委托 + /// execute + public RelayCommand(Action execute, Func canExecute) + { + _execute = execute ?? throw new ArgumentNullException(nameof(execute)); + _canExecute = canExecute; + } + /// + /// 当出现影响是否应执行该命令的更改时发生。 + /// + public event EventHandler CanExecuteChanged + { + add { - add - { - if (_canExecute is not null) - { - CommandManager.RequerySuggested += value; - } - } - remove + if (_canExecute is not null) { - if (_canExecute is not null) - { - CommandManager.RequerySuggested -= value; - } + CommandManager.RequerySuggested += value; } } - /// - /// 定义确定此命令是否可在其当前状态下执行的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - /// - /// 如果可执行此命令,则为 ;否则为 。 - /// - public bool CanExecute(object parameter) + remove { - if (_canExecute is null) + if (_canExecute is not null) { - return true; + CommandManager.RequerySuggested -= value; } - return _canExecute((T)parameter); } - /// - /// 定义在调用此命令时要调用的方法。 - /// - /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 - public void Execute(object parameter) + } + /// + /// 定义确定此命令是否可在其当前状态下执行的方法。 + /// + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + /// + /// 如果可执行此命令,则为 ;否则为 。 + /// + public bool CanExecute(object parameter) + { + if (_canExecute is null) { - if (_execute is not null && CanExecute(parameter)) - { - _execute((T)parameter); - } + return true; } + return _canExecute((T)parameter); } - /// - /// 事件命令 + /// 定义在调用此命令时要调用的方法。 /// - public class EventCommand : TriggerAction + /// 此命令使用的数据。 如果此命令不需要传递数据,则该对象可以设置为 。 + public void Execute(object parameter) { - /// - /// 执行动作 - /// - /// 要执行的动作参数, 如果动作为提供参数,就设置为null - protected override void Invoke(object parameter) + if (_execute is not null && CanExecute(parameter)) { - if (CommandParameter is not null) - { - parameter = CommandParameter; - } - if (Command is not null) - { - Command.Execute(parameter); - } + _execute((T)parameter); } - /// - /// 事件 - /// - public ICommand Command + } +} + +/// +/// 事件命令 +/// +public class EventCommand : TriggerAction +{ + /// + /// 执行动作 + /// + /// 要执行的动作参数, 如果动作为提供参数,就设置为null + protected override void Invoke(object parameter) + { + if (CommandParameter is not null) { - get { return (ICommand)GetValue(CommandProperty); } - set { SetValue(CommandProperty, value); } + parameter = CommandParameter; } - /// - /// 事件属性 - /// - public static readonly DependencyProperty CommandProperty = - DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null)); - - /// - /// 事件参数,如果为空,将自动传入事件的真实参数 - /// - public object CommandParameter + if (Command is not null) { - get { return (object)GetValue(CommandParameterProperty); } - set { SetValue(CommandParameterProperty, value); } + Command.Execute(parameter); } - /// - /// 事件参数属性 - /// - public static readonly DependencyProperty CommandParameterProperty = - DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null)); } + /// + /// 事件 + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + /// + /// 事件属性 + /// + public static readonly DependencyProperty CommandProperty = + DependencyProperty.Register("Command", typeof(ICommand), typeof(EventCommand), new PropertyMetadata(null)); + /// + /// 事件参数,如果为空,将自动传入事件的真实参数 + /// + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + /// + /// 事件参数属性 + /// + public static readonly DependencyProperty CommandParameterProperty = + DependencyProperty.Register("CommandParameter", typeof(object), typeof(EventCommand), new PropertyMetadata(null)); } diff --git a/src/IFoxCAD.WPF/ViewModelBase.cs b/src/IFoxCAD.WPF/ViewModelBase.cs index 0d0a2dc..c992e81 100644 --- a/src/IFoxCAD.WPF/ViewModelBase.cs +++ b/src/IFoxCAD.WPF/ViewModelBase.cs @@ -1,63 +1,58 @@ -using System; -using System.ComponentModel; -using System.Runtime.CompilerServices; +namespace IFoxCAD.WPF; -namespace IFoxCAD.WPF +/// +/// ViewModel基类 +/// +/// +public class ViewModelBase : INotifyPropertyChanged { /// - /// ViewModel基类 + /// 属性值更改事件。 /// - /// - public class ViewModelBase : INotifyPropertyChanged + public event PropertyChangedEventHandler? PropertyChanged; + /// + /// 属性改变时调用 + /// + /// 属性名 + public void OnPropertyChanged([CallerMemberName] string propertyName = "") + { + + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + /// + /// 设置属性函数,自动通知属性改变事件 + /// + /// 属性类型 + /// 属性 + /// 属性值 + /// 属性名 + /// 成功返回 ,反之 + protected virtual bool Set(ref T storage, T value, [CallerMemberName] string propertyName = "") { - /// - /// 属性值更改事件。 - /// - public event PropertyChangedEventHandler PropertyChanged; - /// - /// 属性改变时调用 - /// - /// 属性名 - public void OnPropertyChanged([CallerMemberName]string propertyName = "") - { - - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); - } - /// - /// 设置属性函数,自动通知属性改变事件 - /// - /// 属性类型 - /// 属性 - /// 属性值 - /// 属性名 - /// 成功返回 ,反之 - protected virtual bool Set(ref T storage, T value, [CallerMemberName]string propertyName = null) - { - if (object.Equals(storage, value)) return false; + if (object.Equals(storage, value)) return false; - storage = value; - this.OnPropertyChanged(propertyName); + storage = value; + this.OnPropertyChanged(propertyName); - return true; - } - /// - /// 创建命令 - /// - /// 要调用的命令函数委托 - /// WPF命令 - protected RelayCommand CreateCommand(Action executeMethod) - { - return CreateCommand(executeMethod, (o) => true); - } - /// - /// 创建命令 - /// - /// 要调用的命令函数委托 - /// 命令是否可以执行的委托 - /// WPF命令 - protected RelayCommand CreateCommand(Action executeMethod, Func canExecuteMethod) - { - return new RelayCommand(executeMethod, canExecuteMethod); - } + return true; + } + /// + /// 创建命令 + /// + /// 要调用的命令函数委托 + /// WPF命令 + protected RelayCommand CreateCommand(Action executeMethod) + { + return CreateCommand(executeMethod, (o) => true); + } + /// + /// 创建命令 + /// + /// 要调用的命令函数委托 + /// 命令是否可以执行的委托 + /// WPF命令 + protected RelayCommand CreateCommand(Action executeMethod, Func canExecuteMethod) + { + return new RelayCommand(executeMethod, canExecuteMethod); } } diff --git a/tests/Test/CmdINI.cs b/tests/Test/CmdINI.cs new file mode 100644 index 0000000..2e37c1d --- /dev/null +++ b/tests/Test/CmdINI.cs @@ -0,0 +1,111 @@ +/// +/// 注册中心(自动执行接口): +/// 用于启动cad后写入启动注册表及反射调用以下特性和接口 +/// netload的工程必须继承虚函数后才能使用特性和接口 +/// 启动cad后的执行顺序为: +/// 1:构造函数 +/// 2:特性..多个 +/// 3:接口..多个 +/// 4:本类的构造函数 +/// +public class CmdINI : AutoRegAssem +{ + public CmdINI() : base(AutoRegConfig.All) + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n {nameof(CmdINI)}构造函数,开始自动执行\r\n"); + } + + ///如果netload之后用 删除注册表, + ///由于不是也不能卸载dll,再netload是无法执行自动接口的, + ///所以此时会产生无法再注册的问题...因此需要暴露此注册函数(硬来) + [CommandMethod("IFoxAddReg")] + public void IFoxAddReg() + { + base.RegApp(); + + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n 加入注册表"); + } + + /// + /// 卸载注册表信息 + /// + [CommandMethod("IFoxRemoveReg")] + public void IFoxRemoveReg() + { + //执行命令的时候会再次执行构造函数(导致初始化两次),但是再次执行就不会了 + base.UnRegApp(); + + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage($"\n 卸载注册表"); + } +} + + +/* + * 自动执行特性例子: + */ +public class Cmd_IFoxInitialize +{ + [IFoxInitialize] + public void NameCasual() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 可以分开多个类和多个函数 \r\n"); + } + + [IFoxInitialize] + public void NameCasualtest() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 又一次测试 \r\n"); + } + + [IFoxInitialize] + public void Initialize() + { + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + ed.WriteMessage("\n 开始自动执行 Initialize \r\n"); + } + + [IFoxInitialize(isInitialize: false)] + public void Terminate() + { + //try + //{ + // var dm = Application.DocumentManager; + // var doc = dm.MdiActiveDocument; + // var ed = doc.Editor; //注意此时编辑器已经回收,所以此句没用,并引发错误 + // ed.WriteMessage("\n 结束自动执行 Terminate \r\n"); + //} + //catch (System.Exception) + //{ + //} + } + + //[IFoxInitialize] + //public void Initialize() + //{ + // //文档管理器将比此接口前创建,因此此句会执行 + // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); + //} + //[IFoxInitialize(Sequence.First, false)] + //public void Terminate() + //{ + // //文档管理器将比此接口前死亡,因此此句不会执行 + // Application.DocumentManager.MdiActiveDocument?.Editor.WriteMessage("\nunload...."); + //} +} \ No newline at end of file diff --git a/tests/Test/GlobalUsings.cs b/tests/Test/GlobalUsings.cs new file mode 100644 index 0000000..2896cbf --- /dev/null +++ b/tests/Test/GlobalUsings.cs @@ -0,0 +1,30 @@ +/// 系统引用 +global using System; +global using System.Collections; +global using System.Collections.Generic; +global using System.IO; +global using System.Linq; +global using System.Text; +global using System.Reflection; +global using System.Text.RegularExpressions; +global using Microsoft.Win32; +global using System.ComponentModel; +global using System.Runtime.CompilerServices; + +/// autocad 引用 +global using Autodesk.AutoCAD.ApplicationServices; +global using Autodesk.AutoCAD.EditorInput; +global using Autodesk.AutoCAD.Colors; +global using Autodesk.AutoCAD.DatabaseServices; +global using Autodesk.AutoCAD.Geometry; +global using Autodesk.AutoCAD.Runtime; +global using Acgi = Autodesk.AutoCAD.GraphicsInterface; +global using Acap = Autodesk.AutoCAD.ApplicationServices.Application; + +global using Registry = Microsoft.Win32.Registry; +global using RegistryKey = Microsoft.Win32.RegistryKey; + +/// ifoxcad +global using IFoxCAD.Cad; +global using IFoxCAD.WPF; +global using IFoxCAD.Basal; \ No newline at end of file diff --git a/tests/Test/Properties/launchSettings.json b/tests/Test/Properties/launchSettings.json index 1ecc024..4b464c3 100644 --- a/tests/Test/Properties/launchSettings.json +++ b/tests/Test/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "profiles": { - "DBTrans.test": { + "Test": { "commandName": "Executable", "executablePath": "C:\\Program Files\\Autodesk\\AutoCAD 2021\\acad.exe", "commandLineArgs": "/nologo", diff --git a/tests/Test/Test.cs b/tests/Test/Test.cs index 78746f4..bc45757 100644 --- a/tests/Test/Test.cs +++ b/tests/Test/Test.cs @@ -1,193 +1,179 @@ -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.Colors; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; +#pragma warning disable CS0219 // 变量已被赋值,但从未使用过它的值 +namespace Test; -using IFoxCAD.Cad; - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; - -using test.wpf; - -namespace test +public class Test { - public class Test + [CommandMethod("dbtest")] + public void Dbtest() { - [CommandMethod("dbtest")] - public void Dbtest() + using var tr = new DBTrans(); + tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); + tr.Editor.WriteMessage("\n----------开始测试--------------"); + tr.Editor.WriteMessage("\n测试document属性是否工作"); + if (tr.Document == Getdoc()) { - using var tr = new DBTrans(); - tr.Editor.WriteMessage("\n测试 Editor 属性是否工作!"); - tr.Editor.WriteMessage("\n----------开始测试--------------"); - tr.Editor.WriteMessage("\n测试document属性是否工作"); - if (tr.Document == Getdoc()) - { - tr.Editor.WriteMessage("\ndocument 正常"); - } - tr.Editor.WriteMessage("\n测试database属性是否工作"); - if (tr.Database == Getdb()) - { - tr.Editor.WriteMessage("\ndatabase 正常"); - } - - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); - //var lienid = tr.AddEntity(line); - //var cirid = tr.AddEntity(circle); - //var linent = tr.GetObject(lienid); - //var lineent = tr.GetObject(cirid); - //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null - //var dd = tr.GetObject(lienid); - //List ds = new() { linee, dd }; + tr.Editor.WriteMessage("\ndocument 正常"); } - - //add entity test - [CommandMethod("addent")] - public void Addent() + tr.Editor.WriteMessage("\n测试database属性是否工作"); + if (tr.Database == Getdb()) { - using var tr = new DBTrans(); - Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.CurrentSpace.AddEntity(line); - Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); - tr.ModelSpace.AddEntity(line1); - Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); - tr.PaperSpace.AddEntity(line2); + tr.Editor.WriteMessage("\ndatabase 正常"); } - [CommandMethod("drawarc")] - public void drawarc() - { - using var tr = new DBTrans(); - Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 - Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 - Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 - tr.CurrentSpace.AddEntity(arc1, arc2, arc3); - tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 - } + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 2); + //var lienid = tr.AddEntity(line); + //var cirid = tr.AddEntity(circle); + //var linent = tr.GetObject(lienid); + //var lineent = tr.GetObject(cirid); + //var linee = tr.GetObject(cirid); //经测试,类型不匹配,返回null + //var dd = tr.GetObject(lienid); + //List ds = new() { linee, dd }; + //tr.CurrentSpace.AddEntity(line,tr); + } - [CommandMethod("drawcircle")] - public void draCircle() + //add entity test + [CommandMethod("addent")] + public void Addent() + { + using var tr = new DBTrans(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + Line line1 = new(new Point3d(10, 10, 0), new Point3d(41, 1, 0)); + tr.ModelSpace.AddEntity(line1); + Line line2 = new(new Point3d(-10, 10, 0), new Point3d(41, 1, 0)); + tr.PaperSpace.AddEntity(line2); + } + + [CommandMethod("drawarc")] + public void Drawarc() + { + using var tr = new DBTrans(); + Arc arc1 = EntityEx.CreateArcSCE(new Point3d(2, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 2, 0));//起点,圆心,终点 + Arc arc2 = EntityEx.CreateArc(new Point3d(4, 0, 0), new Point3d(0, 0, 0), Math.PI / 2); //起点,圆心,弧度 + Arc arc3 = EntityEx.CreateArc(new Point3d(1, 0, 0), new Point3d(0, 0, 0), new Point3d(0, 1, 0)); //起点,圆上一点,终点 + tr.CurrentSpace.AddEntity(arc1, arc2, arc3); + tr.CurrentSpace.AddArc(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//起点,圆上一点,终点 + } + + [CommandMethod("drawcircle")] + public void DraCircle() + { + using var tr = new DBTrans(); + Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 + Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 + Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 + tr.CurrentSpace.AddEntity(circle1, circle2); + if (circle3 is not null) { - using var tr = new DBTrans(); - Circle circle1 = EntityEx.CreateCircle(new Point3d(0, 0, 0), new Point3d(1, 0, 0)); //起点,终点 - Circle circle2 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(2, 0, 0), new Point3d(0, 2, 0));//三点画圆,成功 - Circle circle3 = EntityEx.CreateCircle(new Point3d(-2, 0, 0), new Point3d(0, 0, 0), new Point3d(2, 0, 0));//起点,圆心,终点,失败 - tr.CurrentSpace.AddEntity(circle1, circle2); - if (circle3 is not null) - tr.CurrentSpace.AddEntity(circle3); - else - { - tr.Editor.WriteMessage("三点画圆失败"); - return; - } tr.CurrentSpace.AddEntity(circle3); - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 - tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) } - - [CommandMethod("layertest")] - public void Layertest() + else { - using var tr = new DBTrans(); - tr.LayerTable.Add("1"); - tr.LayerTable.Add("2", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); - lt.LineWeight = LineWeight.LineWeight030; - - }); - tr.LayerTable.Remove("3"); - tr.LayerTable.Delete("0"); - tr.LayerTable.Change("4", lt => - { - lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); - }); + tr.Editor.WriteMessage("三点画圆失败"); } + tr.CurrentSpace.AddEntity(circle3); + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 0, 0));//三点画圆,成功 + tr.CurrentSpace.AddCircle(new Point3d(0, 0, 0), new Point3d(1, 1, 0), new Point3d(2, 2, 0));//起点,圆上一点,终点(共线) + } + [CommandMethod("layertest")] + public void Layertest() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("1"); + tr.LayerTable.Add("2", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 1); + lt.LineWeight = LineWeight.LineWeight030; + + }); + tr.LayerTable.Remove("3"); + tr.LayerTable.Delete("0"); + tr.LayerTable.Change("4", lt => { + lt.Color = Color.FromColorIndex(ColorMethod.ByColor, 2); + }); + } - //添加图层 - [CommandMethod("layerAdd1")] - public void Layertest1() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); - } - //添加图层 - [CommandMethod("layerAdd2")] - public void Layertest2() - { - using var tr = new DBTrans(); - tr.LayerTable.Add("test2", 2); - //tr.LayerTable["3"] = new LayerTableRecord(); - } - //删除图层 - [CommandMethod("layerdel")] - public void LayerDel() - { - using var tr = new DBTrans(); - Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 - Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints - Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 - Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 - Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 + //添加图层 + [CommandMethod("layerAdd1")] + public void Layertest1() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("test1", Color.FromColorIndex(ColorMethod.ByColor, 1)); + } - tr.LayerTable.Remove("2"); //测试是否能强制删除 - } + //添加图层 + [CommandMethod("layerAdd2")] + public void Layertest2() + { + using var tr = new DBTrans(); + tr.LayerTable.Add("test2", 2); + //tr.LayerTable["3"] = new LayerTableRecord(); + } + //删除图层 + [CommandMethod("layerdel")] + public void LayerDel() + { + using var tr = new DBTrans(); + Env.Editor.WriteMessage(tr.LayerTable.Delete("0").ToString()); //删除图层 0 + Env.Editor.WriteMessage(tr.LayerTable.Delete("Defpoints").ToString());//删除图层 Defpoints + Env.Editor.WriteMessage(tr.LayerTable.Delete("1").ToString()); //删除不存在的图层 1 + Env.Editor.WriteMessage(tr.LayerTable.Delete("2").ToString()); //删除有图元的图层 2 + Env.Editor.WriteMessage(tr.LayerTable.Delete("3").ToString()); //删除图层 3 + + tr.LayerTable.Remove("2"); //测试是否能强制删除 + } - //添加直线 - [CommandMethod("linedemo1")] - public void AddLine1() - { - using var tr = new DBTrans(); - // tr.ModelSpace.AddEnt(line); - // tr.ModelSpace.AddEnts(line,circle); - - // tr.PaperSpace.AddEnt(line); - // tr.PaperSpace.AddEnts(line,circle); - - // tr.addent(btr,line); - // tr.addents(btr,line,circle); - - - // tr.BlockTable.Add(new BlockTableRecord(), line => - // { - // line. - // }); - Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); - Circle circle = new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); - tr.CurrentSpace.AddEntity(line1); - tr.CurrentSpace.AddEntity(line2, line3, circle); - } + //添加直线 + [CommandMethod("linedemo1")] + public void AddLine1() + { + using var tr = new DBTrans(); + // tr.ModelSpace.AddEnt(line); + // tr.ModelSpace.AddEnts(line,circle); + + // tr.PaperSpace.AddEnt(line); + // tr.PaperSpace.AddEnts(line,circle); + + // tr.addent(btr,line); + // tr.addents(btr,line,circle); + + + // tr.BlockTable.Add(new BlockTableRecord(), line => + // { + // line. + // }); + Line line1 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line2 = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + Line line3 = new(new Point3d(1, 1, 0), new Point3d(3, 3, 0)); + Circle circle = new(new Point3d(0, 0, 0), Vector3d.ZAxis, 10); + tr.CurrentSpace.AddEntity(line1); + tr.CurrentSpace.AddEntity(line2, line3, circle); + } - //增加多段线1 - [CommandMethod("Pldemo1")] - public void AddPolyline1() - { - using var tr = new DBTrans(); - Polyline pl = new Polyline(); - pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); - pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); - pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); - pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); - pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); - pl.Closed = true; - pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); - tr.CurrentSpace.AddEntity(pl); - } + //增加多段线1 + [CommandMethod("Pldemo1")] + public void AddPolyline1() + { + using var tr = new DBTrans(); + Polyline pl = new(); + pl.SetDatabaseDefaults(); + pl.AddVertexAt(0, new Point2d(0, 0), 0, 0, 0); + pl.AddVertexAt(1, new Point2d(10, 10), 0, 0, 0); + pl.AddVertexAt(2, new Point2d(20, 20), 0, 0, 0); + pl.AddVertexAt(3, new Point2d(30, 30), 0, 0, 0); + pl.AddVertexAt(4, new Point2d(40, 40), 0, 0, 0); + pl.Closed = true; + pl.Color = Color.FromColorIndex(ColorMethod.ByColor, 6); + tr.CurrentSpace.AddEntity(pl); + } - //增加多段线2 - [CommandMethod("pldemo2")] - public void Addpl2() - { - var pts = new List<(Point3d, double, double, double)> + //增加多段线2 + [CommandMethod("pldemo2")] + public void Addpl2() + { + var pts = new List<(Point3d, double, double, double)> { (new Point3d(0,0,0),0,0,0), (new Point3d(10,0,0),0,0,0), @@ -195,154 +181,28 @@ public void Addpl2() (new Point3d(0,10,0),0,0,0), (new Point3d(5,5,0),0,0,0) }; - using var tr = new DBTrans(); - tr.CurrentSpace.AddPline(pts); - } - - //块定义 - [CommandMethod("blockdef")] - public void BlockDef() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Add("test", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - }, - () => //图元 - { - return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; - }, - () => //属性定义 - { - var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; - var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; - return new List { id1, id2 }; - } - ); - //ObjectId objectId = tr.BlockTable.Add("a");//新建块 - //objectId.GetObject().AddEntity();//测试添加空实体 - } - //修改块定义 - [CommandMethod("blockdefchange")] - public void BlockDefChange() - { - using var tr = new DBTrans(); - //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - tr.BlockTable.Change("test", btr => - { - btr.Origin = new Point3d(5, 5, 0); - btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); - btr.GetEntities() - .ToList() - .ForEach(e => e.Flush()); //刷新块显示 - - }); - tr.Editor.Regen(); - } - - [CommandMethod("insertblockdef")] - public void InsertBlockDef() - { - using var tr = new DBTrans(); - var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; - var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; - tr.BlockTable.Add("test1", line1, line2, att1, att2); - - - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - - - var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); - var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); - var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; - var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; - tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 - //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 - //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 - - var def1 = new Dictionary - { - { "tagTest1", "1" }, - { "tagTest2", "2" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); - var def2 = new Dictionary - { - { "tagTest3", "1" }, - { "tagTest4", "" } - }; - tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); - tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); - } - - [CommandMethod("testblocknullbug")] - public void TestBlockNullBug() - { - using var tr = new DBTrans(); + using var tr = new DBTrans(); + tr.CurrentSpace.AddPline(pts); + } - var ents = new List(); - var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); - var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); - ents.Add(line5); - ents.Add(line6); - tr.BlockTable.Add("test44", ents); - tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); - } - [CommandMethod("testclip")] - public void TestClipBlock() - { - using var tr = new DBTrans(); - tr.BlockTable.Add("test1", - btr => - { - btr.Origin = new Point3d(0, 0, 0); - btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), - new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) - ); - } - ); - //tr.BlockTable.Add("hah"); - var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); - var bref = tr.GetObject(id); - var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; - bref.ClipBlockRef(pts); - var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); - var bref1 = tr.GetObject(id); - - bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); - } + // 测试扩展数据 + [CommandMethod("addxdata")] + public void AddXdata() + { + using var tr = new DBTrans(); + var appname = "myapp"; + tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 + tr.RegAppTable.Add("myapp2"); - // 测试扩展数据 - [CommandMethod("addxdata")] - public void AddXdata() + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) { - using var tr = new DBTrans(); - var appname = "myapp"; - - tr.RegAppTable.Add(appname); // add函数会默认的在存在这个名字的时候返回这个名字的regapp的id,不存在就新建 - tr.RegAppTable.Add("myapp2"); - - var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) - { - XData = new XDataList() + XData = new XDataList() { { DxfCode.ExtendedDataRegAppName, appname }, //可以用dxfcode和int表示组码 { DxfCode.ExtendedDataAsciiString, "hahhahah" }, @@ -351,278 +211,150 @@ public void AddXdata() { DxfCode.ExtendedDataAsciiString, "hahhahah" }, {1070, 12 } } - }; - - tr.CurrentSpace.AddEntity(line); - } - - [CommandMethod("getxdata")] - public void GetXdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId).XData; - ed.WriteMessage(data.ToString()); - } - } - - [CommandMethod("changexdata")] - public void Changexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); + }; - ed.WriteMessage(data.XData.ToString()); - } - } - [CommandMethod("removexdata")] - public void Removexdata() - { - var doc = Application.DocumentManager.MdiActiveDocument; - var ed = doc.Editor; - var appname = "myapp"; - var res = ed.GetEntity("\n select the entity:"); - if (res.Status == PromptStatus.OK) - { - using var tr = new DBTrans(); - var data = tr.GetObject(res.ObjectId); - data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); + tr.CurrentSpace.AddEntity(line); + } - ed.WriteMessage(data.XData.ToString()); - } - } + [CommandMethod("getxdata")] + public void GetXdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + using var tr = new DBTrans(); + tr.RegAppTable.ForEach(id => + id.GetObject().Name.Print()); + tr.RegAppTable.GetRecords().ForEach(rec => rec.Name.Print()); + tr.RegAppTable.GetRecordNames().ForEach(name => name.Print()); + tr.RegAppTable.ForEach(re => re.Name.Print()); + + //var res = ed.GetEntity("\n select the entity:"); + //if (res.Status == PromptStatus.OK) + //{ + // using var tr = new DBTrans(); + // tr.RegAppTable.ForEach(id => id.GetObject().Print()); + // var data = tr.GetObject(res.ObjectId).XData; + // ed.WriteMessage(data.ToString()); + //} + } - [CommandMethod("PrintLayerName")] - public void PrintLayerName() + [CommandMethod("changexdata")] + public void Changexdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) { using var tr = new DBTrans(); - foreach (var layerRecord in tr.LayerTable.GetRecords()) - { - tr.Editor.WriteMessage(layerRecord.Name); - } - - } - + var data = tr.GetObject(res.ObjectId); + data.ChangeXData(appname, DxfCode.ExtendedDataAsciiString, "change"); - [CommandMethod("testwpf")] - public void TestWPf() - { - - var test = new TestView(); - Application.ShowModalWindow(test); + ed.WriteMessage(data.XData.ToString()); } - - [CommandMethod("testpt")] - public void TestPt() + } + [CommandMethod("removexdata")] + public void Removexdata() + { + var doc = Application.DocumentManager.MdiActiveDocument; + var ed = doc.Editor; + var appname = "myapp"; + var res = ed.GetEntity("\n select the entity:"); + if (res.Status == PromptStatus.OK) { - //var pt = Env.Editor.GetPoint("pick pt:").Value; - //var pl = Env.Editor.GetEntity("pick pl").ObjectId; - var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - using var tr2 = new DBTrans(); - var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr2.Transaction == tr3); - Env.Print(tr3 == tr6); - using var tr4 = new DBTrans(); - var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; - var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; - Env.Print(tr4.Transaction == tr5); - Env.Print(tr5 == tr7); - var trm = HostApplicationServices.WorkingDatabase.TransactionManager; - //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); - //var pt1 = new Point3d(0, 0.00000000000001, 0); - //var pt2 = new Point3d(0, 0.00001, 0); - //Env.Print(Tolerance.Global.EqualPoint); - //Env.Print(pt1.IsEqualTo(pt2).ToString()); - //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); - //Env.Print((pt1 == pt2).ToString()); - //Env.Print((pt1 != pt2).ToString()); - - + using var tr = new DBTrans(); + var data = tr.GetObject(res.ObjectId); + data.RemoveXData(appname, DxfCode.ExtendedDataAsciiString); + ed.WriteMessage(data.XData.ToString()); } + } - - public Database Getdb() - { - var db = Application.DocumentManager.MdiActiveDocument.Database; - return db; - } - - - public Document Getdoc() + [CommandMethod("PrintLayerName")] + public void PrintLayerName() + { + using var tr = new DBTrans(); + foreach (var layerRecord in tr.LayerTable.GetRecords()) { - var doc = Application.DocumentManager.MdiActiveDocument; - return doc; + tr.Editor.WriteMessage(layerRecord.Name); } - //public override void Initialize() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nload...."); - //} - - //public override void Terminate() - //{ - // Application.DocumentManager.MdiActiveDocument.Editor.WriteMessage("\nunload...."); - //} } - public class BlockImportClass + [CommandMethod("testrec")] + public void TestRec() { + Point2d p1 = new(10000.2, 100000.5); + Point2d p2 = new(15000.9, 100000.5); + Point2d p3 = new(15000.9, 105000.7); + Point2d p4 = new(10000.2, 105000.7); - [CommandMethod("CBLL")] - public void cbll() - { - string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; - using var tr = new DBTrans(); - using var tr1 = new DBTrans(filename); - //tr.BlockTable.GetBlockFrom(filename, true); - string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); - tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 + var p12 = p2 - p1; + var p23 = p3 - p2; + var p34 = p4 - p3; + var p41 = p1 - p4; + var p13 = p3 - p1; + var p24 = p4 - p2; - } + const double pi90 = Math.PI / 2; - [CommandMethod("CBL")] - public void CombineBlocksIntoLibrary() - { - Document doc = - Application.DocumentManager.MdiActiveDocument; - Editor ed = doc.Editor; - Database destDb = doc.Database; + Tools.TestTimes(1000000, "对角线", () => { + var result = false; + if (Math.Abs(p13.Length - p24.Length) <= 1e8) + { + result = p41.IsParallelTo(p12); + } - // Get name of folder from which to load and import blocks + }); - PromptResult pr = - ed.GetString("\nEnter the folder of source drawings: "); + Tools.TestTimes(1000000, "三次点乘", () => { + var result = false; - if (pr.Status != PromptStatus.OK) - return; - string pathName = pr.StringResult; + if (Math.Abs(p12.DotProduct(p23)) < 1e8 && + Math.Abs(p23.DotProduct(p34)) < 1e8 && + Math.Abs(p34.DotProduct(p41)) < 1e8) + { + result = true; + } - // Check the folder exists + }); - if (!Directory.Exists(pathName)) + Tools.TestTimes(1000000, "三次垂直", () => { + var result = false; + if (p12.IsParallelTo(p23) && + p23.IsParallelTo(p34) && + p34.IsParallelTo(p41)) { - ed.WriteMessage( - "\nDirectory does not exist: {0}", pathName - ); - return; + result = true; } - // Get the names of our DWG files in that folder + }); - string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); - // A counter for the files we've imported + } - int imported = 0, failed = 0; - // For each file in our list - foreach (string fileName in fileNames) - { - // Double-check we have a DWG file (probably unnecessary) - if (fileName.EndsWith( - ".dwg", - StringComparison.InvariantCultureIgnoreCase - ) - ) - { - // Catch exceptions at the file level to allow skipping - - try - { - // Suggestion from Thorsten Meinecke... - - string destName = - SymbolUtilityServices.GetSymbolNameFromPathName( - fileName, "dwg" - ); - - // And from Dan Glassman... - - destName = - SymbolUtilityServices.RepairSymbolName( - destName, false - ); - - // Create a source database to load the DWG into - - using (Database db = new Database(false, true)) - { - // Read the DWG into our side database - - db.ReadDwgFile(fileName, FileShare.Read, true, ""); - bool isAnno = db.AnnotativeDwg; - - // Insert it into the destination database as - // a named block definition - - ObjectId btrId = destDb.Insert( - destName, - db, - false - ); - - if (isAnno) - { - // If an annotative block, open the resultant BTR - // and set its annotative definition status - - Transaction tr = - destDb.TransactionManager.StartTransaction(); - using (tr) - { - BlockTableRecord btr = - (BlockTableRecord)tr.GetObject( - btrId, - OpenMode.ForWrite - ); - btr.Annotative = AnnotativeStates.True; - tr.Commit(); - } - } - - // Print message and increment imported block counter - - ed.WriteMessage("\nImported from \"{0}\".", fileName); - imported++; - } - } - catch (System.Exception ex) - { - ed.WriteMessage( - "\nProblem importing \"{0}\": {1} - file skipped.", - fileName, ex.Message - ); - failed++; - } - } - } - ed.WriteMessage( - "\nImported block definitions from {0} files{1} in " + - "\"{2}\" into the current drawing.", - imported, - failed > 0 ? " (" + failed + " failed)" : "", - pathName - ); - } + + public Database Getdb() + { + var db = Application.DocumentManager.MdiActiveDocument.Database; + return db; } + + public Document Getdoc() + { + var doc = Application.DocumentManager.MdiActiveDocument; + return doc; + } } + + + +#pragma warning restore CS0219 // 变量已被赋值,但从未使用过它的值 diff --git a/tests/Test/Test.csproj b/tests/Test/Test.csproj index fa8ed34..8e3469a 100644 --- a/tests/Test/Test.csproj +++ b/tests/Test/Test.csproj @@ -1,32 +1,22 @@  - preview - enable + - - 1.0.0.* - 1.0.0.0 - False - git - - - NET45; + net45 true true + x64 - - - - - - + + 1701;1702;CS1685 + - - - + + 1701;1702;CS1685 + @@ -34,4 +24,5 @@ + diff --git a/tests/Test/TestAOP.cs b/tests/Test/TestAOP.cs new file mode 100644 index 0000000..b5b5c92 --- /dev/null +++ b/tests/Test/TestAOP.cs @@ -0,0 +1,66 @@ +//被注入的函数将不能使用断点, +//因此用户要充分了解才能使用 +#if false +/* + * 类库用户想侵入的命名空间是用户的, + * 所以需要用户手动进行AOP.Run(), + * 默认情况不侵入用户的命令,必须用户手动启用此功能; + * 启动执行策略之后,侵入命名空间下的命令, + * 此时有拒绝特性的策略保证括免,因为用户肯定是想少写一个事务注入的特性; + */ +public class AutoAOP +{ + [IFoxInitialize] + public void Initialize() + { + AOP.Run(nameof(Test)); + } +} + +namespace Test +{ + /* + * 天秀的事务注入,让你告别事务处理 + * https://www.cnblogs.com/JJBox/p/16157578.html + */ + public class AopTestClass + { + //类不拒绝,这里拒绝 + [IFoxRefuseInjectionTransaction] + [CommandMethod("IFoxRefuseInjectionTransaction")] + public void IFoxRefuseInjectionTransaction() + { + } + + //不拒绝 + [CommandMethod("InjectionTransaction")] + public void InjectionTransaction() + { + //怎么用事务呢? + //直接用 DBTrans.Top + var dBTrans = new DBTrans(); + dBTrans.Commit(); + } + } + + //拒绝注入事务,写类上,则方法全都拒绝 + [IFoxRefuseInjectionTransaction] + public class AopTestClassRefuseInjection + { + //此时这个也是拒绝的..这里加特性只是无所谓 + [IFoxRefuseInjectionTransaction] + [CommandMethod("IFoxRefuseInjectionTransaction2")] + public void IFoxRefuseInjectionTransaction2() + { + //拒绝注入就要自己开事务,通常用在循环提交事务上面. + //另见 报错0x02 https://www.cnblogs.com/JJBox/p/10798940.html + using var tr = new DBTrans(); + } + + [CommandMethod("InjectionTransaction2")] + public void InjectionTransaction2() + { + } + } +} +#endif \ No newline at end of file diff --git a/tests/Test/TestCurve.cs b/tests/Test/TestCurve.cs new file mode 100644 index 0000000..4d5cd96 --- /dev/null +++ b/tests/Test/TestCurve.cs @@ -0,0 +1,158 @@ +using System.Security.Policy; + +namespace Test +{ + public class TestGraph + { + [CommandMethod("testpointindict")] + public void TestPointInDict() + { + var pt1 = new Point3d(0.0255, 0.452, 0); + var pt2 = new Point3d(0.0255001, 0.452003, 0); + var pt3 = new Point3d(0.0255002, 0.4520001, 0); + var pt4 = new Point3d(0.0255450, 0.45287893, 0); + var pt5 = new Point3d(0.02554935, 0.452092375, 0); + var dict = new Dictionary + { + { pt1, 1 }, + { pt2, 2 }, + { pt3, 3 }, + { pt4, 4 }, + { pt5, 5 } + }; + Env.Print(dict[pt1]); + } + + [CommandMethod("testgraph")] + public void TestGraph1() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + Tools.TestTimes2(1, "new", () => { + + + var res = ents.GetAllCycle(); + + //res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + Env.Print(res.Count()); + tr.CurrentSpace.AddEntity(res); + }); + + } + + [CommandMethod("testgraphspeed")] + public void TestGraphspeed() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet()?.Value?.GetEntities(); + if (ents == null) + return; + + + var graph = new IFoxCAD.Cad.Graph(); // 为了调试先把图的访问改为internal + + foreach (var curve in ents) + { + + graph.AddEdge(curve.GetGeCurve()); + + + } + //新建 dfs + var dfs = new DepthFirst(); +#if true + Tools.TestTimes2(100, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "new", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#else + Tools.TestTimes2(100, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); + Tools.TestTimes2(1000, "old", () => { + // 查询全部的 闭合环 + dfs.FindAll(graph); + }); +#endif + //res.ForEach((i, t) => t.ForWrite(e => e.ColorIndex = i + 1)); + + //tr.CurrentSpace.AddEntity(res); + + } + } + + + + + public class TestCurve + { + [CommandMethod("testbreakcurve")] + public void TestBreakCurve() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet().Value.GetEntities(); + var tt = CurveEx.BreakCurve(ents.ToList()); + tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + tr.CurrentSpace.AddEntity(tt); + } + + [CommandMethod("testCurveCurveIntersector3d")] + public void TestCurveCurveIntersector3d() + { + using var tr = new DBTrans(); + var ents = Env.Editor.SSGet().Value.GetEntities() + .Select(e => e.ToCompositeCurve3d()).ToList(); + + var cci3d = new CurveCurveIntersector3d(); + + + for (int i = 0; i < ents.Count; i++) + { + var gc1 = ents[i]; + var int1 = gc1.GetInterval(); + //var pars1 = paramss[i]; + for (int j = i; j < ents.Count; j++) + { + var gc2 = ents[j]; + //var pars2 = paramss[j]; + var int2 = gc2.GetInterval(); + cci3d.Set(gc1, gc2, int1, int2, Vector3d.ZAxis); + var d = cci3d.OverlapCount(); + var a = cci3d.GetIntersectionRanges(); + Env.Print($"{a[0].LowerBound}-{a[0].UpperBound} and {a[1].LowerBound}-{a[1].UpperBound}"); + for (int m = 0; m < d; m++) + { + var b = cci3d.GetOverlapRanges(m); + Env.Print($"{b[0].LowerBound}-{b[0].UpperBound} and {b[1].LowerBound}-{b[1].UpperBound}"); + } + + for (int k = 0; k < cci3d.NumberOfIntersectionPoints; k++) + { + //var a = cci3d.GetOverlapRanges(k); + //var b = cci3d.IsTangential(k); + //var c = cci3d.IsTransversal(k); + //var d = cci3d.OverlapCount(); + //var e = cci3d.OverlapDirection(); + var pt = cci3d.GetIntersectionParameters(k); + var pts = cci3d.GetIntersectionPoint(k); + Env.Print(pts); + } + + + + } + + } + // var tt = CurveEx.Topo(ents.ToList()); + //tt.ForEach(t => t.ForWrite(e => e.ColorIndex = 1)); + //tr.CurrentSpace.AddEntity(tt); + } + } +} \ No newline at end of file diff --git a/tests/Test/TestDBTrans.cs b/tests/Test/TestDBTrans.cs new file mode 100644 index 0000000..230882e --- /dev/null +++ b/tests/Test/TestDBTrans.cs @@ -0,0 +1,78 @@ +namespace Test; + +public class TestTrans +{ + [CommandMethod("testtr")] + public void Testtr() + { + string filename = @"C:\Users\vic\Desktop\test.dwg"; + using var tr = new DBTrans(filename); + tr.ModelSpace.AddCircle(new Point3d(10, 10, 0), 20); + //tr.Database.SaveAs(filename,DwgVersion.Current); + tr.SaveDwgFile(); + } + [CommandMethod("testifoxcommit")] + public void Testifoxcommit() + { + + using var tr = new DBTrans(); + tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + tr.Abort(); + //tr.Commit(); + } + + // AOP 应用 预计示例: + // 1. 无参数 + //[AOP] + //[CommandMethod("TESTAOP")] + //public void testaop() + //{ + // // 不用 using var tr = new DBTrans(); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + //} + + // 2. 有参数 + //[AOP("file")] + //[CommandMethod("TESTAOP")] + //public void testaop() + //{ + // // 不用 using var tr = new DBTrans(file); + // var tr = DBTrans.Top; + // tr.ModelSpace.AddCircle(new Point3d(0, 0, 0), 20); + //} + + + [CommandMethod("testpt")] + public void TestPt() + { + //var pt = Env.Editor.GetPoint("pick pt:").Value; + //var pl = Env.Editor.GetEntity("pick pl").ObjectId; + + var tr1 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + using var tr2 = new DBTrans(); + var tr3 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr6 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr2.Transaction == tr3); + Env.Print(tr3 == tr6); + using var tr4 = new DBTrans(); + var tr5 = HostApplicationServices.WorkingDatabase.TransactionManager.TopTransaction; + var tr7 = Application.DocumentManager.MdiActiveDocument.TransactionManager.TopTransaction; + Env.Print(tr4.Transaction == tr5); + Env.Print(tr5 == tr7); + var trm = HostApplicationServices.WorkingDatabase.TransactionManager; + + //var ptt = tr.GetObject(pl).GetClosestPointTo(pt,false); + //var pt1 = new Point3d(0, 0.00000000000001, 0); + //var pt2 = new Point3d(0, 0.00001, 0); + //Env.Print(Tolerance.Global.EqualPoint); + //Env.Print(pt1.IsEqualTo(pt2).ToString()); + //Env.Print(pt1.IsEqualTo(pt2,new Tolerance(0.0,1e-6)).ToString()); + //Env.Print((pt1 == pt2).ToString()); + //Env.Print((pt1 != pt2).ToString()); + + + + } + +} diff --git a/tests/Test/TestEnt.cs b/tests/Test/TestEnt.cs new file mode 100644 index 0000000..e3cccf7 --- /dev/null +++ b/tests/Test/TestEnt.cs @@ -0,0 +1,40 @@ +namespace Test; + +public class TestEnt +{ + [CommandMethod("TestEntRoration")] + public void TestEntRoration() + { + var line = new Line(new(0,0,0),new(100,0,0)); + + using var tr = new DBTrans(); + tr.CurrentSpace.AddEntity(line); + var line2 = line.Clone() as Line; + tr.CurrentSpace.AddEntity(line2); + line2.Rotation(new(100, 0, 0), Math.PI / 2); + + + } + + + [CommandMethod("Testtypespeed")] + public void TestTypeSpeed() + { + var line = new Line(); + var line1 = line as Entity; + Tools.TestTimes(100000, "is 匹配:", () => + { + var t = line1 is Line; + }); + Tools.TestTimes(100000, "name 匹配:", () => + { + //var t = line.GetType().Name; + var tt = line1.GetType().Name == nameof(Line); + }); + Tools.TestTimes(100000, "dxfname 匹配:", () => + { + //var t = line.GetType().Name; + var tt = line1.GetRXClass().DxfName == nameof(Line); + }); + } +} diff --git a/tests/Test/TestFileDatabase.cs b/tests/Test/TestFileDatabase.cs new file mode 100644 index 0000000..7493f30 --- /dev/null +++ b/tests/Test/TestFileDatabase.cs @@ -0,0 +1,29 @@ +/************************************************************** +*作者:Leon +*创建时间:2022/2/11 9:55:32 +**************************************************************/ +namespace Test +{ + public class TestFileDatabase + { + [CommandMethod("Test_FileDatabaseInit")] + public void TestDatabase() + { + try + { + var fileName = @"C:\Users\Administrator\Desktop\合并详图测试BUG.dwg"; + using DBTrans trans = new(fileName); + trans.ModelSpace.AddEntity(new Line(new(0, 0, 0), new(1000, 1000, 0))); + if (trans.Document is not null && trans.Document.IsActive) + trans.Document.SendStringToExecute("_qsave\n", false, true, true); + else + trans.Database.SaveAs(fileName, DwgVersion.AC1021); + } + catch (System.Exception e) + { + System.Windows.MessageBox.Show(e.Message); + } + + } + } +} \ No newline at end of file diff --git a/tests/Test/TestJig.cs b/tests/Test/TestJig.cs new file mode 100644 index 0000000..0fb1310 --- /dev/null +++ b/tests/Test/TestJig.cs @@ -0,0 +1,334 @@ +namespace Test; +using System.Windows.Forms; + +public class Commands_Jig +{ + //已在数据库的图元如何进入jig + [CommandMethod("TestCmd_jig33")] + public static void TestCmd_jig33() + { + Circle cir; + using var tr = new DBTrans(); + var per = tr.Editor.GetEntity("\n点选圆形:"); + if (per.Status != PromptStatus.OK) + return; + cir = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + + if (cir == null) + return; + var oldSp = cir.StartPoint; + JigEx moveJig = null; + moveJig = new JigEx((mousePoint, drawEntitys) => { + moveJig.SetOptions(oldSp);//回调过程中也可以修改基点 + //cir.UpgradeOpen();//已经提权了,所以这里不需要提权 + cir.Move(cir.StartPoint, mousePoint); + //cir.DowngradeOpen(); + + //此处会Dispose图元, + //所以此处不加入已经在数据库的图元,而是加入new Entity的. + //drawEntitys.Enqueue(cir); + }); + moveJig.SetOptions(cir.GeometricExtents.MinPoint, orthomode: true); + + //此处详见方法注释 + moveJig.DatabaseEntityDraw(draw => { + draw.RawGeometry.Draw(cir); + }); + + while (true) + { + var prDrag = moveJig.Drag(); + if (prDrag.Status == PromptStatus.OK) + break; + } + } + + + //不在数据库的图元如何进入jig + [CommandMethod("TestCmd_Jig44")] + public void TestCmd_Jig44() + { + using var tr = new DBTrans(); + var per = Env.Editor.GetEntity("\n请选择一条多段线:"); + if (per.Status != PromptStatus.OK) + return; + var ent = tr.GetObject(per.ObjectId, OpenMode.ForWrite); + if (ent is not Polyline pl) + return; + + /* + * 鼠标采样器执行时修改鼠标基点 + * 原因: 多段线与鼠标垂直点作为 BasePoint,jig鼠标点为确定点 + * 所以需要先声明再传入指针,但是我发现null也可以. + */ + JigEx jig = null; + JigPromptPointOptions options = null; + jig = new JigEx((mousePoint, drawEntitys) => { + var closestPt = pl.GetClosestPointTo(mousePoint, false); + + //回调过程中SetOptions会覆盖配置,所以如果想增加关键字或者修改基点, + //不要这样做: jig.SetOptions(closestPt) 而是使用底层暴露 + options.BasePoint = closestPt; + + //需要避免重复加入同一个关键字 + if (!options.Keywords.Contains("A")) + options.Keywords.Add("A"); + + //生成文字 + var dictString = (pl.GetDistAtPoint(closestPt) * 0.001).ToString("0.00"); + var acText = new TextInfo(dictString, closestPt, AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + //加入刷新队列 + drawEntitys.Enqueue(acText); + }); + + options = jig.SetOptions(per.PickedPoint); + + // 如果没有这个,那么空格只会是 PromptStatus.None 而不是 PromptStatus.Keyword + // options.Keywords.Add(" ", " ", "空格结束啊"); + // jig.SetSpaceIsKeyword(); + + bool flag = true; + while (flag) + { + var pr = jig.Drag(); + if (pr.Status == PromptStatus.Keyword) + { + switch (pr.StringResult) + { + case "A": + tr.Editor.WriteMessage($"\n 您触发了关键字{pr.StringResult}"); + flag = false; + break; + case " ": + tr.Editor.WriteMessage("\n 触发关键字空格"); + flag = false; + break; + } + } + else if (pr.Status != PromptStatus.OK)//PromptStatus.None == 右键,空格,回车,都在这里结束 + { + tr.Editor.WriteMessage(Environment.NewLine + pr.Status.ToString()); + return; + } + else + flag = false; + } + tr.CurrentSpace.AddEntity(jig.Entitys); + } + + [CommandMethod("TestCmd_loop")] + public void TestCmd_loop() + { + DocumentCollection dm = Acap.DocumentManager; + Editor ed = dm.MdiActiveDocument.Editor; + // Create and add our message filter + MyMessageFilter filter = new(); + System.Windows.Forms.Application.AddMessageFilter(filter); + // Start the loop + while (true) + { + // Check for user input events + System.Windows.Forms.Application.DoEvents(); + // Check whether the filter has set the flag + if (filter.bCanceled == true) + { + ed.WriteMessage("\nLoop cancelled."); + break; + } + ed.WriteMessage($"\nInside while loop...and {filter.Key}"); + } + // We're done - remove the message filter + System.Windows.Forms.Application.RemoveMessageFilter(filter); + } + // Our message filter class + public class MyMessageFilter : IMessageFilter + { + public const int WM_KEYDOWN = 0x0100; + public bool bCanceled = false; + public Keys Key { get; private set; } + public bool PreFilterMessage(ref Message m) + { + if (m.Msg == WM_KEYDOWN) + { + // Check for the Escape keypress + Keys kc = (Keys)(int)m.WParam & Keys.KeyCode; + if (m.Msg == WM_KEYDOWN && kc == Keys.Escape) + { + bCanceled = true; + } + Key = kc; + // Return true to filter all keypresses + return true; + } + // Return false to let other messages through + return false; + } + } + + + [CommandMethod("TestCmd_QuickText")] + static public void TestCmd_QuickText() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + + PromptStringOptions pso = new("\nEnter text string") + { + AllowSpaces = true + }; + var pr = ed.GetString(pso); + if (pr.Status != PromptStatus.OK) + return; + + var tr = doc.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + db.CurrentSpaceId, OpenMode.ForWrite + ); + // Create the text object, set its normal and contents + + var acText = new TextInfo(pr.StringResult, + Point3d.Origin, + AttachmentPoint.BaseLeft, textHeight: 200) + .AddDBTextToEntity(); + + acText.Normal = ed.CurrentUserCoordinateSystem.CoordinateSystem3d.Zaxis; + btr.AppendEntity(acText); + tr.AddNewlyCreatedDBObject(acText, true); + + // Create our jig + var pj = new TextPlacementJig(tr, db, acText); + // Loop as we run our jig, as we may have keywords + PromptStatus stat = PromptStatus.Keyword; + while (stat == PromptStatus.Keyword) + { + PromptResult res = ed.Drag(pj); + stat = res.Status; + if ( + stat != PromptStatus.OK && + stat != PromptStatus.Keyword + ) + return; + } + tr.Commit(); + } + } + class TextPlacementJig : EntityJig + { + // Declare some internal state + readonly Database _db; + readonly Transaction _tr; + Point3d _position; + double _angle, _txtSize; + // Constructor + public TextPlacementJig( + Transaction tr, Database db, Entity ent + ) : base(ent) + { + _db = db; + _tr = tr; + _angle = 0; + _txtSize = 1; + } + protected override SamplerStatus Sampler( + JigPrompts jp + ) + { + // We acquire a point but with keywords + JigPromptPointOptions po = + new( + "\nPosition of text" + ); + po.UserInputControls = + (UserInputControls.Accept3dCoordinates | + UserInputControls.NullResponseAccepted | + UserInputControls.NoNegativeResponseAccepted | + UserInputControls.GovernedByOrthoMode); + po.SetMessageAndKeywords( + "\nSpecify position of text or " + + "[Bold/Italic/LArger/Smaller/" + + "ROtate90/LEft/Middle/RIght]: ", + "Bold Italic LArger Smaller " + + "ROtate90 LEft Middle RIght" + ); + PromptPointResult ppr = jp.AcquirePoint(po); + if (ppr.Status == PromptStatus.Keyword) + { + switch (ppr.StringResult) + { + case "Bold": + { + break; + } + case "Italic": + { + break; + } + case "LArger": + { + // Multiple the text size by two + _txtSize *= 2; + break; + } + case "Smaller": + { + // Divide the text size by two + _txtSize /= 2; + break; + } + case "ROtate90": + { + // To rotate clockwise we subtract 90 degrees and + // then normalise the angle between 0 and 360 + _angle -= Math.PI / 2; + while (_angle < Math.PI * 2) + { + _angle += Math.PI * 2; + } + break; + } + case "LEft": + { + break; + } + case "RIght": + { + break; + } + case "Middle": + { + break; + } + } + return SamplerStatus.OK; + } + else if (ppr.Status == PromptStatus.OK) + { + // Check if it has changed or not (reduces flicker) + if ( + _position.DistanceTo(ppr.Value) < + Tolerance.Global.EqualPoint + ) + return SamplerStatus.NoChange; + _position = ppr.Value; + return SamplerStatus.OK; + } + return SamplerStatus.Cancel; + } + protected override bool Update() + { + // Set properties on our text object + DBText txt = (DBText)Entity; + txt.Position = _position; + txt.Height = _txtSize; + txt.Rotation = _angle; + return true; + } + } +} diff --git a/tests/Test/TestLisp.cs b/tests/Test/TestLisp.cs new file mode 100644 index 0000000..f657286 --- /dev/null +++ b/tests/Test/TestLisp.cs @@ -0,0 +1,122 @@ +namespace Test +{ + public class TestLisp + { + //定义lisp函数 + [LispFunction("LispTest_RunLisp")] + public static object LispTest_RunLisp(ResultBuffer rb) + { + CmdTest_RunLisp(); + return null!; + } + + //模态命令,只有当CAD发出命令提示或当前没有其他的命令或程序活动的时候才可以被触发 + [CommandMethod("CmdTest_RunLisp1", CommandFlags.Modal)] + //透明命令,可以在一个命令提示输入的时候触发例如正交切换,zoom等 + [CommandMethod("CmdTest_RunLisp2", CommandFlags.Transparent)] + //选择图元之后执行命令将可以从 获取图元 + [CommandMethod("CmdTest_RunLisp3", CommandFlags.UsePickSet)] + //命令执行前已选中部分实体.在命令执行过程中这些标记不会被清除 + [CommandMethod("CmdTest_RunLisp4", CommandFlags.Redraw)] + //命令不能在透视图中使用 + [CommandMethod("CmdTest_RunLisp5", CommandFlags.NoPerspective)] + //命令不能通过 MULTIPLE命令 重复触发 + [CommandMethod("CmdTest_RunLisp6", CommandFlags.NoMultiple)] + //不允许在模型空间使用命令 + [CommandMethod("CmdTest_RunLisp7", CommandFlags.NoTileMode)] + //不允许在布局空间使用命令 + [CommandMethod("CmdTest_RunLisp8", CommandFlags.NoPaperSpace)] + //命令不能在OEM产品中使用 + [CommandMethod("CmdTest_RunLisp9", CommandFlags.NoOem)] + //不能直接使用命令名调用,必须使用 组名.全局名 调用 + [CommandMethod("CmdTest_RunLisp10", CommandFlags.Undefined)] + //定义lisp方法.已废弃 请使用lispfunction + [CommandMethod("CmdTest_RunLisp11", CommandFlags.Defun)] + //命令不会被存储在新的命令堆上 + [CommandMethod("CmdTest_RunLisp12", CommandFlags.NoNewStack)] + //命令不能被内部锁定(命令锁) + [CommandMethod("CmdTest_RunLisp13", CommandFlags.NoInternalLock)] + //调用命令的文档将会被锁定为只读 + [CommandMethod("CmdTest_RunLisp14", CommandFlags.DocReadLock)] + //调用命令的文档将会被锁定,类似document.lockdocument + [CommandMethod("CmdTest_RunLisp15", CommandFlags.DocExclusiveLock)] + //命令在CAD运行期间都能使用,而不只是在当前文档 + [CommandMethod("CmdTest_RunLisp16", CommandFlags.Session)] + //获取用户输入时,可以与属性面板之类的交互 + [CommandMethod("CmdTest_RunLisp17", CommandFlags.Interruptible)] + //命令不会被记录在命令历史记录 + [CommandMethod("CmdTest_RunLisp18", CommandFlags.NoHistory)] + //命令不会被 UNDO取消 + [CommandMethod("CmdTest_RunLisp19", CommandFlags.NoUndoMarker)] + //不能在参照块中使用命令 + [CommandMethod("CmdTest_RunLisp20", CommandFlags.NoBlockEditor)] +#if ac2009 + //acad09增,不会被动作录制器 捕捉到 + [CommandMethod("CmdTest_RunLisp21", CommandFlags.NoActionRecording)] + //acad09增,会被动作录制器捕捉 + [CommandMethod("CmdTest_RunLisp22", CommandFlags.ActionMacro)] +#endif +#if !NET35 + //推断约束时不能使用命令 + [CommandMethod("CmdTest_RunLisp23", CommandFlags.NoInferConstraint)] + //命令允许在选择图元时临时显示动态尺寸 + [CommandMethod("CmdTest_RunLisp24", CommandFlags.TempShowDynDimension)] +#endif + public static void CmdTest_RunLisp() + { + // 测试方法1: (command "CmdTest_RunLisp1") + // 测试方式2: (LispTest_RunLisp) + var dm = Application.DocumentManager; + var doc = dm.MdiActiveDocument; + var ed = doc.Editor; + + var sb = new StringBuilder(); + foreach (var item in Enum.GetValues(typeof(EditorEx.RunLispFlag))) + { + sb.Append((byte)item); + sb.Append(','); + } + sb.Remove(sb.Length - 1, 1); + var option = new PromptIntegerOptions($"\n输入RunLispFlag枚举值:[{sb}]"); + var ppr = ed.GetInteger(option); + + if (ppr.Status != PromptStatus.OK) + return; + var flag = (EditorEx.RunLispFlag)ppr.Value; + + if (flag == EditorEx.RunLispFlag.AdsQueueexpr) + { + // 同步 + Env.Editor.RunLisp("(setq a 10)(princ)", + EditorEx.RunLispFlag.AdsQueueexpr); + Env.Editor.RunLisp("(princ a)", + EditorEx.RunLispFlag.AdsQueueexpr);//成功输出 + } + else if (flag == EditorEx.RunLispFlag.AcedEvaluateLisp) + { + // 使用(command "CmdTest_RunLisp1")发送,然后 !b 查看变量,acad08是有值的,高版本是null + var strlisp0 = "(setq b 20)"; + var res0 = Env.Editor.RunLisp(strlisp0, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + + var strlisp1 = "(defun f1( / )(princ \"aa\"))"; + var res1 = Env.Editor.RunLisp(strlisp1, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + + var strlisp2 = "(defun f2( / )(command \"line\"))"; + var res2 = Env.Editor.RunLisp(strlisp2, + EditorEx.RunLispFlag.AcedEvaluateLisp); //有lisp的返回值 + } + else if (flag == EditorEx.RunLispFlag.SendStringToExecute) + { + //测试异步 + //(command "CmdTest_RunLisp1")和(LispTest_RunLisp)4都是异步 + var str = "(setq c 40)(princ)"; + Env.Editor.RunLisp(str, + EditorEx.RunLispFlag.SendStringToExecute); //异步,后发送 + Env.Editor.RunLisp("(princ c)", + EditorEx.RunLispFlag.AdsQueueexpr); //同步,先发送了,输出是null + } + } + } +} diff --git a/tests/Test/TestLoop.cs b/tests/Test/TestLoop.cs new file mode 100644 index 0000000..8992df9 --- /dev/null +++ b/tests/Test/TestLoop.cs @@ -0,0 +1,36 @@ +using IFoxCAD.Basal; + +using System.Diagnostics.CodeAnalysis; +namespace Test +{ + public class TestLoop + { + [CommandMethod("testloop")] + public void Testloop() + { + var loop = new LoopList + { + 0, + 1, + 2, + 3, + 4, + 5 + }; + + + + + Env.Print(loop); + + loop.SetFirst(loop.Last); + Env.Print(loop); + Env.Print(loop.Min()); + loop.SetFirst(new LoopListNode (loop.Min() ,loop)); + Env.Print(loop); + + + + } + } +} diff --git a/tests/Test/TestMirrorFile.cs b/tests/Test/TestMirrorFile.cs new file mode 100644 index 0000000..b5f212f --- /dev/null +++ b/tests/Test/TestMirrorFile.cs @@ -0,0 +1,73 @@ +public class MirrorFile +{ + const string file = "D:/JX.dwg"; + const string fileSave = "D:/JX222.dwg"; + + /// + /// 测试:后台打开图纸,镜像文字是否存在文字偏移 + /// 答案:不存在 + /// + [CommandMethod("CmdTest_MirrorFile")] + public static void CmdTest_MirrorFile() + { + using var tr = new DBTrans(file, openMode: FileOpenMode.OpenForReadAndReadShare); + + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + foreach (ObjectId entId in modelSpace) + { + var dbText = tr.GetObject(entId, OpenMode.ForRead)!; + if (dbText is null) + continue; + + dbText.UpgradeOpen(); + var pos = dbText.Position; + //text.Move(pos, Point3d.Origin); + //Y轴 + dbText.Mirror(Point3d.Origin, new Point3d(0, 1, 0)); + //text.Move(Point3d.Origin, pos); + dbText.DowngradeOpen(); + } + }); + tr.Database.SaveAs(fileSave, DwgVersion.AC1021/*AC1021 AutoCAD 2007/2008/2009.*/); + } + + /// + /// 测试:后台设置 dbText.IsMirroredInX 属性会令文字偏移 + /// 答案:存在,并提出解决方案 + /// + [CommandMethod("CmdTest_MirrorFile2")] + public static void CmdTest_MirrorFile2() + { + using var tr = new DBTrans(file, openMode: FileOpenMode.OpenForReadAndReadShare); + + tr.Database.DBTextDeviation(() => { + + var yaxis = new Point3d(0, 1, 0); + tr.BlockTable.Change(tr.ModelSpace.ObjectId, modelSpace => { + foreach (ObjectId entId in modelSpace) + { + var entity = tr.GetObject(entId, OpenMode.ForWrite)!; + if (entity is DBText dbText) + { + dbText.Mirror(Point3d.Origin, yaxis); + dbText.IsMirroredInX = true; //这句将导致文字偏移 + + //指定文字的垂直对齐方式 + if (dbText.VerticalMode == TextVerticalMode.TextBase) + dbText.VerticalMode = TextVerticalMode.TextBottom; + + //指定文字的水平对齐方式 + dbText.HorizontalMode = dbText.HorizontalMode switch + { + TextHorizontalMode.TextLeft => TextHorizontalMode.TextRight, + TextHorizontalMode.TextRight => TextHorizontalMode.TextLeft, + _ => dbText.HorizontalMode + }; + dbText.AdjustAlignment(tr.Database); + } + } + }); + }); + tr.Database.SaveAs(fileSave, DwgVersion.AC1021/*AC1021 AutoCAD 2007/2008/2009.*/); + } +} \ No newline at end of file diff --git a/tests/Test/TestPoint.cs b/tests/Test/TestPoint.cs new file mode 100644 index 0000000..46efe2e --- /dev/null +++ b/tests/Test/TestPoint.cs @@ -0,0 +1,160 @@ +using System.Diagnostics; + +namespace Test +{ + public class TestPoint + { + /// + /// 红黑树排序点集 + /// + [CommandMethod("TestptSortedSet")] + public void TestptSortedSet() + { + var ss1 = new SortedSet(); + ss1.Add(new Point2d(1, 1)); + ss1.Add(new Point2d(4.6, 2)); + ss1.Add(new Point2d(8, 3)); + ss1.Add(new Point2d(4, 3)); + ss1.Add(new Point2d(5, 40)); + ss1.Add(new Point2d(6, 5)); + ss1.Add(new Point2d(1, 6)); + ss1.Add(new Point2d(7, 6)); + ss1.Add(new Point2d(9, 6)); + + /*判断区间,超过就中断*/ + foreach (var item in ss1) + { + if (item.X > 3 && item.X < 7) + { + Debug.WriteLine(item); + } + else if (item.X >= 7) + { + break; + } + } + } + + + + [CommandMethod("TestptGethash")] + public void TestptGethash() + { + // test + var pt = Env.Editor.GetPoint("pick pt").Value; + //Tools.TestTimes2(1_000_000, "新语法", () => { + // pt.GetHashString2(); + //}); + Tools.TestTimes2(1_000_000, "旧语法", () => { + pt.GetHashString(); + }); + } + + [CommandMethod("Testpoint3d")] + public void TestPoint3d() + { + Env.Print($"4位小数的hash:{new Point3d(0.0_001, 0.0_002, 0.0).GetHashCode()}"); + Env.Print($"5位小数的hash:{new Point3d(0.00_001, 0.00_002, 0.0).GetHashCode()}"); + Env.Print($"6位小数的hash:{new Point3d(0.000_001, 0.000_002, 0.0).GetHashCode()}"); + Env.Print($"7位小数的hash:{new Point3d(0.000_0_001, 0.000_0_002, 0.0).GetHashCode()}"); + Env.Print($"8位小数的hash:{new Point3d(0.000_00_001, 0.000_00_002, 0.0).GetHashCode()}"); + Env.Print($"9位小数的hash:{new Point3d(0.000_000_001, 0.000_000_002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"10位小数的hash:{new Point3d(0.000_000_0001, 0.000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"11位小数的hash:{new Point3d(0.000_000_000_01, 0.000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"12位小数的hash:{new Point3d(0.000_000_000_001, 0.000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0002, 0.0).GetHashCode()}"); + Env.Print($"13位小数的hash:{new Point3d(0.000_000_000_0001, 0.000_000_000_0001, 0.0).GetHashCode()}"); + + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"14位小数的hash:{new Point3d(0.000_000_000_000_01, 0.000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"15位小数的hash:{new Point3d(0.000_000_000_000_001, 0.000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"16位小数的hash:{new Point3d(0.000_000_000_000_000_1, 0.000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"17位小数的hash:{new Point3d(0.000_000_000_000_000_01, 0.000_000_000_000_000_01, 0.0).GetHashCode()}"); + + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_002, 0.0).GetHashCode()}"); + Env.Print($"18位小数的hash:{new Point3d(0.000_000_000_000_000_001, 0.000_000_000_000_000_001, 0.0).GetHashCode()}"); + + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_2, 0.0).GetHashCode()}"); + Env.Print($"19位小数的hash:{new Point3d(0.000_000_000_000_000_000_1, 0.000_000_000_000_000_000_1, 0.0).GetHashCode()}"); + + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_02, 0.0).GetHashCode()}"); + Env.Print($"20位小数的hash:{new Point3d(0.000_000_000_000_000_000_01, 0.000_000_000_000_000_000_01, 0.0).GetHashCode()}"); + } + + [CommandMethod("Testlistequalspeed")] + public void Testlistequalspeed() + { + var lst1 = new List { 1, 2, 3, 4 }; + var lst2 = new List { 1, 2, 3, 4}; + lst1.EqualsAll(null); + Tools.TestTimes2(1000000, "eqaulspeed:", () => { + lst1.EqualsAll(lst2); + }); + + + } + + [CommandMethod("Testcontains")] + public void Testcontains() + { + // test list and dict contains speed + var lst = new List { 1, 2, 3, 4 , 5,6,7,8,9,10, 11,12,13,14,15,16,17,18,19,20}; + var hashset = new HashSet { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20 }; + var dict = new Dictionary + { + { 1, 0 }, + { 2, 1 }, + { 3, 2 }, + { 4, 3 }, + { 5, 4 }, + { 6, 5 }, + { 7, 6 }, + { 8, 7 }, + { 9, 8 }, + { 10, 9 }, + { 11, 11 }, + { 12, 12 }, + { 13, 13 }, + { 14, 14 }, + { 15, 15 }, + { 16, 16 }, + { 17, 17 }, + { 18, 18 }, + { 19, 19 }, + { 20, 20 }, + }; + + Tools.TestTimes2(100_0000, "list:", () => { + lst.Contains(20); + }); + + Tools.TestTimes2(100_0000, "hashset:", () => { + hashset.Contains(20); + }); + + Tools.TestTimes2(100_0000, "dict:", () => { + dict.ContainsKey(20); + }); + + } + + + + + + } + + +} \ No newline at end of file diff --git a/tests/Test/TestQuadTree.cs b/tests/Test/TestQuadTree.cs new file mode 100644 index 0000000..7670eae --- /dev/null +++ b/tests/Test/TestQuadTree.cs @@ -0,0 +1,461 @@ +namespace Test; + +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 +/* + * 这里属于用户调用例子, + * 调用时候必须要继承它,再提供给四叉树 + * 主要是用户可以扩展属性 + */ +public class CadEntity : QuadEntity +{ + public ObjectId ObjectId; + //这里加入其他字段 + public List? Link;//碰撞链 + public System.Drawing.Color Color; + public double Angle; + public CadEntity(ObjectId objectId, Rect box) : base(box) + { + ObjectId = objectId; + } + public int CompareTo(CadEntity? other) + { + if (other == null) + return -1; + return GetHashCode() ^ other.GetHashCode(); + } + public override int GetHashCode() + { + return (base.GetHashCode(), ObjectId.GetHashCode()).GetHashCode(); + } +} +#pragma warning restore CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + + + + + +public partial class TestQuadTree +{ + QuadTree _quadTreeRoot; + #region 四叉树创建并加入 + [CommandMethod("Test_QuadTree")] + public void Test_QuadTree() + { + using var tr = new DBTrans(); + + Rect dbExt; + //使用数据库边界来进行 + var dbExtent = tr.Database.GetValidExtents3d(); + if (dbExtent == null) + { + //throw new ArgumentException("画一个矩形"); + + //这个初始值的矩形是很有意义, + //主要是四叉树分裂过程中产生多个Rect,Rect内有很多重复的double值,是否可以内存复用,以此减少内存大小? + //接着想了一下,Rect可以是int,long,这样可以利用位运算它扩展和缩小, + //最小就是1,并且可以控制四叉树深度,不至于无限递归. + //而且指针长度跟值是一样的,所以就不需要复用了,毕竟跳转一个函数地址挺麻烦的. + //但是因为啊惊懒的原因,并没有单独制作这样的矩形, + //而且非常糟糕的是,c#不支持模板约束运算符,使得值类型之间需要通过一层接口来委婉处理,拉低了效率..引用类型倒是无所谓.. + //要么忍着,要么换c++去搞四叉树吧 + dbExt = new Rect(0, 0, 1 << 10, 1 << 10); + } + else + { + var a = new Point2d(dbExtent.Value.MinPoint.X, dbExtent.Value.MinPoint.Y); + var b = new Point2d(dbExtent.Value.MaxPoint.X, dbExtent.Value.MaxPoint.Y); + dbExt = new Rect(a, b); + } + + //创建四叉树 + _quadTreeRoot = new QuadTree(dbExt); + + //数据库边界 + var pl = dbExt.ToPoints(); + var databaseBoundary = new List<(Point3d, double, double, double)> + { + (new Point3d(pl[0].X,pl[0].Y,0),0,0,0), + (new Point3d(pl[1].X,pl[1].Y,0),0,0,0), + (new Point3d(pl[2].X,pl[2].Y,0),0,0,0), + (new Point3d(pl[3].X,pl[3].Y,0),0,0,0), + }; + tr.CurrentSpace.AddPline(databaseBoundary); + + //生成多少个图元,导致cad会令undo出错(八叉树深度过大 treemax) + //int maximumItems = 30_0000; + int maximumItems = 1000; + + //随机图元生成 + List ces = new(); //用于随机获取图元 + Timer.RunTime(() => { + //生成外边界和随机圆形 + var grc = GenerateRandomCircle(maximumItems, dbExt); + foreach (var ent in grc) + { + //初始化图元颜色 + ent.ColorIndex = 1; //Color.FromRgb(0, 0, 0);//黑色 + var edge = ent.GeometricExtents; + //四叉树数据 + var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); + var entId = tr.CurrentSpace.AddEntity(ent); + var ce = new CadEntity(entId, entRect) + { + Color = Utility.RandomColor + }; + ces.Add(ce); + /*加入随机点*/ + var p = edge.MinPoint + new Vector3d(10, 10, 0); + entRect = new Rect(p.Point2d(), p.Point2d()); + entId = tr.CurrentSpace.AddEntity(new DBPoint(p)); + var dbPointCe = new CadEntity(entId, entRect); + ces.Add(dbPointCe); + } + }, Timer.TimeEnum.Millisecond, "画圆消耗时间:");//30万图元±3秒.cad2021 + + //测试只加入四叉树的时间 + Timer.RunTime(() => { + for (int i = 0; i < ces.Count; i++) + { + _quadTreeRoot.Insert(ces[i]); + } + }, Timer.TimeEnum.Millisecond, "插入四叉树时间:");//30万图元±0.7秒.cad2021 + + tr.Editor.WriteMessage($"\n加入图元数量:{maximumItems}"); + } + + /// + /// 创建随机圆形 + /// + /// 创建数量 + /// 数据库边界 + static IEnumerable GenerateRandomCircle(int createNumber, Rect dbExt) + { + var x1 = (int)dbExt.X; + var x2 = (int)(dbExt.X + dbExt.Width); + var y1 = (int)dbExt.Y; + var y2 = (int)(dbExt.Y + dbExt.Height); + + var rand = Utility.GetRandom(); + for (int i = 0; i < createNumber; i++) + { + var x = rand.Next(x1, x2) + rand.NextDouble(); + var y = rand.Next(y1, y2) + rand.NextDouble(); + yield return EntityEx.CreateCircle(new Point3d(x, y, 0), rand.Next(1, 100)); //起点,终点 + } + } + + /*TODO 啊惊: 有点懒不想改了*/ +#if true2 + + //选择加入到四叉树 + [CommandMethod("CmdTest_QuadTree21")] + public void CmdTest_QuadTree21() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n选择单个图元加入已有的四叉树"); + + var ss = ed.Ssget(); + if (ss.Count == 0) + return; + + AddQuadTreeRoot(db, ed, ss); + } + + //自动加入全图到四叉树 + [CommandMethod("CmdTest_QuadTree20")] + public void CmdTest_QuadTree20() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n自动加入全图到四叉树"); + + var ss = new List(); + int entnum = 0; + var time1 = Timer.RunTime(() => { + db.Action(tr => { + db.TraverseBlockTable(tr, btRec => { + if (!btRec.IsLayout)//布局跳过 + return false; + + foreach (var item in btRec) + { + //var ent = item.ToEntity(tr); + ss.Add(item); + ++entnum;//图元数量:100000, 遍历全图时间:0.216秒 CmdTest_QuadTree2 + } + return false; + }); + }); + }); + ed.WriteMessage($"\n图元数量:{entnum}, 遍历全图时间:{time1 / 1000.0}秒"); + + //清空原有的 + _quadTreeRoot = null; + AddQuadTreeRoot(db, ed, ss); + } + + void AddQuadTreeRoot(Database db, Editor ed, List ss) + { + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的,重新初始化"); + + Rect dbExt; + //使用数据库边界来进行 + var dbExtent = db.GetValidExtents3d(); + if (dbExtent == null) + { + //throw new ArgumentException("画一个矩形"); + + //测试时候画个矩形,在矩形内画随机坐标的圆形 + dbExt = new Rect(0, 0, 32525, 32525); + } + else + { + dbExt = new Rect(dbExtent.Value.MinPoint.Point2d(), dbExtent.Value.MaxPoint.Point2d()); + } + _quadTreeRoot = new(dbExt); + } + + /* 测试: + * 为了测试删除内容释放了分支,再重复加入是否报错 + * 先创建 CmdTest_QuadTree1 + * 再减去 CmdTest_QuadTree0 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + * 然后加入 CmdTest_QuadTree2 + * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果. + */ + + List ces = new(); + db.Action(tr => { + ss.ForEach(entId => { + var ent = entId.ToEntity(tr); + if (ent is null) + return; + var edge = new EdgeEntity(ent); + //四叉树数据 + var ce = new CadEntity(entId, edge.Edge) + { + Color = Utility.RandomColor + }; + ces.Add(ce); + + edge.Dispose(); + }); + }); + + var time2 = Timer.RunTime(() => { + _quadTreeRoot.Insert(ces); + }); + ed.WriteMessage($"\n图元数量:{ces.Count}, 加入四叉树时间:{time2 / 1000.0}秒"); + } +#endif + + #endregion + + /*TODO 啊惊: 有点懒不想改了*/ +#if true2 + + #region 节点边界显示 + //四叉树减去节点 + [CommandMethod("CmdTest_QuadTree0")] + public void CmdTest_QuadTree0() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + //var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n四叉树减区"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + var rect = GetCorner(ed); + if (rect is null) + return; + _quadTreeRoot.Remove(rect); + } + + //创建节点边界 + [CommandMethod("CmdTest_QuadTree00")] + public void CmdTest_CreateNodesRect() + { + var dm = Acap.DocumentManager; + var doc = dm.MdiActiveDocument; + var db = doc.Database; + var ed = doc.Editor; + ed.WriteMessage("\n创建边界"); + + if (_quadTreeRoot is null) + { + ed.WriteMessage("\n四叉树是空的"); + return; + } + + //此处发现了一个事务处理的bug,提交数量过多的时候,会导致 ctrl+z 无法回滚, + //需要把事务放在循环体内部 + //报错: 0x6B00500A (msvcr80.dll)处(位于 acad.exe 中)引发的异常: 0xC0000005: 写入位置 0xFFE00000 时发生访问冲突。 + //画出所有的四叉树节点边界,因为事务放在外面引起 + var nodeRects = new List(); + _quadTreeRoot.ForEach(node => { + nodeRects.Add(node); + return false; + }); + var rectIds = new List(); + foreach (var item in nodeRects)//Count = 97341 当数量接近这个量级 + { + db.Action(tr => { + var pts = item.ToPoints(); + var rec = EntityAdd.AddPolyLineToEntity(pts.ToPoint2d()); + rec.ColorIndex = 250; + rectIds.Add(tr.AddEntityToMsPs(db, rec)); + }); + } + db.Action(tr => { + db.CoverGroup(tr, rectIds); + }); + + //获取四叉树深度 + int dep = 0; + _quadTreeRoot.ForEach(node => { + dep = dep > node.Depth ? dep : node.Depth; + return false; + }); + ed.WriteMessage($"\n四叉树深度是: {dep}"); + } + #endregion + +#endif + + #region 四叉树查询节点 + //选择范围改图元颜色 + [CommandMethod("CmdTest_QuadTree3")] + public void CmdTest_QuadTree3() + { + Ssget(QuadTreeSelectMode.IntersectsWith); + } + + [CommandMethod("CmdTest_QuadTree4")] + public void CmdTest_QuadTree4() + { + Ssget(QuadTreeSelectMode.Contains); + } + + /// + /// 改颜色 + /// + /// + void Ssget(QuadTreeSelectMode mode) + { + using var tr = new DBTrans(); + + if (_quadTreeRoot is null) + return; + var rect = GetCorner(tr.Editor); + if (rect is null) + return; + + tr.Editor.WriteMessage("选择模式:" + mode); + + //仿选择集 + var ces = _quadTreeRoot.Query(rect, mode); + ces.ForEach(item => { + var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); + ent.Color = Color.FromColor(item.Color); + ent.DowngradeOpen(); + ent.Dispose(); + }); + } + + /// + /// 交互获取 + /// + /// + /// +#pragma warning disable CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + public static Rect? GetCorner(Editor ed) + { + var optionsA = new PromptPointOptions($"{Environment.NewLine}起点位置:"); + var pprA = ed.GetPoint(optionsA); + if (pprA.Status != PromptStatus.OK) + return null; + var optionsB = new PromptCornerOptions(Environment.NewLine + "输入矩形角点2:", pprA.Value) + { + UseDashedLine = true,//使用虚线 + AllowNone = true,//回车 + }; + var pprB = ed.GetCorner(optionsB); + if (pprB.Status != PromptStatus.OK) + return null!; + + return new Rect(new Point2d(pprA.Value.X, pprA.Value.Y), + new Point2d(pprB.Value.X, pprB.Value.Y), + true); + } +#pragma warning restore CS8632 // 只能在 "#nullable" 注释上下文内的代码中使用可为 null 的引用类型的注释。 + + #endregion +} + +//public partial class TestQuadTree +//{ +// public void Cmd_tt6() +// { +// using var tr = new DBTrans(); +// var ed = tr.Editor; +// //创建四叉树,默认参数无所谓 +// var TreeRoot = new QuadTree(new Rect(0, 0, 32525, 32525)); + +// var fil = OpFilter.Bulid(e => e.Dxf(0) == "LINE"); +// var psr = ed.SSGet("\n 选择需要连接的直线", fil); +// if (psr.Status != PromptStatus.OK) return; +// var LineEnts = new List(psr.Value.GetEntities(OpenMode.ForWrite)!); +// //将实体插入到四岔树 +// foreach (var line in LineEnts) +// { +// var edge = line.GeometricExtents; +// var entRect = new Rect(edge.MinPoint.X, edge.MinPoint.Y, edge.MaxPoint.X, edge.MaxPoint.Y); +// var ce = new CadEntity(line.Id, entRect) +// { +// //四叉树数据 +// Angle = line.Angle +// }; +// TreeRoot.Insert(ce); +// } + +// var ppo = new PromptPointOptions(Environment.NewLine + "\n指定标注点:<空格退出>") +// { +// AllowArbitraryInput = true,//任意输入 +// AllowNone = true //允许回车 +// }; +// var ppr = ed.GetPoint(ppo);//用户点选 +// if (ppr.Status != PromptStatus.OK) +// return; +// var rect = new Rect(ppr.Value.Point2d(), 100, 100); +// tr.CurrentSpace.AddEntity(rect.ToPolyLine());//显示选择靶标范围 + +// var nent = TreeRoot.FindNearEntity(rect);//查询最近实体,按逆时针 +// var ent = tr.GetObject(nent.ObjectId, OpenMode.ForWrite);//打开实体 +// ent.ColorIndex = Utility.GetRandom().Next(1, 256);//1~256随机色 +// ent.DowngradeOpen();//实体降级 +// ent.Dispose(); + +// var res = TreeRoot.Query(rect, QuadTreeSelectMode.IntersectsWith);//查询选择靶标范围相碰的ID +// res.ForEach(item => { +// if (item.Angle == 0 || item.Angle == Math.PI) //过滤直线角度为0或180的直线 +// { +// var ent = tr.GetObject(item.ObjectId, OpenMode.ForWrite); +// ent.ColorIndex = Utility.GetRandom().Next(1, 7); +// ent.DowngradeOpen(); +// ent.Dispose(); +// } +// }); +// } +//} \ No newline at end of file diff --git a/tests/Test/Testid.cs b/tests/Test/Testid.cs new file mode 100644 index 0000000..d93b823 --- /dev/null +++ b/tests/Test/Testid.cs @@ -0,0 +1,74 @@ +namespace Test +{ + public class Testid + { + [CommandMethod("testid")] + public void TestId() + { + using var tr = new DBTrans(); + Line line = new(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.CurrentSpace.AddEntity(line); + tr.Dispose(); + + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == PromptStatus.OK) + { + res.ObjectId.Erase(); + } + //using (var tr = new DBTrans()) + //{ + // var res = Env.Editor.GetEntity("\npick ent:"); + // if(res.Status == PromptStatus.OK) + // { + // res.ObjectId.Erase(); + // } + + //} + } + + [CommandMethod("testmycommand")] + public void TestMyCommand() + { + using var dbtrans = new DBTrans(Env.Document, true, false); + using var trans = Env.Database.TransactionManager.StartTransaction(); + + var l1 = new Line(new Point3d(0, 0, 0), new Point3d(100, 100, 0)); + var blkred = trans.GetObject(Env.Database.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + blkred.AppendEntity(l1); + trans.AddNewlyCreatedDBObject(l1, true); + trans.Commit(); + //dbtrans.Dispose(); + } + [CommandMethod("testtextstyle")] + public void TestTextStyle() + { + using var tr = new DBTrans(); + tr.TextStyleTable.Add("宋体", "宋体.ttf", 0.8); + + tr.TextStyleTable.Add("宋体1", FontTTF.宋体, 0.8); + tr.TextStyleTable.Add("仿宋体", FontTTF.仿宋, 0.8); + tr.TextStyleTable.Add("fsgb2312", FontTTF.仿宋GB2312, 0.8); + tr.TextStyleTable.Add("arial", FontTTF.Arial, 0.8); + tr.TextStyleTable.Add("romas", FontTTF.Romans, 0.8); + + + + tr.TextStyleTable.Add("daziti", ttr => + { + ttr.FileName = "ascii.shx"; + ttr.BigFontFileName = "gbcbig.shx"; + }); + } + + [CommandMethod("testtextstylechange")] + public void TestTextStyleChange() + { + using var tr = new DBTrans(); + + + tr.TextStyleTable.AddWithChange("宋体1", "simfang.ttf", height: 5); + tr.TextStyleTable.AddWithChange("仿宋体", "宋体.ttf"); + tr.TextStyleTable.AddWithChange("fsgb2312", "Romans", "gbcbig"); + } + } +} diff --git a/tests/Test/testConvexHull.cs b/tests/Test/testConvexHull.cs index ee792c6..c4b9496 100644 --- a/tests/Test/testConvexHull.cs +++ b/tests/Test/testConvexHull.cs @@ -1,21 +1,9 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.Geometry; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Colors; - -namespace test +namespace Test { - public class testConvexHull + public class TestConvexHull { [CommandMethod("testch")] - public void testch() + public void Testch() { //using var tr = new DBTrans(); //var pts = new List(); @@ -23,7 +11,7 @@ public void testch() //while (flag) //{ // var pt = tr.Editor.GetPoint("qudian"); - // if (pt.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) + // if (pt.Status == PromptStatus.OK) // { // pts.Add(pt.Value); // tr.CurrentSpace.AddEntity(new DBPoint(pt.Value)); diff --git a/tests/Test/testblock.cs b/tests/Test/testblock.cs new file mode 100644 index 0000000..ef615be --- /dev/null +++ b/tests/Test/testblock.cs @@ -0,0 +1,631 @@ +namespace Test; +public class TestBlock +{ + //块定义 + [CommandMethod("blockdef")] + public void BlockDef() + { + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + tr.BlockTable.Add("test", + btr => { + btr.Origin = new Point3d(0, 0, 0); + }, + () => //图元 + { + return new List { new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)) }; + }, + () => //属性定义 + { + var id1 = new AttributeDefinition() { Position = new Point3d(0, 0, 0), Tag = "start", Height = 0.2 }; + var id2 = new AttributeDefinition() { Position = new Point3d(1, 1, 0), Tag = "end", Height = 0.2 }; + return new List { id1, id2 }; + } + ); + //ObjectId objectId = tr.BlockTable.Add("a");//新建块 + //objectId.GetObject().AddEntity();//测试添加空实体 + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + + }, + () => { + var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var acText = new TextInfo("123", Point3d.Origin, AttachmentPoint.BaseLeft) + .AddDBTextToEntity(); + + return new List { line, acText }; + }); + } + //修改块定义 + [CommandMethod("blockdefchange")] + public void BlockDefChange() + { + using var tr = new DBTrans(); + //var line = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + //tr.BlockTable.Change("test", btr => + //{ + // btr.Origin = new Point3d(5, 5, 0); + // btr.AddEntity(new Circle(new Point3d(0, 0, 0), Vector3d.ZAxis, 2)); + // btr.GetEntities() + // .ToList() + // .ForEach(e => e.Flush()); //刷新块显示 + + //}); + + + + + tr.BlockTable.Change("test", btr => { + foreach (var id in btr) + { + var ent = tr.GetObject(id); + using (ent.ForWrite()) + { + if (ent is Dimension dBText) + { + dBText.DimensionText = "234"; + dBText.RecomputeDimensionBlock(true); + } + + if (ent is Hatch hatch) + { + hatch.ColorIndex = 0; + + } + + + } + + } + }); + tr.Editor.Regen(); + } + + [CommandMethod("insertblockdef")] + public void InsertBlockDef() + { + using var tr = new DBTrans(); + var line1 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line2 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + var att1 = new AttributeDefinition() { Position = new Point3d(10, 10, 0), Tag = "tagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + tr.BlockTable.Add("test1", line1, line2, att1, att2); + + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + + + var line3 = new Line(new Point3d(5, 5, 0), new Point3d(6, 6, 0)); + var line4 = new Line(new Point3d(5, 5, 0), new Point3d(-6, 6, 0)); + var att3 = new AttributeDefinition() { Position = new Point3d(10, 14, 0), Tag = "tagTest3", Height = 1, TextString = "valueTest3" }; + var att4 = new AttributeDefinition() { Position = new Point3d(10, 16, 0), Tag = "tagTest4", Height = 1, TextString = "valueTest4" }; + tr.BlockTable.Add("test2", new List { line3, line4 }, new List { att3, att4 }); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1"); // 测试默认 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test2"); + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test3"); //测试插入不存在的块定义 + //tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", new Scale3d(2)); // 测试放大2倍 + //tr.CurrentSpace.InsertBlock(new Point3d(4, 4, 0), "test1", new Scale3d(2), Math.PI / 4); // 测试放大2倍,旋转45度 + + var def1 = new Dictionary + { + { "tagTest1", "1" }, + { "tagTest2", "2" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1", atts: def1); + var def2 = new Dictionary + { + { "tagTest3", "1" }, + { "tagTest4", "" } + }; + tr.CurrentSpace.InsertBlock(new Point3d(10, 10, 0), "test2", atts: def2); + tr.CurrentSpace.InsertBlock(new Point3d(-10, 0, 0), "test44"); + } + + [CommandMethod("addattsdef")] + public void AddAttsDef() + { + using var tr = new DBTrans(); + var blockid = Env.Editor.GetEntity("pick block:").ObjectId; + var blockref = tr.GetObject(blockid).BlockTableRecord; + + var att1 = new AttributeDefinition() { Position = new Point3d(20, 20, 0), Tag = "addtagTest1", Height = 1, TextString = "valueTest1" }; + var att2 = new AttributeDefinition() { Position = new Point3d(10, 12, 0), Tag = "tagTest2", Height = 1, TextString = "valueTest2" }; + + tr.BlockTable.AddAttsToBlocks(blockref, new List { att1, att2 }); + } + + [CommandMethod("testblocknullbug")] + public void TestBlockNullBug() + { + using var tr = new DBTrans(); + + var ents = new List(); + var line5 = new Line(new Point3d(0, 0, 0), new Point3d(1, 1, 0)); + var line6 = new Line(new Point3d(0, 0, 0), new Point3d(-1, 1, 0)); + ents.Add(line5); + ents.Add(line6); + tr.BlockTable.Add("test44", ents); + tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test44"); + } + + [CommandMethod("test_block_file")] + public void TestBlockFile() + { + var tr = new DBTrans(); + var id = tr.BlockTable.GetBlockFrom(@"C:\Users\vic\Desktop\test.dwg", false); + tr.CurrentSpace.InsertBlock(Point3d.Origin, id); + } + + + [CommandMethod("testclip")] + public void TestClipBlock() + { + using var tr = new DBTrans(); + tr.BlockTable.Add("test1", + btr => { + btr.Origin = new Point3d(0, 0, 0); + btr.AddEntity(new Line(new Point3d(0, 0, 0), new Point3d(10, 10, 0)), + new Line(new Point3d(10, 10, 0), new Point3d(10, 0, 0)) + ); + } + ); + //tr.BlockTable.Add("hah"); + var id = tr.CurrentSpace.InsertBlock(new Point3d(0, 0, 0), "test1"); + var bref = tr.GetObject(id); + var pts = new List { new Point3d(3, 3, 0), new Point3d(7, 3, 0), new Point3d(7, 7, 0), new Point3d(3, 7, 0) }; + bref.ClipBlockRef(pts); + + var id1 = tr.CurrentSpace.InsertBlock(new Point3d(20, 20, 0), "test1"); + var bref1 = tr.GetObject(id); + + bref1.ClipBlockRef(new Point3d(13, 13, 0), new Point3d(17, 17, 0)); + } + + /// + /// 给用户的测试程序,不知道对错 + /// + [CommandMethod("test_block_ej")] + public void EJ() + { + using (var tr = new DBTrans()) + { + + //Point3d.Origin.AddBellowToModelSpace(100, 100, 5, 3, 30);//画波纹管 + + //Database db2 = new Database(false, true); + //string fullFileName = @".\MyBlockDwgFile\001.dwg"; + //db2.ReadDwgFile(fullFileName, System.IO.FileShare.Read, true, null); + //db2.CloseInput(true); + //string blockName = "test"; + //if (!tr.BlockTable.Has(blockName)) + //{ + // //tr.Database.Insert(blockName, db2, false);//插入块 + // db.Insert(blockName, db2, false); + + //} + + string fullFileName = @"C:\Users\vic\Desktop\001.dwg"; + var blockdef = tr.BlockTable.GetBlockFrom(fullFileName, false); + + tr.Database.Clayer = tr.LayerTable["0"];//当前图层切换为0图层 + tr.LayerTable.Change(tr.Database.Clayer, ltr => { + ltr.Color = Color.FromColorIndex(ColorMethod.ByAci, 2); //ColorMethod.ByAci可以让我们使用AutoCAD ACI颜色索引……这里为2(表示黄色) + }); + + ObjectId id = tr.ModelSpace.InsertBlock(Point3d.Origin, blockdef);//插入块参照 + + + + + + var entTest = tr.GetObject(id); + entTest.Draw(); + + } + + using var tr2 = new DBTrans(); + PromptEntityOptions PEO = new("\n请选择一个块"); + PEO.SetRejectMessage("\n对象必须是块"); + PEO.AddAllowedClass(typeof(BlockReference), true); + + PromptEntityResult PER = Env.Editor.GetEntity(PEO); + if (PER.Status != PromptStatus.OK) + { + return; + } + + var Bref = tr2.GetObject(PER.ObjectId); + //var BTR = tr.GetObject(Bref.BlockTableRecord, OpenMode.ForWrite); + ////如果知道块名字BTRName + //BlockTableRecord BTR = tr.GetObject(tr.BlockTable[blockName], OpenMode.ForWrite); + + var btr = tr2.BlockTable[Bref.Name]; + + tr2.BlockTable.Change(btr, ltr => { + + foreach (ObjectId OID in ltr) + { + var Ent = tr2.GetObject(OID); + using (Ent.ForWrite()) + { + if (Ent is MText mText) + { + switch (mText.Text) + { + case "$$A": + mText.Contents = "hahaha"; + break; + case "$$B": + ; + break; + default: + ; + break; + } + + }; + if (Ent is DBText dBText) { dBText.TextString = "haha"; }; + if (Ent is Dimension dimension) + { + switch (dimension.DimensionText) + { + case "$$pipeLen": + dimension.DimensionText = "350"; + dimension.RecomputeDimensionBlock(true); + break; + default: + break; + } + }; + } + + + } + + }); + + + tr2.Editor.Regen(); + + + } + + [CommandMethod("W_KSZK")] + public void QuickBlockDef() + { + //Database db = HostApplicationServices.WorkingDatabase; + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + //var rss = ed.GetSelection(promptOpt); + var rss = Env.Editor.GetSelection(promptOpt); + using var tr = new DBTrans(); + if (rss.Status == PromptStatus.OK) + { + //SelectionSet ss = rss.Value; + //ObjectId[] ids = ss.GetObjectIds(); + //var ents = new List>(); + //var extents = new Extents3d(); + //foreach (var id in ids) + //{ + // Entity ent = tr.GetObject(id); + // if (ent is null) + // continue; + // try + // { + // extents.AddExtents(ent.GeometricExtents); + // var order = id.Handle.Value; + // var newEnt = ent.Clone() as Entity; + // ents.Add(new KeyValuePair(newEnt, order)); + // ent.UpgradeOpen(); + // ent.Erase(); + // ent.DowngradeOpen(); + // } + // catch (System.Exception exc) + // { + // ed.WriteMessage(exc.Message); + // } + //} + //ents = ents.OrderBy(x => x.Value).ToList(); + var ents = rss.Value.GetEntities(); + //ents.ForEach(ent => extents.AddExtents(ent.GeometricExtents)); + var extents = ents.GetExtents(); + + Point3d pt = extents.MinPoint; + Matrix3d matrix = Matrix3d.Displacement(Point3d.Origin - pt); + //var newEnts = new List(); + //foreach (var ent in ents) + //{ + // var newEnt = ent.Key; + // newEnt.TransformBy(matrix); + // newEnts.Add(newEnt); + //} + //if (tr.BlockTable.Has(blockName)) + //{ + // Application.ShowAlertDialog(Environment.NewLine + "块名重复,程序退出!"); + // return; + //} + ents.ForEach(ent => + ent.ForWrite(e => e.TransformBy(matrix))); + //var newents = ents.Select(ent => + //{ + // var maping = new IdMapping(); + // return ent.DeepClone(ent, maping, true) as Entity; + //}); + var newents = ents.Select(ent => ent.Clone() as Entity); + + //ents.ForEach(ent => ent.ForWrite(e => e.Erase(true))); // 删除实体就会卡死,比较奇怪,估计是Clone()函数的问题 + // 经过测试不是删除的问题 + var btrId = tr.BlockTable.Add(blockName, newents); + ents.ForEach(ent => ent.ForWrite(e => e.Erase(true))); + var bId = tr.CurrentSpace.InsertBlock(pt, blockName); + //tr.GetObject(bId, OpenMode.ForWrite).Move(Point3d.Origin, Point3d.Origin); + //var ed = Application.DocumentManager.MdiActiveDocument.Editor; + //ed.Regen(); + //tr.Editor.Regen(); + // 调用regen() 卡死 + } + //tr.Editor.Regen(); + //ed.Regen(); + //using (var tr = new DBTrans()) + //{ + // tr.CurrentSpace.InsertBlock(Point3d.Origin, blockName); + // tr.Editor.Regen(); + //} + } + + [CommandMethod("testquickblockdef")] + public void TestQuickBlockDef() + { + Database db = HostApplicationServices.WorkingDatabase; + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + PromptSelectionOptions promptOpt = new() + { + MessageForAdding = "请选择需要快速制作块的对象" + }; + string blockName = "W_BLOCK_" + DateTime.Now.ToString("yyyyMMdd_HHmmss"); + var rss = ed.GetSelection(promptOpt); + //var rss = Env.Editor.GetSelection(promptOpt); + if (rss.Status != PromptStatus.OK) + { + return; + } + + using var tr = db.TransactionManager.StartTransaction(); + var ids = rss.Value.GetObjectIds(); + var bt = tr.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr = new BlockTableRecord + { + Name = blockName + }; + foreach (var item in ids) + { + var ent = tr.GetObject(item, OpenMode.ForRead) as Entity; + + btr.AppendEntity(ent.Clone() as Entity); + ent.ForWrite(e => e.Erase(true)); + } + bt.UpgradeOpen(); + bt.Add(btr); + tr.AddNewlyCreatedDBObject(btr, true); + bt.DowngradeOpen(); + // tr.Commit(); + //} + + //using (var tr1 = db.TransactionManager.StartTransaction()) + //{ + //var bt = tr1.GetObject(db.BlockTableId, OpenMode.ForRead) as BlockTable; + var btr1 = tr.GetObject(db.CurrentSpaceId, OpenMode.ForWrite) as BlockTableRecord; + var br = new BlockReference(Point3d.Origin, bt[blockName]) + { + ScaleFactors = default + }; + btr1.AppendEntity(br); + tr.AddNewlyCreatedDBObject(br, true); + btr1.DowngradeOpen(); + ed.Regen(); + tr.Commit(); + //ed.Regen(); + + } + + public void TestWblock() + { + var curdb = HostApplicationServices.WorkingDatabase; + PromptSelectionOptions opts = new(); + opts.MessageForAdding = "选择对象"; + var ss = Env.Editor.GetSelection(opts).Value; + var ids = new ObjectIdCollection(ss.GetObjectIds()); + var db = curdb.Wblock(ids, Point3d.Origin); + db.SaveAs(@"c:\test.dwg", DwgVersion.Current); + } + + public void TestChangeDynameicBlock() + { + var pro = new Dictionary + { + { "haha", 1 } + }; + var blockid = Env.Editor.GetEntity("选择个块").ObjectId; + using var tr = new DBTrans(); + var blockref = tr.GetObject(blockid); + blockref.ChangeBlockProperty(pro); + // 这是第一个函数的用法 + } + + [CommandMethod("TestBack")] + public void TestBack() + { + string dir = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory); + string dwg = dir + "\\test.dwg"; + if (!File.Exists(dwg)) + { + System.Windows.Forms.MessageBox.Show(dwg, "你还没有创建此文件"); + return; + } + + using var tr = new DBTrans(dwg); + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 3); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + + tr.ModelSpace.GetEntities().ForEach(ent => { + ent.ForWrite(e => e.ColorIndex = 4); + }); + tr.Database.SaveAs(dwg, DwgVersion.Current); + + } + +} + +public class BlockImportClass +{ + + [CommandMethod("CBLL")] + public void Cbll() + { + string filename = @"C:\Users\vic\Desktop\Drawing1.dwg"; + using var tr = new DBTrans(); + using var tr1 = new DBTrans(filename); + //tr.BlockTable.GetBlockFrom(filename, true); + string blkdefname = SymbolUtilityServices.RepairSymbolName(SymbolUtilityServices.GetSymbolNameFromPathName(filename, "dwg"), false); + tr.Database.Insert(blkdefname, tr1.Database, false); //插入了块定义,未插入块参照 + } + + + [CommandMethod("CBL")] + public void CombineBlocksIntoLibrary() + { + Document doc = + Application.DocumentManager.MdiActiveDocument; + Editor ed = doc.Editor; + Database destDb = doc.Database; + + // Get name of folder from which to load and import blocks + + PromptResult pr = + ed.GetString("\nEnter the folder of source drawings: "); + + if (pr.Status != PromptStatus.OK) + return; + string pathName = pr.StringResult; + + // Check the folder exists + + if (!Directory.Exists(pathName)) + { + ed.WriteMessage( + "\nDirectory does not exist: {0}", pathName + ); + return; + } + + // Get the names of our DWG files in that folder + + string[] fileNames = Directory.GetFiles(pathName, "*.dwg"); + + // A counter for the files we've imported + + int imported = 0, failed = 0; + + // For each file in our list + + foreach (string fileName in fileNames) + { + // Double-check we have a DWG file (probably unnecessary) + + if (fileName.EndsWith( + ".dwg", + StringComparison.InvariantCultureIgnoreCase + ) + ) + { + // Catch exceptions at the file level to allow skipping + + try + { + // Suggestion from Thorsten Meinecke... + + string destName = + SymbolUtilityServices.GetSymbolNameFromPathName( + fileName, "dwg" + ); + + // And from Dan Glassman... + + destName = + SymbolUtilityServices.RepairSymbolName( + destName, false + ); + + // Create a source database to load the DWG into + + using Database db = new(false, true); + // Read the DWG into our side database + + db.ReadDwgFile(fileName, FileShare.Read, true, ""); + bool isAnno = db.AnnotativeDwg; + + // Insert it into the destination database as + // a named block definition + + ObjectId btrId = destDb.Insert( + destName, + db, + false + ); + + if (isAnno) + { + // If an annotative block, open the resultant BTR + // and set its annotative definition status + + Transaction tr = + destDb.TransactionManager.StartTransaction(); + using (tr) + { + BlockTableRecord btr = + (BlockTableRecord)tr.GetObject( + btrId, + OpenMode.ForWrite + ); + btr.Annotative = AnnotativeStates.True; + tr.Commit(); + } + } + + // Print message and increment imported block counter + + ed.WriteMessage("\nImported from \"{0}\".", fileName); + imported++; + } + catch (System.Exception ex) + { + ed.WriteMessage( + "\nProblem importing \"{0}\": {1} - file skipped.", + fileName, ex.Message + ); + failed++; + } + } + } + + ed.WriteMessage( + "\nImported block definitions from {0} files{1} in " + + "\"{2}\" into the current drawing.", + imported, + failed > 0 ? " (" + failed + " failed)" : "", + pathName + ); + } +} \ No newline at end of file diff --git a/tests/Test/testeditor.cs b/tests/Test/testeditor.cs index 397d5c4..7d610cf 100644 --- a/tests/Test/testeditor.cs +++ b/tests/Test/testeditor.cs @@ -1,38 +1,69 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.EditorInput; -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using IFoxCAD.Cad; -namespace test +namespace Test; + +public class Testeditor { - public class testeditor + [CommandMethod("tested")] + public void Tested() + { + var pts = new List + { + new Point2d(0,0), + new Point2d(0,1), + new Point2d(1,1), + new Point2d(1,0) + }; + var res = EditorEx.GetLines(pts, false); + var res1 = EditorEx.GetLines(pts, true); + var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); + + Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; + var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); + var d = ed.GetDouble("qudoule"); + var i = ed.GetInteger("quint"); + var s = ed.GetString("qustr"); + Env.Editor.WriteMessage(""); + } + [CommandMethod("testzoom")] + public void Testzoom() { - [CommandMethod("tested")] - public void tested() + using var tr = new DBTrans(); + var res = Env.Editor.GetEntity("\npick ent:"); + if (res.Status == PromptStatus.OK) { - var pts = new List - { - new Point2d(0,0), - new Point2d(0,1), - new Point2d(1,1), - new Point2d(1,0) - }; - var res = EditorEx.GetLines(pts, false); - var res1 = EditorEx.GetLines(pts, true); - var res2 = pts.Select(pt => new TypedValue((int)LispDataType.Point2d, pt)).ToList(); - - Editor ed = Application.DocumentManager.MdiActiveDocument.Editor; - var pt = ed.GetPoint("qudiam", new Point3d(0, 0, 0)); - var d = ed.GetDouble("qudoule"); - var i = ed.GetInteger("quint"); - var s = ed.GetString("qustr"); - Env.Editor.WriteMessage(""); + Env.Editor.ZoomObject(res.ObjectId.GetObject()); } + + + } + [CommandMethod("testzoomextent")] + public void Testzoomextent() + { + //using var tr = new DBTrans(); + //var res = Env.Editor.GetEntity("\npick ent:"); + //if (res.Status == PromptStatus.OK) + //{ + // Env.Editor.ZoomObject(res.ObjectId.GetObject()); + //} + + Env.Editor.ZoomExtents(); + } + + [CommandMethod("testssget")] + public void Testssget() + { + var action_a = () => { Env.Print("this is a"); }; + var action_b = () => { Env.Print("this is b"); }; + + var keyword = new Dictionary + { + { "A", action_a }, + { "B", action_b } + }; + + var ss = Env.Editor.SSGet( ":S", + messages: new string[2] { "get", "del" }, + keywords: keyword); + + Env.Print(ss); } } diff --git a/tests/Test/testenv.cs b/tests/Test/testenv.cs index 6cd7c9b..05ffc5c 100644 --- a/tests/Test/testenv.cs +++ b/tests/Test/testenv.cs @@ -1,71 +1,89 @@ -using IFoxCAD.Cad; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.ApplicationServices; -using Autodesk.AutoCAD.DatabaseServices; +namespace Test; -namespace test +public class Testenv { - public class testenv + [CommandMethod("testenum")] + public void Testenum() { - [CommandMethod("testenum")] - public void testenum() - { - Env.CmdEcho = true; - } - [CommandMethod("testenum1")] - public void testenum1() - { - Env.CmdEcho = false; - } + + Env.CmdEcho = true; + + } + [CommandMethod("testenum1")] + public void Testenum1() + { + + Env.CmdEcho = false; + + } - [CommandMethod("testdimblk")] - public void testdimblk() - { + [CommandMethod("testdimblk")] + public void Testdimblk() + { - Env.Dimblk = Env.DimblkType.Dot; - Env.Dimblk = Env.DimblkType.Defult; + Env.Dimblk = Env.DimblkType.Dot; + Env.Dimblk = Env.DimblkType.Defult; + Env.Dimblk = Env.DimblkType.Oblique; - } - [CommandMethod("testdimblk1")] - public void testdimblk1() - { - var dim = Env.Dimblk; - Env.Editor.WriteMessage(dim.ToString()); + } + [CommandMethod("testdimblk1")] + public void Testdimblk1() + { + var dim = Env.Dimblk; + Env.Editor.WriteMessage(dim.ToString()); - } + } - [CommandMethod("testosmode")] - public void testosmode() - { - // 设置osmode变量,多个值用逻辑或 - Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; - // 也可以直接写数值,进行强转 - Env.OSMode = (Env.OSModeType)5179; - // 追加模式 - Env.OSMode |= Env.OSModeType.Center; - //检查是否有某个模式 - var os = Env.OSMode.Include(Env.OSModeType.Center); - // 取消某个模式 - Env.OSMode ^= Env.OSModeType.Center; - Env.Editor.WriteMessage(Env.OSMode.ToString()); - } - [CommandMethod("testosmode1")] - public void testosmode1() - { - var dim = Env.OSMode; - Env.Editor.WriteMessage(dim.ToString()); + [CommandMethod("testosmode")] + public void Testosmode() + { + // 设置osmode变量,多个值用逻辑或 + Env.OSMode = Env.OSModeType.End | Env.OSModeType.Middle; + // 也可以直接写数值,进行强转 + Env.OSMode = (Env.OSModeType)5179; + // 追加模式 + Env.OSMode |= Env.OSModeType.Center; + //检查是否有某个模式 + var os = Env.OSMode.Include(Env.OSModeType.Center); + // 取消某个模式 + Env.OSMode ^= Env.OSModeType.Center; + Env.Editor.WriteMessage(Env.OSMode.ToString()); + } + [CommandMethod("testosmode1")] + public void Testosmode1() + { + var dim = Env.OSMode; + Env.Editor.WriteMessage(dim.ToString()); - } + } + + [CommandMethod("testcadver")] + public void Testcadver() + { + //Env.Print(AcadVersion.Versions); + AcadVersion.Versions.ForEach(v => Env.Print(v)); + AcadVersion.FromApp(Application.AcadApplication).Print(); + 1.Print(); + "1".Print(); + + } - [CommandMethod("testzoom")] - public void testzoom() - { - using var tr = new DBTrans(); - var res = Env.Editor.GetEntity("\npick ent:"); - if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) - { - Env.Editor.ZoomObject(res.ObjectId.GetObject()); - } - } + [CommandMethod("TestGetVar")] + public void TestGetVar() + { + // test getvar + var a = Env.GetVar("dbmod"); + a.Print(); + Env.SetVar("dbmod1", 1); + } + + [CommandMethod("TestDwgVersion")] + public void TestDwgVersion() + { + // + //string filename = @"C:\Users\vic\Desktop\test.dwg"; + //var a = Helper.GetCadFileVersion(filename); + //a.Print(); + //((DwgVersion)a).Print(); } } diff --git a/tests/Test/testselectfilter.cs b/tests/Test/testselectfilter.cs index 9b658ee..d4056c9 100644 --- a/tests/Test/testselectfilter.cs +++ b/tests/Test/testselectfilter.cs @@ -1,29 +1,17 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.DatabaseServices; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.EditorInput; - -namespace test +namespace Test { - public class testselectfilter + public class Testselectfilter { [CommandMethod("testfilter")] - public void testfilter() + public void Testfilter() { - + var p = new Point3d(10, 10, 0); var f = OpFilter.Bulid( - e => !(e.Dxf(0) == "line" & e.Dxf(8) == "0") + e =>!(e.Dxf(0) == "line" & e.Dxf(8) == "0") | e.Dxf(0) != "circle" & e.Dxf(8) == "2" & e.Dxf(10) >= p); - - + + var f2 = OpFilter.Bulid( e => e.Or( !e.And(e.Dxf(0) == "line", e.Dxf(8) == "0"), @@ -37,7 +25,7 @@ public void testfilter() } [CommandMethod("testselectanpoint")] - public void testselectanpoint() + public void Testselectanpoint() { var sel2 = Env.Editor.SelectAtPoint(new Point3d(0, 0, 0)); Env.Editor.WriteMessage(""); diff --git a/tests/Test/wpf/Class1.cs b/tests/Test/wpf/Class1.cs new file mode 100644 index 0000000..4358668 --- /dev/null +++ b/tests/Test/wpf/Class1.cs @@ -0,0 +1,13 @@ +namespace Test.wpf +{ + public class Class1 + { + [CommandMethod("testwpf")] + public void TestWPf() + { + + var test = new TestView(); + Application.ShowModalWindow(test); + } + } +} diff --git a/tests/Test/wpf/TestView.xaml b/tests/Test/wpf/TestView.xaml index 42cb304..bd75779 100644 --- a/tests/Test/wpf/TestView.xaml +++ b/tests/Test/wpf/TestView.xaml @@ -1,9 +1,9 @@ - /// TestView.xaml 的交互逻辑 diff --git a/tests/Test/wpf/TestViewModel.cs b/tests/Test/wpf/TestViewModel.cs index dc6fce0..4cb7563 100644 --- a/tests/Test/wpf/TestViewModel.cs +++ b/tests/Test/wpf/TestViewModel.cs @@ -1,119 +1,113 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; + using System.Windows; using System.Windows.Input; -using IFoxCAD.WPF; -namespace test.wpf +namespace Test.wpf; + +class TestViewModel : ViewModelBase { - class TestViewModel : ViewModelBase - { - - private string name; + + private string name; - public string Name - { - get { return name; } - set { Set(ref name, value); } - } + public string Name + { + get { return name; } + set { Set(ref name, value); } + } - private RelayCommand clickCommand; + private RelayCommand clickCommand; - public RelayCommand ClickCommand + public RelayCommand ClickCommand + { + get { - get + if (clickCommand is null) { - if (clickCommand is null) - { - clickCommand = new( - execute => Name = "hello " + Name, - can => !string.IsNullOrEmpty(Name)); - } - return clickCommand; + clickCommand = new( + execute => Name = "hello " + Name, + can => !string.IsNullOrEmpty(Name)); } + return clickCommand; } + } - private bool receiveMouseMove; + private bool receiveMouseMove; - public bool ReceiveMouseMove - { - get { return receiveMouseMove; } - set { Set(ref receiveMouseMove, value); } - } + public bool ReceiveMouseMove + { + get { return receiveMouseMove; } + set { Set(ref receiveMouseMove, value); } + } - private string tipText; + private string tipText; - public string TipText - { - get { return tipText; } - set { Set(ref tipText, value); } - } + public string TipText + { + get { return tipText; } + set { Set(ref tipText, value); } + } - private RelayCommand loadedCommand; - public RelayCommand LoadCommand + private RelayCommand loadedCommand; + public RelayCommand LoadCommand + { + get { - get + if (loadedCommand is null) { - if (loadedCommand is null) - { - loadedCommand = new( - execute => MessageBox.Show("程序加载完毕")); - } - return loadedCommand; + loadedCommand = new( + execute => MessageBox.Show("程序加载完毕")); } + return loadedCommand; } + } - private RelayCommand mouseMoveCommand; + private RelayCommand mouseMoveCommand; - public RelayCommand MouseMoveCommand + public RelayCommand MouseMoveCommand + { + get { - get + if (mouseMoveCommand is null) { - if (mouseMoveCommand is null) - { - mouseMoveCommand = new( - execute => + mouseMoveCommand = new( + execute => + { + var pt = execute.GetPosition(execute.Device.Target); + var left = "左键放开"; + var mid = "中键放开"; + var right = "右键放开"; + + if (execute.LeftButton == MouseButtonState.Pressed) + { + left = "左键放下"; + } + if (execute.MiddleButton == MouseButtonState.Pressed) + { + mid = "中键放下"; + } + if (execute.RightButton == MouseButtonState.Pressed) { - var pt = execute.GetPosition(execute.Device.Target); - var left = "左键放开"; - var mid = "中键放开"; - var right = "右键放开"; - - if (execute.LeftButton == MouseButtonState.Pressed) - { - left = "左键放下"; - } - if (execute.MiddleButton == MouseButtonState.Pressed) - { - mid = "中键放下"; - } - if (execute.RightButton == MouseButtonState.Pressed) - { - right = "右键放下"; - } - TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; - }, - can => ReceiveMouseMove); - } - return mouseMoveCommand; + right = "右键放下"; + } + TipText = $"当前鼠标位置:X={pt.X},Y={pt.Y}。当前鼠标状态:{left}、{mid}、{right}"; + }, + can => ReceiveMouseMove); } + return mouseMoveCommand; } + } - public TestViewModel() - { - Name = "world"; - } + public TestViewModel() + { + Name = "world"; + } - } } diff --git a/tests/TestConsole/Program.cs b/tests/TestConsole/Program.cs new file mode 100644 index 0000000..aa31926 --- /dev/null +++ b/tests/TestConsole/Program.cs @@ -0,0 +1,55 @@ +// See https://aka.ms/new-console-template for more information +using System; +using System.Text; + + +//表达式树例子 +TestConsole.Test_Expression.Demo3(); +//TestConsole.Test_Expression.Demo1(); + +#region 元组测试 +var valuetuple = (1, 2); + +Console.WriteLine(valuetuple.ToString()); + +int[] someArray = new int[5] { 1, 2, 3, 4, 5 }; +int lastElement = someArray[^1]; // lastElement = 5 +Console.WriteLine(lastElement); +int midElement = someArray[^3]; +Console.WriteLine(midElement); +var range = someArray[1..3]; +foreach (var item in range) + Console.WriteLine(item); +#endregion + +Console.ReadLine(); + + +#region 测试遍历枚举 +//Season a = Season.Autumn; +//Console.WriteLine($"Integral value of {a} is {(int)a}"); // output: Integral value of Autumn is 2 +//foreach (var enumItem in Enum.GetValues(typeof(Season))) +// Console.WriteLine((byte)enumItem); + +var sb = new StringBuilder(); +/*因为 net framework 没写好的原因,导致直接使用迭代器反而更慢,到了net60就迭代器比foreach更快*/ +var enums = Enum.GetValues(typeof(Season)).GetEnumerator(); +while (enums.MoveNext()) +{ + sb.Append(((byte)enums.Current).ToString()); + sb.Append(","); +} +Console.WriteLine(sb); + +sb.Remove(sb.Length - 1, 1);//剔除末尾, +//因为有返回值所以容易理解成 sb = sb.Remove(sb.Length - 1, 1); +Console.WriteLine(sb); + +public enum Season : byte +{ + Spring, + Summer, + Autumn, + Winter +} +#endregion \ No newline at end of file diff --git a/tests/TestConsole/RuntimeHelpers.cs b/tests/TestConsole/RuntimeHelpers.cs new file mode 100644 index 0000000..9e09b29 --- /dev/null +++ b/tests/TestConsole/RuntimeHelpers.cs @@ -0,0 +1,50 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +// 如果要用range的语法比如 a[1..3],那么需要将本文件复制到你的项目里 +#if true +namespace System.Runtime.CompilerServices +{ + internal static class RuntimeHelpers + { + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + { + throw new ArgumentNullException(); + } + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T)! != null || typeof(T[]) == array.GetType()) // TODO-NULLABLE: default(T) == null warning (https://github.com/dotnet/roslyn/issues/34757) + { + // We know the type of the array to be exactly T[]. + + if (length == 0) + { + //return Array.Empty(); + return new T[0]; + + + } + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + T[] dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + } +} + +#endif \ No newline at end of file diff --git a/tests/TestConsole/TestConsole.csproj b/tests/TestConsole/TestConsole.csproj new file mode 100644 index 0000000..293a6aa --- /dev/null +++ b/tests/TestConsole/TestConsole.csproj @@ -0,0 +1,22 @@ + + + + preview + enable + + Exe + net45 + enable + preview + + + + + + + + + + + + diff --git "a/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" new file mode 100644 index 0000000..f5cc477 --- /dev/null +++ "b/tests/TestConsole/\350\241\250\350\276\276\345\274\217\346\240\221.cs" @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Linq.Expressions; + +namespace TestConsole +{ + /// + /// 表达式树 + /// MSDN链接 + /// + public class Test_Expression + { + public static void Demo1() + { + // 官方例子:表达式体内只有一个式子 + // 创建表达式树 + Expression> exprTree = num => num < 5; + + // 分解表达式树 + ParameterExpression param = exprTree.Parameters[0];//num + BinaryExpression operation = (BinaryExpression)exprTree.Body;//函数体 {(num < 5)} + ParameterExpression left = (ParameterExpression)operation.Left;//左节点 num + ConstantExpression right = (ConstantExpression)operation.Right;//右表达式 5 + + Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + param.Name, left.Name, operation.NodeType, right.Value); + Console.Read(); + } + + + public static void Demo2() + { + // 这里是会报错的!! 原因就是体内有多个例子需要分解!! + // Expression> exprTree = x => x > 5 && x < 50; + // + // // 分解表达式树 + // ParameterExpression param = exprTree.Parameters[0];// x + // BinaryExpression operation = (BinaryExpression)exprTree.Body;//函数体 {((x > 5) AndAlso (x < 50))} + // + // ParameterExpression left = (ParameterExpression)operation.Left;//左节点.......这里报错 + // ConstantExpression right = (ConstantExpression)operation.Right;//右表达式.....这里报错 + // + // Console.WriteLine("表达式树例子: {0} => {1} {2} {3}", + // param.Name, left.Name, operation.NodeType, right.Value); + } + + + //博客园例子,表达式体内有多个式子 + public static void Demo3() + { + List names = new() { "Cai", "Edward", "Beauty" }; + + Console.WriteLine("******************一个表达式"); + Expression> lambda2 = name => name.Length > 2 && name.Length < 4; + var method2 = ReBuildExpression(lambda2); + var query2 = names.Where(method2); + foreach (string n in query2) + Console.WriteLine(n); + + Console.WriteLine("******************二个表达式"); + Expression> lambda0 = item => item.Length > 2; + Expression> lambda1 = item => item.Length < 4; + var method = ReBuildExpression(lambda0, lambda1); + var query = names.Where(method); + foreach (string n in query) + Console.WriteLine(n); + Console.WriteLine("******************表达式结束"); + Console.Read(); + } + + + static Func ReBuildExpression(Expression> lambda) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda.Body); + //构造一个新的表达式 + var newLambda = Expression.Lambda>(left, my.Parameter); + return newLambda.Compile(); + } + + + + /// + /// 重构表达式_合并 + /// + /// 匿名函数表达式1 + /// 匿名函数表达式2 + /// + static Func ReBuildExpression(Expression> lambda0, + Expression> lambda1) + { + MyExpressionVisitor my = new() + { + Parameter = Expression.Parameter(typeof(string), "name") + }; + + Expression left = my.Modify(lambda0.Body); + Expression right = my.Modify(lambda1.Body); + var expression = Expression.AndAlso(left, right);//就是 && 合并两个匿名函数 + + //构造一个新的表达式 + var newLambda = Expression.Lambda>(expression, my.Parameter); + return newLambda.Compile(); + } + } + + + /// + /// 表达式参数分解 + /// 博客园链接 + /// + public class MyExpressionVisitor : ExpressionVisitor + { + /// + /// 公共参数 + /// + public ParameterExpression? Parameter; + /// + /// 返回替换后的参数表达式 + /// + /// + /// + public Expression Modify(Expression exp) + { + return Visit(exp); + } + /// + /// 重写参数 + /// + /// + /// + protected override Expression? VisitParameter(ParameterExpression p) + { + return Parameter; + } + } +} -- Gitee From ea0db93ad5db86e6e428983c5b6078c6ec5622b5 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sat, 13 Aug 2022 00:13:41 +0800 Subject: [PATCH 15/18] =?UTF-8?q?=E5=A4=84=E7=90=86=E5=86=B2=E7=AA=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data | 0 tests/Test/TestJig.cs | 9 --------- tests/Test/TestLisp.cs | 7 ------- tests/Test/testConvexHull.cs | 16 ---------------- tests/Test/testeditor.cs | 8 -------- tests/Test/testselectfilter.cs | 16 ---------------- 6 files changed, 56 deletions(-) delete mode 100644 src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj.data deleted file mode 100644 index e69de29..0000000 diff --git a/tests/Test/TestJig.cs b/tests/Test/TestJig.cs index 0fdf1a0..0fb1310 100644 --- a/tests/Test/TestJig.cs +++ b/tests/Test/TestJig.cs @@ -121,12 +121,7 @@ public void TestCmd_Jig44() [CommandMethod("TestCmd_loop")] public void TestCmd_loop() { -<<<<<<< HEAD DocumentCollection dm = Acap.DocumentManager; -======= - DocumentCollection dm = - Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager; ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 Editor ed = dm.MdiActiveDocument.Editor; // Create and add our message filter MyMessageFilter filter = new(); @@ -176,11 +171,7 @@ public bool PreFilterMessage(ref Message m) [CommandMethod("TestCmd_QuickText")] static public void TestCmd_QuickText() { -<<<<<<< HEAD var dm = Acap.DocumentManager; -======= - var dm = Autodesk.AutoCAD.ApplicationServices.Application.DocumentManager; ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 var doc = dm.MdiActiveDocument; var db = doc.Database; var ed = doc.Editor; diff --git a/tests/Test/TestLisp.cs b/tests/Test/TestLisp.cs index 616d96e..f657286 100644 --- a/tests/Test/TestLisp.cs +++ b/tests/Test/TestLisp.cs @@ -51,17 +51,10 @@ public static object LispTest_RunLisp(ResultBuffer rb) //不能在参照块中使用命令 [CommandMethod("CmdTest_RunLisp20", CommandFlags.NoBlockEditor)] #if ac2009 -<<<<<<< HEAD //acad09增,不会被动作录制器 捕捉到 [CommandMethod("CmdTest_RunLisp21", CommandFlags.NoActionRecording)] //acad09增,会被动作录制器捕捉 [CommandMethod("CmdTest_RunLisp22", CommandFlags.ActionMacro)] -======= - //acad09增,不会被动作录制器 捕捉到 - [CommandMethod("CmdTest_RunLisp21", CommandFlags.NoActionRecording)] - //acad09增,会被动作录制器捕捉 - [CommandMethod("CmdTest_RunLisp22", CommandFlags.ActionMacro)] ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 #endif #if !NET35 //推断约束时不能使用命令 diff --git a/tests/Test/testConvexHull.cs b/tests/Test/testConvexHull.cs index e984c32..e031562 100644 --- a/tests/Test/testConvexHull.cs +++ b/tests/Test/testConvexHull.cs @@ -1,20 +1,4 @@ -<<<<<<< HEAD -namespace Test -======= -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.Geometry; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.DatabaseServices; -using Autodesk.AutoCAD.Colors; - namespace Test ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 { public class TestConvexHull { diff --git a/tests/Test/testeditor.cs b/tests/Test/testeditor.cs index ab8525f..7d610cf 100644 --- a/tests/Test/testeditor.cs +++ b/tests/Test/testeditor.cs @@ -28,11 +28,7 @@ public void Testzoom() { using var tr = new DBTrans(); var res = Env.Editor.GetEntity("\npick ent:"); -<<<<<<< HEAD if (res.Status == PromptStatus.OK) -======= - if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 { Env.Editor.ZoomObject(res.ObjectId.GetObject()); } @@ -44,11 +40,7 @@ public void Testzoomextent() { //using var tr = new DBTrans(); //var res = Env.Editor.GetEntity("\npick ent:"); -<<<<<<< HEAD //if (res.Status == PromptStatus.OK) -======= - //if (res.Status == Autodesk.AutoCAD.EditorInput.PromptStatus.OK) ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 //{ // Env.Editor.ZoomObject(res.ObjectId.GetObject()); //} diff --git a/tests/Test/testselectfilter.cs b/tests/Test/testselectfilter.cs index 78b1a7a..d49f278 100644 --- a/tests/Test/testselectfilter.cs +++ b/tests/Test/testselectfilter.cs @@ -1,20 +1,4 @@ -<<<<<<< HEAD -namespace Test -======= -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -using Autodesk.AutoCAD.Geometry; -using Autodesk.AutoCAD.Runtime; -using Autodesk.AutoCAD.DatabaseServices; -using IFoxCAD.Cad; -using Autodesk.AutoCAD.EditorInput; - namespace Test ->>>>>>> a9efe02fa01a7a5fba5f84c0714e4331f823d916 { public class Testselectfilter { -- Gitee From 5307ad087ad3a5eb00774f4575eb04c301d20225 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sat, 13 Aug 2022 01:27:58 +0800 Subject: [PATCH 16/18] =?UTF-8?q?=E5=A4=9A=E4=BA=86=E4=B8=80=E4=B8=AA?= =?UTF-8?q?=E7=9B=B8=E5=AF=B9=E8=B7=AF=E5=BE=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/IFoxCAD.Cad/IFoxCAD.Cad.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj index 4768677..ac9bf4e 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj @@ -86,6 +86,5 @@ - -- Gitee From 237b3a3bfbd3b6a5cde34a9b3f9d2b454467d118 Mon Sep 17 00:00:00 2001 From: liuqihong <540762622@qq.com> Date: Sat, 13 Aug 2022 20:32:07 +0800 Subject: [PATCH 17/18] =?UTF-8?q?=E4=B8=BA=E4=BA=86=E5=A4=9A=E5=B7=A5?= =?UTF-8?q?=E7=A8=8B=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- IFoxCAD.sln | 48 +++++++-- .../Algorithms/Graph/Graph.cs | 0 .../Algorithms/Graph/IGraph.cs | 0 .../Algorithms/QuadTree/QuadEntity.cs | 0 .../Algorithms/QuadTree/QuadTree.cs | 0 .../Algorithms/QuadTree/QuadTreeEvn.cs | 0 .../Algorithms/QuadTree/QuadTreeNode.cs | 0 .../Algorithms/QuadTree/QuadTreeSelectMode.cs | 0 .../Algorithms/QuadTree/Rect.cs | 0 .../Algorithms/QuadTree/Utility.cs | 0 .../ExtensionMethod/BulgeVertexWidth.cs | 0 .../ExtensionMethod/CollectionEx.cs | 0 .../ExtensionMethod/Curve2dEx.cs | 0 .../ExtensionMethod/Curve3dEx.cs | 0 .../ExtensionMethod/CurveEx.cs | 0 .../ExtensionMethod/DBDictionaryEx.cs | 0 .../ExtensionMethod/DBObjectEx.cs | 0 .../ExtensionMethod/DatabaseEx.cs | 0 .../ExtensionMethod/EditorEx.cs | 0 .../ExtensionMethod/EntityEx.cs | 0 .../ExtensionMethod/Enums.cs | 0 .../ExtensionMethod/GeometryEx.cs | 0 .../ExtensionMethod/Jig.cs | 0 .../ExtensionMethod/ObjEx.cs | 0 .../ExtensionMethod/ObjectIdEx.cs | 0 .../ExtensionMethod/PointEx.cs | 0 .../ExtensionMethod/SelectionSetEx.cs | 0 .../ExtensionMethod/SymbolTableEx.cs | 0 .../ExtensionMethod/SymbolTableRecordEx.cs | 0 .../ExtensionMethod/Tools.cs | 0 .../HatchConverter.cs" | 0 .../HatchEx.cs" | 0 .../HatchInfo.cs" | 0 .../AttachmentPointHelper.cs" | 0 .../TextEntityAdd.cs" | 0 .../TextInfo.cs" | 0 .../IFoxCAD.Cad.Shared.projitems | 71 ++++++++++++ .../IFoxCAD.Cad.Shared.shproj | 13 +++ .../ResultData/LispDottedPair.cs | 0 .../ResultData/LispList.cs | 0 .../ResultData/TypedValueList.cs | 0 .../ResultData/XRecordDataList.cs | 0 .../ResultData/XdataList.cs | 0 .../Runtime/AOP.cs | 0 .../Runtime/AcadVersion.cs | 0 .../Runtime/AssemInfo.cs | 0 .../Runtime/AutoRegAssem.cs | 0 .../Runtime/CadVersion.cs | 0 .../Runtime/DBTrans.cs | 0 .../Runtime/Env.cs | 0 .../Runtime/FileOpenMode.cs | 0 .../Runtime/IAutoGo.cs | 0 .../Runtime/Log.cs | 0 .../Runtime/MethodInfoHelper.cs | 0 .../Runtime/SymbolTable.cs | 0 .../Runtime/Utils.cs | 0 .../SelectionFilter/OpComp.cs | 0 .../SelectionFilter/OpEqual.cs | 0 .../SelectionFilter/OpFilter.cs | 0 .../SelectionFilter/OpList.cs | 0 .../SelectionFilter/OpLogi.cs | 0 ...oxCAD.Cad.csproj => IFoxCAD.Acad08.csproj} | 2 + src/IFoxCAD.Cad/IFoxCAD.Acad09plus.csproj | 102 ++++++++++++++++++ src/IFoxCAD.Cad/IFoxCAD.Gcad.csproj | 94 ++++++++++++++++ src/IFoxCAD.Cad/IFoxCAD.ZWcad.csproj | 94 ++++++++++++++++ tests/Test/Test.csproj | 4 +- 66 files changed, 419 insertions(+), 9 deletions(-) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/Graph/Graph.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/Graph/IGraph.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/QuadEntity.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/QuadTree.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/QuadTreeEvn.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/QuadTreeNode.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/QuadTreeSelectMode.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/Rect.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Algorithms/QuadTree/Utility.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/BulgeVertexWidth.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/CollectionEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/Curve2dEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/Curve3dEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/CurveEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/DBDictionaryEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/DBObjectEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/DatabaseEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/EditorEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/EntityEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/Enums.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/GeometryEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/Jig.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/ObjEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/ObjectIdEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/PointEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/SelectionSetEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/SymbolTableEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/SymbolTableRecordEx.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ExtensionMethod/Tools.cs (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" (100%) rename "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" => "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" (100%) create mode 100644 src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.projitems create mode 100644 src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.shproj rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ResultData/LispDottedPair.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ResultData/LispList.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ResultData/TypedValueList.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ResultData/XRecordDataList.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/ResultData/XdataList.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/AOP.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/AcadVersion.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/AssemInfo.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/AutoRegAssem.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/CadVersion.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/DBTrans.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/Env.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/FileOpenMode.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/IAutoGo.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/Log.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/MethodInfoHelper.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/SymbolTable.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/Runtime/Utils.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/SelectionFilter/OpComp.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/SelectionFilter/OpEqual.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/SelectionFilter/OpFilter.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/SelectionFilter/OpList.cs (100%) rename src/{IFoxCAD.Cad => IFoxCAD.Cad.Shared}/SelectionFilter/OpLogi.cs (100%) rename src/IFoxCAD.Cad/{IFoxCAD.Cad.csproj => IFoxCAD.Acad08.csproj} (97%) create mode 100644 src/IFoxCAD.Cad/IFoxCAD.Acad09plus.csproj create mode 100644 src/IFoxCAD.Cad/IFoxCAD.Gcad.csproj create mode 100644 src/IFoxCAD.Cad/IFoxCAD.ZWcad.csproj diff --git a/IFoxCAD.sln b/IFoxCAD.sln index a9e0664..aef4706 100644 --- a/IFoxCAD.sln +++ b/IFoxCAD.sln @@ -3,8 +3,6 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.1.32113.165 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Cad", "src\IFoxCAD.Cad\IFoxCAD.Cad.csproj", "{D5FAED7C-A99B-4BEE-A745-45442DC44971}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Test", "tests\Test\Test.csproj", "{B1602568-F023-46C7-B635-3CB281F1EC00}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.WPF", "src\IFoxCAD.WPF\IFoxCAD.WPF.csproj", "{D820D629-1AB3-4BE3-A772-CB753D98CCB8}" @@ -13,16 +11,24 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Basal", "src\IFoxCA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestConsole", "tests\TestConsole\TestConsole.csproj", "{E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Gcad", "src\IFoxCAD.Cad\IFoxCAD.Gcad.csproj", "{D7756AF6-601D-40C2-97E9-940E5AFC2E08}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "IFoxCAD.Cad.Shared", "src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.shproj", "{82FB8450-B971-4E30-859F-4B2DDB81F590}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Cad", "Cad", "{465C4E39-FBA2-417A-AB31-BDC01601F420}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Acad08", "src\IFoxCAD.Cad\IFoxCAD.Acad08.csproj", "{F5C0DA54-2031-436D-B6FA-03C745B98862}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.Acad09plus", "src\IFoxCAD.Cad\IFoxCAD.Acad09plus.csproj", "{34213E53-C0F5-49DF-9FB2-0A5D861013EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "IFoxCAD.ZWcad", "src\IFoxCAD.Cad\IFoxCAD.ZWcad.csproj", "{C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5FAED7C-A99B-4BEE-A745-45442DC44971}.Release|Any CPU.Build.0 = Release|Any CPU {B1602568-F023-46C7-B635-3CB281F1EC00}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B1602568-F023-46C7-B635-3CB281F1EC00}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1602568-F023-46C7-B635-3CB281F1EC00}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -39,11 +45,41 @@ Global {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Debug|Any CPU.Build.0 = Debug|Any CPU {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.ActiveCfg = Release|Any CPU {E2873F0B-CAD2-45E8-8FF0-C05C0C6AD955}.Release|Any CPU.Build.0 = Release|Any CPU + {D7756AF6-601D-40C2-97E9-940E5AFC2E08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7756AF6-601D-40C2-97E9-940E5AFC2E08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7756AF6-601D-40C2-97E9-940E5AFC2E08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7756AF6-601D-40C2-97E9-940E5AFC2E08}.Release|Any CPU.Build.0 = Release|Any CPU + {F5C0DA54-2031-436D-B6FA-03C745B98862}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5C0DA54-2031-436D-B6FA-03C745B98862}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5C0DA54-2031-436D-B6FA-03C745B98862}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5C0DA54-2031-436D-B6FA-03C745B98862}.Release|Any CPU.Build.0 = Release|Any CPU + {34213E53-C0F5-49DF-9FB2-0A5D861013EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34213E53-C0F5-49DF-9FB2-0A5D861013EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34213E53-C0F5-49DF-9FB2-0A5D861013EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34213E53-C0F5-49DF-9FB2-0A5D861013EF}.Release|Any CPU.Build.0 = Release|Any CPU + {C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {D7756AF6-601D-40C2-97E9-940E5AFC2E08} = {465C4E39-FBA2-417A-AB31-BDC01601F420} + {82FB8450-B971-4E30-859F-4B2DDB81F590} = {465C4E39-FBA2-417A-AB31-BDC01601F420} + {F5C0DA54-2031-436D-B6FA-03C745B98862} = {465C4E39-FBA2-417A-AB31-BDC01601F420} + {34213E53-C0F5-49DF-9FB2-0A5D861013EF} = {465C4E39-FBA2-417A-AB31-BDC01601F420} + {C6FC723B-B4CD-475A-BBA7-4FE5CE82AC5A} = {465C4E39-FBA2-417A-AB31-BDC01601F420} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {31D6C754-CF6B-4AB0-9861-6923DD312350} EndGlobalSection + GlobalSection(SharedMSBuildProjectFiles) = preSolution + src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.projitems*{34213e53-c0f5-49df-9fb2-0a5d861013ef}*SharedItemsImports = 5 + src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.projitems*{82fb8450-b971-4e30-859f-4b2ddb81f590}*SharedItemsImports = 13 + src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.projitems*{c6fc723b-b4cd-475a-bba7-4fe5ce82ac5a}*SharedItemsImports = 5 + src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.projitems*{d7756af6-601d-40c2-97e9-940e5afc2e08}*SharedItemsImports = 5 + src\IFoxCAD.Cad.Shared\IFoxCAD.Cad.Shared.projitems*{f5c0da54-2031-436d-b6fa-03c745b98862}*SharedItemsImports = 5 + EndGlobalSection EndGlobal diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs b/src/IFoxCAD.Cad.Shared/Algorithms/Graph/Graph.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/Graph/Graph.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/Graph/Graph.cs diff --git a/src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs b/src/IFoxCAD.Cad.Shared/Algorithms/Graph/IGraph.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/Graph/IGraph.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/Graph/IGraph.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadEntity.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/QuadEntity.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadEntity.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTree.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTree.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTree.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeEvn.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeEvn.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeEvn.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeNode.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeNode.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeNode.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeSelectMode.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/QuadTreeSelectMode.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/QuadTreeSelectMode.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/Rect.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/Rect.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/Rect.cs diff --git a/src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs b/src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/Utility.cs similarity index 100% rename from src/IFoxCAD.Cad/Algorithms/QuadTree/Utility.cs rename to src/IFoxCAD.Cad.Shared/Algorithms/QuadTree/Utility.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/BulgeVertexWidth.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/BulgeVertexWidth.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/BulgeVertexWidth.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/CollectionEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/CollectionEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/CollectionEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/Curve2dEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/Curve2dEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/Curve2dEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/Curve3dEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/Curve3dEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/Curve3dEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/CurveEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/CurveEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/CurveEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/DBDictionaryEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/DBDictionaryEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/DBDictionaryEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/DBObjectEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/DBObjectEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/DBObjectEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/DatabaseEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/DatabaseEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/DatabaseEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/EditorEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/EditorEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/EditorEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/EntityEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/EntityEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/EntityEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Enums.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/Enums.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/Enums.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/Enums.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/GeometryEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/GeometryEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/GeometryEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Jig.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/Jig.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/Jig.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/Jig.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/ObjEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/ObjEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/ObjEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/ObjectIdEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/ObjectIdEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/ObjectIdEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/PointEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/PointEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/PointEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/SelectionSetEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/SelectionSetEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/SelectionSetEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/SymbolTableEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/SymbolTableEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/SymbolTableEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/SymbolTableRecordEx.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/SymbolTableRecordEx.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/SymbolTableRecordEx.cs diff --git a/src/IFoxCAD.Cad/ExtensionMethod/Tools.cs b/src/IFoxCAD.Cad.Shared/ExtensionMethod/Tools.cs similarity index 100% rename from src/IFoxCAD.Cad/ExtensionMethod/Tools.cs rename to src/IFoxCAD.Cad.Shared/ExtensionMethod/Tools.cs diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchConverter.cs" diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchEx.cs" diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\345\241\253\345\205\205/HatchInfo.cs" diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/AttachmentPointHelper.cs" diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextEntityAdd.cs" diff --git "a/src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" "b/src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" similarity index 100% rename from "src/IFoxCAD.Cad/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" rename to "src/IFoxCAD.Cad.Shared/ExtensionMethod/\346\226\260\345\273\272\346\226\207\345\255\227/TextInfo.cs" diff --git a/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.projitems b/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.projitems new file mode 100644 index 0000000..33c7ddd --- /dev/null +++ b/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.projitems @@ -0,0 +1,71 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + 82fb8450-b971-4e30-859f-4b2ddb81f590 + + + IFoxCAD.Cad.Shared + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.shproj b/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.shproj new file mode 100644 index 0000000..75a3d5e --- /dev/null +++ b/src/IFoxCAD.Cad.Shared/IFoxCAD.Cad.Shared.shproj @@ -0,0 +1,13 @@ + + + + 82fb8450-b971-4e30-859f-4b2ddb81f590 + 14.0 + + + + + + + + diff --git a/src/IFoxCAD.Cad/ResultData/LispDottedPair.cs b/src/IFoxCAD.Cad.Shared/ResultData/LispDottedPair.cs similarity index 100% rename from src/IFoxCAD.Cad/ResultData/LispDottedPair.cs rename to src/IFoxCAD.Cad.Shared/ResultData/LispDottedPair.cs diff --git a/src/IFoxCAD.Cad/ResultData/LispList.cs b/src/IFoxCAD.Cad.Shared/ResultData/LispList.cs similarity index 100% rename from src/IFoxCAD.Cad/ResultData/LispList.cs rename to src/IFoxCAD.Cad.Shared/ResultData/LispList.cs diff --git a/src/IFoxCAD.Cad/ResultData/TypedValueList.cs b/src/IFoxCAD.Cad.Shared/ResultData/TypedValueList.cs similarity index 100% rename from src/IFoxCAD.Cad/ResultData/TypedValueList.cs rename to src/IFoxCAD.Cad.Shared/ResultData/TypedValueList.cs diff --git a/src/IFoxCAD.Cad/ResultData/XRecordDataList.cs b/src/IFoxCAD.Cad.Shared/ResultData/XRecordDataList.cs similarity index 100% rename from src/IFoxCAD.Cad/ResultData/XRecordDataList.cs rename to src/IFoxCAD.Cad.Shared/ResultData/XRecordDataList.cs diff --git a/src/IFoxCAD.Cad/ResultData/XdataList.cs b/src/IFoxCAD.Cad.Shared/ResultData/XdataList.cs similarity index 100% rename from src/IFoxCAD.Cad/ResultData/XdataList.cs rename to src/IFoxCAD.Cad.Shared/ResultData/XdataList.cs diff --git a/src/IFoxCAD.Cad/Runtime/AOP.cs b/src/IFoxCAD.Cad.Shared/Runtime/AOP.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/AOP.cs rename to src/IFoxCAD.Cad.Shared/Runtime/AOP.cs diff --git a/src/IFoxCAD.Cad/Runtime/AcadVersion.cs b/src/IFoxCAD.Cad.Shared/Runtime/AcadVersion.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/AcadVersion.cs rename to src/IFoxCAD.Cad.Shared/Runtime/AcadVersion.cs diff --git a/src/IFoxCAD.Cad/Runtime/AssemInfo.cs b/src/IFoxCAD.Cad.Shared/Runtime/AssemInfo.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/AssemInfo.cs rename to src/IFoxCAD.Cad.Shared/Runtime/AssemInfo.cs diff --git a/src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs b/src/IFoxCAD.Cad.Shared/Runtime/AutoRegAssem.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/AutoRegAssem.cs rename to src/IFoxCAD.Cad.Shared/Runtime/AutoRegAssem.cs diff --git a/src/IFoxCAD.Cad/Runtime/CadVersion.cs b/src/IFoxCAD.Cad.Shared/Runtime/CadVersion.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/CadVersion.cs rename to src/IFoxCAD.Cad.Shared/Runtime/CadVersion.cs diff --git a/src/IFoxCAD.Cad/Runtime/DBTrans.cs b/src/IFoxCAD.Cad.Shared/Runtime/DBTrans.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/DBTrans.cs rename to src/IFoxCAD.Cad.Shared/Runtime/DBTrans.cs diff --git a/src/IFoxCAD.Cad/Runtime/Env.cs b/src/IFoxCAD.Cad.Shared/Runtime/Env.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/Env.cs rename to src/IFoxCAD.Cad.Shared/Runtime/Env.cs diff --git a/src/IFoxCAD.Cad/Runtime/FileOpenMode.cs b/src/IFoxCAD.Cad.Shared/Runtime/FileOpenMode.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/FileOpenMode.cs rename to src/IFoxCAD.Cad.Shared/Runtime/FileOpenMode.cs diff --git a/src/IFoxCAD.Cad/Runtime/IAutoGo.cs b/src/IFoxCAD.Cad.Shared/Runtime/IAutoGo.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/IAutoGo.cs rename to src/IFoxCAD.Cad.Shared/Runtime/IAutoGo.cs diff --git a/src/IFoxCAD.Cad/Runtime/Log.cs b/src/IFoxCAD.Cad.Shared/Runtime/Log.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/Log.cs rename to src/IFoxCAD.Cad.Shared/Runtime/Log.cs diff --git a/src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs b/src/IFoxCAD.Cad.Shared/Runtime/MethodInfoHelper.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/MethodInfoHelper.cs rename to src/IFoxCAD.Cad.Shared/Runtime/MethodInfoHelper.cs diff --git a/src/IFoxCAD.Cad/Runtime/SymbolTable.cs b/src/IFoxCAD.Cad.Shared/Runtime/SymbolTable.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/SymbolTable.cs rename to src/IFoxCAD.Cad.Shared/Runtime/SymbolTable.cs diff --git a/src/IFoxCAD.Cad/Runtime/Utils.cs b/src/IFoxCAD.Cad.Shared/Runtime/Utils.cs similarity index 100% rename from src/IFoxCAD.Cad/Runtime/Utils.cs rename to src/IFoxCAD.Cad.Shared/Runtime/Utils.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpComp.cs b/src/IFoxCAD.Cad.Shared/SelectionFilter/OpComp.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpComp.cs rename to src/IFoxCAD.Cad.Shared/SelectionFilter/OpComp.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs b/src/IFoxCAD.Cad.Shared/SelectionFilter/OpEqual.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpEqual.cs rename to src/IFoxCAD.Cad.Shared/SelectionFilter/OpEqual.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs b/src/IFoxCAD.Cad.Shared/SelectionFilter/OpFilter.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpFilter.cs rename to src/IFoxCAD.Cad.Shared/SelectionFilter/OpFilter.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpList.cs b/src/IFoxCAD.Cad.Shared/SelectionFilter/OpList.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpList.cs rename to src/IFoxCAD.Cad.Shared/SelectionFilter/OpList.cs diff --git a/src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs b/src/IFoxCAD.Cad.Shared/SelectionFilter/OpLogi.cs similarity index 100% rename from src/IFoxCAD.Cad/SelectionFilter/OpLogi.cs rename to src/IFoxCAD.Cad.Shared/SelectionFilter/OpLogi.cs diff --git a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Acad08.csproj similarity index 97% rename from src/IFoxCAD.Cad/IFoxCAD.Cad.csproj rename to src/IFoxCAD.Cad/IFoxCAD.Acad08.csproj index ac9bf4e..82097c2 100644 --- a/src/IFoxCAD.Cad/IFoxCAD.Cad.csproj +++ b/src/IFoxCAD.Cad/IFoxCAD.Acad08.csproj @@ -87,4 +87,6 @@ + + diff --git a/src/IFoxCAD.Cad/IFoxCAD.Acad09plus.csproj b/src/IFoxCAD.Cad/IFoxCAD.Acad09plus.csproj new file mode 100644 index 0000000..7e5eeee --- /dev/null +++ b/src/IFoxCAD.Cad/IFoxCAD.Acad09plus.csproj @@ -0,0 +1,102 @@ + + + + preview + enable + + net35;net40;net45 + true + true + true + 0.3.6.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + 增加四叉树测试. + true + true + true + LICENSE + true + x64 + + + + + runtime + + + + + + runtime + + + + + + runtime + + + 4.3.0 + + + + + + + + DEBUG + + + $(Configuration);ac2009 + + + $(Configuration);ac2013 + + + $(Configuration);ac2015 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + + + True + + + + + + + + + + + + diff --git a/src/IFoxCAD.Cad/IFoxCAD.Gcad.csproj b/src/IFoxCAD.Cad/IFoxCAD.Gcad.csproj new file mode 100644 index 0000000..797e090 --- /dev/null +++ b/src/IFoxCAD.Cad/IFoxCAD.Gcad.csproj @@ -0,0 +1,94 @@ + + + + preview + enable + + net35;net40;net45 + true + true + true + 0.3.6.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + 增加四叉树测试. + true + true + true + LICENSE + true + x64 + + + + + + + + + + + + + + + + + + + + DEBUG + + + $(Configuration);ac2008;ac2009 + + + $(Configuration);ac2013 + + + $(Configuration);ac2015 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + + + True + + + + + + + + + + + + + + diff --git a/src/IFoxCAD.Cad/IFoxCAD.ZWcad.csproj b/src/IFoxCAD.Cad/IFoxCAD.ZWcad.csproj new file mode 100644 index 0000000..797e090 --- /dev/null +++ b/src/IFoxCAD.Cad/IFoxCAD.ZWcad.csproj @@ -0,0 +1,94 @@ + + + + preview + enable + + net35;net40;net45 + true + true + true + 0.3.6.1 + InspireFunction + xsfhlzh;vicwjb + 基于.NET的Cad二次开发类库 + InspireFunction + https://gitee.com/inspirefunction/ifoxcad + https://gitee.com/inspirefunction/ifoxcad.git + git + IFoxCAD;CAD;AutoCad;C#;NET + 增加四叉树测试. + true + true + true + LICENSE + true + x64 + + + + + + + + + + + + + + + + + + + + DEBUG + + + $(Configuration);ac2008;ac2009 + + + $(Configuration);ac2013 + + + $(Configuration);ac2015 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + 1701;1702;CS1685 + + + + + True + + + + + + + + + + + + + + diff --git a/tests/Test/Test.csproj b/tests/Test/Test.csproj index 8e3469a..640d26c 100644 --- a/tests/Test/Test.csproj +++ b/tests/Test/Test.csproj @@ -19,10 +19,8 @@ - + - - -- Gitee From 5142af1f8c7b84e23ea09dba5c39e8eb30e8b1b6 Mon Sep 17 00:00:00 2001 From: FanJianWei <369034346@qq.com> Date: Sat, 13 Aug 2022 21:00:30 +0800 Subject: [PATCH 18/18] =?UTF-8?q?=E5=B0=8F=E8=B4=B1=E8=B4=B1=E6=8F=90?= =?UTF-8?q?=E4=BA=A4=E6=B5=8B=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...6\254\241\346\217\220\344\272\244\346\265\213\350\257\225.txt" | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 "\345\260\217\350\264\261\350\264\261\344\270\200\346\254\241\346\217\220\344\272\244\346\265\213\350\257\225.txt" diff --git "a/\345\260\217\350\264\261\350\264\261\344\270\200\346\254\241\346\217\220\344\272\244\346\265\213\350\257\225.txt" "b/\345\260\217\350\264\261\350\264\261\344\270\200\346\254\241\346\217\220\344\272\244\346\265\213\350\257\225.txt" new file mode 100644 index 0000000..e69de29 -- Gitee