添加项目文件。

This commit is contained in:
GG Z
2025-09-03 21:46:26 +08:00
parent 717d50d2d3
commit da46257be2
16 changed files with 913 additions and 0 deletions

View File

@@ -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<INamedTypeSymbol>();
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));
}
}
}

View File

@@ -0,0 +1,18 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.3" />
</ItemGroup>
<ItemGroup>
<!-- 标准引用,用于写代码 -->
<ProjectReference Include="..\RevitGen.Common\RevitGen.Common.csproj" />
<!-- 分析器引用,用于代码生成 -->
</ItemGroup>
</Project>

View File

@@ -0,0 +1,279 @@
using Microsoft.CodeAnalysis;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
namespace RevitGen.Generator
{
/// <summary>
/// 一个静态辅助类包含了所有用于生成C#源代码的逻辑。
/// </summary>
internal static class SourceGenerationHelper
{
// 定义我们属性的完整名称,以使用最终确定的、正确的命名方案。
private const string RevitCommandAttributeFullName = "RevitGen.Attributes.RevitCommandAttribute";
private const string CommandHandlerAttributeFullName = "RevitGen.Attributes.CommandHandlerAttribute";
/// <summary>
/// 为一个具体的命令生成其 partial class 的【另一半】。
/// 这一半会实现 IExternalCommand 并提供所有上下文属性。
/// </summary>
public static string GenerateCommandPartialClass(INamedTypeSymbol classSymbol)
{
var ns = classSymbol.ContainingNamespace.ToDisplayString();
var className = classSymbol.Name;
var commandHandlerMethod = classSymbol.GetMembers()
.OfType<IMethodSymbol>()
.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("// <auto-generated/>");
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();
}
/// <summary>
/// 生成唯一的 IExternalApplication 类用于创建Revit界面 (选项卡, 面板, 按钮)。
/// </summary>
public static string GenerateApplicationClass(IEnumerable<INamedTypeSymbol> commandClasses)
{
var source = new StringBuilder();
source.AppendLine("// <auto-generated/>");
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<string>(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();
}
/// <summary>
/// 一个辅助方法,用于从特性的数据中安全地读取一个具名属性。
/// </summary>
private static T GetAttributeProperty<T>(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;
}
/// <summary>
/// 一个辅助方法用于清理字符串使其可以用作C#变量名。
/// </summary>
private static string SanitizeIdentifier(string name)
{
return Regex.Replace(name, @"[^\w]", "_");
}
}
}

View File

@@ -0,0 +1,40 @@
// RevitGen.Generator/SyntaxReceiver.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using System.Collections.Generic;
namespace RevitGen.Generator
{
/// <summary>
/// 在语法树中查找所有带有属性的、定义为 partial 的类,作为代码生成的候选对象。
/// </summary>
internal class SyntaxReceiver : ISyntaxReceiver
{
public List<ClassDeclarationSyntax> CandidateClasses { get; } = new List<ClassDeclarationSyntax>();
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);
}
}
}
}