From 694553e43aba5def76af4e264b0b76adc2287c28 Mon Sep 17 00:00:00 2001 From: Bendik Tobias Berg Date: Wed, 30 Oct 2024 19:11:52 +0100 Subject: [PATCH 1/2] Preview server tests --- src/DynamoRevit/Preview/PreviewServer.cs | 1399 +++++++++++++++++ .../ViewModel/DynamoRevitViewModel.cs | 5 +- .../ViewModel/RevitWatch3DViewModel.cs | 484 +++++- 3 files changed, 1885 insertions(+), 3 deletions(-) create mode 100644 src/DynamoRevit/Preview/PreviewServer.cs diff --git a/src/DynamoRevit/Preview/PreviewServer.cs b/src/DynamoRevit/Preview/PreviewServer.cs new file mode 100644 index 000000000..85a451e42 --- /dev/null +++ b/src/DynamoRevit/Preview/PreviewServer.cs @@ -0,0 +1,1399 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security.Cryptography; +using Autodesk.DesignScript.Interfaces; +using Autodesk.Revit.DB; +using Autodesk.Revit.DB.DirectContext3D; +using Autodesk.Revit.DB.ExternalService; +using Dynamo.Visualization; +using RevitServices.Persistence; + +namespace Dynamo.Applications.Preview +{ + public sealed class PreviewServer : IDirectContext3DServer, IDisposable + { + private static Dictionary activeServers = new Dictionary(); + + private bool disposed = false; + private Guid _serverId; + private Dictionary _previewObjects = new Dictionary(); + private Outline _outline; + //private BufferPools _pools; + bool _outlineDirty = true; + private RenderEffects _selectedEffect; + private RenderEffects _basicEffect; + private DisplayStyle _effectStyle; + + private object _renderMutex; + + private PreviewServer(BufferPools pools) + { + _effectStyle = DisplayStyle.Undefined; + _serverId = Guid.NewGuid(); + _renderMutex = new object(); + //_pools = pools; + } + + public bool CanExecute(View dBView) + { + return dBView.ViewType == ViewType.FloorPlan || + dBView.ViewType == ViewType.AreaPlan || + dBView.ViewType == ViewType.Detail || + dBView.ViewType == ViewType.DraftingView || + dBView.ViewType == ViewType.Elevation || + dBView.ViewType == ViewType.FloorPlan || + dBView.ViewType == ViewType.Section || + dBView.ViewType == ViewType.ThreeD || + dBView.ViewType == ViewType.Walkthrough; + } + + public string GetApplicationId() => ""; + + public Outline GetBoundingBox(View dBView) + { + if (!_outlineDirty) + return _outline; + + _outline = null; + lock(_renderMutex) + { + foreach(var item in _previewObjects.Values) + OutlineWrapper.UpdateOrCreateNew(ref _outline, item?.Outline); + } + + _outlineDirty = _outline == null; + return _outline; + } + + public string GetDescription() => "Dynamo preview server"; + + public string GetName() => "Dynamo preview server new"; + + public Guid GetServerId() => _serverId; + + public ExternalServiceId GetServiceId() => ExternalServices.BuiltInExternalServices.DirectContext3DService; + + public string GetSourceId() => ""; + + public string GetVendorId() => "Dynamo team"; + + public void InitEffects(DisplayStyle displayStyle) + { + if (displayStyle == _effectStyle && _selectedEffect.IsValid && _basicEffect.IsValid) + return; + _effectStyle = displayStyle; + _selectedEffect?.Dispose(); + _basicEffect?.Dispose(); + + var format = displayStyle switch { + DisplayStyle.Shading => VertexFormatBits.PositionNormal, + DisplayStyle.ShadingWithEdges => VertexFormatBits.PositionNormal, + _ => VertexFormatBits.Position, + }; + + _selectedEffect = new RenderEffects { EdgeEffect = new EffectInstance(VertexFormatBits.Position), MeshEffect = new EffectInstance(format) }; + _selectedEffect.EdgeEffect.SetColor(new Color(255, 255, 0)); + _selectedEffect.EdgeEffect.SetTransparency(0.3); + _selectedEffect.MeshEffect.SetColor(new Color(255, 255, 0)); + _selectedEffect.MeshEffect.SetDiffuseColor(new Color(255, 255, 0)); + _selectedEffect.MeshEffect.SetAmbientColor(new Color(255, 255, 0)); + _selectedEffect.MeshEffect.SetTransparency(0.3); + + _basicEffect = new RenderEffects { EdgeEffect = new EffectInstance(VertexFormatBits.Position), MeshEffect = new EffectInstance(format) }; + _basicEffect.EdgeEffect.SetColor(new Color(150, 150, 150)); + _basicEffect.EdgeEffect.SetTransparency(0.4); + _basicEffect.MeshEffect.SetColor(new Color(150, 150, 150)); + _basicEffect.MeshEffect.SetTransparency(0.4); + } + + public void RenderScene(View dBView, DisplayStyle displayStyle) + { + if (!DrawContext.IsTransparentPass()) + return; + Debug.WriteLine($"Rendering for server {GetServerId()}"); + + InitEffects(displayStyle); + var xform = Transform.Identity; + lock(_renderMutex) + { + foreach(var key in _previewObjects.Keys) + { + var item = _previewObjects[key]; + if (!item.Visible) + continue; + if (item.Selected) + { + item.Render(xform, _selectedEffect); + } + else + { + item.Render(xform, _basicEffect); + } + } + } + } + + public bool UseInTransparentPass(View dBView) => true; // TODO check for necessity? + + public bool UsesHandles() => false; + + internal static PreviewServer StartNewServer(BufferPools pools) + { + ExternalServiceId serviceId = ExternalServices.BuiltInExternalServices.DirectContext3DService; + var directContext3dService = ExternalServiceRegistry.GetService(serviceId) as MultiServerService; + + var doc = DocumentManager.Instance.CurrentDBDocument; + Debug.WriteLine($"doc null? {doc == null} {doc?.Title} "); + //IList activeList = directContext3dService.GetActiveServerIds(doc); + IList activeList = directContext3dService.GetActiveServerIds(); + + var previewServer = new PreviewServer(pools); + Debug.WriteLine($"Adding server {previewServer.GetServerId()} ..."); + activeServers.Add(previewServer.GetServerId(), previewServer); + activeList.Add(previewServer.GetServerId()); + directContext3dService.AddServer(previewServer); + directContext3dService.SetActiveServers(activeList); + //directContext3dService.SetActiveServers(activeList, doc); + Debug.WriteLine($"Server was added"); + + return previewServer; + } + + public void StopServer() + { + Dispose(); + } + + //internal void AddPreviewObject(IPreviewObject previewObject) + //{ + // //var outline = previewObject.Outline; + // //if (_outline == null) + // //{ + // // _outline = new Outline(outline); + // //} + // //else + // //{ + // // _outline.AddPoint(outline.MinimumPoint); + // // _outline.AddPoint(outline.MaximumPoint); + // //} + // _previewObjects.Add(previewObject); + // _outlineDirty = true; + //} + + internal void WithNodeCache(Guid nodeGuid, Action action) + { + _outlineDirty = true; + lock(_renderMutex) + { + NodePreviewObject cached; + if (_previewObjects.TryGetValue(nodeGuid, out var cache)) + { + cached = cache as NodePreviewObject; + } + else + { + cached = new NodePreviewObject(nodeGuid); + _previewObjects[nodeGuid] = cached; + } + action(cached); + } + _outlineDirty = true; + //AddPreviewObject() + //var cache = _previewObjects.OfType().FirstOrDefault(); + //if (cache == null) + //{ + // cache = new BufferCache(_pools); + // AddPreviewObject(cache); + //} + //return cache; + } + + internal void DeletePreview(Guid nodeGuid) + { + lock(_renderMutex) + { + if (_previewObjects.TryGetValue(nodeGuid, out var node)) + { + _previewObjects.Remove(nodeGuid); + node.Dispose(); + } + } + } + + public void Dispose() + { + if (disposed) + return; + + ExternalServiceId serviceId = ExternalServices.BuiltInExternalServices.DirectContext3DService; + var directContext3dService = ExternalServiceRegistry.GetService(serviceId) as MultiServerService; + + var id = GetServerId(); + Debug.WriteLine($"Removing preview server {id} ..."); + if (!activeServers.Remove(id)) + return; // this server was already removed + + Debug.WriteLine($"Disposing {_previewObjects.Count} preview object(s)"); + lock(_renderMutex) + { + foreach (var item in _previewObjects.Values) + item.Dispose(); + } + _outline?.Dispose(); + + directContext3dService.RemoveServer(id); + Debug.WriteLine($"Server was removed"); + disposed = true; + } + } + + internal interface IPreviewObject : IDisposable + { + void Render(Transform transform); + Outline Outline { get; } + } + + internal interface IPreviewObject2 : IDisposable + { + void Render(Transform transform, RenderEffects effects); + Outline Outline { get; } + } + + internal class RenderEffects : IDisposable + { + public required EffectInstance EdgeEffect { get; init; } + public required EffectInstance MeshEffect { get; init; } + public bool IsValid => EdgeEffect == null && EdgeEffect.IsValid() && MeshEffect == null && MeshEffect.IsValid(); + + public void Dispose() + { + EdgeEffect.Dispose(); + MeshEffect.Dispose(); + } + } + + internal class BufferPools + { + public BufferPool VertexPool { get; } + public BufferPool IndexPool { get; } + public BufferPool RawIndexPool { get; } + public BufferPool RawVertexPool { get; } + + public BufferPools() + { + VertexPool = new BufferPool(16, NewVertexBuffer); + IndexPool = new BufferPool(16, NewIndexBuffer); + RawIndexPool = new BufferPool(16, NewRawIndexBuffer); + RawVertexPool = new BufferPool(16, NewRawVertexBuffer); + } + + private SizedBuffer NewVertexBuffer(int capacity) + { + return new SizedBuffer(capacity, new VertexBuffer(capacity)); + } + + private SizedBuffer NewIndexBuffer(int capacity) + { + return new SizedBuffer(capacity, new IndexBuffer(capacity)); + } + + private SizedBuffer NewRawIndexBuffer(int capacity) + { + return new SizedBuffer(capacity, new byte[capacity]); + } + + private SizedBuffer NewRawVertexBuffer(int capacity) + { + return new SizedBuffer(capacity, new float[capacity]); + } + } + + internal class NodePreviewObject : IPreviewObject2 + { + public Guid NodeGuid { get; set; } + public bool Selected { get; set; } = false; + public bool Visible { get; set; } = false; + + private Outline _outline; + + public Outline Outline => _outline; + + private BufferCache _edgeCache; + private BufferCache _meshCache; + private BufferCache _pointCache; + + public NodePreviewObject(Guid nodeGuid) + { + NodeGuid = nodeGuid; + } + + public void Render(Transform transform, RenderEffects effects) + { + _edgeCache?.Render(transform, effects.EdgeEffect); + _meshCache?.Render(transform, effects.MeshEffect); + _pointCache?.Render(transform, effects.MeshEffect); + } + + public void Clear() + { + // Using this instance after clear is fine, but wrap it for clarity + Dispose(); + } + + public void Dispose() + { + _edgeCache?.Dispose(); + _meshCache?.Dispose(); + _pointCache?.Dispose(); + _outline?.Dispose(); + _edgeCache = null; + _meshCache = null; + _pointCache = null; + _outline = null; + } + + public void AddMesh(IRenderPackage meshRenderPackage) + { + if (_meshCache == null) + _meshCache = new BufferCache(PrimitiveType.TriangleList); + _meshCache.FromMeshRenderPackage(meshRenderPackage); + OutlineWrapper.UpdateOrCreateNew(ref _outline, _meshCache.Outline); + } + + public void AddEdge(IRenderPackage edgeRenderPackage) + { + if (_edgeCache == null) + _edgeCache = new BufferCache(PrimitiveType.LineList); + _edgeCache.FromLineRenderPackage(edgeRenderPackage); + OutlineWrapper.UpdateOrCreateNew(ref _outline, _edgeCache.Outline); + } + } + + internal class LazyProtoPreview : IPreviewObject + { + protected IPreviewObject _inner = null; + protected Func _instantiate; + + public Outline Outline => _inner?.Outline; + + public LazyProtoPreview(Func instantiate) + { + _instantiate = instantiate; + } + + public void Dispose() + { + _inner?.Dispose(); + _inner = null; + } + + public virtual void Render(Transform transform) + { + if (_inner == null) + _inner = _instantiate(); + if (_inner == null) + return; + _inner.Render(transform); + } + } + + internal class LazyInstanceProtoPreview : LazyProtoPreview + { + private List _transforms; + public LazyInstanceProtoPreview(Func instantiateObject) + : base(instantiateObject) + { + _transforms = new List(); + } + + public void AddTransform(Transform transform) + { + _transforms.Add(transform); + } + + public override void Render(Transform transform) + { + if (_inner == null) + { + _inner = _instantiate(); + if (_inner == null) + return; + var instance = (InstanceProtoPreview)_inner; + foreach (var xform in _transforms) + instance.AddTransform(xform); + } + base.Render(transform); + } + } + + internal class ProtoPreview : IDisposable, IPreviewObject + { + class DoublesToXYZ + { + List _vertices; + private double _scale = 1; + public int Count => _vertices.Count / 3; + public Outline Outline { get; } + public DoublesToXYZ(IEnumerable vertices, bool withOutline) + { + _vertices = vertices.ToList(); + if (withOutline) + Outline = new Outline(GetXYZ(0), GetXYZ(1)); + } + + public DoublesToXYZ(IEnumerable vertices, bool withOutline, double scale) + { + _vertices = vertices.ToList(); + _scale = scale; + if (withOutline) + Outline = new Outline(GetXYZ(0), GetXYZ(1)); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public XYZ GetXYZ(int index) + { + int idx = index * 3; + return new XYZ(_vertices[idx], _vertices[idx + 1], _vertices[idx + 2]); + } + + public XYZ GetVertex(int index) + { + int idx = index * 3; + var xyz = new XYZ(_vertices[idx] * _scale, _vertices[idx + 1] * _scale, _vertices[idx + 2] * _scale); + Outline.AddPoint(xyz); + return xyz; + } + } + + class BytesToColor + { + List _colors; + public int Count => _colors.Count / 4; + public BytesToColor(IEnumerable colors) + { + _colors = colors.ToList(); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ColorWithTransparency GetColor(int index) + { + var idx = index * 4; + return new ColorWithTransparency(_colors[idx], _colors[idx + 1], _colors[idx + 2], _colors[idx + 3]); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ColorWithTransparency GetAColor(int index) + { + var v = (byte)Random.Shared.Next(255); + return new ColorWithTransparency(v, v, v, 200); + } + } + + private VertexFormatBits _format; + private PrimitiveType _type; + private int _vertsPerPrimitive; + private int _vertexCount; + private int _indexCount; + private int _primitiveCount; + private VertexBuffer _vertexBuffer; + private IndexBuffer _indexBuffer; + private VertexFormat _vertexFormat; + private EffectInstance _effectInstance; + private Outline _outline; + + private bool disposedValue = false; + + public Outline Outline { get => _outline; private set => _outline = value; } + public EffectInstance EffectInstance { get => _effectInstance; private set => _effectInstance = value; } + public VertexFormatBits FormatBits => _format; + + public ProtoPreview(PrimitiveType type, int vertexCount, bool hasNormals, bool hasColors) + { + var (bits, vertSize) = (hasNormals, hasColors) switch + { + (true, true) => (VertexFormatBits.PositionNormalColored, VertexPositionNormalColored.GetSizeInFloats()), + (true, false) => (VertexFormatBits.PositionNormal, VertexPositionNormal.GetSizeInFloats()), + (false, true) => (VertexFormatBits.PositionColored, VertexPositionColored.GetSizeInFloats()), + (false, false) => (VertexFormatBits.Position, VertexPosition.GetSizeInFloats()), + }; + var (vertsPerPrimitive, indexSize) = type switch + { + PrimitiveType.TriangleList => (3, IndexTriangle.GetSizeInShortInts()), + PrimitiveType.LineList => (2, IndexLine.GetSizeInShortInts()), + PrimitiveType.PointList => (1, IndexPoint.GetSizeInShortInts()), + _ => throw new NotImplementedException(), + }; + + _format = bits; + _type = type; + + //_vertexFormat = new VertexFormat(bits); + //_effectInstance = new EffectInstance(bits); + + _vertsPerPrimitive = vertsPerPrimitive; + _vertexCount = vertexCount; + _primitiveCount = _vertexCount / _vertsPerPrimitive; + _indexCount = _primitiveCount; + + _vertexBuffer = new VertexBuffer(_vertexCount * vertSize); + _vertexBuffer.Map(_vertexCount * vertSize); + + _indexBuffer = new IndexBuffer(_vertexCount * indexSize); + _indexBuffer.Map(_vertexCount * indexSize); + } + + public void Render(Transform transform) + { + var effectInstance = new EffectInstance(_format); + effectInstance.SetTransparency(0.5); + //effectInstance.SetColor(new Color(100, 100, 150)); + //effectInstance.SetDiffuseColor(new Color(100, 100, 150)); + //effectInstance.SetAmbientColor(new Color(100, 100, 150)); + //effectInstance.SetSpecularColor(new Color(255, 255, 255)); + //effectInstance.SetGlossiness(0.1); + DrawContext.SetWorldTransform(transform); + DrawContext.FlushBuffer( + _vertexBuffer, + _vertexCount, + _indexBuffer, + _indexCount, + new VertexFormat(_format), + effectInstance, + _type, + 0, + _primitiveCount + ); + } + + public static ProtoPreview FromSolid(Autodesk.DesignScript.Geometry.Solid solid, IRenderPackageFactory renderPackageFactory) + { + var pkg = renderPackageFactory.CreateRenderPackage(); + solid.Tessellate(pkg, renderPackageFactory.TessellationParameters); + + return FromMeshRenderPackage(pkg); + } + + public static ProtoPreview FromMeshRenderPackage(IRenderPackage pkg) + { + var scale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); + + var verts = new DoublesToXYZ(pkg.MeshVertices, true, scale); + var numVerts = verts.Count; + var numTris = numVerts / 3; + var hasVerts = numVerts > 0; + var colors = new BytesToColor(pkg.MeshVertexColors); + var hasColors = colors.Count == numVerts; + hasColors = false; + var normals = new DoublesToXYZ(pkg.MeshNormals, false); + var hasNormals = normals.Count == numVerts; + //hasNormals = false; + + + var preview = new ProtoPreview(PrimitiveType.TriangleList, numVerts, hasNormals, hasColors); + var indexBuffer = preview._indexBuffer; + var vertexBuffer = preview._vertexBuffer; + var bits = preview.FormatBits; + + using (var idxStream = indexBuffer.GetIndexStreamTriangle()) + using (var tri = new IndexTriangle(0, 0, 0)) + { + if (bits == VertexFormatBits.Position) + { + using (var stream = vertexBuffer.GetVertexStreamPosition()) + using (var vert = new VertexPosition(XYZ.Zero)) + { + for (int j = 0; j < numTris; j++) + { + var v = j * 3; + var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... + vert.Position = verts.GetVertex(j0); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j2); stream.AddVertex(vert); + tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; + idxStream.AddTriangle(tri); + } + } + } + else if (bits == VertexFormatBits.PositionNormal) + { + using (var stream = vertexBuffer.GetVertexStreamPositionNormal()) + using (var vert = new VertexPositionNormal(XYZ.Zero, XYZ.Zero)) + { + for (int j = 0; j < numTris; j++) + { + var v = j * 3; + var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... + vert.Position = verts.GetVertex(j0); vert.Normal = normals.GetXYZ(j0); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); vert.Normal = normals.GetXYZ(j1); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j2); vert.Normal = normals.GetXYZ(j2); stream.AddVertex(vert); + tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; + idxStream.AddTriangle(tri); + } + } + } + else if (bits == VertexFormatBits.PositionColored) + { + using (var stream = vertexBuffer.GetVertexStreamPositionColored()) + using (var vert = new VertexPositionColored(XYZ.Zero, new ColorWithTransparency())) + { + for (int j = 0; j < numTris; j++) + { + var v = j * 3; + var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... + vert.Position = verts.GetVertex(j0); vert.SetColor(colors.GetAColor(j0)); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); vert.SetColor(colors.GetAColor(j1)); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j2); vert.SetColor(colors.GetAColor(j2)); stream.AddVertex(vert); + tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; + idxStream.AddTriangle(tri); + } + } + } + else if (bits == VertexFormatBits.PositionNormalColored) + { + using (var stream = vertexBuffer.GetVertexStreamPositionNormalColored()) + using (var vert = new VertexPositionNormalColored(XYZ.Zero, XYZ.Zero, new ColorWithTransparency())) + { + for (int j = 0; j < numTris; j++) + { + var v = j * 3; + var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... + vert.Position = verts.GetVertex(j0); vert.Normal = normals.GetXYZ(j0); vert.SetColor(colors.GetAColor(j0)); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); vert.Normal = normals.GetXYZ(j1); vert.SetColor(colors.GetAColor(j1)); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j2); vert.Normal = normals.GetXYZ(j2); vert.SetColor(colors.GetAColor(j2)); stream.AddVertex(vert); + tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; + idxStream.AddTriangle(tri); + } + } + } + } + indexBuffer.Unmap(); + vertexBuffer.Unmap(); + + preview.Outline = verts.Outline; + //Debug.WriteLine($"Created preview item"); + return preview; + } + + + public static ProtoPreview FromLineRenderPackage(IRenderPackage pkg) + { + var scale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); + + var verts = new DoublesToXYZ(pkg.LineStripVertices, true, scale); + var numVerts = verts.Count; + var hasVerts = numVerts > 0; + var colors = new BytesToColor(pkg.LineStripVertexColors); + var hasColors = colors.Count == numVerts; + var lineIndices = pkg.LineStripIndices.ToList(); + var numLines = lineIndices.Count; + + var preview = new ProtoPreview(PrimitiveType.LineList, numVerts, false, hasColors); + var indexBuffer = preview._indexBuffer; + var vertexBuffer = preview._vertexBuffer; + var bits = preview.FormatBits; + + using (var idxStream = indexBuffer.GetIndexStreamLine()) + using (var line = new IndexLine(0, 0)) + { + if (bits == VertexFormatBits.Position) + { + using (var stream = vertexBuffer.GetVertexStreamPosition()) + using (var vert = new VertexPosition(XYZ.Zero)) + { + for (int j = 0; j < numVerts; j += 2) + { + var j1 = j + 1; + vert.Position = verts.GetVertex(j); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); stream.AddVertex(vert); + line.Index0 = j; line.Index1 = j1; + idxStream.AddLine(line); + } + } + } + else if (bits == VertexFormatBits.PositionColored) + { + using (var stream = vertexBuffer.GetVertexStreamPositionColored()) + using (var vert = new VertexPositionColored(XYZ.Zero, new ColorWithTransparency())) + { + for (int j = 0; j < numVerts; j++) + { + var j1 = j + 1; + vert.Position = verts.GetVertex(j); vert.SetColor(colors.GetColor(j)); stream.AddVertex(vert); + vert.Position = verts.GetVertex(j1); vert.SetColor(colors.GetColor(j1)); stream.AddVertex(vert); + line.Index0 = j; line.Index1 = j1; + idxStream.AddLine(line); + } + } + } + } + indexBuffer.Unmap(); + vertexBuffer.Unmap(); + + preview.Outline = verts.Outline; + return preview; + } + + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + _vertexBuffer?.Dispose(); + _indexBuffer?.Dispose(); + _vertexFormat?.Dispose(); + _effectInstance?.Dispose(); + _outline?.Dispose(); + } + disposedValue = true; + } + } + + public void Dispose() + { + Dispose(disposing: true); + GC.SuppressFinalize(this); + } + } + + internal class InstanceProtoPreview : IPreviewObject + { + private ProtoPreview _instanceGeometry; + private XYZ[] _outlinePoints; + private List _transforms; + + public Outline Outline { get; private set; } + + public InstanceProtoPreview(ProtoPreview instancePreviewGeometry) + { + _instanceGeometry = instancePreviewGeometry; + var min = _instanceGeometry.Outline.MinimumPoint; + var max = _instanceGeometry.Outline.MaximumPoint; + var (ax, ay, az) = (min.X, min.Y, min.Z); + var (bx, by, bz) = (max.X, max.Y, max.Z); + _outlinePoints = + [ + new XYZ(ax, ay, az), // 0 0 0 + new XYZ(ax, ay, bz), // 0 0 1 + new XYZ(ax, by, bz), // 0 1 1 + new XYZ(ax, by, az), // 0 1 0 + new XYZ(bx, ay, az), // 1 0 0 + new XYZ(bx, by, az), // 1 1 0 + new XYZ(bx, ay, bz), // 1 0 1 + new XYZ(bx, by, bz), // 1 1 1 + ]; + } + + public void AddTransform(Transform transform) + { + if (_transforms == null) + { + _transforms = new List(); + Outline = new Outline(transform.OfPoint(_outlinePoints[0]), transform.OfPoint(_outlinePoints[1])); + } + for (int i = 0; i < 8; i++) + Outline.AddPoint(transform.OfPoint(_outlinePoints[i])); + + _transforms.Add(transform); + } + + public void Render(Transform transform) + { + if (_transforms == null) + return; + foreach (var xform in _transforms) + _instanceGeometry.Render(xform); + } + + public void Dispose() + { + _instanceGeometry?.Dispose(); + _instanceGeometry = null; + } + } + + internal class BufferCache : IPreviewObject + { + private Outline _outline; + private List _buffers; + //private BufferPools _pools; + private PrimitiveType _type; + + double _modelScale = double.NaN; + + public BufferCache(PrimitiveType type) + { + //_pools = pools; + _type = type; + _buffers = new List(); + } + + public Outline Outline => _outline; + + public void Dispose() + { + _outline?.Dispose(); + foreach(var buffer in _buffers) + buffer.Dispose(); + } + + public BufferedProtoPreview GetBufferWithCapacity(int capacity) + { + // get the last buffer in the list of buffers, as it should always + // be the one with the highest capacity + var bufCount = _buffers.Count; + if (bufCount > 0 && _buffers[bufCount - 1].Capacity > capacity) + return _buffers[bufCount - 1]; + + Debug.WriteLine($"Creating buffer {bufCount + 1}"); + var newBuffer = new BufferedProtoPreview(_type); + _buffers.Add(newBuffer); + return newBuffer; + } + + public void UpdateCache(BufferedProtoPreview buffer) + { + OutlineWrapper.UpdateOrCreateNew(ref _outline, buffer.Outline); + + // the returned buffer must always be the last buffer in the list + var bufCount = _buffers.Count; + Debug.Assert(ReferenceEquals(buffer, _buffers[bufCount - 1])); + if (bufCount > 1) + { + var capacity = buffer.Capacity; + var idx = bufCount - 2; + while (idx >= 0) + { + var other = _buffers[idx]; + if (other.Capacity > capacity) + { + _buffers[idx + 1] = other; + _buffers[idx] = buffer; + idx--; + } + else + { + break; + } + } + } + } + + public void Render(Transform transform) + { + var interrupted = DrawContext.IsInterrupted(); + var numPrimitives = 0; + foreach(var item in _buffers) + { + if (interrupted) + return; + + numPrimitives += item.PrimitiveCount; + item.Render(transform); + } + //Debug.WriteLine($"Cache has {numPrimitives} triangle(s)"); + } + + public void Render(Transform transform, EffectInstance effectInstance) + { + var interrupted = DrawContext.IsInterrupted(); + var numPrimitives = 0; + foreach(var item in _buffers) + { + if (interrupted) + return; + + numPrimitives += item.PrimitiveCount; + item.Render(transform, effectInstance); + } + //Debug.WriteLine($"Cache has {numPrimitives} triangle(s)"); + } + + + public BufferedProtoPreview FromMeshRenderPackage(IRenderPackage pkg) + { + if (double.IsNaN(_modelScale)) + _modelScale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); + //_modelScale = 1; + + var max = ushort.MaxValue + 1; + var vertexComponents = pkg.MeshVertices.ToArray(); + var normalComponents = pkg.MeshNormals.ToArray(); + var numVertices = vertexComponents.Length / 3; + var numTris = numVertices / 3; + //var remainingTris = numTris; + var remainingVerts = numVertices; + var end = 0; + BufferedProtoPreview buffer = null; + while (remainingVerts > 0) + { + try + { + var start = end; + var bufCapacity = (Math.Min(remainingVerts, max) / 3) * 3; + remainingVerts -= bufCapacity; + end = start + bufCapacity; + buffer = GetBufferWithCapacity(bufCapacity * 6); // buffer for 3 vertices per tri, and 6 components per vertex + buffer.AppendMeshParts( + new ReadOnlySpan(vertexComponents, start * 3, bufCapacity * 3), + new ReadOnlySpan(normalComponents, start * 3, bufCapacity * 3), + _modelScale); + UpdateCache(buffer); + + } catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + Debug.WriteLine(ex.StackTrace); + throw; + } + } + return buffer; + } + + internal void FromLineRenderPackage(IRenderPackage edgeRenderPackage) + { + if (double.IsNaN(_modelScale)) + _modelScale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); + + var max = ushort.MaxValue + 1; + var vertexComponents = edgeRenderPackage.LineStripVertices.ToArray(); + var indices = edgeRenderPackage.LineStripIndices.ToArray(); + var clampedIndexLength = indices.Length & ~1; // unset the 1-bit to make divisible by 2 + + var numVertices = vertexComponents.Length / 3; + BufferedProtoPreview buffer = null; + + var indexBuffer = new int[max - 2]; // can't use the 65k buffer for some reason + var currentIndex = 0; + var bufferIndex = 0; + var vertexStart = indices[0]; + while (currentIndex < clampedIndexLength) + { + var a = indices[currentIndex++]; + var b = indices[currentIndex++]; + + indexBuffer[bufferIndex++] = a - vertexStart; + indexBuffer[bufferIndex++] = b - vertexStart; + + // either at buffer capacity, or at end of index array + if (bufferIndex >= indexBuffer.Length || currentIndex >= clampedIndexLength) + { + var vertexEnd = b + 1; + var numVerts = vertexEnd - vertexStart; + var vertexSpan = new ReadOnlySpan(vertexComponents, vertexStart * 3, numVerts * 3); + var indexSpan = new ReadOnlySpan(indexBuffer, 0, bufferIndex); + + try + { + buffer = GetBufferWithCapacity(vertexSpan.Length); // buffer for 2 vertices per line, and 3 components per vertex + buffer.AppendLineSegments(vertexSpan, indexSpan, _modelScale); + Debug.WriteLine($"Linecount {buffer.PrimitiveCount}"); + UpdateCache(buffer); + } + catch (Exception ex) + { + Debug.WriteLine(ex.ToString()); + Debug.WriteLine(ex.StackTrace); + throw; + } + bufferIndex = 0; + if (currentIndex < clampedIndexLength) + vertexStart = indices[currentIndex]; + } + } + } + } + + internal struct SizedBuffer + { + public int Size { get; } + public T Buffer { get; } + public SizedBuffer(int size, T buffer) + { + Size = size; + Buffer = buffer; + } + } + + internal class BufferPool + { + private object _bufferMutex; + private int _poolSize; + private Dictionary>> _buffers; + private Func> _factory; + + public BufferPool(int poolSize, Func> bufferFactory) + { + _factory = bufferFactory; + _buffers = new Dictionary>>(); + _poolSize = poolSize; + } + + public int GetBufferPoolIdx(int targetCapacity, int mult) + { + var bufferLogSize = Math.Log2(targetCapacity / mult); + var bufferTargetSize = (int)bufferLogSize; + if (bufferLogSize % 1 > 0) + bufferTargetSize++; + return bufferTargetSize; + } + + public SizedBuffer RentBuffer(int capacity, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(capacity, mult); + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var rentableBuffers) && rentableBuffers.Count > 0) + { + var idx = rentableBuffers.Count - 1; + var buffer = rentableBuffers[idx]; + rentableBuffers.RemoveAt(idx); + return buffer; + } + else + { + var bufferSize = (int)Math.Pow(2, bufferTargetPool); + var buffer = _factory(bufferSize * mult); + return buffer; + } + } + } + + public bool ReturnBuffer(SizedBuffer buffer, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); + + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) + { + if (buffers.Count < _poolSize) + { + buffers.Add(buffer); + return true; + } + return false; // no space to store the buffer + } + else + { + _buffers[bufferTargetPool] = new List> { buffer }; + return true; + } + } + } + + public bool ReturnBufferOrDispose(SizedBuffer buffer, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); + + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) + { + if (buffers.Count < _poolSize) + { + buffers.Add(buffer); + return true; + } + if (buffer.Buffer is IDisposable disposable) + disposable.Dispose(); + + return false; // no space to store the buffer + } + else + { + _buffers[bufferTargetPool] = new List> { buffer }; + return true; + } + } + } + } + + internal class BufferedProtoPreview : IPreviewObject + { + const int MAX_SIZE = ushort.MaxValue + 1; + + private float[] _vertexComponents; + private int _vertexComponentCount; + + private byte[] _indices; // this should be a ushort[], but C# Marshal.Copy does not support ushort[] => IntPtr + private int _indexComponentCount; // this offset is in ushorts, so the buffer fill length will be _indexOffset * 2 + + private bool _initialized = false; + private bool _synced = false; + + private (double, double, double) _min = (double.MaxValue, double.MaxValue, double.MaxValue); + private (double, double, double) _max = (double.MinValue, double.MinValue, double.MinValue); + + private SizedBuffer _sizedVertexBuffer; + private VertexBuffer _vertexBuffer; + private IndexBuffer _indexBuffer; + private VertexFormat _vertexFormat; + private EffectInstance _effectInstance; + + //private BufferPools _pools; + + private Outline _outline; + + private PrimitiveType _type; + private int _vertexSize; + private int _indexSize; + + public int Capacity => (MAX_SIZE * _vertexSize) - _vertexComponentCount; + public int PrimitiveCount => _indexComponentCount / _indexSize; // 3 for triangles + + public bool HasValidBuffers => _vertexBuffer != null && _vertexBuffer.IsValid() + && _indexBuffer != null && _indexBuffer.IsValid() + && _vertexFormat != null && _vertexFormat.IsValid(); + + public Outline Outline + { + get + { + UpdateOutline(); + return _outline; + } + } + + internal BufferedProtoPreview(PrimitiveType primitiveType) + { + //Debug.WriteLine($"actual {VertexBufferSize} expected {MAX_SIZE * 6}"); + //_pools = pools; + _type = primitiveType; + (_vertexSize, _indexSize) = _type switch + { + PrimitiveType.TriangleList => (6, 3), + PrimitiveType.LineList => (3, 2), + _ => throw new NotImplementedException() + }; + // unsure if the max size is the size of the backing byte buffer, or the amount of floats + //var rawComponents = _pools.RawVertexPool.RentBuffer(MAX_SIZE * _vertexSize, _vertexSize); // new float[VertexBufferSize]; + _vertexComponents = new float[MAX_SIZE * _vertexSize]; + //var rawIndices = _pools.RawIndexPool.RentBuffer(MAX_SIZE * _indexSize * 2, _indexSize * 2); // new float[VertexBufferSize]; + _indices = new byte[MAX_SIZE * _indexSize * 2]; + } + + public void UpdateOutline() + { + if (_min.Item1 > _max.Item1) + return; + var pMin = new XYZ(_min.Item1, _min.Item2, _min.Item3); + var pMax = new XYZ(_max.Item1, _max.Item2, _max.Item3); + OutlineWrapper.UpdateOrCreateNew(ref _outline, pMin, pMax); + } + + private void InitializeRenderBuffers() + { + //var rawVertexBuffer = _pools.VertexPool.RentBuffer(MAX_SIZE * _vertexSize, _vertexSize); + _vertexBuffer = new VertexBuffer(MAX_SIZE * _vertexSize); + //var rawIndexBuffer = _pools.IndexPool.RentBuffer(MAX_SIZE * _indexSize, _indexSize); + _indexBuffer = new IndexBuffer(MAX_SIZE * _indexSize); + + _vertexFormat = _type switch + { + PrimitiveType.TriangleList => new VertexFormat(VertexFormatBits.PositionNormal), + PrimitiveType.LineList => new VertexFormat(VertexFormatBits.Position), + _ => throw new NotImplementedException() + }; + //_vertexFormat = new VertexFormat(VertexFormatBits.PositionNormal); + _initialized = true; + } + + private void CopyBuffers() + { + _vertexBuffer.Map(_vertexComponentCount); + Marshal.Copy(_vertexComponents, 0, _vertexBuffer.GetMappedHandle(), _vertexComponentCount); + _vertexBuffer.Unmap(); + + var byteOffset = _indexComponentCount * 2; + _indexBuffer.Map(_indexComponentCount); + //Debug.WriteLine($"index buf {_indexOffset}"); + Marshal.Copy(_indices, 0, _indexBuffer.GetMappedHandle(), byteOffset); + _indexBuffer.Unmap(); + _synced = true; + } + + internal void AppendMeshParts(ReadOnlySpan vertexComponents, ReadOnlySpan normalComponents, double scale) + { + _synced = false; + // assume we have a normal for each vertex + var numVertices = vertexComponents.Length / 3; + var numTriangles = numVertices / _indexSize; + var vertIndex = (ushort)(_vertexComponentCount / _vertexSize); + var idxSpan = MemoryMarshal.Cast(_indices); + var (xMin, yMin, zMin) = _min; + var (xMax, yMax, zMax) = _max; + for (int i = 0; i < numVertices; i++) + { + var j = i * 3; + var x = vertexComponents[j++] * scale; + //var y = vertexComponents[j++] * scale; + //var z = vertexComponents[j] * scale; + var z = vertexComponents[j++] * scale; + var y = -vertexComponents[j] * scale; + xMin = Math.Min(xMin, x); + yMin = Math.Min(yMin, y); + zMin = Math.Min(zMin, z); + xMax = Math.Max(xMax, x); + yMax = Math.Max(yMax, y); + zMax = Math.Max(zMax, z); + _vertexComponents[_vertexComponentCount++] = (float)x; + _vertexComponents[_vertexComponentCount++] = (float)y; + _vertexComponents[_vertexComponentCount++] = (float)z; + + j = i * 3; // restart the component index for the normals + var nx = (float)normalComponents[j++]; + //var ny = (float)normalComponents[j++]; + //var nz = (float)normalComponents[j]; + var nz = (float)normalComponents[j++]; + var ny = -(float)normalComponents[j]; + _vertexComponents[_vertexComponentCount++] = nx; + _vertexComponents[_vertexComponentCount++] = ny; + _vertexComponents[_vertexComponentCount++] = nz; + //_componentOffset++; // add one for color + } + + for (int i = 0; i < numTriangles; i++) + { + // TODO: check if these need to use the pkg.MeshIndices variable? + idxSpan[_indexComponentCount++] = vertIndex++; + idxSpan[_indexComponentCount++] = vertIndex++; + idxSpan[_indexComponentCount++] = vertIndex++; + //var byteValue = BitConverter.ToUInt16(new ReadOnlySpan(_indices, (_indexOffset - 1) * 2, 2)); + //Debug.WriteLine($"Written value: {vertIndex}, stored value: {byteValue}"); + } + + _min = (xMin, yMin, zMin); + _max = (xMax, yMax, zMax); + } + + internal void AppendLineSegments(ReadOnlySpan vertexComponents, ReadOnlySpan segmentIndices, double scale) + { + _synced = false; + // assume we have a normal for each vertex + var numVertices = vertexComponents.Length / 3; + var numLines = numVertices / _indexSize; + var vertexIndex = (ushort)(_vertexComponentCount / _vertexSize); + var idxSpan = MemoryMarshal.Cast(_indices); + var (xMin, yMin, zMin) = _min; + var (xMax, yMax, zMax) = _max; + for (int i = 0; i < numVertices; i++) + { + var j = i * 3; + var x = vertexComponents[j++] * scale; + //var y = vertexComponents[j++] * scale; + //var z = vertexComponents[j] * scale; + var z = vertexComponents[j++] * scale; + var y = -vertexComponents[j] * scale; + xMin = Math.Min(xMin, x); + yMin = Math.Min(yMin, y); + zMin = Math.Min(zMin, z); + xMax = Math.Max(xMax, x); + yMax = Math.Max(yMax, y); + zMax = Math.Max(zMax, z); + _vertexComponents[_vertexComponentCount++] = (float)x; + _vertexComponents[_vertexComponentCount++] = (float)y; + _vertexComponents[_vertexComponentCount++] = (float)z; + } + + var startOffset = segmentIndices[0]; // likely not necessary? + for (int i = 0; i < segmentIndices.Length; i += 2) + { + var a = segmentIndices[i] - startOffset; + var b = segmentIndices[i + 1] - startOffset; + idxSpan[_indexComponentCount++] = (ushort)(a + vertexIndex); + idxSpan[_indexComponentCount++] = (ushort)(b + vertexIndex); + } + + _min = (xMin, yMin, zMin); + _max = (xMax, yMax, zMax); + } + + public void Dispose() + { + DestroyBuffers(); + } + + private void DestroyBuffers() + { + _vertexBuffer?.Dispose(); + _indexBuffer?.Dispose(); + //_vertexFormat?.Dispose(); + _outline?.Dispose(); + _vertexBuffer = null; + _indexBuffer = null; + //_vertexFormat = null; + _outline = null; + + _initialized = false; + } + + public void Render(Transform transform) + { + _effectInstance ??= new EffectInstance(VertexFormatBits.PositionNormal); + Render(transform, _effectInstance); + } + + public void Render(Transform transform, EffectInstance effectInstance) + { + if (!HasValidBuffers) + DestroyBuffers(); // the old buffers have been invalidated, remove them + + if (!_initialized) + InitializeRenderBuffers(); // ensure we have buffers to render + + if (!_synced) + CopyBuffers(); // ensure the buffer content is up-to-date + + DrawContext.SetWorldTransform(transform); + var primitiveCount = _indexComponentCount / _indexSize; + DrawContext.FlushBuffer( + _vertexBuffer, + _vertexComponentCount / _vertexSize, + _indexBuffer, + _indexComponentCount, + _vertexFormat, + effectInstance, + _type, + 0, + primitiveCount + ); + } + } + + internal class OutlineWrapper + { + private Outline _outline = null; + public Outline Outline => _outline; + + public void UpdateOutline(Outline other) + { + if (other == null || !other.IsValidObject) + return; + UpdateOrCreateNew(ref _outline, other.MinimumPoint, other.MaximumPoint); + } + + public static void UpdateOrCreateNew(ref Outline current, XYZ minPoint, XYZ maxPoint) + { + if (current == null) + { + current = new Outline(minPoint, maxPoint); + } + else + { + current.AddPoint(minPoint); + current.AddPoint(maxPoint); + } + } + public static void UpdateOrCreateNew(ref Outline current, Outline other) + { + if (other == null || !other.IsValidObject) + return; + UpdateOrCreateNew(ref current, other.MinimumPoint, other.MaximumPoint); + } + } +} diff --git a/src/DynamoRevit/ViewModel/DynamoRevitViewModel.cs b/src/DynamoRevit/ViewModel/DynamoRevitViewModel.cs index 131064e13..58c20e0a5 100644 --- a/src/DynamoRevit/ViewModel/DynamoRevitViewModel.cs +++ b/src/DynamoRevit/ViewModel/DynamoRevitViewModel.cs @@ -26,10 +26,11 @@ private DynamoRevitViewModel(StartConfiguration startConfiguration) : model.RevitViewChanged += model_RevitViewChanged; model.InvalidRevitDocumentActivated += model_InvalidRevitDocumentActivated; - if (RevitWatch3DViewModel.GetTransientDisplayMethod() == null) return; + //if (RevitWatch3DViewModel.GetTransientDisplayMethod() == null) return; var watch3DParams = new Watch3DViewModelStartupParams(model); - var watch3DVm = new RevitWatch3DViewModel(watch3DParams); + //var watch3DVm = new RevitWatch3DViewModel(watch3DParams); + var watch3DVm = new RevitDirectContextWatch3DViewModel(watch3DParams); RegisterWatch3DViewModel(watch3DVm, new DefaultRenderPackageFactory()); } diff --git a/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs b/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs index e8b156b0b..28d1b203d 100644 --- a/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs +++ b/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs @@ -1,14 +1,26 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; using System.ComponentModel; +using System.Diagnostics; using System.Linq; using System.Reflection; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Threading; +using System.Xml; using Autodesk.DesignScript.Geometry; using Autodesk.DesignScript.Interfaces; using Autodesk.Revit.DB; +using Dynamo.Applications.Preview; +using Dynamo.Graph.Connectors; using Dynamo.Graph.Nodes; +using Dynamo.Graph.Workspaces; using Dynamo.Models; +using Dynamo.Visualization; using Dynamo.Wpf.ViewModels.Watch3D; +using Dynamo.Wpf.Views.Debug; using Revit.GeometryConversion; using RevitServices.Persistence; using RevitServices.Threading; @@ -109,15 +121,18 @@ protected override void OnNodePropertyChanged(object sender, PropertyChangedEven break; } } - + #region private methods + Stopwatch _sw; private void Draw(NodeModel node = null) { // If there is no open Revit document, some nodes cannot be executed. if (!Active || DocumentManager.Instance.CurrentDBDocument == null) return; IEnumerable graphicItems = new List(); + _sw = Stopwatch.StartNew(); + if (node != null) { if (node.IsVisible) @@ -172,6 +187,8 @@ private void Draw(IEnumerable geoms) keeperId = (ElementId)method.Invoke(null, argsM); TransactionManager.Instance.ForceCloseTransaction(); + var elapsed = _sw.ElapsedMilliseconds; + Debug.WriteLine($"Transient element update took {elapsed}ms"); }); } @@ -349,6 +366,8 @@ private List Tessellate(Surface surface) /// private List Tessellate(Solid solid) { + var pkg = renderPackageFactory.CreateRenderPackage(); + solid.Tessellate(pkg, renderPackageFactory.TessellationParameters); List rtGeoms = new List(); try @@ -415,4 +434,467 @@ internal static MethodInfo GetTransientDisplayMethod() } #endregion } + + internal class NodeState + { + public required Guid Guid { get; set; } + public bool Selected { get; set; } = false; + public bool Visible { get; set; } + public bool Dirty { get; set; } = true; + public bool GeometryUpdated { get; set; } = false; + + public void ClearFlags() + { + Dirty = false; + GeometryUpdated = false; + } + + public void UpdateState(NodeModel node) + { + if (node.IsSelected != Selected) + { + Dirty = true; + Selected = node.IsSelected; + } + if (node.IsVisible != Visible) + { + Dirty = true; + Visible = node.IsVisible; + } + } + + public void UpdateGeometry() + { + GeometryUpdated = true; + Dirty = true; + } + } + + public class RevitDirectContextWatch3DViewModel : DefaultWatch3DViewModel + { + private class NodeGraphicsDebouncer + { + Dispatcher _dispatcher; + CancellationTokenSource _cts; + public NodeGraphicsDebouncer(Dispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public void Debounce(int timeoutMillis, Action action) + { + _cts?.Cancel(); + if (timeoutMillis <= 0) + { + _cts = null; + _dispatcher.Invoke(action, DispatcherPriority.Background); + return; + } + _cts = new CancellationTokenSource(); + + Task.Delay(timeoutMillis, _cts.Token).ContinueWith(t => + { + if (t.IsCompletedSuccessfully) + _dispatcher.Invoke(action, DispatcherPriority.Background); + }); + } + } + + private NodeGraphicsDebouncer _debouncer; + private PreviewServer _server; + private BufferPools _pools; + private Dictionary _nodeStates = new Dictionary(); + + public override string PreferenceWatchName { get { return "IsRevitBackgroundPreviewActive"; } } + + public RevitDirectContextWatch3DViewModel(Watch3DViewModelStartupParams parameters) : base(null, parameters) + { + Name = Resources.BackgroundPreviewName; + _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); + //Draw(redrawAll: true); + } + + protected override void OnShutdown() + { + DynamoRevitApp.AddIdleAction(StopServer); + } + + private void StopServer() + { + _server?.StopServer(); + _server = null; + DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); + } + + protected override void OnClear() + { + IdlePromise.ExecuteOnIdleAsync(StopServer); + } + + public override bool Active + { + get => base.Active; + set + { + if (active == value) + return; + + active = value; + preferences.SetIsBackgroundPreviewActive(PreferenceWatchName, value); + RaisePropertyChanged(nameof(Active)); + + OnActiveStateChanged(); + } + } + + protected override void OnActiveStateChanged() + { + if (active) + { + //this.del + Debug.WriteLine("OnActiveStateChanged"); + var nodes = AllNodes().ToList(); + nodes.ForEach(n => GetNodeState(n.GUID).UpdateGeometry()); + ScheduleRedraw(0); + } + else + { + OnClear(); + } + } + + private void ScheduleRedraw(int timeoutMillis) + { + if (_debouncer == null) + _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); + + _debouncer.Debounce(timeoutMillis, () => + { + Debug.WriteLine("Successful debounce!"); + var nodes = AllNodes().Where(n => _nodeStates.TryGetValue(n.GUID, out var state) && state.Dirty).ToList(); + Draw(nodes, null, true); + }); + } + + private NodeState GetNodeState(Guid nodeGuid) + { + NodeState state; + if (_nodeStates.TryGetValue(nodeGuid, out state)) + return state; + state = _nodeStates[nodeGuid] = new NodeState { Guid = nodeGuid }; + return state; + } + + private IEnumerable AllNodes() + { + return dynamoModel.CurrentWorkspace.Nodes; + } + + private Dictionary GetRelevantNodes(IEnumerable raw, IEnumerable guids = null) + { + if (guids == null) + return raw.ToDictionary(n => n.GUID); + + var nodes = guids.ToDictionary(g => g, g => null); + foreach(var node in raw) + { + if (nodes.ContainsKey(node.GUID)) + nodes[node.GUID] = node; + } + return nodes; + } + + protected override void OnEvaluationCompleted(object sender, EvaluationCompletedEventArgs e) + { + Debug.WriteLine($"Evaluation completed, redrawing changed nodes"); + var nodes = AllNodes().Where(n => n.WasInvolvedInExecution).ToList(); + nodes.ForEach(n => GetNodeState(n.GUID).UpdateGeometry()); + ScheduleRedraw(0); + //Draw(nodes, null, true); + } + + protected override void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (sender is not NodeModel node) + return; + + if (e.PropertyName == nameof(node.IsVisible) || e.PropertyName == nameof(node.IsSelected)) + { + Debug.WriteLine($"IsVisible or IsSelected changed for node {node.GUID}"); + GetNodeState(node.GUID).UpdateState(node); + ScheduleRedraw(50); + + //Draw([node], null, false); + } + } + + public override void RemoveGeometryForNode(NodeModel node) + { + Debug.WriteLine($"Redrawing geometry for node {node.GUID}"); + GetNodeState(node.GUID).UpdateGeometry(); + ScheduleRedraw(50); + //var nodeGraphics = UpdateNodeState(node, true); + //Draw([nodeGraphics], []); + //Draw([node], null, true); + } + + public override void DeleteGeometryForNode(NodeModel node, bool requestUpdate = true) + { + var guid = node.GUID; + Debug.WriteLine($"Deleting geometry for node {guid}"); + if (_nodeStates.TryGetValue(guid, out var nodeState)) + { + _nodeStates.Remove(guid); + var downStreamNodes = new HashSet(); + node.GetDownstreamNodes(node, downStreamNodes); + downStreamNodes.Remove(node); + Draw(downStreamNodes, [guid], true); + } + } + + #region private methods + private class NodeGraphicItems + { + public NodeState State { get; set; } + public List GraphicItems { get; set; } + } + + private NodeGraphicItems UpdateNodeState(NodeModel node, bool rebuildGeometry) + { + var guid = node.GUID; + + NodeState state; + if (!_nodeStates.TryGetValue(guid, out state)) + state = _nodeStates[guid] = new NodeState { Guid = guid }; + + state.Dirty = true; + state.UpdateState(node); + List items = null; + if (rebuildGeometry) + { + state.GeometryUpdated = true; + items = node.GeneratedGraphicItems(engineManager.EngineController); + } + + return new NodeGraphicItems { State = state, GraphicItems = items }; + } + + private void Draw(IEnumerable nodes, IEnumerable deleted, bool rebuildGeometry) + { + if (!Active || DocumentManager.Instance.CurrentDBDocument == null) return; + + //var dirty = new HashSet(dirtyNodeGuids ?? []); + var graphicItems = new List(); + //var deletedNodeGuids = _nodeStates.Keys.ToHashSet(); + foreach(var node in nodes) + { + var guid = node.GUID; + //deletedNodeGuids.Remove(guid); + + NodeState state; + if (!_nodeStates.TryGetValue(guid, out state)) + state = _nodeStates[guid] = new NodeState { Guid = guid }; + + state.Dirty = true; + state.UpdateState(node); + List items = null; + if (state.GeometryUpdated) + { + state.GeometryUpdated = true; + items = node.GeneratedGraphicItems(engineManager.EngineController); + } + + //if (state.Dirty) + graphicItems.Add(new NodeGraphicItems { State = state, GraphicItems = items }); + } + + if (graphicItems.Count == 0 && deleted == null) + return; + + Draw(graphicItems, deleted ?? Enumerable.Empty()); + } + + IRenderPackage _pkg; + private void UpdateCached(NodePreviewObject cached, NodeGraphicItems node, IRenderPackage _pkg) + { + cached.Selected = node.State.Selected; + cached.Visible = node.State.Visible; + node.State.ClearFlags(); + if (node.GraphicItems == null || node.GraphicItems.Count == 0) + return; + + cached.Clear(); + foreach(var item in node.GraphicItems) + { + _pkg.Clear(); + switch (item) { + //case Point point: + // var scale = UnitConverter.DynamoToHostFactor(SpecTypeId.Length); + // var transform = Transform.CreateTranslation(point.ToXyz() * scale); + // //GetLazyPreviewBox().AddTransform(transform); + // // GetPreviewBox().AddTransform(transform); + // break; + //case PolyCurve polyCurve: + // polyCurve.Tessellate(_pkg, renderPackageFactory.TessellationParameters); + // break; + //case Curve curve: + // curve.Tessellate(_pkg, renderPackageFactory.TessellationParameters); + // break; + case Surface surface: + //pkg = renderPackageFactory.CreateRenderPackage(); + surface.Tessellate(_pkg, renderPackageFactory.TessellationParameters); + cached.AddMesh(_pkg); + break; + case Solid solid: + //pkg = renderPackageFactory.CreateRenderPackage(); + solid.Tessellate(_pkg, renderPackageFactory.TessellationParameters); + cached.AddMesh(_pkg); + break; + default: + break; + } + } + } + + private void Draw(IEnumerable nodes, IEnumerable deletedNodes) + { + IdlePromise.ExecuteOnIdleAsync( + () => + { + if (_server == null) + _server = PreviewServer.StartNewServer(_pools); + + if (_pkg == null) + _pkg = renderPackageFactory.CreateRenderPackage(); + + foreach(var nodeGuid in deletedNodes) + _server.DeletePreview(nodeGuid); + + var sw = Stopwatch.StartNew(); + var nodeElapsed = sw.ElapsedMilliseconds; + foreach(var node in nodes) + { + var wasDirty = node.State.Dirty; + _server.WithNodeCache(node.State.Guid, cached => UpdateCached(cached, node, _pkg)); + var current = sw.ElapsedMilliseconds; + Debug.WriteLine($"Graphics update for node {node.State.Guid} took {current - nodeElapsed}ms, dirty? {wasDirty}"); + nodeElapsed = current; + } + + var elapsed = sw.ElapsedMilliseconds; + Debug.WriteLine($"Spent {elapsed}ms building the preview geometry"); + DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); + elapsed = sw.ElapsedMilliseconds - elapsed; + Debug.WriteLine($"Spent {elapsed}ms refreshing the active view"); + } + ); + } + + private void DebugMe([CallerMemberName] string callerName = null) + { + Debug.WriteLine($"Debugging from inside from {callerName}"); + } + + protected override void OnIsolationModeRequestUpdate() + { + // TODO dont override + DebugMe(); + base.OnIsolationModeRequestUpdate(); + } + + protected override void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + DebugMe(); + base.OnModelPropertyChanged(sender, e); + } + + protected override void OnWorkspaceCleared(WorkspaceModel workspace) + { + // TODO dont override + DebugMe(); + base.OnWorkspaceCleared(workspace); + } + + protected override void OnWorkspaceOpening(object obj) + { + // TODO dont override + DebugMe(); + base.OnWorkspaceOpening(obj); + } + + protected override void OnWorkspaceSaving(XmlDocument doc) + { + DebugMe(); + base.OnWorkspaceSaving(doc); + } + + protected override void PortConnectedHandler(PortModel arg1, ConnectorModel arg2) + { + DebugMe(); + base.PortConnectedHandler(arg1, arg2); + } + + protected override void PortDisconnectedHandler(PortModel port) + { + DebugMe(); + base.PortDisconnectedHandler(port); + } + + protected override void SelectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e) + { + // TODO dont override - handled by node selection state + DebugMe(); + base.SelectionChangedHandler(sender, e); + } + + Stopwatch _watch; + TimeSpan _last; + protected override void OnRenderPackagesUpdated(NodeModel node, RenderPackageCache packages) + { + // TODO dont override - handled by RemoveGeometryForNode itself + DebugMe(); + _watch ??= Stopwatch.StartNew(); + var span = _watch.Elapsed; + if (_last == TimeSpan.Zero) + Debug.WriteLine($"Hello {span}"); + else + Debug.WriteLine($"Hello {span - _last}"); + _last = span; + return; + + var pkg = packages.Packages.FirstOrDefault(); + if (pkg != null) + { + if (pkg.MeshVertexCount > 0 || pkg.LineVertexCount > 0) + { + return; + IdlePromise.ExecuteOnIdleAsync( + () => + { + if (_server == null) + _server = PreviewServer.StartNewServer(_pools); + + var sw = Stopwatch.StartNew(); + _server.WithNodeCache(node.GUID, cached => + { + cached.Clear(); + cached.Visible = true; + if (pkg.MeshVertexCount > 0) + cached.AddMesh(pkg); + if (pkg.LineVertexCount > 0) + cached.AddEdge(pkg); + }); + var elapsed = sw.ElapsedMilliseconds; + Debug.WriteLine($"Graphics update for node {node.GUID} took {elapsed}ms"); + DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); + elapsed = sw.ElapsedMilliseconds - elapsed; + Debug.WriteLine($"Active view refresh took {elapsed}ms"); + } + ); + } + } + base.OnRenderPackagesUpdated(node, packages); + } + #endregion + } } From 3820ee74e9585108e71ed803317e60db970f6b2c Mon Sep 17 00:00:00 2001 From: Bendik Tobias Berg Date: Thu, 31 Oct 2024 02:00:03 +0100 Subject: [PATCH 2/2] Preview server and viewmodel cleanup --- src/DynamoRevit/Preview/PreviewServer.cs | 1008 +++++------------ .../RevitDirectContextWatch3DViewModel.cs | 350 ++++++ .../ViewModel/RevitWatch3DViewModel.cs | 469 -------- 3 files changed, 606 insertions(+), 1221 deletions(-) create mode 100644 src/DynamoRevit/ViewModel/RevitDirectContextWatch3DViewModel.cs diff --git a/src/DynamoRevit/Preview/PreviewServer.cs b/src/DynamoRevit/Preview/PreviewServer.cs index 85a451e42..b4910ef0b 100644 --- a/src/DynamoRevit/Preview/PreviewServer.cs +++ b/src/DynamoRevit/Preview/PreviewServer.cs @@ -16,19 +16,20 @@ namespace Dynamo.Applications.Preview { public sealed class PreviewServer : IDirectContext3DServer, IDisposable { - private static Dictionary activeServers = new Dictionary(); + private readonly static Dictionary activeServers = new Dictionary(); private bool disposed = false; - private Guid _serverId; - private Dictionary _previewObjects = new Dictionary(); + private readonly Guid _serverId; + + private readonly Dictionary _previewObjects = new Dictionary(); private Outline _outline; //private BufferPools _pools; bool _outlineDirty = true; private RenderEffects _selectedEffect; - private RenderEffects _basicEffect; + private RenderEffects _defaultEffect; private DisplayStyle _effectStyle; - private object _renderMutex; + private readonly object _renderMutex; private PreviewServer(BufferPools pools) { @@ -81,13 +82,13 @@ public Outline GetBoundingBox(View dBView) public string GetVendorId() => "Dynamo team"; - public void InitEffects(DisplayStyle displayStyle) + private void InitEffects(DisplayStyle displayStyle) { - if (displayStyle == _effectStyle && _selectedEffect.IsValid && _basicEffect.IsValid) + if (displayStyle == _effectStyle && _selectedEffect.IsValid && _defaultEffect.IsValid) return; _effectStyle = displayStyle; _selectedEffect?.Dispose(); - _basicEffect?.Dispose(); + _defaultEffect?.Dispose(); var format = displayStyle switch { DisplayStyle.Shading => VertexFormatBits.PositionNormal, @@ -98,16 +99,18 @@ public void InitEffects(DisplayStyle displayStyle) _selectedEffect = new RenderEffects { EdgeEffect = new EffectInstance(VertexFormatBits.Position), MeshEffect = new EffectInstance(format) }; _selectedEffect.EdgeEffect.SetColor(new Color(255, 255, 0)); _selectedEffect.EdgeEffect.SetTransparency(0.3); + _selectedEffect.MeshEffect.SetColor(new Color(255, 255, 0)); _selectedEffect.MeshEffect.SetDiffuseColor(new Color(255, 255, 0)); _selectedEffect.MeshEffect.SetAmbientColor(new Color(255, 255, 0)); _selectedEffect.MeshEffect.SetTransparency(0.3); - _basicEffect = new RenderEffects { EdgeEffect = new EffectInstance(VertexFormatBits.Position), MeshEffect = new EffectInstance(format) }; - _basicEffect.EdgeEffect.SetColor(new Color(150, 150, 150)); - _basicEffect.EdgeEffect.SetTransparency(0.4); - _basicEffect.MeshEffect.SetColor(new Color(150, 150, 150)); - _basicEffect.MeshEffect.SetTransparency(0.4); + _defaultEffect = new RenderEffects { EdgeEffect = new EffectInstance(VertexFormatBits.Position), MeshEffect = new EffectInstance(format) }; + _defaultEffect.EdgeEffect.SetColor(new Color(150, 150, 150)); + _defaultEffect.EdgeEffect.SetTransparency(0.4); + + _defaultEffect.MeshEffect.SetColor(new Color(150, 150, 150)); + _defaultEffect.MeshEffect.SetTransparency(0.4); } public void RenderScene(View dBView, DisplayStyle displayStyle) @@ -131,13 +134,13 @@ public void RenderScene(View dBView, DisplayStyle displayStyle) } else { - item.Render(xform, _basicEffect); + item.Render(xform, _defaultEffect); } } } } - public bool UseInTransparentPass(View dBView) => true; // TODO check for necessity? + public bool UseInTransparentPass(View dBView) => true; public bool UsesHandles() => false; @@ -148,7 +151,6 @@ internal static PreviewServer StartNewServer(BufferPools pools) var doc = DocumentManager.Instance.CurrentDBDocument; Debug.WriteLine($"doc null? {doc == null} {doc?.Title} "); - //IList activeList = directContext3dService.GetActiveServerIds(doc); IList activeList = directContext3dService.GetActiveServerIds(); var previewServer = new PreviewServer(pools); @@ -157,7 +159,6 @@ internal static PreviewServer StartNewServer(BufferPools pools) activeList.Add(previewServer.GetServerId()); directContext3dService.AddServer(previewServer); directContext3dService.SetActiveServers(activeList); - //directContext3dService.SetActiveServers(activeList, doc); Debug.WriteLine($"Server was added"); return previewServer; @@ -168,33 +169,13 @@ public void StopServer() Dispose(); } - //internal void AddPreviewObject(IPreviewObject previewObject) - //{ - // //var outline = previewObject.Outline; - // //if (_outline == null) - // //{ - // // _outline = new Outline(outline); - // //} - // //else - // //{ - // // _outline.AddPoint(outline.MinimumPoint); - // // _outline.AddPoint(outline.MaximumPoint); - // //} - // _previewObjects.Add(previewObject); - // _outlineDirty = true; - //} - internal void WithNodeCache(Guid nodeGuid, Action action) { _outlineDirty = true; lock(_renderMutex) { NodePreviewObject cached; - if (_previewObjects.TryGetValue(nodeGuid, out var cache)) - { - cached = cache as NodePreviewObject; - } - else + if (!_previewObjects.TryGetValue(nodeGuid, out cached)) { cached = new NodePreviewObject(nodeGuid); _previewObjects[nodeGuid] = cached; @@ -202,14 +183,6 @@ internal void WithNodeCache(Guid nodeGuid, Action action) action(cached); } _outlineDirty = true; - //AddPreviewObject() - //var cache = _previewObjects.OfType().FirstOrDefault(); - //if (cache == null) - //{ - // cache = new BufferCache(_pools); - // AddPreviewObject(cache); - //} - //return cache; } internal void DeletePreview(Guid nodeGuid) @@ -229,6 +202,9 @@ public void Dispose() if (disposed) return; + _selectedEffect?.Dispose(); + _defaultEffect?.Dispose(); + ExternalServiceId serviceId = ExternalServices.BuiltInExternalServices.DirectContext3DService; var directContext3dService = ExternalServiceRegistry.GetService(serviceId) as MultiServerService; @@ -251,68 +227,20 @@ public void Dispose() } } - internal interface IPreviewObject : IDisposable - { - void Render(Transform transform); - Outline Outline { get; } - } - - internal interface IPreviewObject2 : IDisposable - { - void Render(Transform transform, RenderEffects effects); - Outline Outline { get; } - } - internal class RenderEffects : IDisposable { public required EffectInstance EdgeEffect { get; init; } public required EffectInstance MeshEffect { get; init; } - public bool IsValid => EdgeEffect == null && EdgeEffect.IsValid() && MeshEffect == null && MeshEffect.IsValid(); + public bool IsValid => EdgeEffect != null && EdgeEffect.IsValid() && MeshEffect != null && MeshEffect.IsValid(); public void Dispose() { - EdgeEffect.Dispose(); - MeshEffect.Dispose(); - } - } - - internal class BufferPools - { - public BufferPool VertexPool { get; } - public BufferPool IndexPool { get; } - public BufferPool RawIndexPool { get; } - public BufferPool RawVertexPool { get; } - - public BufferPools() - { - VertexPool = new BufferPool(16, NewVertexBuffer); - IndexPool = new BufferPool(16, NewIndexBuffer); - RawIndexPool = new BufferPool(16, NewRawIndexBuffer); - RawVertexPool = new BufferPool(16, NewRawVertexBuffer); - } - - private SizedBuffer NewVertexBuffer(int capacity) - { - return new SizedBuffer(capacity, new VertexBuffer(capacity)); - } - - private SizedBuffer NewIndexBuffer(int capacity) - { - return new SizedBuffer(capacity, new IndexBuffer(capacity)); - } - - private SizedBuffer NewRawIndexBuffer(int capacity) - { - return new SizedBuffer(capacity, new byte[capacity]); - } - - private SizedBuffer NewRawVertexBuffer(int capacity) - { - return new SizedBuffer(capacity, new float[capacity]); + EdgeEffect?.Dispose(); + MeshEffect?.Dispose(); } } - internal class NodePreviewObject : IPreviewObject2 + internal class NodePreviewObject : IDisposable { public Guid NodeGuid { get; set; } public bool Selected { get; set; } = false; @@ -340,483 +268,38 @@ public void Render(Transform transform, RenderEffects effects) public void Clear() { - // Using this instance after clear is fine, but wrap it for clarity + // Reusing this instance after dispose is fine, but wrap it for clarity Dispose(); } - public void Dispose() - { - _edgeCache?.Dispose(); - _meshCache?.Dispose(); - _pointCache?.Dispose(); - _outline?.Dispose(); - _edgeCache = null; - _meshCache = null; - _pointCache = null; - _outline = null; - } - public void AddMesh(IRenderPackage meshRenderPackage) { - if (_meshCache == null) - _meshCache = new BufferCache(PrimitiveType.TriangleList); + _meshCache ??= new BufferCache(PrimitiveType.TriangleList); _meshCache.FromMeshRenderPackage(meshRenderPackage); OutlineWrapper.UpdateOrCreateNew(ref _outline, _meshCache.Outline); } public void AddEdge(IRenderPackage edgeRenderPackage) { - if (_edgeCache == null) - _edgeCache = new BufferCache(PrimitiveType.LineList); + _edgeCache ??= new BufferCache(PrimitiveType.LineList); _edgeCache.FromLineRenderPackage(edgeRenderPackage); OutlineWrapper.UpdateOrCreateNew(ref _outline, _edgeCache.Outline); } - } - - internal class LazyProtoPreview : IPreviewObject - { - protected IPreviewObject _inner = null; - protected Func _instantiate; - - public Outline Outline => _inner?.Outline; - - public LazyProtoPreview(Func instantiate) - { - _instantiate = instantiate; - } - - public void Dispose() - { - _inner?.Dispose(); - _inner = null; - } - - public virtual void Render(Transform transform) - { - if (_inner == null) - _inner = _instantiate(); - if (_inner == null) - return; - _inner.Render(transform); - } - } - - internal class LazyInstanceProtoPreview : LazyProtoPreview - { - private List _transforms; - public LazyInstanceProtoPreview(Func instantiateObject) - : base(instantiateObject) - { - _transforms = new List(); - } - - public void AddTransform(Transform transform) - { - _transforms.Add(transform); - } - - public override void Render(Transform transform) - { - if (_inner == null) - { - _inner = _instantiate(); - if (_inner == null) - return; - var instance = (InstanceProtoPreview)_inner; - foreach (var xform in _transforms) - instance.AddTransform(xform); - } - base.Render(transform); - } - } - - internal class ProtoPreview : IDisposable, IPreviewObject - { - class DoublesToXYZ - { - List _vertices; - private double _scale = 1; - public int Count => _vertices.Count / 3; - public Outline Outline { get; } - public DoublesToXYZ(IEnumerable vertices, bool withOutline) - { - _vertices = vertices.ToList(); - if (withOutline) - Outline = new Outline(GetXYZ(0), GetXYZ(1)); - } - - public DoublesToXYZ(IEnumerable vertices, bool withOutline, double scale) - { - _vertices = vertices.ToList(); - _scale = scale; - if (withOutline) - Outline = new Outline(GetXYZ(0), GetXYZ(1)); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public XYZ GetXYZ(int index) - { - int idx = index * 3; - return new XYZ(_vertices[idx], _vertices[idx + 1], _vertices[idx + 2]); - } - - public XYZ GetVertex(int index) - { - int idx = index * 3; - var xyz = new XYZ(_vertices[idx] * _scale, _vertices[idx + 1] * _scale, _vertices[idx + 2] * _scale); - Outline.AddPoint(xyz); - return xyz; - } - } - - class BytesToColor - { - List _colors; - public int Count => _colors.Count / 4; - public BytesToColor(IEnumerable colors) - { - _colors = colors.ToList(); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ColorWithTransparency GetColor(int index) - { - var idx = index * 4; - return new ColorWithTransparency(_colors[idx], _colors[idx + 1], _colors[idx + 2], _colors[idx + 3]); - } - - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ColorWithTransparency GetAColor(int index) - { - var v = (byte)Random.Shared.Next(255); - return new ColorWithTransparency(v, v, v, 200); - } - } - - private VertexFormatBits _format; - private PrimitiveType _type; - private int _vertsPerPrimitive; - private int _vertexCount; - private int _indexCount; - private int _primitiveCount; - private VertexBuffer _vertexBuffer; - private IndexBuffer _indexBuffer; - private VertexFormat _vertexFormat; - private EffectInstance _effectInstance; - private Outline _outline; - - private bool disposedValue = false; - - public Outline Outline { get => _outline; private set => _outline = value; } - public EffectInstance EffectInstance { get => _effectInstance; private set => _effectInstance = value; } - public VertexFormatBits FormatBits => _format; - - public ProtoPreview(PrimitiveType type, int vertexCount, bool hasNormals, bool hasColors) - { - var (bits, vertSize) = (hasNormals, hasColors) switch - { - (true, true) => (VertexFormatBits.PositionNormalColored, VertexPositionNormalColored.GetSizeInFloats()), - (true, false) => (VertexFormatBits.PositionNormal, VertexPositionNormal.GetSizeInFloats()), - (false, true) => (VertexFormatBits.PositionColored, VertexPositionColored.GetSizeInFloats()), - (false, false) => (VertexFormatBits.Position, VertexPosition.GetSizeInFloats()), - }; - var (vertsPerPrimitive, indexSize) = type switch - { - PrimitiveType.TriangleList => (3, IndexTriangle.GetSizeInShortInts()), - PrimitiveType.LineList => (2, IndexLine.GetSizeInShortInts()), - PrimitiveType.PointList => (1, IndexPoint.GetSizeInShortInts()), - _ => throw new NotImplementedException(), - }; - - _format = bits; - _type = type; - - //_vertexFormat = new VertexFormat(bits); - //_effectInstance = new EffectInstance(bits); - - _vertsPerPrimitive = vertsPerPrimitive; - _vertexCount = vertexCount; - _primitiveCount = _vertexCount / _vertsPerPrimitive; - _indexCount = _primitiveCount; - - _vertexBuffer = new VertexBuffer(_vertexCount * vertSize); - _vertexBuffer.Map(_vertexCount * vertSize); - - _indexBuffer = new IndexBuffer(_vertexCount * indexSize); - _indexBuffer.Map(_vertexCount * indexSize); - } - - public void Render(Transform transform) - { - var effectInstance = new EffectInstance(_format); - effectInstance.SetTransparency(0.5); - //effectInstance.SetColor(new Color(100, 100, 150)); - //effectInstance.SetDiffuseColor(new Color(100, 100, 150)); - //effectInstance.SetAmbientColor(new Color(100, 100, 150)); - //effectInstance.SetSpecularColor(new Color(255, 255, 255)); - //effectInstance.SetGlossiness(0.1); - DrawContext.SetWorldTransform(transform); - DrawContext.FlushBuffer( - _vertexBuffer, - _vertexCount, - _indexBuffer, - _indexCount, - new VertexFormat(_format), - effectInstance, - _type, - 0, - _primitiveCount - ); - } - - public static ProtoPreview FromSolid(Autodesk.DesignScript.Geometry.Solid solid, IRenderPackageFactory renderPackageFactory) - { - var pkg = renderPackageFactory.CreateRenderPackage(); - solid.Tessellate(pkg, renderPackageFactory.TessellationParameters); - - return FromMeshRenderPackage(pkg); - } - - public static ProtoPreview FromMeshRenderPackage(IRenderPackage pkg) - { - var scale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); - - var verts = new DoublesToXYZ(pkg.MeshVertices, true, scale); - var numVerts = verts.Count; - var numTris = numVerts / 3; - var hasVerts = numVerts > 0; - var colors = new BytesToColor(pkg.MeshVertexColors); - var hasColors = colors.Count == numVerts; - hasColors = false; - var normals = new DoublesToXYZ(pkg.MeshNormals, false); - var hasNormals = normals.Count == numVerts; - //hasNormals = false; - - - var preview = new ProtoPreview(PrimitiveType.TriangleList, numVerts, hasNormals, hasColors); - var indexBuffer = preview._indexBuffer; - var vertexBuffer = preview._vertexBuffer; - var bits = preview.FormatBits; - - using (var idxStream = indexBuffer.GetIndexStreamTriangle()) - using (var tri = new IndexTriangle(0, 0, 0)) - { - if (bits == VertexFormatBits.Position) - { - using (var stream = vertexBuffer.GetVertexStreamPosition()) - using (var vert = new VertexPosition(XYZ.Zero)) - { - for (int j = 0; j < numTris; j++) - { - var v = j * 3; - var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... - vert.Position = verts.GetVertex(j0); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j2); stream.AddVertex(vert); - tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; - idxStream.AddTriangle(tri); - } - } - } - else if (bits == VertexFormatBits.PositionNormal) - { - using (var stream = vertexBuffer.GetVertexStreamPositionNormal()) - using (var vert = new VertexPositionNormal(XYZ.Zero, XYZ.Zero)) - { - for (int j = 0; j < numTris; j++) - { - var v = j * 3; - var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... - vert.Position = verts.GetVertex(j0); vert.Normal = normals.GetXYZ(j0); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); vert.Normal = normals.GetXYZ(j1); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j2); vert.Normal = normals.GetXYZ(j2); stream.AddVertex(vert); - tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; - idxStream.AddTriangle(tri); - } - } - } - else if (bits == VertexFormatBits.PositionColored) - { - using (var stream = vertexBuffer.GetVertexStreamPositionColored()) - using (var vert = new VertexPositionColored(XYZ.Zero, new ColorWithTransparency())) - { - for (int j = 0; j < numTris; j++) - { - var v = j * 3; - var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... - vert.Position = verts.GetVertex(j0); vert.SetColor(colors.GetAColor(j0)); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); vert.SetColor(colors.GetAColor(j1)); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j2); vert.SetColor(colors.GetAColor(j2)); stream.AddVertex(vert); - tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; - idxStream.AddTriangle(tri); - } - } - } - else if (bits == VertexFormatBits.PositionNormalColored) - { - using (var stream = vertexBuffer.GetVertexStreamPositionNormalColored()) - using (var vert = new VertexPositionNormalColored(XYZ.Zero, XYZ.Zero, new ColorWithTransparency())) - { - for (int j = 0; j < numTris; j++) - { - var v = j * 3; - var (j0, j1, j2) = (v, v + 1, v + 2); // => (0, 1, 2), (3, 4, 5) ... - vert.Position = verts.GetVertex(j0); vert.Normal = normals.GetXYZ(j0); vert.SetColor(colors.GetAColor(j0)); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); vert.Normal = normals.GetXYZ(j1); vert.SetColor(colors.GetAColor(j1)); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j2); vert.Normal = normals.GetXYZ(j2); vert.SetColor(colors.GetAColor(j2)); stream.AddVertex(vert); - tri.Index0 = j0; tri.Index1 = j1; tri.Index2 = j2; - idxStream.AddTriangle(tri); - } - } - } - } - indexBuffer.Unmap(); - vertexBuffer.Unmap(); - - preview.Outline = verts.Outline; - //Debug.WriteLine($"Created preview item"); - return preview; - } - - - public static ProtoPreview FromLineRenderPackage(IRenderPackage pkg) - { - var scale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); - - var verts = new DoublesToXYZ(pkg.LineStripVertices, true, scale); - var numVerts = verts.Count; - var hasVerts = numVerts > 0; - var colors = new BytesToColor(pkg.LineStripVertexColors); - var hasColors = colors.Count == numVerts; - var lineIndices = pkg.LineStripIndices.ToList(); - var numLines = lineIndices.Count; - - var preview = new ProtoPreview(PrimitiveType.LineList, numVerts, false, hasColors); - var indexBuffer = preview._indexBuffer; - var vertexBuffer = preview._vertexBuffer; - var bits = preview.FormatBits; - - using (var idxStream = indexBuffer.GetIndexStreamLine()) - using (var line = new IndexLine(0, 0)) - { - if (bits == VertexFormatBits.Position) - { - using (var stream = vertexBuffer.GetVertexStreamPosition()) - using (var vert = new VertexPosition(XYZ.Zero)) - { - for (int j = 0; j < numVerts; j += 2) - { - var j1 = j + 1; - vert.Position = verts.GetVertex(j); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); stream.AddVertex(vert); - line.Index0 = j; line.Index1 = j1; - idxStream.AddLine(line); - } - } - } - else if (bits == VertexFormatBits.PositionColored) - { - using (var stream = vertexBuffer.GetVertexStreamPositionColored()) - using (var vert = new VertexPositionColored(XYZ.Zero, new ColorWithTransparency())) - { - for (int j = 0; j < numVerts; j++) - { - var j1 = j + 1; - vert.Position = verts.GetVertex(j); vert.SetColor(colors.GetColor(j)); stream.AddVertex(vert); - vert.Position = verts.GetVertex(j1); vert.SetColor(colors.GetColor(j1)); stream.AddVertex(vert); - line.Index0 = j; line.Index1 = j1; - idxStream.AddLine(line); - } - } - } - } - indexBuffer.Unmap(); - vertexBuffer.Unmap(); - - preview.Outline = verts.Outline; - return preview; - } - - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - _vertexBuffer?.Dispose(); - _indexBuffer?.Dispose(); - _vertexFormat?.Dispose(); - _effectInstance?.Dispose(); - _outline?.Dispose(); - } - disposedValue = true; - } - } - - public void Dispose() - { - Dispose(disposing: true); - GC.SuppressFinalize(this); - } - } - - internal class InstanceProtoPreview : IPreviewObject - { - private ProtoPreview _instanceGeometry; - private XYZ[] _outlinePoints; - private List _transforms; - - public Outline Outline { get; private set; } - - public InstanceProtoPreview(ProtoPreview instancePreviewGeometry) - { - _instanceGeometry = instancePreviewGeometry; - var min = _instanceGeometry.Outline.MinimumPoint; - var max = _instanceGeometry.Outline.MaximumPoint; - var (ax, ay, az) = (min.X, min.Y, min.Z); - var (bx, by, bz) = (max.X, max.Y, max.Z); - _outlinePoints = - [ - new XYZ(ax, ay, az), // 0 0 0 - new XYZ(ax, ay, bz), // 0 0 1 - new XYZ(ax, by, bz), // 0 1 1 - new XYZ(ax, by, az), // 0 1 0 - new XYZ(bx, ay, az), // 1 0 0 - new XYZ(bx, by, az), // 1 1 0 - new XYZ(bx, ay, bz), // 1 0 1 - new XYZ(bx, by, bz), // 1 1 1 - ]; - } - - public void AddTransform(Transform transform) - { - if (_transforms == null) - { - _transforms = new List(); - Outline = new Outline(transform.OfPoint(_outlinePoints[0]), transform.OfPoint(_outlinePoints[1])); - } - for (int i = 0; i < 8; i++) - Outline.AddPoint(transform.OfPoint(_outlinePoints[i])); - - _transforms.Add(transform); - } - - public void Render(Transform transform) - { - if (_transforms == null) - return; - foreach (var xform in _transforms) - _instanceGeometry.Render(xform); - } public void Dispose() { - _instanceGeometry?.Dispose(); - _instanceGeometry = null; + _edgeCache?.Dispose(); + _meshCache?.Dispose(); + _pointCache?.Dispose(); + _outline?.Dispose(); + _edgeCache = null; + _meshCache = null; + _pointCache = null; + _outline = null; } } - internal class BufferCache : IPreviewObject + internal class BufferCache : IDisposable { private Outline _outline; private List _buffers; @@ -862,9 +345,9 @@ public void UpdateCache(BufferedProtoPreview buffer) // the returned buffer must always be the last buffer in the list var bufCount = _buffers.Count; Debug.Assert(ReferenceEquals(buffer, _buffers[bufCount - 1])); + var capacity = buffer.Capacity; if (bufCount > 1) { - var capacity = buffer.Capacity; var idx = bufCount - 2; while (idx >= 0) { @@ -883,21 +366,6 @@ public void UpdateCache(BufferedProtoPreview buffer) } } - public void Render(Transform transform) - { - var interrupted = DrawContext.IsInterrupted(); - var numPrimitives = 0; - foreach(var item in _buffers) - { - if (interrupted) - return; - - numPrimitives += item.PrimitiveCount; - item.Render(transform); - } - //Debug.WriteLine($"Cache has {numPrimitives} triangle(s)"); - } - public void Render(Transform transform, EffectInstance effectInstance) { var interrupted = DrawContext.IsInterrupted(); @@ -910,22 +378,18 @@ public void Render(Transform transform, EffectInstance effectInstance) numPrimitives += item.PrimitiveCount; item.Render(transform, effectInstance); } - //Debug.WriteLine($"Cache has {numPrimitives} triangle(s)"); } - public BufferedProtoPreview FromMeshRenderPackage(IRenderPackage pkg) { if (double.IsNaN(_modelScale)) _modelScale = Revit.GeometryConversion.UnitConverter.DynamoToHostFactor(SpecTypeId.Length); - //_modelScale = 1; var max = ushort.MaxValue + 1; var vertexComponents = pkg.MeshVertices.ToArray(); var normalComponents = pkg.MeshNormals.ToArray(); var numVertices = vertexComponents.Length / 3; var numTris = numVertices / 3; - //var remainingTris = numTris; var remainingVerts = numVertices; var end = 0; BufferedProtoPreview buffer = null; @@ -967,7 +431,7 @@ internal void FromLineRenderPackage(IRenderPackage edgeRenderPackage) var numVertices = vertexComponents.Length / 3; BufferedProtoPreview buffer = null; - var indexBuffer = new int[max - 2]; // can't use the 65k buffer for some reason + var indexBuffer = new int[max - 2]; // can't use the full 65k buffer for some reason (the last line is removed) var currentIndex = 0; var bufferIndex = 0; var vertexStart = indices[0]; @@ -1008,112 +472,7 @@ internal void FromLineRenderPackage(IRenderPackage edgeRenderPackage) } } - internal struct SizedBuffer - { - public int Size { get; } - public T Buffer { get; } - public SizedBuffer(int size, T buffer) - { - Size = size; - Buffer = buffer; - } - } - - internal class BufferPool - { - private object _bufferMutex; - private int _poolSize; - private Dictionary>> _buffers; - private Func> _factory; - - public BufferPool(int poolSize, Func> bufferFactory) - { - _factory = bufferFactory; - _buffers = new Dictionary>>(); - _poolSize = poolSize; - } - - public int GetBufferPoolIdx(int targetCapacity, int mult) - { - var bufferLogSize = Math.Log2(targetCapacity / mult); - var bufferTargetSize = (int)bufferLogSize; - if (bufferLogSize % 1 > 0) - bufferTargetSize++; - return bufferTargetSize; - } - - public SizedBuffer RentBuffer(int capacity, int mult) - { - var bufferTargetPool = GetBufferPoolIdx(capacity, mult); - lock (_bufferMutex) - { - if (_buffers.TryGetValue(bufferTargetPool, out var rentableBuffers) && rentableBuffers.Count > 0) - { - var idx = rentableBuffers.Count - 1; - var buffer = rentableBuffers[idx]; - rentableBuffers.RemoveAt(idx); - return buffer; - } - else - { - var bufferSize = (int)Math.Pow(2, bufferTargetPool); - var buffer = _factory(bufferSize * mult); - return buffer; - } - } - } - - public bool ReturnBuffer(SizedBuffer buffer, int mult) - { - var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); - - lock (_bufferMutex) - { - if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) - { - if (buffers.Count < _poolSize) - { - buffers.Add(buffer); - return true; - } - return false; // no space to store the buffer - } - else - { - _buffers[bufferTargetPool] = new List> { buffer }; - return true; - } - } - } - - public bool ReturnBufferOrDispose(SizedBuffer buffer, int mult) - { - var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); - - lock (_bufferMutex) - { - if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) - { - if (buffers.Count < _poolSize) - { - buffers.Add(buffer); - return true; - } - if (buffer.Buffer is IDisposable disposable) - disposable.Dispose(); - - return false; // no space to store the buffer - } - else - { - _buffers[bufferTargetPool] = new List> { buffer }; - return true; - } - } - } - } - - internal class BufferedProtoPreview : IPreviewObject + internal class BufferedProtoPreview : IDisposable { const int MAX_SIZE = ushort.MaxValue + 1; @@ -1129,22 +488,20 @@ internal class BufferedProtoPreview : IPreviewObject private (double, double, double) _min = (double.MaxValue, double.MaxValue, double.MaxValue); private (double, double, double) _max = (double.MinValue, double.MinValue, double.MinValue); - private SizedBuffer _sizedVertexBuffer; private VertexBuffer _vertexBuffer; private IndexBuffer _indexBuffer; private VertexFormat _vertexFormat; - private EffectInstance _effectInstance; //private BufferPools _pools; private Outline _outline; - private PrimitiveType _type; - private int _vertexSize; - private int _indexSize; + private readonly PrimitiveType _type; + private readonly int _vertexSize; + private readonly int _indexSize; public int Capacity => (MAX_SIZE * _vertexSize) - _vertexComponentCount; - public int PrimitiveCount => _indexComponentCount / _indexSize; // 3 for triangles + public int PrimitiveCount => _indexComponentCount / _indexSize; public bool HasValidBuffers => _vertexBuffer != null && _vertexBuffer.IsValid() && _indexBuffer != null && _indexBuffer.IsValid() @@ -1154,14 +511,17 @@ public Outline Outline { get { - UpdateOutline(); + if (_min.Item1 > _max.Item1) + return null; // this outline is invalid + var pMin = new XYZ(_min.Item1, _min.Item2, _min.Item3); + var pMax = new XYZ(_max.Item1, _max.Item2, _max.Item3); + OutlineWrapper.UpdateOrCreateNew(ref _outline, pMin, pMax); return _outline; } } internal BufferedProtoPreview(PrimitiveType primitiveType) { - //Debug.WriteLine($"actual {VertexBufferSize} expected {MAX_SIZE * 6}"); //_pools = pools; _type = primitiveType; (_vertexSize, _indexSize) = _type switch @@ -1170,27 +530,15 @@ internal BufferedProtoPreview(PrimitiveType primitiveType) PrimitiveType.LineList => (3, 2), _ => throw new NotImplementedException() }; + // unsure if the max size is the size of the backing byte buffer, or the amount of floats - //var rawComponents = _pools.RawVertexPool.RentBuffer(MAX_SIZE * _vertexSize, _vertexSize); // new float[VertexBufferSize]; _vertexComponents = new float[MAX_SIZE * _vertexSize]; - //var rawIndices = _pools.RawIndexPool.RentBuffer(MAX_SIZE * _indexSize * 2, _indexSize * 2); // new float[VertexBufferSize]; _indices = new byte[MAX_SIZE * _indexSize * 2]; } - public void UpdateOutline() - { - if (_min.Item1 > _max.Item1) - return; - var pMin = new XYZ(_min.Item1, _min.Item2, _min.Item3); - var pMax = new XYZ(_max.Item1, _max.Item2, _max.Item3); - OutlineWrapper.UpdateOrCreateNew(ref _outline, pMin, pMax); - } - private void InitializeRenderBuffers() { - //var rawVertexBuffer = _pools.VertexPool.RentBuffer(MAX_SIZE * _vertexSize, _vertexSize); _vertexBuffer = new VertexBuffer(MAX_SIZE * _vertexSize); - //var rawIndexBuffer = _pools.IndexPool.RentBuffer(MAX_SIZE * _indexSize, _indexSize); _indexBuffer = new IndexBuffer(MAX_SIZE * _indexSize); _vertexFormat = _type switch @@ -1199,7 +547,6 @@ private void InitializeRenderBuffers() PrimitiveType.LineList => new VertexFormat(VertexFormatBits.Position), _ => throw new NotImplementedException() }; - //_vertexFormat = new VertexFormat(VertexFormatBits.PositionNormal); _initialized = true; } @@ -1217,6 +564,32 @@ private void CopyBuffers() _synced = true; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static (double x, double y, double z) GetHelixComponents(in ReadOnlySpan components, int idx, double scale) + { + var x = components[idx++] * scale; + var z = components[idx++] * scale; + var y = -components[idx] * scale; + return (x, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + static (double x, double y, double z) GetComponents(in ReadOnlySpan components, int idx) + { + var x = components[idx++]; + var y = components[idx++]; + var z = components[idx]; + return (x, y, z); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private void SetComponents(double x, double y, double z) + { + _vertexComponents[_vertexComponentCount++] = (float)x; + _vertexComponents[_vertexComponentCount++] = (float)y; + _vertexComponents[_vertexComponentCount++] = (float)z; + } + internal void AppendMeshParts(ReadOnlySpan vertexComponents, ReadOnlySpan normalComponents, double scale) { _synced = false; @@ -1229,31 +602,32 @@ internal void AppendMeshParts(ReadOnlySpan vertexComponents, ReadOnlySpa var (xMax, yMax, zMax) = _max; for (int i = 0; i < numVertices; i++) { - var j = i * 3; - var x = vertexComponents[j++] * scale; - //var y = vertexComponents[j++] * scale; - //var z = vertexComponents[j] * scale; - var z = vertexComponents[j++] * scale; - var y = -vertexComponents[j] * scale; + var (x, y, z) = GetHelixComponents(in vertexComponents, i * 3, scale); + //var j = i * 3; + //var x = vertexComponents[j++] * scale; + ////var y = vertexComponents[j++] * scale; + ////var z = vertexComponents[j] * scale; + //var z = vertexComponents[j++] * scale; + //var y = -vertexComponents[j] * scale; xMin = Math.Min(xMin, x); yMin = Math.Min(yMin, y); zMin = Math.Min(zMin, z); xMax = Math.Max(xMax, x); yMax = Math.Max(yMax, y); zMax = Math.Max(zMax, z); - _vertexComponents[_vertexComponentCount++] = (float)x; - _vertexComponents[_vertexComponentCount++] = (float)y; - _vertexComponents[_vertexComponentCount++] = (float)z; + SetComponents(x, y, z); - j = i * 3; // restart the component index for the normals - var nx = (float)normalComponents[j++]; - //var ny = (float)normalComponents[j++]; - //var nz = (float)normalComponents[j]; - var nz = (float)normalComponents[j++]; - var ny = -(float)normalComponents[j]; - _vertexComponents[_vertexComponentCount++] = nx; - _vertexComponents[_vertexComponentCount++] = ny; - _vertexComponents[_vertexComponentCount++] = nz; + //j = i * 3; // restart the component index for the normals + //var nx = (float)normalComponents[j++]; + ////var ny = (float)normalComponents[j++]; + ////var nz = (float)normalComponents[j]; + //var nz = (float)normalComponents[j++]; + //var ny = -(float)normalComponents[j]; + var (nx, ny, nz) = GetHelixComponents(in normalComponents, i * 3, 1); + SetComponents(nx, ny, nz); + //_vertexComponents[_vertexComponentCount++] = nx; + //_vertexComponents[_vertexComponentCount++] = ny; + //_vertexComponents[_vertexComponentCount++] = nz; //_componentOffset++; // add one for color } @@ -1283,21 +657,23 @@ internal void AppendLineSegments(ReadOnlySpan vertexComponents, ReadOnly var (xMax, yMax, zMax) = _max; for (int i = 0; i < numVertices; i++) { - var j = i * 3; - var x = vertexComponents[j++] * scale; - //var y = vertexComponents[j++] * scale; - //var z = vertexComponents[j] * scale; - var z = vertexComponents[j++] * scale; - var y = -vertexComponents[j] * scale; + var (x, y, z) = GetHelixComponents(in vertexComponents, i * 3, scale); + //var j = i * 3; + //var x = vertexComponents[j++] * scale; + ////var y = vertexComponents[j++] * scale; + ////var z = vertexComponents[j] * scale; + //var z = vertexComponents[j++] * scale; + //var y = -vertexComponents[j] * scale; xMin = Math.Min(xMin, x); yMin = Math.Min(yMin, y); zMin = Math.Min(zMin, z); xMax = Math.Max(xMax, x); yMax = Math.Max(yMax, y); zMax = Math.Max(zMax, z); - _vertexComponents[_vertexComponentCount++] = (float)x; - _vertexComponents[_vertexComponentCount++] = (float)y; - _vertexComponents[_vertexComponentCount++] = (float)z; + SetComponents(x, y, z); + //_vertexComponents[_vertexComponentCount++] = (float)x; + //_vertexComponents[_vertexComponentCount++] = (float)y; + //_vertexComponents[_vertexComponentCount++] = (float)z; } var startOffset = segmentIndices[0]; // likely not necessary? @@ -1315,10 +691,10 @@ internal void AppendLineSegments(ReadOnlySpan vertexComponents, ReadOnly public void Dispose() { - DestroyBuffers(); + DestroyRenderBuffers(); } - private void DestroyBuffers() + private void DestroyRenderBuffers() { _vertexBuffer?.Dispose(); _indexBuffer?.Dispose(); @@ -1332,22 +708,16 @@ private void DestroyBuffers() _initialized = false; } - public void Render(Transform transform) - { - _effectInstance ??= new EffectInstance(VertexFormatBits.PositionNormal); - Render(transform, _effectInstance); - } - public void Render(Transform transform, EffectInstance effectInstance) { if (!HasValidBuffers) - DestroyBuffers(); // the old buffers have been invalidated, remove them + DestroyRenderBuffers(); // the old buffers have been invalidated, remove them if (!_initialized) InitializeRenderBuffers(); // ensure we have buffers to render if (!_synced) - CopyBuffers(); // ensure the buffer content is up-to-date + CopyBuffers(); // ensure the buffer content is up to date DrawContext.SetWorldTransform(transform); var primitiveCount = _indexComponentCount / _indexSize; @@ -1365,18 +735,8 @@ public void Render(Transform transform, EffectInstance effectInstance) } } - internal class OutlineWrapper + internal static class OutlineWrapper { - private Outline _outline = null; - public Outline Outline => _outline; - - public void UpdateOutline(Outline other) - { - if (other == null || !other.IsValidObject) - return; - UpdateOrCreateNew(ref _outline, other.MinimumPoint, other.MaximumPoint); - } - public static void UpdateOrCreateNew(ref Outline current, XYZ minPoint, XYZ maxPoint) { if (current == null) @@ -1389,6 +749,7 @@ public static void UpdateOrCreateNew(ref Outline current, XYZ minPoint, XYZ maxP current.AddPoint(maxPoint); } } + public static void UpdateOrCreateNew(ref Outline current, Outline other) { if (other == null || !other.IsValidObject) @@ -1396,4 +757,147 @@ public static void UpdateOrCreateNew(ref Outline current, Outline other) UpdateOrCreateNew(ref current, other.MinimumPoint, other.MaximumPoint); } } + + #region reusable buffers + internal struct SizedBuffer + { + public int Size { get; } + public T Buffer { get; } + public SizedBuffer(int size, T buffer) + { + Size = size; + Buffer = buffer; + } + } + + internal class BufferPool + { + private object _bufferMutex; + private int _poolSize; + private Dictionary>> _buffers; + private Func> _factory; + + public BufferPool(int poolSize, Func> bufferFactory) + { + _factory = bufferFactory; + _buffers = new Dictionary>>(); + _poolSize = poolSize; + } + + public int GetBufferPoolIdx(int targetCapacity, int mult) + { + var bufferLogSize = Math.Log2(targetCapacity / mult); + var bufferTargetSize = (int)bufferLogSize; + if (bufferLogSize % 1 > 0) + bufferTargetSize++; + return bufferTargetSize; + } + + public SizedBuffer RentBuffer(int capacity, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(capacity, mult); + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var rentableBuffers) && rentableBuffers.Count > 0) + { + var idx = rentableBuffers.Count - 1; + var buffer = rentableBuffers[idx]; + rentableBuffers.RemoveAt(idx); + return buffer; + } + else + { + var bufferSize = (int)Math.Pow(2, bufferTargetPool); + var buffer = _factory(bufferSize * mult); + return buffer; + } + } + } + + public bool ReturnBuffer(SizedBuffer buffer, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); + + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) + { + if (buffers.Count < _poolSize) + { + buffers.Add(buffer); + return true; + } + return false; // no space to store the buffer + } + else + { + _buffers[bufferTargetPool] = new List> { buffer }; + return true; + } + } + } + + public bool ReturnBufferOrDispose(SizedBuffer buffer, int mult) + { + var bufferTargetPool = GetBufferPoolIdx(buffer.Size, mult); + + lock (_bufferMutex) + { + if (_buffers.TryGetValue(bufferTargetPool, out var buffers) && buffers.Count > 0) + { + if (buffers.Count < _poolSize) + { + buffers.Add(buffer); + return true; + } + if (buffer.Buffer is IDisposable disposable) + disposable.Dispose(); + + return false; // no space to store the buffer + } + else + { + _buffers[bufferTargetPool] = new List> { buffer }; + return true; + } + } + } + } + + internal class BufferPools + { + public BufferPool VertexPool { get; } + public BufferPool IndexPool { get; } + public BufferPool RawIndexPool { get; } + public BufferPool RawVertexPool { get; } + + public BufferPools() + { + VertexPool = new BufferPool(16, NewVertexBuffer); + IndexPool = new BufferPool(16, NewIndexBuffer); + RawIndexPool = new BufferPool(16, NewRawIndexBuffer); + RawVertexPool = new BufferPool(16, NewRawVertexBuffer); + } + + private SizedBuffer NewVertexBuffer(int capacity) + { + return new SizedBuffer(capacity, new VertexBuffer(capacity)); + } + + private SizedBuffer NewIndexBuffer(int capacity) + { + return new SizedBuffer(capacity, new IndexBuffer(capacity)); + } + + private SizedBuffer NewRawIndexBuffer(int capacity) + { + return new SizedBuffer(capacity, new byte[capacity]); + } + + private SizedBuffer NewRawVertexBuffer(int capacity) + { + return new SizedBuffer(capacity, new float[capacity]); + } + } + #endregion } diff --git a/src/DynamoRevit/ViewModel/RevitDirectContextWatch3DViewModel.cs b/src/DynamoRevit/ViewModel/RevitDirectContextWatch3DViewModel.cs new file mode 100644 index 000000000..ae6ea6e90 --- /dev/null +++ b/src/DynamoRevit/ViewModel/RevitDirectContextWatch3DViewModel.cs @@ -0,0 +1,350 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using System.Windows.Threading; +using Autodesk.DesignScript.Interfaces; +using Dynamo.Applications.Preview; +using Dynamo.Graph.Nodes; +using Dynamo.Models; +using Dynamo.Visualization; +using Dynamo.Wpf.ViewModels.Watch3D; +using RevitServices.Persistence; +using RevitServices.Threading; +using Resources = Dynamo.Applications.Properties.Resources; + +namespace Dynamo.Applications.ViewModel +{ + public class RevitDirectContextWatch3DViewModel : DefaultWatch3DViewModel + { + private class NodeGraphicsDebouncer + { + Dispatcher _dispatcher; + CancellationTokenSource _cts; + public NodeGraphicsDebouncer(Dispatcher dispatcher) + { + _dispatcher = dispatcher; + } + + public void Debounce(int timeoutMillis, Action action) + { + _cts?.Cancel(); + _cts?.Dispose(); + if (timeoutMillis <= 0) + { + _cts = null; + _dispatcher.Invoke(action, DispatcherPriority.Background); + return; + } + _cts = new CancellationTokenSource(); + + Task.Delay(timeoutMillis, _cts.Token).ContinueWith(t => + { + if (t.IsCompletedSuccessfully) + _dispatcher.Invoke(action, DispatcherPriority.Background); + }); + } + } + + private NodeGraphicsDebouncer _debouncer; + private PreviewServer _server; + private BufferPools _pools; + private Dictionary _nodeStates = new Dictionary(); + private Dictionary _inactiveNodeGeometry = new Dictionary(); + + public override string PreferenceWatchName { get { return "IsRevitBackgroundPreviewActive"; } } + + public RevitDirectContextWatch3DViewModel(Watch3DViewModelStartupParams parameters) : base(null, parameters) + { + Name = Resources.BackgroundPreviewName; + _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); + } + + protected override void OnShutdown() + { + DynamoRevitApp.AddIdleAction(StopServer); + } + + private void StopServer() + { + _server?.StopServer(); + _server = null; + DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); + } + + protected override void OnClear() + { + _nodeStates.Clear(); + _inactiveNodeGeometry.Clear(); + IdlePromise.ExecuteOnIdleAsync(StopServer); + } + + public override bool Active + { + get => base.Active; + set + { + if (active == value) + return; + + active = value; + preferences.SetIsBackgroundPreviewActive(PreferenceWatchName, value); + RaisePropertyChanged(nameof(Active)); + + OnActiveStateChanged(); + } + } + + public bool CanDraw => Active && DocumentManager.Instance.CurrentDBDocument != null; + + protected override void OnActiveStateChanged() + { + if (active) + { + Debug.WriteLine("OnActiveStateChanged"); + var nodesToUpdate = new List(); + foreach(var node in dynamoModel.CurrentWorkspace.Nodes) + { + var guid = node.GUID; + if (_inactiveNodeGeometry.TryGetValue(guid, out var cached) && cached != null) + { + var state = GetOrCreateNodeState(guid); + state.UpdateGeometry(); + + nodesToUpdate.Add(new NodeRenderPackage { State = state, RenderPackage = cached }); + } + } + _inactiveNodeGeometry.Clear(); + + if (nodesToUpdate.Count == 0) + return; + + ExecuteOnPreviewServer(server => + { + foreach(var node in nodesToUpdate) + server.WithNodeCache(node.State.Guid, cached => UpdateCached(cached, node)); + ScheduleRefresh(0); + }); + } + else + { + OnClear(); + } + } + + private void ScheduleRefresh(int timeoutMillis) + { + if (_debouncer == null) + _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); + + _debouncer.Debounce(timeoutMillis, () => + { + Debug.WriteLine("Refresh active view in debouncer"); + DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); + }); + } + + private NodeState GetOrCreateNodeState(Guid nodeGuid) + { + NodeState state; + if (_nodeStates.TryGetValue(nodeGuid, out state)) + return state; + state = _nodeStates[nodeGuid] = new NodeState { Guid = nodeGuid }; + return state; + } + + protected override void OnEvaluationCompleted(object sender, EvaluationCompletedEventArgs e) + { + DebugMe(); + } + + protected override void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (sender is not NodeModel node) + return; + + if (e.PropertyName == nameof(node.IsVisible) || e.PropertyName == nameof(node.IsSelected)) + { + var guid = node.GUID; + Debug.WriteLine($"IsVisible or IsSelected changed for node {guid}"); + var state = GetOrCreateNodeState(guid).UpdateState(node); + UpdateNode(guid, null); + } + } + + public override void RemoveGeometryForNode(NodeModel node) + { + DebugMe(); + } + + public override void DeleteGeometryForNode(NodeModel node, bool requestUpdate = true) + { + var guid = node.GUID; + Debug.WriteLine($"Deleting geometry for node {guid}"); + if (_nodeStates.TryGetValue(guid, out var nodeState)) + { + _nodeStates.Remove(guid); + _inactiveNodeGeometry.Remove(guid); + ExecuteOnPreviewServer(server => { + _server.DeletePreview(guid); + ScheduleRefresh(50); + }); + } + } + + #region private methods + private class NodeRenderPackage + { + public NodeState State { get; set; } + public IRenderPackage RenderPackage { get; set; } + } + + private void ExecuteOnPreviewServer(Action action) + { + IdlePromise.ExecuteOnIdleAsync(() => + { + Debug.WriteLine($"Executing action on preview server"); + var sw = Stopwatch.StartNew(); + if (_server == null) + _server = PreviewServer.StartNewServer(_pools); + + action(_server); + }); + } + + private void DebugMe([CallerMemberName] string callerName = null) + { + Debug.WriteLine($"Debug from {callerName}"); + } + + protected override void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) + { + DebugMe(); + } + + protected override void OnWorkspaceOpening(object obj) + { + // TODO dont override + DebugMe(); + } + + public override void DeleteGeometryForIdentifier(string identifier, bool requestUpdate = true) + { + // PortDisconnectedHandler calls this + // this could let us remove stale geometry for a node, which is what the Helix preview does + DebugMe(); + } + + private void UpdateCached(NodePreviewObject cached, NodeRenderPackage node) + { + cached.Selected = node.State.Selected; + cached.Visible = node.State.Visible; + node.State.ClearFlags(); + var pkg = node.RenderPackage; + if (pkg == null) + return; + + cached.Clear(); + if (pkg.MeshVertexCount > 0) + cached.AddMesh(pkg); + if (pkg.LineVertexCount > 0) + cached.AddEdge(pkg); + } + + private void UpdateNode(Guid nodeGuid, IRenderPackage renderPackage) + { + var state = GetOrCreateNodeState(nodeGuid); + var geometry = new NodeRenderPackage { State = state, RenderPackage = renderPackage }; + Debug.WriteLine($"Enqueuing preview server action, waiting for package to finish building"); + ExecuteOnPreviewServer(server => + { + Debug.WriteLine($"Starting preview server update"); + var sw = Stopwatch.StartNew(); + server.WithNodeCache(state.Guid, cached => UpdateCached(cached, geometry)); + + var elapsed = sw.ElapsedMilliseconds; + Debug.WriteLine($"Graphics update for node {state.Guid} took {elapsed}ms"); + ScheduleRefresh(50); + }); + } + + //Stopwatch _watch; + //TimeSpan _last; + protected override void OnRenderPackagesUpdated(NodeModel node, RenderPackageCache packages) + { + var sw = Stopwatch.StartNew(); + DebugMe(); + + var guid = node.GUID; + // this assumes the first package is a HelixRenderPackage, which might be a bad + // assumption, but it enables us to piggyback off of the work that the Helix + // render package has already done, essentially making the Revit preview free + var pkg = packages.Packages.FirstOrDefault(); + if (pkg != null && (pkg.MeshVertexCount > 0 || pkg.LineVertexCount > 0)) + { + // cache the package so that we have something to draw + // once the server is reactivated (if ever) + // NOTE: this might use a large amount of memory + _inactiveNodeGeometry[guid] = pkg; + if (!CanDraw) + { + Debug.WriteLine($"OnRenderPackagesUpdated saved inactive geometry in {sw.Elapsed}"); + return; + } + + UpdateNode(guid, pkg); + Debug.WriteLine($"OnRenderPackagesUpdated enqueued in {sw.Elapsed}"); + } + else + { + // remove old geometry if the renderpackage is empty, which prevents keeping + // stale geometry while reducing the memory usage + _inactiveNodeGeometry.Remove(guid); + Debug.WriteLine($"OnRenderPackagesUpdated did nothing in {sw.Elapsed}"); + } + //base.OnRenderPackagesUpdated(node, packages); + } + #endregion + } + + internal class NodeState + { + public required Guid Guid { get; init; } + public bool Selected { get; private set; } = false; + public bool Visible { get; private set; } = true; + public bool Dirty { get; private set; } = true; + public bool GeometryUpdated { get; private set; } = false; + + public void ClearFlags() + { + Dirty = false; + GeometryUpdated = false; + } + + public bool UpdateState(NodeModel node) + { + if (node.IsSelected != Selected) + { + Dirty = true; + Selected = node.IsSelected; + } + if (node.IsVisible != Visible) + { + Dirty = true; + Visible = node.IsVisible; + } + return Dirty; + } + + public void UpdateGeometry() + { + GeometryUpdated = true; + Dirty = true; + } + } + +} diff --git a/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs b/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs index 28d1b203d..3e7303964 100644 --- a/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs +++ b/src/DynamoRevit/ViewModel/RevitWatch3DViewModel.cs @@ -5,20 +5,14 @@ using System.Diagnostics; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using System.Windows.Threading; using System.Xml; using Autodesk.DesignScript.Geometry; using Autodesk.DesignScript.Interfaces; using Autodesk.Revit.DB; -using Dynamo.Applications.Preview; using Dynamo.Graph.Connectors; using Dynamo.Graph.Nodes; using Dynamo.Graph.Workspaces; using Dynamo.Models; -using Dynamo.Visualization; using Dynamo.Wpf.ViewModels.Watch3D; using Dynamo.Wpf.Views.Debug; using Revit.GeometryConversion; @@ -434,467 +428,4 @@ internal static MethodInfo GetTransientDisplayMethod() } #endregion } - - internal class NodeState - { - public required Guid Guid { get; set; } - public bool Selected { get; set; } = false; - public bool Visible { get; set; } - public bool Dirty { get; set; } = true; - public bool GeometryUpdated { get; set; } = false; - - public void ClearFlags() - { - Dirty = false; - GeometryUpdated = false; - } - - public void UpdateState(NodeModel node) - { - if (node.IsSelected != Selected) - { - Dirty = true; - Selected = node.IsSelected; - } - if (node.IsVisible != Visible) - { - Dirty = true; - Visible = node.IsVisible; - } - } - - public void UpdateGeometry() - { - GeometryUpdated = true; - Dirty = true; - } - } - - public class RevitDirectContextWatch3DViewModel : DefaultWatch3DViewModel - { - private class NodeGraphicsDebouncer - { - Dispatcher _dispatcher; - CancellationTokenSource _cts; - public NodeGraphicsDebouncer(Dispatcher dispatcher) - { - _dispatcher = dispatcher; - } - - public void Debounce(int timeoutMillis, Action action) - { - _cts?.Cancel(); - if (timeoutMillis <= 0) - { - _cts = null; - _dispatcher.Invoke(action, DispatcherPriority.Background); - return; - } - _cts = new CancellationTokenSource(); - - Task.Delay(timeoutMillis, _cts.Token).ContinueWith(t => - { - if (t.IsCompletedSuccessfully) - _dispatcher.Invoke(action, DispatcherPriority.Background); - }); - } - } - - private NodeGraphicsDebouncer _debouncer; - private PreviewServer _server; - private BufferPools _pools; - private Dictionary _nodeStates = new Dictionary(); - - public override string PreferenceWatchName { get { return "IsRevitBackgroundPreviewActive"; } } - - public RevitDirectContextWatch3DViewModel(Watch3DViewModelStartupParams parameters) : base(null, parameters) - { - Name = Resources.BackgroundPreviewName; - _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); - //Draw(redrawAll: true); - } - - protected override void OnShutdown() - { - DynamoRevitApp.AddIdleAction(StopServer); - } - - private void StopServer() - { - _server?.StopServer(); - _server = null; - DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); - } - - protected override void OnClear() - { - IdlePromise.ExecuteOnIdleAsync(StopServer); - } - - public override bool Active - { - get => base.Active; - set - { - if (active == value) - return; - - active = value; - preferences.SetIsBackgroundPreviewActive(PreferenceWatchName, value); - RaisePropertyChanged(nameof(Active)); - - OnActiveStateChanged(); - } - } - - protected override void OnActiveStateChanged() - { - if (active) - { - //this.del - Debug.WriteLine("OnActiveStateChanged"); - var nodes = AllNodes().ToList(); - nodes.ForEach(n => GetNodeState(n.GUID).UpdateGeometry()); - ScheduleRedraw(0); - } - else - { - OnClear(); - } - } - - private void ScheduleRedraw(int timeoutMillis) - { - if (_debouncer == null) - _debouncer = new NodeGraphicsDebouncer(Dispatcher.CurrentDispatcher); - - _debouncer.Debounce(timeoutMillis, () => - { - Debug.WriteLine("Successful debounce!"); - var nodes = AllNodes().Where(n => _nodeStates.TryGetValue(n.GUID, out var state) && state.Dirty).ToList(); - Draw(nodes, null, true); - }); - } - - private NodeState GetNodeState(Guid nodeGuid) - { - NodeState state; - if (_nodeStates.TryGetValue(nodeGuid, out state)) - return state; - state = _nodeStates[nodeGuid] = new NodeState { Guid = nodeGuid }; - return state; - } - - private IEnumerable AllNodes() - { - return dynamoModel.CurrentWorkspace.Nodes; - } - - private Dictionary GetRelevantNodes(IEnumerable raw, IEnumerable guids = null) - { - if (guids == null) - return raw.ToDictionary(n => n.GUID); - - var nodes = guids.ToDictionary(g => g, g => null); - foreach(var node in raw) - { - if (nodes.ContainsKey(node.GUID)) - nodes[node.GUID] = node; - } - return nodes; - } - - protected override void OnEvaluationCompleted(object sender, EvaluationCompletedEventArgs e) - { - Debug.WriteLine($"Evaluation completed, redrawing changed nodes"); - var nodes = AllNodes().Where(n => n.WasInvolvedInExecution).ToList(); - nodes.ForEach(n => GetNodeState(n.GUID).UpdateGeometry()); - ScheduleRedraw(0); - //Draw(nodes, null, true); - } - - protected override void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (sender is not NodeModel node) - return; - - if (e.PropertyName == nameof(node.IsVisible) || e.PropertyName == nameof(node.IsSelected)) - { - Debug.WriteLine($"IsVisible or IsSelected changed for node {node.GUID}"); - GetNodeState(node.GUID).UpdateState(node); - ScheduleRedraw(50); - - //Draw([node], null, false); - } - } - - public override void RemoveGeometryForNode(NodeModel node) - { - Debug.WriteLine($"Redrawing geometry for node {node.GUID}"); - GetNodeState(node.GUID).UpdateGeometry(); - ScheduleRedraw(50); - //var nodeGraphics = UpdateNodeState(node, true); - //Draw([nodeGraphics], []); - //Draw([node], null, true); - } - - public override void DeleteGeometryForNode(NodeModel node, bool requestUpdate = true) - { - var guid = node.GUID; - Debug.WriteLine($"Deleting geometry for node {guid}"); - if (_nodeStates.TryGetValue(guid, out var nodeState)) - { - _nodeStates.Remove(guid); - var downStreamNodes = new HashSet(); - node.GetDownstreamNodes(node, downStreamNodes); - downStreamNodes.Remove(node); - Draw(downStreamNodes, [guid], true); - } - } - - #region private methods - private class NodeGraphicItems - { - public NodeState State { get; set; } - public List GraphicItems { get; set; } - } - - private NodeGraphicItems UpdateNodeState(NodeModel node, bool rebuildGeometry) - { - var guid = node.GUID; - - NodeState state; - if (!_nodeStates.TryGetValue(guid, out state)) - state = _nodeStates[guid] = new NodeState { Guid = guid }; - - state.Dirty = true; - state.UpdateState(node); - List items = null; - if (rebuildGeometry) - { - state.GeometryUpdated = true; - items = node.GeneratedGraphicItems(engineManager.EngineController); - } - - return new NodeGraphicItems { State = state, GraphicItems = items }; - } - - private void Draw(IEnumerable nodes, IEnumerable deleted, bool rebuildGeometry) - { - if (!Active || DocumentManager.Instance.CurrentDBDocument == null) return; - - //var dirty = new HashSet(dirtyNodeGuids ?? []); - var graphicItems = new List(); - //var deletedNodeGuids = _nodeStates.Keys.ToHashSet(); - foreach(var node in nodes) - { - var guid = node.GUID; - //deletedNodeGuids.Remove(guid); - - NodeState state; - if (!_nodeStates.TryGetValue(guid, out state)) - state = _nodeStates[guid] = new NodeState { Guid = guid }; - - state.Dirty = true; - state.UpdateState(node); - List items = null; - if (state.GeometryUpdated) - { - state.GeometryUpdated = true; - items = node.GeneratedGraphicItems(engineManager.EngineController); - } - - //if (state.Dirty) - graphicItems.Add(new NodeGraphicItems { State = state, GraphicItems = items }); - } - - if (graphicItems.Count == 0 && deleted == null) - return; - - Draw(graphicItems, deleted ?? Enumerable.Empty()); - } - - IRenderPackage _pkg; - private void UpdateCached(NodePreviewObject cached, NodeGraphicItems node, IRenderPackage _pkg) - { - cached.Selected = node.State.Selected; - cached.Visible = node.State.Visible; - node.State.ClearFlags(); - if (node.GraphicItems == null || node.GraphicItems.Count == 0) - return; - - cached.Clear(); - foreach(var item in node.GraphicItems) - { - _pkg.Clear(); - switch (item) { - //case Point point: - // var scale = UnitConverter.DynamoToHostFactor(SpecTypeId.Length); - // var transform = Transform.CreateTranslation(point.ToXyz() * scale); - // //GetLazyPreviewBox().AddTransform(transform); - // // GetPreviewBox().AddTransform(transform); - // break; - //case PolyCurve polyCurve: - // polyCurve.Tessellate(_pkg, renderPackageFactory.TessellationParameters); - // break; - //case Curve curve: - // curve.Tessellate(_pkg, renderPackageFactory.TessellationParameters); - // break; - case Surface surface: - //pkg = renderPackageFactory.CreateRenderPackage(); - surface.Tessellate(_pkg, renderPackageFactory.TessellationParameters); - cached.AddMesh(_pkg); - break; - case Solid solid: - //pkg = renderPackageFactory.CreateRenderPackage(); - solid.Tessellate(_pkg, renderPackageFactory.TessellationParameters); - cached.AddMesh(_pkg); - break; - default: - break; - } - } - } - - private void Draw(IEnumerable nodes, IEnumerable deletedNodes) - { - IdlePromise.ExecuteOnIdleAsync( - () => - { - if (_server == null) - _server = PreviewServer.StartNewServer(_pools); - - if (_pkg == null) - _pkg = renderPackageFactory.CreateRenderPackage(); - - foreach(var nodeGuid in deletedNodes) - _server.DeletePreview(nodeGuid); - - var sw = Stopwatch.StartNew(); - var nodeElapsed = sw.ElapsedMilliseconds; - foreach(var node in nodes) - { - var wasDirty = node.State.Dirty; - _server.WithNodeCache(node.State.Guid, cached => UpdateCached(cached, node, _pkg)); - var current = sw.ElapsedMilliseconds; - Debug.WriteLine($"Graphics update for node {node.State.Guid} took {current - nodeElapsed}ms, dirty? {wasDirty}"); - nodeElapsed = current; - } - - var elapsed = sw.ElapsedMilliseconds; - Debug.WriteLine($"Spent {elapsed}ms building the preview geometry"); - DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); - elapsed = sw.ElapsedMilliseconds - elapsed; - Debug.WriteLine($"Spent {elapsed}ms refreshing the active view"); - } - ); - } - - private void DebugMe([CallerMemberName] string callerName = null) - { - Debug.WriteLine($"Debugging from inside from {callerName}"); - } - - protected override void OnIsolationModeRequestUpdate() - { - // TODO dont override - DebugMe(); - base.OnIsolationModeRequestUpdate(); - } - - protected override void OnModelPropertyChanged(object sender, PropertyChangedEventArgs e) - { - DebugMe(); - base.OnModelPropertyChanged(sender, e); - } - - protected override void OnWorkspaceCleared(WorkspaceModel workspace) - { - // TODO dont override - DebugMe(); - base.OnWorkspaceCleared(workspace); - } - - protected override void OnWorkspaceOpening(object obj) - { - // TODO dont override - DebugMe(); - base.OnWorkspaceOpening(obj); - } - - protected override void OnWorkspaceSaving(XmlDocument doc) - { - DebugMe(); - base.OnWorkspaceSaving(doc); - } - - protected override void PortConnectedHandler(PortModel arg1, ConnectorModel arg2) - { - DebugMe(); - base.PortConnectedHandler(arg1, arg2); - } - - protected override void PortDisconnectedHandler(PortModel port) - { - DebugMe(); - base.PortDisconnectedHandler(port); - } - - protected override void SelectionChangedHandler(object sender, NotifyCollectionChangedEventArgs e) - { - // TODO dont override - handled by node selection state - DebugMe(); - base.SelectionChangedHandler(sender, e); - } - - Stopwatch _watch; - TimeSpan _last; - protected override void OnRenderPackagesUpdated(NodeModel node, RenderPackageCache packages) - { - // TODO dont override - handled by RemoveGeometryForNode itself - DebugMe(); - _watch ??= Stopwatch.StartNew(); - var span = _watch.Elapsed; - if (_last == TimeSpan.Zero) - Debug.WriteLine($"Hello {span}"); - else - Debug.WriteLine($"Hello {span - _last}"); - _last = span; - return; - - var pkg = packages.Packages.FirstOrDefault(); - if (pkg != null) - { - if (pkg.MeshVertexCount > 0 || pkg.LineVertexCount > 0) - { - return; - IdlePromise.ExecuteOnIdleAsync( - () => - { - if (_server == null) - _server = PreviewServer.StartNewServer(_pools); - - var sw = Stopwatch.StartNew(); - _server.WithNodeCache(node.GUID, cached => - { - cached.Clear(); - cached.Visible = true; - if (pkg.MeshVertexCount > 0) - cached.AddMesh(pkg); - if (pkg.LineVertexCount > 0) - cached.AddEdge(pkg); - }); - var elapsed = sw.ElapsedMilliseconds; - Debug.WriteLine($"Graphics update for node {node.GUID} took {elapsed}ms"); - DocumentManager.Instance.CurrentUIDocument.RefreshActiveView(); - elapsed = sw.ElapsedMilliseconds - elapsed; - Debug.WriteLine($"Active view refresh took {elapsed}ms"); - } - ); - } - } - base.OnRenderPackagesUpdated(node, packages); - } - #endregion - } }