From da46257be2071b22b62b8b9eac104fc3b028d4af Mon Sep 17 00:00:00 2001
From: GG Z <903524121@qq.com>
Date: Wed, 3 Sep 2025 21:46:26 +0800
Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E9=A1=B9=E7=9B=AE=E6=96=87?=
=?UTF-8?q?=E4=BB=B6=E3=80=82?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../Attributes/CommandHandlerAttribute.cs | 13 +
.../Attributes/RevitCommandAttribute.cs | 56 ++++
RevitGen.Common/RevitGen.Common.csproj | 14 +
RevitGen.Generator/RevitCommandGenerator.cs | 102 +++++++
RevitGen.Generator/RevitGen.Generator.csproj | 18 ++
RevitGen.Generator/SourceGenerationHelper.cs | 279 ++++++++++++++++++
RevitGen.Generator/SyntaxReceiver.cs | 40 +++
RevitGen.sln | 43 +++
RevitGen/RevitGen.csproj | 35 +++
RevitGenTest/Properties/Resources.Designer.cs | 83 ++++++
RevitGenTest/Properties/Resources.resx | 127 ++++++++
RevitGenTest/Resources/CodeList_16px.png | Bin 0 -> 307 bytes
RevitGenTest/Resources/CodeList_32px.png | Bin 0 -> 498 bytes
RevitGenTest/RevitAddin.cs | 59 ++++
RevitGenTest/RevitGenTest.csproj | 33 +++
nuget.config | 11 +
16 files changed, 913 insertions(+)
create mode 100644 RevitGen.Common/Attributes/CommandHandlerAttribute.cs
create mode 100644 RevitGen.Common/Attributes/RevitCommandAttribute.cs
create mode 100644 RevitGen.Common/RevitGen.Common.csproj
create mode 100644 RevitGen.Generator/RevitCommandGenerator.cs
create mode 100644 RevitGen.Generator/RevitGen.Generator.csproj
create mode 100644 RevitGen.Generator/SourceGenerationHelper.cs
create mode 100644 RevitGen.Generator/SyntaxReceiver.cs
create mode 100644 RevitGen.sln
create mode 100644 RevitGen/RevitGen.csproj
create mode 100644 RevitGenTest/Properties/Resources.Designer.cs
create mode 100644 RevitGenTest/Properties/Resources.resx
create mode 100644 RevitGenTest/Resources/CodeList_16px.png
create mode 100644 RevitGenTest/Resources/CodeList_32px.png
create mode 100644 RevitGenTest/RevitAddin.cs
create mode 100644 RevitGenTest/RevitGenTest.csproj
create mode 100644 nuget.config
diff --git a/RevitGen.Common/Attributes/CommandHandlerAttribute.cs b/RevitGen.Common/Attributes/CommandHandlerAttribute.cs
new file mode 100644
index 0000000..6a6e155
--- /dev/null
+++ b/RevitGen.Common/Attributes/CommandHandlerAttribute.cs
@@ -0,0 +1,13 @@
+using System;
+
+namespace RevitGen.Attributes
+{
+ ///
+ /// 标记一个方法作为 RevitGen 命令的执行逻辑入口点。
+ /// ★★ 这个方法必须是无参数的,并且返回 void。★★
+ ///
+ [AttributeUsage(AttributeTargets.Method, Inherited = false, AllowMultiple = false)]
+ public sealed class CommandHandlerAttribute : Attribute
+ {
+ }
+}
\ No newline at end of file
diff --git a/RevitGen.Common/Attributes/RevitCommandAttribute.cs b/RevitGen.Common/Attributes/RevitCommandAttribute.cs
new file mode 100644
index 0000000..b5767da
--- /dev/null
+++ b/RevitGen.Common/Attributes/RevitCommandAttribute.cs
@@ -0,0 +1,56 @@
+// RevitGen.Common/Attributes/RevitCommandAttribute.cs
+
+using System;
+
+using Autodesk.Revit.Attributes;
+
+namespace RevitGen.Attributes
+{
+ ///
+ /// 将一个类标记为Revit外部命令,并自动为其生成UI按钮和必要的接口实现。
+ ///
+ [AttributeUsage(AttributeTargets.Class, Inherited = false, AllowMultiple = false)]
+ public sealed class RevitCommandAttribute : Attribute
+ {
+ ///
+ /// 按钮上显示的文本。
+ ///
+ public string Text { get; }
+
+ ///
+ /// 按钮所在的Ribbon Tab的名称。
+ ///
+ public string TabName { get; set; } = "RevitGen";
+
+ ///
+ /// 按钮所在的Ribbon Panel的名称。
+ ///
+ public string PanelName { get; set; } = "Commands";
+ ///
+ /// 图标
+ ///
+ public string Icon { get; set; } = "";
+ ///
+ /// 鼠标悬停在按钮上时显示的工具提示。
+ ///
+ public string ToolTip { get; set; } = "";
+
+ ///
+ /// 命令的事务模式。生成器将根据此模式自动处理事务。
+ ///
+ public TransactionMode TransactionMode { get; set; } = TransactionMode.Manual;
+
+ ///
+ /// 构造函数
+ ///
+ /// 按钮上显示的文本。
+ public RevitCommandAttribute(string text)
+ {
+ if (string.IsNullOrWhiteSpace(text))
+ {
+ throw new ArgumentNullException(nameof(text), "Command button text cannot be empty.");
+ }
+ Text = text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/RevitGen.Common/RevitGen.Common.csproj b/RevitGen.Common/RevitGen.Common.csproj
new file mode 100644
index 0000000..a7f2d98
--- /dev/null
+++ b/RevitGen.Common/RevitGen.Common.csproj
@@ -0,0 +1,14 @@
+
+
+
+
+ netstandard2.0
+
+ $(NoWarn);NU1701
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RevitGen.Generator/RevitCommandGenerator.cs b/RevitGen.Generator/RevitCommandGenerator.cs
new file mode 100644
index 0000000..b0509a9
--- /dev/null
+++ b/RevitGen.Generator/RevitCommandGenerator.cs
@@ -0,0 +1,102 @@
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Text;
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace RevitGen.Generator
+{
+ [Generator(LanguageNames.CSharp)]
+ public class RevitCommandGenerator : ISourceGenerator
+ {
+ // ★★ 1. 修正常量,使用 RevitCommandAttribute ★★
+ private const string RevitCommandAttributeFullName = "RevitGen.Attributes.RevitCommandAttribute";
+ private const string CommandHandlerAttributeFullName = "RevitGen.Attributes.CommandHandlerAttribute";
+
+ public void Initialize(GeneratorInitializationContext context)
+ {
+ context.RegisterForSyntaxNotifications(() => new SyntaxReceiver());
+ }
+
+ public void Execute(GeneratorExecutionContext context)
+ {
+ var log = new StringBuilder();
+ log.AppendLine("// RevitGen Log:");
+ log.AppendLine($"// Compilation assembly: {context.Compilation.AssemblyName}");
+
+ if (!(context.SyntaxReceiver is SyntaxReceiver receiver))
+ {
+ // ... [日志和返回逻辑不变] ...
+ return;
+ }
+
+ log.AppendLine($"// Candidate classes found by SyntaxReceiver: {receiver.CandidateClasses.Count}");
+ if (receiver.CandidateClasses.Count == 0)
+ {
+ // ... [日志和返回逻辑不变] ...
+ return;
+ }
+
+ // ★★ 2. 确保我们查找的是正确的特性符号 ★★
+ var attributeSymbol = context.Compilation.GetTypeByMetadataName(RevitCommandAttributeFullName);
+ if (attributeSymbol == null)
+ {
+ log.AppendLine($"// ERROR: Could not find attribute symbol: {RevitCommandAttributeFullName}");
+ AddSource(context, "RevitGen_Debug_Log.g.cs", log.ToString());
+ return;
+ }
+ log.AppendLine($"// Successfully found attribute symbol: {attributeSymbol.Name}");
+
+ var commandClasses = new List();
+ foreach (var candidateClass in receiver.CandidateClasses)
+ {
+ log.AppendLine($"// -> Processing candidate: {candidateClass.Identifier.ValueText}");
+ var model = context.Compilation.GetSemanticModel(candidateClass.SyntaxTree);
+ var classSymbol = model.GetDeclaredSymbol(candidateClass) as INamedTypeSymbol;
+
+ if (classSymbol == null)
+ {
+ log.AppendLine($"// -> SKIPPED: Could not get class symbol.");
+ continue;
+ }
+
+ // ★★ 3. 检查是否应用了正确的 [RevitCommand] 特性 ★★
+ bool hasAttribute = classSymbol.GetAttributes().Any(ad =>
+ ad.AttributeClass?.Equals(attributeSymbol, SymbolEqualityComparer.Default) ?? false);
+
+ if (hasAttribute)
+ {
+ log.AppendLine($"// -> SUCCESS: Found [RevitCommand] attribute. Adding to list.");
+ commandClasses.Add(classSymbol);
+ }
+ else
+ {
+ log.AppendLine($"// -> SKIPPED: Did not find [RevitCommand] attribute.");
+ }
+ }
+
+ log.AppendLine($"// Total command classes to generate: {commandClasses.Count}");
+
+ if (commandClasses.Any())
+ {
+ // ... [生成 partial 类和 App 类的逻辑不变] ...
+ foreach (var classSymbol in commandClasses)
+ {
+ var partialClassSource = SourceGenerationHelper.GenerateCommandPartialClass(classSymbol);
+ AddSource(context, $"{classSymbol.Name}.g.cs", partialClassSource);
+ }
+
+ var appSource = SourceGenerationHelper.GenerateApplicationClass(commandClasses);
+ AddSource(context, "RevitGenApplication.g.cs", appSource);
+ }
+
+ AddSource(context, "RevitGen_Debug_Log.g.cs", log.ToString());
+ }
+
+ private void AddSource(GeneratorExecutionContext context, string hintName, string source)
+ {
+ context.AddSource(hintName, SourceText.From(source, Encoding.UTF8));
+ }
+ }
+}
\ No newline at end of file
diff --git a/RevitGen.Generator/RevitGen.Generator.csproj b/RevitGen.Generator/RevitGen.Generator.csproj
new file mode 100644
index 0000000..bbf9f67
--- /dev/null
+++ b/RevitGen.Generator/RevitGen.Generator.csproj
@@ -0,0 +1,18 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/RevitGen.Generator/SourceGenerationHelper.cs b/RevitGen.Generator/SourceGenerationHelper.cs
new file mode 100644
index 0000000..96e1fb3
--- /dev/null
+++ b/RevitGen.Generator/SourceGenerationHelper.cs
@@ -0,0 +1,279 @@
+using Microsoft.CodeAnalysis;
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+
+namespace RevitGen.Generator
+{
+ ///
+ /// 一个静态辅助类,包含了所有用于生成C#源代码的逻辑。
+ ///
+ internal static class SourceGenerationHelper
+ {
+ // 定义我们属性的完整名称,以使用最终确定的、正确的命名方案。
+ private const string RevitCommandAttributeFullName = "RevitGen.Attributes.RevitCommandAttribute";
+ private const string CommandHandlerAttributeFullName = "RevitGen.Attributes.CommandHandlerAttribute";
+
+ ///
+ /// 为一个具体的命令生成其 partial class 的【另一半】。
+ /// 这一半会实现 IExternalCommand 并提供所有上下文属性。
+ ///
+ public static string GenerateCommandPartialClass(INamedTypeSymbol classSymbol)
+ {
+ var ns = classSymbol.ContainingNamespace.ToDisplayString();
+ var className = classSymbol.Name;
+
+ var commandHandlerMethod = classSymbol.GetMembers()
+ .OfType()
+ .FirstOrDefault(m => m.GetAttributes().Any(a => a.AttributeClass?.ToDisplayString() == CommandHandlerAttributeFullName));
+
+ if (commandHandlerMethod == null || commandHandlerMethod.Parameters.Any() || !commandHandlerMethod.ReturnsVoid)
+ {
+ return $"// 错误({className}): 必须有一个被[CommandHandler]标记的、无参数且返回void的方法。";
+ }
+
+ var attributeData = classSymbol.GetAttributes().First(ad => ad.AttributeClass?.ToDisplayString() == RevitCommandAttributeFullName);
+ var transactionMode = GetAttributeProperty(attributeData, "TransactionMode", 1);
+
+ var source = new StringBuilder();
+ source.AppendLine("// ");
+ source.AppendLine($"namespace {ns}");
+ source.AppendLine("{");
+ source.AppendLine(" using Autodesk.Revit.DB;");
+ source.AppendLine(" using Autodesk.Revit.UI;");
+ source.AppendLine(" using Autodesk.Revit.Attributes;");
+ source.AppendLine(" using System.ComponentModel;");
+ source.AppendLine();
+
+ source.AppendLine($" [Transaction((TransactionMode){transactionMode})]");
+ source.AppendLine($" public partial class {className} : IExternalCommand");
+ source.AppendLine(" {");
+ source.AppendLine(" private ExternalCommandData _commandData;");
+ source.AppendLine(" public UIApplication UIApplication => _commandData.Application;");
+ source.AppendLine(" public UIDocument UIDocument => UIApplication.ActiveUIDocument;");
+ source.AppendLine(" public Document Document => UIDocument.Document;");
+ source.AppendLine(" public View ActiveView => UIDocument.ActiveView;");
+ source.AppendLine(" public Result Result { get; set; } = Result.Succeeded;");
+ source.AppendLine(" public string ErrorMessage { get; set; } = string.Empty;");
+ source.AppendLine(" public ElementSet ElementSet { get; private set; }");
+ source.AppendLine();
+
+ source.AppendLine(" public Result Execute(ExternalCommandData commandData, ref string message, ElementSet elements)");
+ source.AppendLine(" {");
+ source.AppendLine(" this._commandData = commandData;");
+ source.AppendLine(" this.ElementSet = elements;");
+ source.AppendLine();
+ source.AppendLine(" try");
+ source.AppendLine(" {");
+ if (transactionMode == 1)
+ {
+ source.AppendLine($" using (var trans = new Transaction(this.Document, \"{className}\"))");
+ source.AppendLine(" {");
+ source.AppendLine(" trans.Start();");
+ source.AppendLine($" this.{commandHandlerMethod.Name}();");
+ source.AppendLine(" if (this.Result == Result.Succeeded) trans.Commit(); else trans.RollBack();");
+ source.AppendLine(" }");
+ }
+ else
+ {
+ source.AppendLine($" this.{commandHandlerMethod.Name}();");
+ }
+ source.AppendLine(" }");
+ source.AppendLine(" catch (System.Exception ex)");
+ source.AppendLine(" {");
+ source.AppendLine(" this.ErrorMessage = ex.ToString();");
+ source.AppendLine(" this.Result = Result.Failed;");
+ source.AppendLine(" }");
+ source.AppendLine();
+ source.AppendLine(" message = this.ErrorMessage;");
+ source.AppendLine(" return this.Result;");
+ source.AppendLine(" }");
+ source.AppendLine(" }");
+ source.AppendLine("}");
+ return source.ToString();
+ }
+
+ ///
+ /// 生成唯一的 IExternalApplication 类,用于创建Revit界面 (选项卡, 面板, 按钮)。
+ ///
+ public static string GenerateApplicationClass(IEnumerable commandClasses)
+ {
+ var source = new StringBuilder();
+ source.AppendLine("// ");
+ source.AppendLine("using Autodesk.Revit.UI;");
+ source.AppendLine("using System;");
+ source.AppendLine("using System.Reflection;");
+ source.AppendLine("using System.Windows.Media;");
+ source.AppendLine("using System.Windows.Media.Imaging;");
+ source.AppendLine("using System.IO;");
+ source.AppendLine("using System.Drawing;");
+ source.AppendLine("using System.Resources;");
+
+ source.AppendLine();
+ source.AppendLine("namespace RevitGen.Runtime");
+ source.AppendLine("{");
+ source.AppendLine(" public class RevitGenApplication : IExternalApplication");
+ source.AppendLine(" {");
+ source.AppendLine(" private static ResourceManager _resourceManager;");
+
+ source.AppendLine(" private BitmapSource BitmapToImageSource(Bitmap bitmap)");
+ source.AppendLine(" {");
+ source.AppendLine(" if (bitmap == null) return null;");
+ source.AppendLine(" using (MemoryStream stream = new MemoryStream())");
+ source.AppendLine(" {");
+ source.AppendLine(" bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);");
+ source.AppendLine(" stream.Position = 0;");
+ source.AppendLine(" BitmapImage result = new BitmapImage();");
+ source.AppendLine(" result.BeginInit();");
+ source.AppendLine(" result.CacheOption = BitmapCacheOption.OnLoad;");
+ source.AppendLine(" result.StreamSource = stream;");
+ source.AppendLine(" result.EndInit();");
+ source.AppendLine(" result.Freeze();");
+ source.AppendLine(" return result;");
+ source.AppendLine(" }");
+ source.AppendLine(" }");
+ source.AppendLine();
+
+ source.AppendLine(" private BitmapSource LoadImageFromEmbeddedResource(Assembly assembly, string resourcePath)");
+ source.AppendLine(" {");
+ source.AppendLine(" try");
+ source.AppendLine(" {");
+ source.AppendLine(" string assemblyName = assembly.GetName().Name;");
+ source.AppendLine(" string resourceName = $\"{assemblyName}.{resourcePath.Replace('/', '.').Replace('\\\\', '.')}\";");
+ source.AppendLine(" using (Stream stream = assembly.GetManifestResourceStream(resourceName))");
+ source.AppendLine(" {");
+ source.AppendLine(" if (stream == null) return null;");
+ source.AppendLine(" var image = new BitmapImage();");
+ source.AppendLine(" image.BeginInit();");
+ source.AppendLine(" image.StreamSource = stream;");
+ source.AppendLine(" image.CacheOption = BitmapCacheOption.OnLoad;");
+ source.AppendLine(" image.EndInit();");
+ source.AppendLine(" image.Freeze();");
+ source.AppendLine(" return image;");
+ source.AppendLine(" }");
+ source.AppendLine(" }");
+ source.AppendLine(" catch { return null; }");
+ source.AppendLine(" }");
+ source.AppendLine();
+
+ source.AppendLine(" public Result OnStartup(UIControlledApplication app)");
+ source.AppendLine(" {");
+ source.AppendLine(" var assembly = Assembly.GetExecutingAssembly();");
+ source.AppendLine(" string assemblyPath = assembly.Location;");
+
+ source.AppendLine(" if (_resourceManager == null)");
+ source.AppendLine(" {");
+ source.AppendLine(" string resourceName = assembly.GetName().Name + \".Properties.Resources\";");
+ source.AppendLine(" try { _resourceManager = new ResourceManager(resourceName, assembly); } catch { /* ResX 不存在时忽略 */ }");
+ source.AppendLine(" }");
+
+ source.AppendLine();
+
+ var commandsWithData = commandClasses.Select(c => new {
+ Symbol = c,
+ Attribute = c.GetAttributes().First(ad => ad.AttributeClass?.ToDisplayString() == RevitCommandAttributeFullName)
+ }).ToList();
+
+ var groupedByTab = commandsWithData.GroupBy(data => GetAttributeProperty(data.Attribute, "TabName", null));
+
+ foreach (var tabGroup in groupedByTab)
+ {
+ var tabName = tabGroup.Key;
+
+ if (!string.IsNullOrEmpty(tabName))
+ {
+ source.AppendLine($" try {{ app.CreateRibbonTab(\"{tabName}\"); }} catch {{ /* 选项卡已存在 */ }}");
+ }
+
+ var groupedByPanel = tabGroup.GroupBy(data => GetAttributeProperty(data.Attribute, "PanelName", "Commands"));
+ foreach (var panelGroup in groupedByPanel)
+ {
+ var panelName = panelGroup.Key;
+ var panelVar = $"panel_{SanitizeIdentifier(tabName ?? "AddIns")}_{SanitizeIdentifier(panelName)}";
+
+ if (!string.IsNullOrEmpty(tabName))
+ {
+ source.AppendLine($" RibbonPanel {panelVar} = app.CreateRibbonPanel(\"{tabName}\", \"{panelName}\");");
+ }
+ else
+ {
+ source.AppendLine($" RibbonPanel {panelVar} = app.CreateRibbonPanel(\"{panelName}\");");
+ }
+
+ foreach (var commandData in panelGroup)
+ {
+ var commandSymbol = commandData.Symbol;
+ var attr = commandData.Attribute;
+ var buttonText = (string)attr.ConstructorArguments.First().Value;
+ var fullClassName = commandSymbol.ToDisplayString();
+ var iconName = GetAttributeProperty(attr, "Icon", string.Empty);
+ var tooltip = GetAttributeProperty(attr, "ToolTip", string.Empty);
+
+ source.AppendLine($" var pbd_{commandSymbol.Name} = new PushButtonData(\"cmd_{commandSymbol.Name}\", \"{buttonText}\", assemblyPath, \"{fullClassName}\");");
+
+ if (!string.IsNullOrEmpty(tooltip))
+ {
+ source.AppendLine($" pbd_{commandSymbol.Name}.ToolTip = \"{tooltip}\";");
+ }
+
+ if (!string.IsNullOrEmpty(iconName))
+ {
+ // ★★ 核心修正:为每个按钮的图标处理逻辑创建一个独立的局部作用域 ★★
+ source.AppendLine(" {");
+ source.AppendLine($" string iconExtension = System.IO.Path.GetExtension(\"{iconName}\");");
+ source.AppendLine(" if (!string.IsNullOrEmpty(iconExtension))");
+ source.AppendLine(" {");
+ source.AppendLine($" pbd_{commandSymbol.Name}.LargeImage = LoadImageFromEmbeddedResource(assembly, \"{iconName}\");");
+ source.AppendLine(" }");
+ source.AppendLine(" else");
+ source.AppendLine(" {");
+ source.AppendLine($" if (_resourceManager != null)");
+ source.AppendLine($" {{");
+ source.AppendLine($" try");
+ source.AppendLine($" {{");
+ source.AppendLine($" var iconBitmap = (Bitmap)_resourceManager.GetObject(\"{iconName}\");");
+ source.AppendLine($" pbd_{commandSymbol.Name}.LargeImage = BitmapToImageSource(iconBitmap);");
+ source.AppendLine($" }}");
+ source.AppendLine($" catch {{ /* 资源未找到或加载失败 */ }}");
+ source.AppendLine($" }}");
+ source.AppendLine(" }");
+ source.AppendLine(" }"); // ★★ 结束局部作用域 ★★
+ }
+
+ source.AppendLine($" {panelVar}.AddItem(pbd_{commandSymbol.Name});");
+ }
+ }
+ }
+
+ source.AppendLine(" return Result.Succeeded;");
+ source.AppendLine(" }");
+ source.AppendLine(" public Result OnShutdown(UIControlledApplication app) => Result.Succeeded;");
+ source.AppendLine(" }");
+ source.AppendLine("}");
+ return source.ToString();
+ }
+
+ ///
+ /// 一个辅助方法,用于从特性的数据中安全地读取一个具名属性。
+ ///
+ private static T GetAttributeProperty(AttributeData attributeData, string propertyName, T defaultValue)
+ {
+ var namedArgument = attributeData.NamedArguments.FirstOrDefault(kvp => kvp.Key == propertyName);
+ if (namedArgument.Value.Value == null)
+ {
+ return defaultValue;
+ }
+ return (T)namedArgument.Value.Value;
+ }
+
+ ///
+ /// 一个辅助方法,用于清理字符串,使其可以用作C#变量名。
+ ///
+ private static string SanitizeIdentifier(string name)
+ {
+ return Regex.Replace(name, @"[^\w]", "_");
+ }
+ }
+}
\ No newline at end of file
diff --git a/RevitGen.Generator/SyntaxReceiver.cs b/RevitGen.Generator/SyntaxReceiver.cs
new file mode 100644
index 0000000..d5704e6
--- /dev/null
+++ b/RevitGen.Generator/SyntaxReceiver.cs
@@ -0,0 +1,40 @@
+// RevitGen.Generator/SyntaxReceiver.cs
+
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.CSharp.Syntax;
+
+using System.Collections.Generic;
+
+namespace RevitGen.Generator
+{
+ ///
+ /// 在语法树中查找所有带有属性的、定义为 partial 的类,作为代码生成的候选对象。
+ ///
+ internal class SyntaxReceiver : ISyntaxReceiver
+ {
+ public List CandidateClasses { get; } = new List();
+
+ public void OnVisitSyntaxNode(SyntaxNode syntaxNode)
+ {
+ // 检查节点是否是一个类声明,并且它带有属性
+ //if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
+ // classDeclarationSyntax.AttributeLists.Count > 0)
+ //{
+ // // 检查类是否被声明为 partial
+ // foreach (var modifier in classDeclarationSyntax.Modifiers)
+ // {
+ // if (modifier.ValueText == "partial")
+ // {
+ // CandidateClasses.Add(classDeclarationSyntax);
+ // break;
+ // }
+ // }
+ //}
+ if (syntaxNode is ClassDeclarationSyntax classDeclarationSyntax &&
+ classDeclarationSyntax.AttributeLists.Count > 0)
+ {
+ CandidateClasses.Add(classDeclarationSyntax);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/RevitGen.sln b/RevitGen.sln
new file mode 100644
index 0000000..684872f
--- /dev/null
+++ b/RevitGen.sln
@@ -0,0 +1,43 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.14.36414.22 d17.14
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitGen", "RevitGen\RevitGen.csproj", "{36A67B40-B574-495A-A8EA-ED45B19E2A79}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitGenTest", "RevitGenTest\RevitGenTest.csproj", "{6303F3AA-6ECC-4619-8260-6A87E2BCBD50}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitGen.Generator", "RevitGen.Generator\RevitGen.Generator.csproj", "{01D32114-2908-4AEF-A420-DB3D257A6FDD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RevitGen.Common", "RevitGen.Common\RevitGen.Common.csproj", "{DFFA8B05-51AE-4A96-834E-726090A329AD}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {36A67B40-B574-495A-A8EA-ED45B19E2A79}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {36A67B40-B574-495A-A8EA-ED45B19E2A79}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {36A67B40-B574-495A-A8EA-ED45B19E2A79}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {36A67B40-B574-495A-A8EA-ED45B19E2A79}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6303F3AA-6ECC-4619-8260-6A87E2BCBD50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6303F3AA-6ECC-4619-8260-6A87E2BCBD50}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6303F3AA-6ECC-4619-8260-6A87E2BCBD50}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6303F3AA-6ECC-4619-8260-6A87E2BCBD50}.Release|Any CPU.Build.0 = Release|Any CPU
+ {01D32114-2908-4AEF-A420-DB3D257A6FDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {01D32114-2908-4AEF-A420-DB3D257A6FDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {01D32114-2908-4AEF-A420-DB3D257A6FDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {01D32114-2908-4AEF-A420-DB3D257A6FDD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DFFA8B05-51AE-4A96-834E-726090A329AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DFFA8B05-51AE-4A96-834E-726090A329AD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DFFA8B05-51AE-4A96-834E-726090A329AD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DFFA8B05-51AE-4A96-834E-726090A329AD}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {655FFF69-A9C7-470E-9D97-47894C4DC93D}
+ EndGlobalSection
+EndGlobal
diff --git a/RevitGen/RevitGen.csproj b/RevitGen/RevitGen.csproj
new file mode 100644
index 0000000..2f2f557
--- /dev/null
+++ b/RevitGen/RevitGen.csproj
@@ -0,0 +1,35 @@
+
+
+
+
+ netstandard2.0
+ RevitGen
+ 1.0.0
+ ShrlAlgo
+ A powerful source generator to accelerate Revit add-in development.
+ Revit;SourceGenerator;CodeGen
+
+
+ false
+ true
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/RevitGenTest/Properties/Resources.Designer.cs b/RevitGenTest/Properties/Resources.Designer.cs
new file mode 100644
index 0000000..432a9e4
--- /dev/null
+++ b/RevitGenTest/Properties/Resources.Designer.cs
@@ -0,0 +1,83 @@
+//------------------------------------------------------------------------------
+//
+// 此代码由工具生成。
+// 运行时版本:4.0.30319.42000
+//
+// 对此文件的更改可能会导致不正确的行为,并且如果
+// 重新生成代码,这些更改将会丢失。
+//
+//------------------------------------------------------------------------------
+
+namespace RevitGenTest.Properties {
+ using System;
+
+
+ ///
+ /// 一个强类型的资源类,用于查找本地化的字符串等。
+ ///
+ // 此类是由 StronglyTypedResourceBuilder
+ // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。
+ // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen
+ // (以 /str 作为命令选项),或重新生成 VS 项目。
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class Resources {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal Resources() {
+ }
+
+ ///
+ /// 返回此类使用的缓存的 ResourceManager 实例。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("RevitGenTest.Properties.Resources", typeof(Resources).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// 重写当前线程的 CurrentUICulture 属性,对
+ /// 使用此强类型资源类的所有资源查找执行重写。
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap CodeList_16px {
+ get {
+ object obj = ResourceManager.GetObject("CodeList_16px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+
+ ///
+ /// 查找 System.Drawing.Bitmap 类型的本地化资源。
+ ///
+ internal static System.Drawing.Bitmap CodeList_32px {
+ get {
+ object obj = ResourceManager.GetObject("CodeList_32px", resourceCulture);
+ return ((System.Drawing.Bitmap)(obj));
+ }
+ }
+ }
+}
diff --git a/RevitGenTest/Properties/Resources.resx b/RevitGenTest/Properties/Resources.resx
new file mode 100644
index 0000000..918b2ce
--- /dev/null
+++ b/RevitGenTest/Properties/Resources.resx
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+
+ ..\Resources\CodeList_16px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
+ ..\Resources\CodeList_32px.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
+
+
\ No newline at end of file
diff --git a/RevitGenTest/Resources/CodeList_16px.png b/RevitGenTest/Resources/CodeList_16px.png
new file mode 100644
index 0000000000000000000000000000000000000000..ced1916e7e561fcb26674c7febd76e24a1db9203
GIT binary patch
literal 307
zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`FFaiwLn;{81aQOlx}XVI1Fy7uE(BBCZ@0BT<$cfVD>#bPFK0~
zoY-g((vT9ibV8}98&5(+_~Bi}LQlBTg)}xXvPDn18TV<|sWlU0CbHb+@@BmzQS)Ef
z@84NlrvQuAf;CKcY~p@Zu!-j!(Awg#t+%c7!#S
z@0~P*shcar!ERryInb;B|KHu1#tLMxf&9$Opjxp>@!_|N?|>ozopr0D;MJ
Au>b%7
literal 0
HcmV?d00001
diff --git a/RevitGenTest/Resources/CodeList_32px.png b/RevitGenTest/Resources/CodeList_32px.png
new file mode 100644
index 0000000000000000000000000000000000000000..64a11be31198f69f16e3933f8bd858d0910f32ec
GIT binary patch
literal 498
zcmV)!}*KHs?_b$Z(fZJ4ybp1F!^HqDVWm0EQm;7K$Ni
zR9GMiS^!IqM2tiqkijU(l5%PR^vp}bNc6#dKsTaCZ98O})CVY}1rM;%w7>Owlb>;z
z)CWk&e3LvwE~Um=7p}pKBL{AB$tUyyppq9$Sn?f0gH;g=o664P@D9*Kr!_9}&%@+=
zF9G@jHeIRplqTTvy3hjbH{aigc&lmmx|zSJ1+erO{t#%N&*eR-1@P9h87&D`mm@_B
zur->CeL2)PiT`eD0rp!RU)#X+GnNDG&;oe#L-|j&PmY#dRxLm_{(7zg7LUMKN`C+V
o0RR68=vUqV000I_L_t&o0Q5p+P^N-RX8-^I07*qoM6N<$g6;m=5&!@I
literal 0
HcmV?d00001
diff --git a/RevitGenTest/RevitAddin.cs b/RevitGenTest/RevitAddin.cs
new file mode 100644
index 0000000..8f8c5e2
--- /dev/null
+++ b/RevitGenTest/RevitAddin.cs
@@ -0,0 +1,59 @@
+using System;
+using System.Drawing;
+
+using Autodesk.Revit.Attributes;
+using Autodesk.Revit.DB;
+using Autodesk.Revit.UI;
+using Autodesk.Revit.UI.Selection;
+
+using RevitGen.Attributes;
+
+using RevitGenTest.Properties;
+
+namespace RevitGenTest
+{
+ [RevitCommand("我的第一个命令", ToolTip = "这是一个自动生成的酷炫命令!", PanelName = "核心功能", Icon = nameof(Resources.CodeList_32px))]
+ public partial class RevitAddin
+ {
+ [CommandHandler]
+ private void Run()
+ {
+ var walls = new FilteredElementCollector(Document)
+ .OfCategory(BuiltInCategory.OST_Walls)
+ .WhereElementIsNotElementType()
+ .ToElements();
+
+ if (walls.Count == 0)
+ {
+ // 如果出现问题,只需设置属性即可
+ this.ErrorMessage = "项目中没有找到任何墙。";
+ this.Result = Result.Failed;
+ return; // 提前返回
+ }
+
+ TaskDialog.Show("成功", $"找到了 {walls.Count} 面墙。");
+ }
+ }
+ [RevitCommand("我的第一个命令", ToolTip = "这是一个自动生成的酷炫命令!", PanelName = "核心功能", Icon = "Resources/CodeList_32px.png")]
+ public partial class RevitAddinX
+ {
+ [CommandHandler]
+ private void Run()
+ {
+ var walls = new FilteredElementCollector(Document)
+ .OfCategory(BuiltInCategory.OST_Walls)
+ .WhereElementIsNotElementType()
+ .ToElements();
+
+ if (walls.Count == 0)
+ {
+ // 如果出现问题,只需设置属性即可
+ this.ErrorMessage = "项目中没有找到任何墙。";
+ this.Result = Result.Failed;
+ return; // 提前返回
+ }
+
+ TaskDialog.Show("成功", $"找到了 {walls.Count} 面墙。");
+ }
+ }
+}
\ No newline at end of file
diff --git a/RevitGenTest/RevitGenTest.csproj b/RevitGenTest/RevitGenTest.csproj
new file mode 100644
index 0000000..3ffd3ca
--- /dev/null
+++ b/RevitGenTest/RevitGenTest.csproj
@@ -0,0 +1,33 @@
+
+
+ net472
+ Library
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ Resources.resx
+
+
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+
+
+
\ No newline at end of file
diff --git a/nuget.config b/nuget.config
new file mode 100644
index 0000000..7c3cd6f
--- /dev/null
+++ b/nuget.config
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file