Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| c04328bfa4 | |||
| 63e96f93a1 | |||
| e8d7a936c2 | |||
| 51ad4a8ff9 | |||
| 4a2c22c287 |
@@ -1,7 +1,16 @@
|
||||
using Objects;
|
||||
using Speckle.Automate.Sdk;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.Core.Models;
|
||||
using Speckle.Core.Models.Extensions;
|
||||
|
||||
public enum SpeckleType
|
||||
{
|
||||
Wall,
|
||||
Window,
|
||||
Roof
|
||||
}
|
||||
|
||||
public static class AutomateFunction
|
||||
{
|
||||
public static async Task Run(
|
||||
@@ -15,19 +24,188 @@ public static class AutomateFunction
|
||||
Console.WriteLine("Receiving version");
|
||||
var commitObject = await automationContext.ReceiveVersion();
|
||||
|
||||
Console.WriteLine("Received version: " + commitObject);
|
||||
var flatten = commitObject.Flatten().ToList();
|
||||
var failedObjectIds = new List<string>();
|
||||
var failedWallIds = CheckWalls(automationContext, functionInputs, flatten);
|
||||
failedObjectIds.AddRange(failedWallIds);
|
||||
var failedWindowIds = CheckWindows(automationContext, functionInputs, flatten);
|
||||
failedObjectIds.AddRange(failedWindowIds);
|
||||
var failedRoofIds = CheckRoofs(automationContext, functionInputs, flatten);
|
||||
failedObjectIds.AddRange(failedRoofIds);
|
||||
|
||||
if (failedObjectIds.Count > 0)
|
||||
{
|
||||
var message = "";
|
||||
if (functionInputs.CheckWalls)
|
||||
{
|
||||
var wallCount = GetByType(flatten, SpeckleType.Wall).Count();
|
||||
message += "WALLS:\n";
|
||||
if (wallCount > 0)
|
||||
{
|
||||
message += $"{failedWallIds.Count}/{wallCount} wall(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no walls\n\n";
|
||||
}
|
||||
}
|
||||
if (functionInputs.CheckWindows)
|
||||
{
|
||||
var windowCount = GetByType(flatten, SpeckleType.Window).Count();
|
||||
message += "WINDOWS:\n";
|
||||
if (windowCount > 0)
|
||||
{
|
||||
message += $"{failedWindowIds.Count}/{windowCount} window(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no windows\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (functionInputs.CheckWindows)
|
||||
{
|
||||
var roofCount = GetByType(flatten, SpeckleType.Roof).Count();
|
||||
message += "ROOFS:\n";
|
||||
if (roofCount > 0)
|
||||
{
|
||||
message += $"{failedRoofIds.Count}/{roofCount} roof(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no roofs\n\n";
|
||||
}
|
||||
}
|
||||
automationContext.MarkRunFailed(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
automationContext.MarkRunSuccess($"Your building is compliant with selected climate zone!");
|
||||
}
|
||||
}
|
||||
|
||||
var count = commitObject
|
||||
.Flatten()
|
||||
.Count(b => b.speckle_type == functionInputs.SpeckleTypeToCount);
|
||||
private static List<string> CheckWalls(AutomationContext automationContext, FunctionInputs functionInputs, IEnumerable<Base> flatten)
|
||||
{
|
||||
if (!functionInputs.CheckWalls)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var walls = GetByType(flatten, SpeckleType.Wall);
|
||||
var uValues = walls.Select(GetThermalResistance);
|
||||
// Attempt to parse ClimateZone as a ClimateZones enum
|
||||
if (Enum.TryParse(functionInputs.ClimateZone, out ClimateZones climateZoneEnum))
|
||||
{
|
||||
var expectedValue = UValues.Wall[climateZoneEnum];
|
||||
var failedObjectIds = uValues.Where(val => val.value < expectedValue).Select(v => (v.id, v.value)).ToList();
|
||||
|
||||
Console.WriteLine($"Counted {count} objects");
|
||||
|
||||
if (count < functionInputs.SpeckleTypeTargetCount) {
|
||||
automationContext.MarkRunFailed($"Counted {count} objects where {functionInputs.SpeckleTypeTargetCount} were expected");
|
||||
return;
|
||||
foreach (var (id, value) in failedObjectIds)
|
||||
{
|
||||
automationContext.AttachResultToObjects(
|
||||
ObjectResultLevel.Error,
|
||||
"Walls",
|
||||
new []{id},
|
||||
$"Wall expected to have maximum {expectedValue} U-value but it is {value}."
|
||||
);
|
||||
}
|
||||
return failedObjectIds.Select(i => i.id).ToList();
|
||||
}
|
||||
|
||||
automationContext.MarkRunSuccess($"Counted {count} objects");
|
||||
// Handle the case where the ClimateZone string is not a valid ClimateZones value
|
||||
throw new ArgumentException($"Invalid ClimateZone: {functionInputs.ClimateZone}");
|
||||
}
|
||||
|
||||
private static List<string> CheckWindows(AutomationContext automationContext, FunctionInputs functionInputs, IEnumerable<Base> flatten)
|
||||
{
|
||||
if (!functionInputs.CheckWindows)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var walls = GetByType(flatten, SpeckleType.Window);
|
||||
var uValues = walls.Select(GetThermalResistance);
|
||||
if (Enum.TryParse(functionInputs.ClimateZone, out ClimateZones climateZoneEnum))
|
||||
{
|
||||
var expectedValue = UValues.Window[climateZoneEnum];
|
||||
var failedObjectIds = uValues.Where(val => val.value < expectedValue).Select(v => (v.id, v.value)).ToList();
|
||||
|
||||
foreach (var (id, value) in failedObjectIds)
|
||||
{
|
||||
automationContext.AttachResultToObjects(
|
||||
ObjectResultLevel.Error,
|
||||
"Windows",
|
||||
new []{id},
|
||||
$"Window expected to have maximum {expectedValue} U-value but it is {value}."
|
||||
);
|
||||
}
|
||||
return failedObjectIds.Select(i => i.id).ToList();
|
||||
}
|
||||
|
||||
// Handle the case where the ClimateZone string is not a valid ClimateZones value
|
||||
throw new ArgumentException($"Invalid ClimateZone: {functionInputs.ClimateZone}");
|
||||
}
|
||||
|
||||
private static List<string> CheckRoofs(AutomationContext automationContext, FunctionInputs functionInputs, IEnumerable<Base> flatten)
|
||||
{
|
||||
if (!functionInputs.CheckRoofs)
|
||||
{
|
||||
return new List<string>();
|
||||
}
|
||||
|
||||
var walls = GetByType(flatten, SpeckleType.Roof);
|
||||
var uValues = walls.Select(GetThermalResistance);
|
||||
if (Enum.TryParse(functionInputs.ClimateZone, out ClimateZones climateZoneEnum))
|
||||
{
|
||||
var expectedValue = UValues.Roof[climateZoneEnum];
|
||||
var failedObjectIds = uValues.Where(val => val.value < expectedValue).Select(v => (v.id, v.value)).ToList();
|
||||
|
||||
foreach (var (id, value) in failedObjectIds)
|
||||
{
|
||||
automationContext.AttachResultToObjects(
|
||||
ObjectResultLevel.Error,
|
||||
"Roofs",
|
||||
new []{id},
|
||||
$"Roof expected to have maximum {expectedValue} U-value but it is {value}."
|
||||
);
|
||||
}
|
||||
return failedObjectIds.Select(i => i.id).ToList();
|
||||
}
|
||||
|
||||
// Handle the case where the ClimateZone string is not a valid ClimateZones value
|
||||
throw new ArgumentException($"Invalid ClimateZone: {functionInputs.ClimateZone}");
|
||||
}
|
||||
|
||||
private static IEnumerable<Base> GetByType(IEnumerable<Base> objects, SpeckleType speckleType)
|
||||
{
|
||||
return objects.Where(b => b.speckle_type == SpeckleTypes[speckleType] &&
|
||||
(string)b["category"]! == SpeckleCategories[speckleType]);
|
||||
}
|
||||
|
||||
private static (string id, double value) GetThermalResistance(Base obj)
|
||||
{
|
||||
var properties = obj["properties"] as Dictionary<string, object>;
|
||||
if (properties is null)
|
||||
{
|
||||
return (obj.id, 0);
|
||||
}
|
||||
var typeParameters = properties!["Type Parameters"] as Dictionary<string, object>;
|
||||
var analyticalProperties = typeParameters!["Analytical Properties"] as Dictionary<string, object>;
|
||||
var u = analyticalProperties!["Heat Transfer Coefficient (U)"] as Dictionary<string, object>;
|
||||
var value = u!["value"] is double ? (double)u!["value"] : 0;
|
||||
return (obj.id, value);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<SpeckleType, string> SpeckleTypes = new Dictionary<SpeckleType, string>()
|
||||
{
|
||||
{ SpeckleType.Wall, "Objects.BuiltElements.Wall:Objects.BuiltElements.Revit.RevitWall"},
|
||||
{ SpeckleType.Window, "Objects.BuiltElements.Revit.RevitElement"},
|
||||
{ SpeckleType.Roof, "Objects.BuiltElements.Roof:Objects.BuiltElements.Revit.RevitRoof.RevitRoof:Objects.BuiltElements.Revit.RevitRoof.RevitExtrusionRoof"},
|
||||
};
|
||||
|
||||
private static readonly Dictionary<SpeckleType, string> SpeckleCategories = new Dictionary<SpeckleType, string>()
|
||||
{
|
||||
{ SpeckleType.Wall, "Walls"},
|
||||
{ SpeckleType.Window, "Windows"},
|
||||
{ SpeckleType.Roof, "Roofs"},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,7 +1,158 @@
|
||||
using Speckle.Automate.Sdk.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
public enum ClimateZones
|
||||
{
|
||||
// Tropical Climates
|
||||
Af_TropicalRainforest,
|
||||
Am_TropicalMonsoon,
|
||||
Aw_TropicalSavanna,
|
||||
As_TropicalSavanna,
|
||||
|
||||
// Dry Climates
|
||||
BWh_HotDesert,
|
||||
BWk_ColdDesert,
|
||||
BSh_HotSemiArid,
|
||||
BSk_ColdSemiArid,
|
||||
|
||||
// Temperate Climates
|
||||
Cfa_HumidSubtropical,
|
||||
Cfb_Oceanic,
|
||||
Cfc_SubpolarOceanic,
|
||||
Csa_MediterraneanHotSummer,
|
||||
Csb_MediterraneanWarmSummer,
|
||||
Csc_MediterraneanCoolSummer,
|
||||
|
||||
// Continental Climates
|
||||
Dfa_HumidContinentalHotSummer,
|
||||
Dfb_HumidContinentalMildSummer,
|
||||
Dfc_Subarctic,
|
||||
Dfd_SubarcticExtremeWinter,
|
||||
Dsa_MediterraneanInfluenceSnowyWinter,
|
||||
Dsb_MediterraneanInfluenceSnowyWinter,
|
||||
Dsc_MediterraneanInfluenceSnowyWinter,
|
||||
Dsd_MediterraneanInfluenceSnowyWinter,
|
||||
|
||||
// Polar Climates
|
||||
ET_Tundra,
|
||||
EF_IceCap
|
||||
}
|
||||
|
||||
public static class UValues
|
||||
{
|
||||
public static Dictionary<ClimateZones, double> Wall => new ()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZones.Af_TropicalRainforest, 0.9 },
|
||||
{ ClimateZones.Am_TropicalMonsoon, 1.0 },
|
||||
{ ClimateZones.Aw_TropicalSavanna, 1.1 },
|
||||
{ ClimateZones.As_TropicalSavanna, 1.1 },
|
||||
|
||||
// Dry Climates
|
||||
{ ClimateZones.BWh_HotDesert, 1.0 },
|
||||
{ ClimateZones.BWk_ColdDesert, 1.2 },
|
||||
{ ClimateZones.BSh_HotSemiArid, 1.2 },
|
||||
{ ClimateZones.BSk_ColdSemiArid, 1.5 },
|
||||
|
||||
// Temperate Climates
|
||||
{ ClimateZones.Cfa_HumidSubtropical, 1.4 },
|
||||
{ ClimateZones.Cfb_Oceanic, 1.3 },
|
||||
{ ClimateZones.Cfc_SubpolarOceanic, 1.2 },
|
||||
{ ClimateZones.Csa_MediterraneanHotSummer, 1.51 },
|
||||
{ ClimateZones.Csb_MediterraneanWarmSummer, 1.4 },
|
||||
{ ClimateZones.Csc_MediterraneanCoolSummer, 1.3 },
|
||||
|
||||
// Continental Climates
|
||||
{ ClimateZones.Dfa_HumidContinentalHotSummer, 1.3 },
|
||||
{ ClimateZones.Dfb_HumidContinentalMildSummer, 1.2 },
|
||||
{ ClimateZones.Dfc_Subarctic, 0.7 },
|
||||
{ ClimateZones.Dfd_SubarcticExtremeWinter, 0.6 },
|
||||
{ ClimateZones.Dsa_MediterraneanInfluenceSnowyWinter, 1.2 },
|
||||
{ ClimateZones.Dsb_MediterraneanInfluenceSnowyWinter, 1.1 },
|
||||
{ ClimateZones.Dsc_MediterraneanInfluenceSnowyWinter, 0.9 },
|
||||
{ ClimateZones.Dsd_MediterraneanInfluenceSnowyWinter, 0.8 },
|
||||
|
||||
// Polar Climates
|
||||
{ ClimateZones.ET_Tundra, 0.5 },
|
||||
{ ClimateZones.EF_IceCap, 0.4 }
|
||||
};
|
||||
|
||||
public static Dictionary<ClimateZones, double> Window => new ()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZones.Af_TropicalRainforest, 0.8 },
|
||||
{ ClimateZones.Am_TropicalMonsoon, 0.8 },
|
||||
{ ClimateZones.Aw_TropicalSavanna, 0.9 },
|
||||
{ ClimateZones.As_TropicalSavanna, 0.9 },
|
||||
|
||||
// Dry Climates
|
||||
{ ClimateZones.BWh_HotDesert, 0.7 },
|
||||
{ ClimateZones.BWk_ColdDesert, 0.9 },
|
||||
{ ClimateZones.BSh_HotSemiArid, 0.8 },
|
||||
{ ClimateZones.BSk_ColdSemiArid, 0.85 },
|
||||
|
||||
// Temperate Climates
|
||||
{ ClimateZones.Cfa_HumidSubtropical, 0.6 },
|
||||
{ ClimateZones.Cfb_Oceanic, 0.7 },
|
||||
{ ClimateZones.Cfc_SubpolarOceanic, 0.75 },
|
||||
{ ClimateZones.Csa_MediterraneanHotSummer, 0.55 },
|
||||
{ ClimateZones.Csb_MediterraneanWarmSummer, 0.65 },
|
||||
{ ClimateZones.Csc_MediterraneanCoolSummer, 0.7 },
|
||||
|
||||
// Continental Climates
|
||||
{ ClimateZones.Dfa_HumidContinentalHotSummer, 0.75 },
|
||||
{ ClimateZones.Dfb_HumidContinentalMildSummer, 0.8 },
|
||||
{ ClimateZones.Dfc_Subarctic, 0.5 },
|
||||
{ ClimateZones.Dfd_SubarcticExtremeWinter, 0.45 },
|
||||
{ ClimateZones.Dsa_MediterraneanInfluenceSnowyWinter, 0.7 },
|
||||
{ ClimateZones.Dsb_MediterraneanInfluenceSnowyWinter, 0.65 },
|
||||
{ ClimateZones.Dsc_MediterraneanInfluenceSnowyWinter, 0.55 },
|
||||
{ ClimateZones.Dsd_MediterraneanInfluenceSnowyWinter, 0.5 },
|
||||
|
||||
// Polar Climates
|
||||
{ ClimateZones.ET_Tundra, 0.3 },
|
||||
{ ClimateZones.EF_IceCap, 0.25 }
|
||||
};
|
||||
|
||||
public static Dictionary<ClimateZones, double> Roof => new ()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZones.Af_TropicalRainforest, 1.2 },
|
||||
{ ClimateZones.Am_TropicalMonsoon, 1.3 },
|
||||
{ ClimateZones.Aw_TropicalSavanna, 1.4 },
|
||||
{ ClimateZones.As_TropicalSavanna, 1.4 },
|
||||
|
||||
// Dry Climates
|
||||
{ ClimateZones.BWh_HotDesert, 1.1 },
|
||||
{ ClimateZones.BWk_ColdDesert, 1.3 },
|
||||
{ ClimateZones.BSh_HotSemiArid, 1.2 },
|
||||
{ ClimateZones.BSk_ColdSemiArid, 1.3 },
|
||||
|
||||
// Temperate Climates
|
||||
{ ClimateZones.Cfa_HumidSubtropical, 1.1 },
|
||||
{ ClimateZones.Cfb_Oceanic, 1.0 },
|
||||
{ ClimateZones.Cfc_SubpolarOceanic, 0.9 },
|
||||
{ ClimateZones.Csa_MediterraneanHotSummer, 1.2 },
|
||||
{ ClimateZones.Csb_MediterraneanWarmSummer, 1.1 },
|
||||
{ ClimateZones.Csc_MediterraneanCoolSummer, 1.0 },
|
||||
|
||||
// Continental Climates
|
||||
{ ClimateZones.Dfa_HumidContinentalHotSummer, 1.0 },
|
||||
{ ClimateZones.Dfb_HumidContinentalMildSummer, 0.9 },
|
||||
{ ClimateZones.Dfc_Subarctic, 0.6 },
|
||||
{ ClimateZones.Dfd_SubarcticExtremeWinter, 0.5 },
|
||||
{ ClimateZones.Dsa_MediterraneanInfluenceSnowyWinter, 0.9 },
|
||||
{ ClimateZones.Dsb_MediterraneanInfluenceSnowyWinter, 0.8 },
|
||||
{ ClimateZones.Dsc_MediterraneanInfluenceSnowyWinter, 0.7 },
|
||||
{ ClimateZones.Dsd_MediterraneanInfluenceSnowyWinter, 0.6 },
|
||||
|
||||
// Polar Climates
|
||||
{ ClimateZones.ET_Tundra, 0.4 },
|
||||
{ ClimateZones.EF_IceCap, 0.35 }
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// This class describes the user specified variables that the function wants to work with.
|
||||
/// </summary>
|
||||
@@ -9,24 +160,20 @@ using System.ComponentModel.DataAnnotations;
|
||||
/// are valid and match the required schema.
|
||||
public struct FunctionInputs
|
||||
{
|
||||
/// <summary>
|
||||
/// The object type to count instances of in the given model version.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string SpeckleTypeToCount;
|
||||
|
||||
/// <summary>
|
||||
/// The total number of the specified type expected.
|
||||
/// </summary>
|
||||
[DefaultValue(10)]
|
||||
[Range(1, 100)]
|
||||
[EnumDataType(typeof(ClimateZones))]
|
||||
[DefaultValue(ClimateZones.Csa_MediterraneanHotSummer)]
|
||||
public string ClimateZone;
|
||||
|
||||
[Required]
|
||||
public int SpeckleTypeTargetCount;
|
||||
|
||||
/// <summary>
|
||||
/// An arbitrary example of using a secret input value.
|
||||
/// </summary>
|
||||
[DefaultValue(true)]
|
||||
public bool CheckWalls;
|
||||
|
||||
[Required]
|
||||
[Secret]
|
||||
public string ExternalServiceKey;
|
||||
[DefaultValue(true)]
|
||||
public bool CheckWindows;
|
||||
|
||||
[Required]
|
||||
[DefaultValue(true)]
|
||||
public bool CheckRoofs;
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ public sealed class AutomationContextTest : IDisposable
|
||||
{
|
||||
var inputs = new FunctionInputs
|
||||
{
|
||||
SpeckleTypeToCount = "Base",
|
||||
SpeckleTypeTargetCount = 1
|
||||
ClimateZone = ClimateZones.Csa_MediterraneanHotSummer.ToString(),
|
||||
CheckWindows = true,
|
||||
CheckWalls = true,
|
||||
CheckRoofs = true
|
||||
};
|
||||
|
||||
var automationRunData = await TestAutomateUtils.CreateTestRun(client);
|
||||
|
||||
Reference in New Issue
Block a user