Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b148e50438 | |||
| 92d8303834 | |||
| 1c8fe8927e | |||
| b2b8791d28 | |||
| cbfdd67306 | |||
| a62633974f | |||
| c04328bfa4 | |||
| 63e96f93a1 | |||
| e8d7a936c2 |
@@ -3,6 +3,16 @@ using Speckle.Automate.Sdk;
|
||||
using Speckle.Automate.Sdk.Schema;
|
||||
using Speckle.Core.Models;
|
||||
using Speckle.Core.Models.Extensions;
|
||||
using Speckle.Core.Models.GraphTraversal;
|
||||
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
public enum SpeckleType
|
||||
{
|
||||
Wall,
|
||||
Window,
|
||||
Roof,
|
||||
}
|
||||
|
||||
public static class AutomateFunction
|
||||
{
|
||||
@@ -13,41 +23,156 @@ public static class AutomateFunction
|
||||
{
|
||||
Console.WriteLine("Starting execution");
|
||||
_ = typeof(ObjectsKit).Assembly; // INFO: Force objects kit to initialize
|
||||
var threshold = 0.02;
|
||||
Console.WriteLine("Receiving version");
|
||||
var commitObject = await automationContext.ReceiveVersion();
|
||||
|
||||
var objects = commitObject
|
||||
.Flatten()
|
||||
.Where(b => b.speckle_type == "Objects.BuiltElements.Wall:Objects.BuiltElements.Revit.RevitWall" &&
|
||||
(string)b["category"]! == "Walls");
|
||||
// 0- Get climate zone from function inputs
|
||||
|
||||
var values = objects.Select(GetThermalResistance);
|
||||
// 1- Receive version from automation context
|
||||
|
||||
var failedObjectIds = values.Where(val => val.value > threshold).Select(v => v.id).ToList();
|
||||
// 2- Flatten the objects in received root object
|
||||
|
||||
if (failedObjectIds.Count > 0)
|
||||
// 3- Get the objects we need
|
||||
|
||||
// 4- Check the compliance for given object types
|
||||
|
||||
// 5- Attach report to failed objects to be able to highlight them in viewer or Revit connector
|
||||
|
||||
// 6- Report the automation result as SUCCESS/FAIL
|
||||
automationContext.MarkRunSuccess(
|
||||
"We are going to fail successfully in a bit, don't worry!"
|
||||
);
|
||||
}
|
||||
|
||||
private static void AttachReportToFailedObjects(
|
||||
AutomationContext automationContext,
|
||||
IEnumerable<ObjectToCheck> failedObjects
|
||||
)
|
||||
{
|
||||
foreach (var failedObject in failedObjects)
|
||||
{
|
||||
automationContext.AttachResultToObjects(ObjectResultLevel.Error, "test", failedObjectIds);
|
||||
automationContext.MarkRunFailed("FAILED");
|
||||
var speckleTypeString = failedObject.SpeckleType.ToString();
|
||||
string message = "";
|
||||
if (failedObject.UValue == 0)
|
||||
{
|
||||
message =
|
||||
$"{speckleTypeString} has no any material that have thermal properties.";
|
||||
}
|
||||
else
|
||||
{
|
||||
message =
|
||||
$"{speckleTypeString} expected to have maximum {failedObject.ExpectedUValue} U-value but it is {failedObject.UValue}.";
|
||||
}
|
||||
|
||||
automationContext.AttachResultToObjects(
|
||||
ObjectResultLevel.Error,
|
||||
speckleTypeString,
|
||||
new[] { failedObject.Id },
|
||||
message
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ReportStatus(
|
||||
AutomationContext automationContext,
|
||||
FunctionInputs functionInputs,
|
||||
int numberOfWalls,
|
||||
int numberOfFailedWalls,
|
||||
int numberOfWindows,
|
||||
int numberOfFailedWindows,
|
||||
int numberOfRoofs,
|
||||
int numberOfFailedRoofs
|
||||
)
|
||||
{
|
||||
if (numberOfFailedWalls + numberOfFailedWindows + numberOfFailedRoofs > 0)
|
||||
{
|
||||
var message = "";
|
||||
if (true) // TODO: Check the whether walls included or not from function inputs
|
||||
{
|
||||
message += "WALLS:\n";
|
||||
if (numberOfWalls > 0)
|
||||
{
|
||||
message += $"{numberOfFailedWalls}/{numberOfWalls} wall(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no walls\n\n";
|
||||
}
|
||||
}
|
||||
if (true) // TODO: Check the whether windows included or not from function inputs
|
||||
{
|
||||
message += "WINDOWS:\n";
|
||||
if (numberOfWindows > 0)
|
||||
{
|
||||
message += $"{numberOfFailedWindows}/{numberOfWindows} window(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no windows\n\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (true) // TODO: Check the whether roofs included or not from function inputs
|
||||
{
|
||||
message += "ROOFS:\n";
|
||||
if (numberOfRoofs > 0)
|
||||
{
|
||||
message += $"{numberOfFailedRoofs}/{numberOfRoofs} roof(s) failed.\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
message += "There are no roofs\n\n";
|
||||
}
|
||||
}
|
||||
automationContext.MarkRunFailed(message);
|
||||
}
|
||||
else
|
||||
{
|
||||
automationContext.MarkRunSuccess($"SUCCESS");
|
||||
automationContext.MarkRunSuccess(
|
||||
$"Your building is compliant with selected climate zone!"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private static (string id, double value) GetThermalResistance(Base obj)
|
||||
private static double GetExpectedValue(
|
||||
ClimateZone climateZone,
|
||||
SpeckleType speckleType
|
||||
)
|
||||
{
|
||||
var properties = obj["properties"] as Dictionary<string, object>;
|
||||
if (properties is null)
|
||||
switch (speckleType)
|
||||
{
|
||||
return (obj.id, 0);
|
||||
case SpeckleType.Wall:
|
||||
return UValues.Wall[climateZone];
|
||||
case SpeckleType.Window:
|
||||
return UValues.Window[climateZone];
|
||||
case SpeckleType.Roof:
|
||||
return UValues.Roof[climateZone];
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
var typeParameters = properties!["Type Parameters"] as Dictionary<string, object>;
|
||||
var analyticalProperties = typeParameters!["Analytical Properties"] as Dictionary<string, object>;
|
||||
var thermalResistance = analyticalProperties!["Thermal Resistance (R)"] as Dictionary<string, object>;
|
||||
var value = thermalResistance!["value"] is double ? (double)thermalResistance!["value"] : 0;
|
||||
return (obj.id, value);
|
||||
}
|
||||
|
||||
private static IEnumerable<ObjectToCheck> CheckCompliance(
|
||||
IEnumerable<Base> objects,
|
||||
ClimateZone climateZone,
|
||||
SpeckleType speckleType
|
||||
)
|
||||
{
|
||||
var expectedValue = GetExpectedValue(climateZone, speckleType);
|
||||
var objectsToCheck = objects.Select(o => new ObjectToCheck(
|
||||
o,
|
||||
expectedValue,
|
||||
speckleType
|
||||
));
|
||||
return objectsToCheck.Where(obj => obj.UValue > expectedValue || obj.UValue == 0);
|
||||
}
|
||||
|
||||
private static ClimateZone GetClimateZone(string climateZoneString)
|
||||
{
|
||||
if (Enum.TryParse(climateZoneString, out ClimateZone climateZoneEnum))
|
||||
{
|
||||
return climateZoneEnum;
|
||||
}
|
||||
|
||||
// Handle the case where the ClimateZone string is not a valid ClimateZones value
|
||||
throw new ArgumentException($"Invalid ClimateZone: {climateZoneString}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
public enum ClimateZone
|
||||
{
|
||||
// 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<ClimateZone, double> Window =>
|
||||
new()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZone.Af_TropicalRainforest, 0.9 },
|
||||
{ ClimateZone.Am_TropicalMonsoon, 1.0 },
|
||||
{ ClimateZone.Aw_TropicalSavanna, 1.1 },
|
||||
{ ClimateZone.As_TropicalSavanna, 1.1 },
|
||||
// Dry Climates
|
||||
{ ClimateZone.BWh_HotDesert, 1.0 },
|
||||
{ ClimateZone.BWk_ColdDesert, 1.2 },
|
||||
{ ClimateZone.BSh_HotSemiArid, 1.2 },
|
||||
{ ClimateZone.BSk_ColdSemiArid, 1.5 },
|
||||
// Temperate Climates
|
||||
{ ClimateZone.Cfa_HumidSubtropical, 1.4 },
|
||||
{ ClimateZone.Cfb_Oceanic, 1.3 },
|
||||
{ ClimateZone.Cfc_SubpolarOceanic, 1.2 },
|
||||
{ ClimateZone.Csa_MediterraneanHotSummer, 1.51 },
|
||||
{ ClimateZone.Csb_MediterraneanWarmSummer, 1.4 },
|
||||
{ ClimateZone.Csc_MediterraneanCoolSummer, 1.3 },
|
||||
// Continental Climates
|
||||
{ ClimateZone.Dfa_HumidContinentalHotSummer, 1.3 },
|
||||
{ ClimateZone.Dfb_HumidContinentalMildSummer, 1.2 },
|
||||
{ ClimateZone.Dfc_Subarctic, 0.7 },
|
||||
{ ClimateZone.Dfd_SubarcticExtremeWinter, 0.6 },
|
||||
{ ClimateZone.Dsa_MediterraneanInfluenceSnowyWinter, 1.2 },
|
||||
{ ClimateZone.Dsb_MediterraneanInfluenceSnowyWinter, 1.1 },
|
||||
{ ClimateZone.Dsc_MediterraneanInfluenceSnowyWinter, 0.9 },
|
||||
{ ClimateZone.Dsd_MediterraneanInfluenceSnowyWinter, 0.8 },
|
||||
// Polar Climates
|
||||
{ ClimateZone.ET_Tundra, 0.5 },
|
||||
{ ClimateZone.EF_IceCap, 0.4 },
|
||||
};
|
||||
|
||||
public static Dictionary<ClimateZone, double> Wall =>
|
||||
new()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZone.Af_TropicalRainforest, 0.8 },
|
||||
{ ClimateZone.Am_TropicalMonsoon, 0.8 },
|
||||
{ ClimateZone.Aw_TropicalSavanna, 0.9 },
|
||||
{ ClimateZone.As_TropicalSavanna, 0.9 },
|
||||
// Dry Climates
|
||||
{ ClimateZone.BWh_HotDesert, 0.7 },
|
||||
{ ClimateZone.BWk_ColdDesert, 0.9 },
|
||||
{ ClimateZone.BSh_HotSemiArid, 0.8 },
|
||||
{ ClimateZone.BSk_ColdSemiArid, 0.85 },
|
||||
// Temperate Climates
|
||||
{ ClimateZone.Cfa_HumidSubtropical, 0.6 },
|
||||
{ ClimateZone.Cfb_Oceanic, 0.7 },
|
||||
{ ClimateZone.Cfc_SubpolarOceanic, 0.75 },
|
||||
{ ClimateZone.Csa_MediterraneanHotSummer, 0.55 },
|
||||
{ ClimateZone.Csb_MediterraneanWarmSummer, 0.65 },
|
||||
{ ClimateZone.Csc_MediterraneanCoolSummer, 0.7 },
|
||||
// Continental Climates
|
||||
{ ClimateZone.Dfa_HumidContinentalHotSummer, 0.75 },
|
||||
{ ClimateZone.Dfb_HumidContinentalMildSummer, 0.8 },
|
||||
{ ClimateZone.Dfc_Subarctic, 0.5 },
|
||||
{ ClimateZone.Dfd_SubarcticExtremeWinter, 0.45 },
|
||||
{ ClimateZone.Dsa_MediterraneanInfluenceSnowyWinter, 0.7 },
|
||||
{ ClimateZone.Dsb_MediterraneanInfluenceSnowyWinter, 0.65 },
|
||||
{ ClimateZone.Dsc_MediterraneanInfluenceSnowyWinter, 0.55 },
|
||||
{ ClimateZone.Dsd_MediterraneanInfluenceSnowyWinter, 0.5 },
|
||||
// Polar Climates
|
||||
{ ClimateZone.ET_Tundra, 0.3 },
|
||||
{ ClimateZone.EF_IceCap, 0.25 },
|
||||
};
|
||||
|
||||
public static Dictionary<ClimateZone, double> Roof =>
|
||||
new()
|
||||
{
|
||||
// Tropical Climates
|
||||
{ ClimateZone.Af_TropicalRainforest, 1.2 },
|
||||
{ ClimateZone.Am_TropicalMonsoon, 1.3 },
|
||||
{ ClimateZone.Aw_TropicalSavanna, 1.4 },
|
||||
{ ClimateZone.As_TropicalSavanna, 1.4 },
|
||||
// Dry Climates
|
||||
{ ClimateZone.BWh_HotDesert, 1.1 },
|
||||
{ ClimateZone.BWk_ColdDesert, 1.3 },
|
||||
{ ClimateZone.BSh_HotSemiArid, 1.2 },
|
||||
{ ClimateZone.BSk_ColdSemiArid, 1.3 },
|
||||
// Temperate Climates
|
||||
{ ClimateZone.Cfa_HumidSubtropical, 1.1 },
|
||||
{ ClimateZone.Cfb_Oceanic, 1.0 },
|
||||
{ ClimateZone.Cfc_SubpolarOceanic, 0.9 },
|
||||
{ ClimateZone.Csa_MediterraneanHotSummer, 1.2 },
|
||||
{ ClimateZone.Csb_MediterraneanWarmSummer, 1.1 },
|
||||
{ ClimateZone.Csc_MediterraneanCoolSummer, 1.0 },
|
||||
// Continental Climates
|
||||
{ ClimateZone.Dfa_HumidContinentalHotSummer, 1.0 },
|
||||
{ ClimateZone.Dfb_HumidContinentalMildSummer, 0.9 },
|
||||
{ ClimateZone.Dfc_Subarctic, 0.6 },
|
||||
{ ClimateZone.Dfd_SubarcticExtremeWinter, 0.5 },
|
||||
{ ClimateZone.Dsa_MediterraneanInfluenceSnowyWinter, 0.9 },
|
||||
{ ClimateZone.Dsb_MediterraneanInfluenceSnowyWinter, 0.8 },
|
||||
{ ClimateZone.Dsc_MediterraneanInfluenceSnowyWinter, 0.7 },
|
||||
{ ClimateZone.Dsd_MediterraneanInfluenceSnowyWinter, 0.6 },
|
||||
// Polar Climates
|
||||
{ ClimateZone.ET_Tundra, 0.4 },
|
||||
{ ClimateZone.EF_IceCap, 0.35 },
|
||||
};
|
||||
}
|
||||
@@ -1,13 +1,7 @@
|
||||
using Speckle.Automate.Sdk.DataAnnotations;
|
||||
using System.ComponentModel;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
|
||||
public enum ClimateZones
|
||||
{
|
||||
ONEA,
|
||||
TWOA
|
||||
}
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
/// <summary>
|
||||
/// This class describes the user specified variables that the function wants to work with.
|
||||
@@ -16,22 +10,11 @@ public enum ClimateZones
|
||||
/// are valid and match the required schema.
|
||||
public struct FunctionInputs
|
||||
{
|
||||
[Required]
|
||||
[EnumDataType(typeof(ClimateZones))]
|
||||
[DefaultValue(ClimateZones.ONEA)]
|
||||
public string ClimateZone;
|
||||
// 0- Create dropdown for available climate zones as "ClimateZones"
|
||||
|
||||
/// <summary>
|
||||
/// The object type to count instances of in the given model version.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string SpeckleTypeToCheck;
|
||||
// 1- Create toggle for whether including walls or not
|
||||
|
||||
/// <summary>
|
||||
/// The total number of the specified type expected.
|
||||
/// </summary>
|
||||
[DefaultValue(10)]
|
||||
[Range(1, 100)]
|
||||
[Required]
|
||||
public int SpeckleTypeTargetCount;
|
||||
// 2- Create toggle for whether including windows or not
|
||||
|
||||
// 3- Create toggle for whether including roofs or not
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
using Speckle.Core.Models;
|
||||
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
public class ObjectToCheck
|
||||
{
|
||||
public string Id { get; }
|
||||
public double UValue { get; }
|
||||
public double ExpectedUValue { get; }
|
||||
public SpeckleType SpeckleType { get; }
|
||||
|
||||
public ObjectToCheck(Base obj, double expectedUValue, SpeckleType speckleType)
|
||||
{
|
||||
Id = obj.id;
|
||||
ExpectedUValue = expectedUValue;
|
||||
SpeckleType = speckleType;
|
||||
var properties = obj["properties"] as Dictionary<string, object>;
|
||||
if (properties is null)
|
||||
{
|
||||
UValue = 0;
|
||||
return;
|
||||
}
|
||||
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;
|
||||
UValue = value;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using Speckle.Automate.Sdk;
|
||||
using TestAutomateFunction;
|
||||
|
||||
// WARNING do not delete this call, this is the actual execution of your function
|
||||
return await AutomationRunner
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
using Speckle.Core.Models;
|
||||
using Speckle.Core.Models.GraphTraversal;
|
||||
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
public class SpeckleTypeUtilities
|
||||
{
|
||||
public static IEnumerable<Base> GetByType(
|
||||
IEnumerable<Base> objects,
|
||||
SpeckleType speckleType
|
||||
)
|
||||
{
|
||||
return objects.Where(b =>
|
||||
b.speckle_type == SpeckleTypes[speckleType]
|
||||
&& (string)b["category"]! == SpeckleCategories[speckleType]
|
||||
);
|
||||
}
|
||||
|
||||
public static IEnumerable<Base> GetByType<T>(Base root)
|
||||
where T : Base
|
||||
{
|
||||
var traversal = new GraphTraversal();
|
||||
return traversal
|
||||
.Traverse(root)
|
||||
.Where(obj => obj.Current is T)
|
||||
.Select(obj => obj.Current);
|
||||
}
|
||||
|
||||
private static readonly Dictionary<SpeckleType, string> SpeckleTypes =
|
||||
new()
|
||||
{
|
||||
{
|
||||
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()
|
||||
{
|
||||
{ SpeckleType.Wall, "Walls" },
|
||||
{ SpeckleType.Window, "Windows" },
|
||||
{ SpeckleType.Roof, "Roofs" },
|
||||
};
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
using Speckle.Automate.Sdk;
|
||||
using Speckle.Automate.Sdk.Test;
|
||||
using Speckle.Core.Api;
|
||||
using Speckle.Core.Credentials;
|
||||
|
||||
namespace TestAutomateFunction;
|
||||
|
||||
[TestFixture]
|
||||
public sealed class AutomationContextTest : IDisposable
|
||||
{
|
||||
|
||||
private Client client;
|
||||
private Account account;
|
||||
|
||||
@@ -18,7 +17,10 @@ public sealed class AutomationContextTest : IDisposable
|
||||
account = new Account
|
||||
{
|
||||
token = TestAutomateEnvironment.GetSpeckleToken(),
|
||||
serverInfo = new ServerInfo { url = TestAutomateEnvironment.GetSpeckleServerUrl().ToString() }
|
||||
serverInfo = new ServerInfo
|
||||
{
|
||||
url = TestAutomateEnvironment.GetSpeckleServerUrl().ToString(),
|
||||
},
|
||||
};
|
||||
client = new Client(account);
|
||||
}
|
||||
@@ -28,8 +30,7 @@ public sealed class AutomationContextTest : IDisposable
|
||||
{
|
||||
var inputs = new FunctionInputs
|
||||
{
|
||||
SpeckleTypeToCheck = "Base",
|
||||
SpeckleTypeTargetCount = 1
|
||||
// TODO: Define test inputs
|
||||
};
|
||||
|
||||
var automationRunData = await TestAutomateUtils.CreateTestRun(client);
|
||||
|
||||
Reference in New Issue
Block a user