mirror of
https://github.com/ShrlAlgo/RevitGen.git
synced 2026-03-07 17:28:54 +00:00
添加项目文件。
This commit is contained in:
102
RevitGen.Generator/RevitCommandGenerator.cs
Normal file
102
RevitGen.Generator/RevitCommandGenerator.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
18
RevitGen.Generator/RevitGen.Generator.csproj
Normal file
18
RevitGen.Generator/RevitGen.Generator.csproj
Normal 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>
|
||||
279
RevitGen.Generator/SourceGenerationHelper.cs
Normal file
279
RevitGen.Generator/SourceGenerationHelper.cs
Normal 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]", "_");
|
||||
}
|
||||
}
|
||||
}
|
||||
40
RevitGen.Generator/SyntaxReceiver.cs
Normal file
40
RevitGen.Generator/SyntaxReceiver.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user