Compare commits

...

7 Commits

Author SHA1 Message Date
Björn Steinhagen df6e2070b0 feat(grasshopper): adds isDesignOption flag (#1365) 2026-04-13 16:11:00 +02:00
Mucahit Bilal GOKER b49884e211 fix(revit): Lighting source material is included in the material quantities (#1361)
* exclude default light source material

* move the check inside TryAddMaterialPropertiesToQuantitiesDict as an early return

* go simple
2026-04-10 15:59:26 +03:00
Björn Steinhagen 1b9640954e fix(revit): apply accumulated transform to curves in linked DirectShapes (#1359)
* fix(revit): apply accumulated transform to curves in linked DirectShape elements

* refactor(revit): pre combine transforms for polylines
2026-04-09 19:19:32 +00:00
Jedd Morgan 3d24a7b16b feat(connectors): disable cache config (#1349) (#1358)
* feat(all): adds disable cache functionality

* feat(connectors): prevents StoreSendResult if cache disabled

* fix(connectors): restores IsBypassed state

* Explicit flag instead implicit

* Delete unused flag

* Add packfile support

---------

Co-authored-by: Oğuzhan Koral <45078678+oguzhankoral@users.noreply.github.com>
Co-authored-by: Björn Steinhagen <88777268+bjoernsteinhagen@users.noreply.github.com>
Co-authored-by: oguzhankoral <oguzhankoral@gmail.com>
2026-04-08 11:42:24 +01:00
Jedd Morgan 9f3a333beb Merge remote-tracking branch 'origin/dev' into jrm/main-dev-10 2026-04-08 11:27:57 +01:00
Jedd Morgan 6c2a98c1d4 Update dev from main (#1357) 2026-04-08 10:17:43 +01:00
Jedd Morgan 4cbf5628b1 Merge remote-tracking branch 'origin' into jrm/main-dev-9 2026-04-08 10:11:09 +01:00
3 changed files with 143 additions and 35 deletions
@@ -1,4 +1,5 @@
using System.Runtime.InteropServices;
using GH_IO.Serialization;
using Grasshopper.Kernel;
using Speckle.Connectors.GrasshopperShared.HostApp;
using Speckle.Connectors.GrasshopperShared.Parameters;
@@ -16,6 +17,24 @@ public class SpeckleDataObjectPassthrough()
ComponentCategories.OBJECTS
)
{
private const string DESIGN_OPTION_KEY = "isDesignOption";
private bool _isDesignOption;
private bool IsDesignOption
{
get => _isDesignOption;
set
{
if (_isDesignOption == value)
{
return;
}
_isDesignOption = value;
UpdateMessage();
ExpireSolution(true);
}
}
public override Guid ComponentGuid => GetType().GUID;
protected override Bitmap Icon => Resources.speckle_objects_dataobject;
public override GH_Exposure Exposure => GH_Exposure.secondary;
@@ -186,6 +205,11 @@ public class SpeckleDataObjectPassthrough()
result.ApplicationId ??= Guid.NewGuid().ToString();
}
if (_isDesignOption)
{
result.DataObject[DESIGN_OPTION_KEY] = true;
}
// get the path
string? path =
result.Path.Count > 1 ? string.Join(Constants.LAYER_PATH_DELIMITER, result.Path) : result.Path.FirstOrDefault();
@@ -198,4 +222,32 @@ public class SpeckleDataObjectPassthrough()
da.SetData(4, path);
SetApplicationIdOutput(da, result.ApplicationId);
}
public override void AppendAdditionalMenuItems(ToolStripDropDown menu)
{
base.AppendAdditionalMenuItems(menu);
Menu_AppendSeparator(menu);
Menu_AppendItem(menu, "Mark as Design Option", (_, _) => IsDesignOption = !IsDesignOption, true, IsDesignOption);
}
public override bool Write(GH_IWriter writer)
{
var result = base.Write(writer);
writer.SetBoolean("IsDesignOption", _isDesignOption);
return result;
}
public override bool Read(GH_IReader reader)
{
var result = base.Read(reader);
bool isDesignOption = false;
if (reader.TryGetBoolean("IsDesignOption", ref isDesignOption))
{
_isDesignOption = isDesignOption;
UpdateMessage();
}
return result;
}
private void UpdateMessage() => Message = _isDesignOption ? "Design Option" : string.Empty;
}
@@ -318,32 +318,16 @@ public sealed class DisplayValueExtractor
);
}
foreach (var curve in collections.Curves)
foreach (var (curve, accumulatedTransform) in collections.Curves)
{
if (curveTransform is not null)
{
using var transformedCurve = curve.CreateTransformed(curveTransform);
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(transformedCurve)));
}
else
{
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(curve)));
}
using var resolvedCurve = ResolveCurveTransforms(curve, accumulatedTransform, curveTransform);
displayValue.Add(DisplayValueResult.WithoutTransform(GetCurveDisplayValue(resolvedCurve)));
}
foreach (var polyline in collections.Polylines)
foreach (var (polyline, accumulatedTransform) in collections.Polylines)
{
if (curveTransform is not null)
{
var coords = polyline.GetCoordinates();
var transformedCoords = coords.Select(coord => curveTransform.OfPoint(coord)).ToList();
using var transformedPolyline = DB.PolyLine.Create(transformedCoords);
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(transformedPolyline)));
}
else
{
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(polyline)));
}
using var resolvedPolyline = ResolvePolylineTransforms(polyline, accumulatedTransform, curveTransform);
displayValue.Add(DisplayValueResult.WithoutTransform(_polylineConverter.Convert(resolvedPolyline)));
}
foreach (var point in collections.Points)
@@ -498,13 +482,15 @@ public sealed class DisplayValueExtractor
break;
case DB.Curve curve:
// curves are stored as-is; transforms are applied later in ProcessGeometryCollections
collections.Curves.Add(curve);
// store the curve together with whatever accumulatedTransform is active at this
// recursion depth. See GeometryCollections remarks for why we do this rather than
// applying the transform here the way we do for meshes and solids.
collections.Curves.Add((curve, accumulatedTransform));
break;
case DB.PolyLine polyline:
// polylines also handled later during display value processing
collections.Polylines.Add(polyline);
// same reasoning as curves above
collections.Polylines.Add((polyline, accumulatedTransform));
break;
case DB.Point point:
@@ -758,22 +744,87 @@ public sealed class DisplayValueExtractor
}
/// <summary>
/// Represents sorted collections of different geometry types extracted from an element.
/// Used to pass multiple geometry collections as a single parameter to improve code readability
/// and reduce the risk of parameter ordering errors.
/// Applies up to two transforms to a curve in order:
/// 1. accumulatedTransform — the GeometryInstance transform from SortGeometry.
/// Only set for DirectShape elements (linked IFC/DWG) where the real position
/// lives inside a nested GeometryInstance rather than on the element itself.
/// 2. curveTransform — the instance transform (localToDocument).
/// Only set for FamilyInstance elements.
/// </summary>
private DB.Curve ResolveCurveTransforms(
DB.Curve curve,
DB.Transform? accumulatedTransform,
DB.Transform? curveTransform
)
{
var result = accumulatedTransform is not null ? curve.CreateTransformed(accumulatedTransform) : curve;
if (curveTransform is not null)
{
var next = result.CreateTransformed(curveTransform);
if (accumulatedTransform is not null)
{
result.Dispose();
}
return next;
}
return result;
}
/// <summary>
/// Same two-step transform logic as <see cref="ResolveCurveTransforms"/>.
/// Operates on raw XYZ coordinates since PolyLine has no CreateTransformed API.
/// </summary>
private DB.PolyLine ResolvePolylineTransforms(
DB.PolyLine polyline,
DB.Transform? accumulatedTransform,
DB.Transform? curveTransform
)
{
DB.Transform? combined = (accumulatedTransform, curveTransform) switch
{
(not null, not null) => curveTransform.Multiply(accumulatedTransform),
_ => accumulatedTransform ?? curveTransform
};
var coords = polyline.GetCoordinates();
if (combined is null || combined.IsIdentity)
{
return DB.PolyLine.Create(coords);
}
var transformed = new List<DB.XYZ>(coords.Count);
foreach (var pt in coords)
{
transformed.Add(combined.OfPoint(pt));
}
return DB.PolyLine.Create(transformed);
}
/// <remarks>
/// <see cref="Solids"/> and <see cref="Meshes"/> are transformed to symbol space in SortGeometry.
/// <see cref="Curves"/>, <see cref="Polylines"/>, and <see cref="Points"/> remain in their original coordinate space
/// and receive only the instance transform (if any) in ProcessGeometryCollections - reference point
/// transform is handled by the point converters during conversion.
/// Solids and meshes are transformed to world space inline in SortGeometry.
/// Curves and polylines can't follow the same pattern because their transform
/// path splits by element type — see ResolveCurveTransforms for details.
/// The AccumulatedTransform in each tuple carries the GeometryInstance transform
/// that SortGeometry would otherwise drop.
/// </remarks>
private sealed record GeometryCollections
{
public List<DB.Solid> Solids { get; } = new();
public List<DB.Mesh> Meshes { get; } = new();
public List<DB.Curve> Curves { get; } = new();
public List<DB.PolyLine> Polylines { get; } = new();
// The transform stored alongside each curve/polyline is the accumulatedTransform that was
// active when SortGeometry encountered it. For FamilyInstance this will be identity (the
// instance and its inverse cancel out), so applying it is a no-op. For DirectShape elements
// from linked IFC/DWG files it carries the real GeometryInstance transform that would
// otherwise be silently dropped, placing curves at the origin instead of their correct position.
public List<(DB.Curve Curve, DB.Transform? AccumulatedTransform)> Curves { get; } = new();
public List<(DB.PolyLine Polyline, DB.Transform? AccumulatedTransform)> Polylines { get; } = new();
public List<DB.Point> Points { get; } = new();
public int TotalCount => Solids.Count + Meshes.Count + Curves.Count + Polylines.Count + Points.Count;
@@ -203,6 +203,11 @@ public class MaterialQuantitiesToSpeckleLite : ITypedConverter<DB.Element, Dicti
matName = "";
if (_converterSettings.Current.Document.GetElement(matId) is DB.Material material)
{
// No API to identify light-cone materials by ID; exclude by well-known default name.
if (material.Name == "Default Light Source")
{
return false;
}
materialQuantity["materialName"] = material.Name;
materialQuantity["materialCategory"] = material.MaterialCategory;
materialQuantity["materialClass"] = material.MaterialClass;