static void Main(string[] args)
        {
            var config = new SampleConfiguration();

            new ConfigurationBuilder()
            .AddCommandLine(args)
            .Build()
            .Bind(config);

            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(config)
                                  .BuildServiceProvider();

            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var configFile = fileManager.WriteJson(config);

            ColourConsole.WriteSuccess($"Configuraiton file {configFile.FullName}");
        }
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // call Forge-DM to get the hub/project
            var forgeDataClient = serviceProvider.GetRequiredService <IForgeDataClient>();

            _ = await forgeDataClient.GetProject() ?? throw new InvalidOperationException("Could access a test Account/Project");

            ColourConsole.WriteSuccess($"Loaded project {configuration.ProjectId}");

            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            _ = await modelSetClient.GetModelSetsAsync(configuration.ProjectId, null, null, null, null, null);

            ColourConsole.WriteSuccess($"Queried model sets for container {configuration.ProjectId}");
        }
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from CreateModelSetSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var modelSetCreateSampleState = await fileManager.ReadJsonAsync <CreateModelSetSampleState>();

            ColourConsole.WriteSuccess($"Loaded sample state");

            // make sure this model set exists
            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            var modelSet = await modelSetClient.GetModelSetAsync(modelSetCreateSampleState.ModelSet.ContainerId, modelSetCreateSampleState.ModelSet.ModelSetId)
                           ?? throw new InvalidOperationException($"Could not find model set {modelSetCreateSampleState.ModelSet.ModelSetId}, have you run CreateModelSetSample?");

            ColourConsole.WriteSuccess($"Found model set {modelSet.ModelSetId}");

            // See if we can find any model set versions, remember there is a time dalay between documents being extracted
            // by BIM 360 Docs and the model coordination system's processing of these extracted documents. If you have jumped
            // immediatly to this sample having run CreateModelSetSample it could be that you are working faster than Docs :-)
            var modelSetVersions = await modelSetClient.GetModelSetVersionsAsync(modelSetCreateSampleState.ModelSet.ContainerId, modelSetCreateSampleState.ModelSet.ModelSetId, null, null);

            if (modelSetVersions.ModelSetVersions.Count > 0)
            {
                foreach (var version in modelSetVersions.ModelSetVersions.OrderBy(v => v.Version))
                {
                    ColourConsole.WriteSuccess($"Found model set version {version.Version:00} : {version.CreateTime:u}, {version.Status}");
                }
            }
            else
            {
                ColourConsole.WriteInfo($"No model set verisons found for {modelSetCreateSampleState.ModelSet.ModelSetId}, try again later.");
            }

            // If we found model set versions, demonstrate loading a model set by verison number
            if (modelSetVersions?.ModelSetVersions.Count > 0)
            {
                ColourConsole.WriteInfo("Demonstrate loading model set version by number");

                var version = await modelSetClient.GetModelSetVersionAsync(modelSetCreateSampleState.ModelSet.ContainerId, modelSetCreateSampleState.ModelSet.ModelSetId, 1);

                ColourConsole.WriteSuccess($"First model set version {version.Version:00} : {version.CreateTime:u)}, {version.Status}");
            }

            if (modelSetVersions?.ModelSetVersions.Count > 0)
            {
                ColourConsole.WriteInfo("Demonstrate loading latest model set version");

                var latest = await modelSetClient.GetModelSetVersionLatestAsync(modelSetCreateSampleState.ModelSet.ContainerId, modelSetCreateSampleState.ModelSet.ModelSetId);

                ColourConsole.WriteSuccess($"Latest model set version {latest.Version:00} : {latest.CreateTime:u}, {latest.Status}");
            }
        }
Exemple #4
0
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from GetClashResultsSample this includes the latest
            // model set version that was associated with the clash test. It means
            // the results of this query relate to the clashing objects in the
            // proceeding sample, GetClashresultsSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var clashResultSampleState = await fileManager.ReadJsonAsync <GetClashResultsSampleState>()
                                         ?? throw new InvalidOperationException("Could not load GetClashResultsSampleState.json, have you run GetClashResultsSample?");

            // load the model set verison from the GetClashresultsSample
            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            var modelSetVersion = await modelSetClient.GetModelSetVersionAsync(
                clashResultSampleState.Container, clashResultSampleState.Latest.ModelSetId, clashResultSampleState.Latest.ModelSetVersion)
                                  ?? throw new InvalidOperationException($"Error could not load latest model set version for model set {clashResultSampleState.Latest.ModelSetId}, have you run GetClashResultsSample?");

            // next load the index manifest for the model set verison
            // this object contains the look-up values for the seed files
            // documents and databases which were used to build the index. You
            // will need these valuse to display query results in the LMV if
            // they are viewable in 3D
            var modelSetIndex = serviceProvider.GetRequiredService <IModelSetIndex>();

            var indexManifest = await modelSetIndex.GetManifest(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.ModelSetId,
                clashResultSampleState.Latest.ModelSetVersion)
                                ?? throw new InvalidOperationException($"No indfex manifest found for {clashResultSampleState.Latest.ModelSetVersion}:{clashResultSampleState.Latest.ModelSetVersion}");

            ColourConsole.WriteSuccess($"Manifest contains {indexManifest.SeedFiles.Count} seed files.");

            // load the fields for the index and go after the field with a name == "name" and category == "__name__"
            var fields = await modelSetIndex.GetFields(clashResultSampleState.Container, clashResultSampleState.Latest.ModelSetId, clashResultSampleState.Latest.ModelSetVersion);

            ColourConsole.WriteSuccess($"Loaded {fields.Count} unique index fields");

            var nameField = fields.Values.SingleOrDefault(
                f => f.Name.Equals("name", StringComparison.OrdinalIgnoreCase) &&
                f.Category.Equals("__name__", StringComparison.OrdinalIgnoreCase));

            // run a query to find all the objects which have a name as defined by (name == "name" && category == "__name__")
            // the not missing S3 select keywords assert that this projection is in the data
            string query = $"select s.file, s.db, s.docs, s.id, s.{nameField.Key} from s3object s where s.{nameField.Key} is not missing";

            ColourConsole.WriteInfo(query);

            var queryResults = await modelSetIndex.Query(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.ModelSetId,
                clashResultSampleState.Latest.ModelSetVersion, query);

            ColourConsole.WriteSuccess($"Query results downloaded to {queryResults.FullName}");

            // itterate over the results and count the number of rows returned
            // substituteFieldkeys == true below will pull out the unique set of
            // fields in this data
            var reader = new IndexResultReader(queryResults, fields);

            var summary = await reader.ReadToEndAsync(null, true);

            ColourConsole.WriteSuccess($"Query returned {summary.RowCount} objects with {summary.Fields.Count} unique fields.");
        }
Exemple #5
0
        public static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Configure(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from GetClashResultsSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            // get the issue container for this project
            var dataClient = serviceProvider.GetRequiredService <IForgeDataClient>();

            //Verifying project id and name
            dynamic obj = await dataClient.GetProjectAsJObject()
                          ?? throw new InvalidOperationException($"Could not load prject {configuration.ProjectId}");

            string project_id   = obj.data.id;
            string project_name = obj.data.attributes.name;

            ColourConsole.WriteSuccess($"Verifying project id and name {project_id}, {project_name}");

            //initialize relationship client
            var  relationshipClient   = serviceProvider.GetRequiredService <IRelationshipClient>();
            Guid project_id_without_b = new Guid(project_id.Replace("b.", ""));

            //search all relationships
            var res_search_without_arg = await relationshipClient.SearchRelationshipsAsync(project_id_without_b, null, null, null, null, null, null, null, null, null, null, null);

            ColourConsole.WriteSuccess($"Search relationships without arguments: count: {res_search_without_arg.Relationships.Count}");
            if (res_search_without_arg.Relationships.Count > 0)
            {
                //dump one relationship data

                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_without_arg.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }


            //search relationships by createAfter and createdBefore

            var res_search_with_date = await relationshipClient.SearchRelationshipsAsync(project_id_without_b,
                                                                                         null, null, null, new DateTime(2020, 7, 15), new DateTime(2020, 7, 20), null, null, null, null, null, null);

            ColourConsole.WriteSuccess($"Search relationships with Date arguments: count: {res_search_with_date.Relationships.Count}");
            if (res_search_with_date.Relationships.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_with_date.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }


            //search relationships by createAfter and createdBefore and continuationToken
            string one_continuationToken = res_search_without_arg.Page.ContinuationToken;

            var res_search_with_date_continuetoken = await relationshipClient.SearchRelationshipsAsync(project_id_without_b,
                                                                                                       null, null, null, new DateTime(2020, 7, 15), new DateTime(2020, 7, 20), null, null, null, null, null, one_continuationToken);

            ColourConsole.WriteSuccess($"Search relationships with ContinuationToken: count: {res_search_with_date_continuetoken.Relationships.Count}");
            if (res_search_with_date_continuetoken.Relationships.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_with_date_continuetoken.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }



            //sync
            string oneSyncToken            = res_search_without_arg.Page.SyncToken;
            RelationshipSyncRequest rsBody = new RelationshipSyncRequest();

            rsBody.SyncToken = oneSyncToken;
            rsBody.Filters   = null;
            var res_sync = await relationshipClient.RelationshipsSyncAsync(project_id_without_b, rsBody);

            ColourConsole.WriteSuccess($"Sync relationships with SyncToken:");
            ColourConsole.WriteInfo($"Current.Data Count: {res_sync.Current.Data.Count}");
            ColourConsole.WriteInfo($"Deleted.Data Count: {res_sync.Deleted.Data.Count}");
            ColourConsole.WriteInfo($"moreData : {res_sync.MoreData}");
            ColourConsole.WriteInfo($"overwrite : {res_sync.Overwrite}");

            if (res_sync.Current.Data.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Current.Data: ");

                var oneRelationship = res_sync.Current.Data[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }


            if (res_sync.Deleted.Data.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Deleted.Data: ");

                var oneRelationship = res_sync.Deleted.Data[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }



            //search relationships with one domain .e.g. modelcoordination
            var res_search_with_domain = await relationshipClient.SearchRelationshipsAsync(project_id_without_b,
                                                                                           "autodesk-bim360-modelcoordination", null, null, new DateTime(2020, 7, 15), new DateTime(2020, 7, 20), null, null, null, null, null, one_continuationToken);

            ColourConsole.WriteSuccess($"Search relationships with one domain(e.g. modelcoordination) count: {res_search_with_domain.Relationships.Count}");
            if (res_search_with_domain.Relationships.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_with_domain.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }

            //search relationships with one domain and type .e.g. modelcoordination and clashgroup
            var res_search_with_domain_type = await relationshipClient.SearchRelationshipsAsync(project_id_without_b,
                                                                                                "autodesk-bim360-modelcoordination", "clashgroup", null, new DateTime(2020, 7, 15), new DateTime(2020, 7, 20), null, null, null, null, null, one_continuationToken);

            ColourConsole.WriteSuccess($"Search relationships with one domain and type (e.g. modelcoordination & clashgroup). count: {res_search_with_domain_type.Relationships.Count}");
            if (res_search_with_domain_type.Relationships.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_with_domain_type.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }


            //search relationships with one domain and type , and withDomain and withType
            //e.g. modelcoordination & clashgroup  - autodesk-bim360-documentmanagement & documentversion
            //
            var res_search_with_withDomain_type = await relationshipClient.SearchRelationshipsAsync(project_id_without_b,
                                                                                                    "autodesk-bim360-modelcoordination", "clashgroup", null, new DateTime(2020, 7, 15), new DateTime(2020, 7, 20), "autodesk-bim360-documentmanagement", "documentversion", null, null, null, one_continuationToken);

            ColourConsole.WriteSuccess($"Search relationships with one domain and type and withDomain and withType" +
                                       $" (e.g. modelcoordination & clashgroup  - autodesk-bim360-documentmanagement & documentversion). count: {res_search_with_domain_type.Relationships.Count}");
            if (res_search_with_withDomain_type.Relationships.Count > 0)
            {
                //dump one relationship data
                ColourConsole.WriteInfo($"One Relationship");

                var oneRelationship = res_search_with_withDomain_type.Relationships[0];
                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }



            //intersect without withEntities
            IntersectRelationshipsRequest intersectReqBody = new IntersectRelationshipsRequest();
            DomainEntity oneDomainEntity = new DomainEntity();

            oneDomainEntity.Domain = "autodesk-bim360-modelcoordination";
            oneDomainEntity.Type   = "scope";
            oneDomainEntity.Id     = "2e905b04-5666-4a8d-a303-27807c132900";

            intersectReqBody.Entities = new List <NewDomainEntity>();
            intersectReqBody.Entities.Add(oneDomainEntity);


            var res_intersect_relationship = await relationshipClient.IntersectRelationshipsAsync(project_id_without_b, false, null, null, intersectReqBody);

            ColourConsole.WriteSuccess($"get intersect relationships [autodesk-bim360-modelcoordination & clashgroup & df0743c0-e1bb-11e9-96ff-ad6fd4c4196b]\n" +
                                       $"Count:{res_intersect_relationship.Relationships.Count}");
            if (res_intersect_relationship != null && res_intersect_relationship.Relationships.Count > 0)
            {
                //dump one relationship data
                var oneRelationship = res_intersect_relationship.Relationships[0];

                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }


            //intersect with withEntities
            PartialDomainEntity oneWithDomainEntity = new PartialDomainEntity();

            oneWithDomainEntity.Domain = "autodesk-bim360-issue";
            oneWithDomainEntity.Type   = "coordination";
            oneWithDomainEntity.Id     = "23733044-049d-4ee6-8add-8257248e116f";

            intersectReqBody.WithEntities = new List <PartialDomainEntity>();
            intersectReqBody.WithEntities.Add(oneWithDomainEntity);


            res_intersect_relationship = await relationshipClient.IntersectRelationshipsAsync(project_id_without_b, false, null, null, intersectReqBody);

            ColourConsole.WriteSuccess($"get intersect relationships [autodesk-bim360-modelcoordination & clashgroup & df0743c0-e1bb-11e9-96ff-ad6fd4c4196b]\n" +
                                       $" [autodesk-bim360-issue & coordination & 9b510ffb-d0c5-4c7d-91ba-044bdb0e77f0]\n" +
                                       $"Count:{res_intersect_relationship.Relationships.Count}");
            if (res_intersect_relationship != null && res_intersect_relationship.Relationships.Count > 0)
            {
                //dump one relationship data
                var oneRelationship = res_intersect_relationship.Relationships[0];

                printOneRelationship(oneRelationship.Id, oneRelationship.CreatedOn, oneRelationship.IsReadOnly, oneRelationship.IsService, oneRelationship.IsDeleted,
                                     oneRelationship.Entities[0].Domain, oneRelationship.Entities[0].Type, oneRelationship.Entities[0].Id,
                                     oneRelationship.Entities[1].Domain, oneRelationship.Entities[1].Type, oneRelationship.Entities[1].Id);
            }
        }
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from CreateModelSetSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var modelSetCreateSampleState = await fileManager.ReadJsonAsync <CreateModelSetSampleState>()
                                            ?? throw new InvalidOperationException("Could not load CreateModelSetSampleState, have you run CreateModelSetSample?");

            // get the first page of model set views
            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            ModelSetView sampleView = null;

            bool createSampleView = true;

            var views = await modelSetClient.GetModelSetViewsAsync(
                containerId : modelSetCreateSampleState.ModelSet.ContainerId,
                modelSetId : modelSetCreateSampleState.ModelSet.ModelSetId,
                pageLimit : null,
                continuationToken : null,
                createdBy : null,
                modifiedBy : null,
                after : null,
                before : null,
                isPrivate : null,
                sortBy : null,
                sortDirection : null);

            if (views?.ModelSetViews.Count > 0)
            {
                foreach (var view in views.ModelSetViews)
                {
                    if (view.Name.Equals(SampleModelSetView.Name))
                    {
                        createSampleView = false;

                        sampleView = view;

                        ColourConsole.WriteInfo($"Found sample view, {view.Name}, is private:{view.IsPrivate}");
                    }
                    else
                    {
                        ColourConsole.WriteInfo($"Found, {view.Name}, is private:{view.IsPrivate}");
                    }
                }
            }
            else
            {
                ColourConsole.WriteInfo($"No views found for model set {modelSetCreateSampleState.ModelSet.ModelSetId}, create sample...");
            }

            // if the sample view does not exist create it using the lineages we
            // lave on the current tip verison of the model set
            ModelSetVersion modelSetTipVersion = await modelSetClient.GetModelSetVersionLatestAsync(
                modelSetCreateSampleState.ModelSet.ContainerId,
                modelSetCreateSampleState.ModelSet.ModelSetId);

            if (createSampleView)
            {
                if (modelSetTipVersion != null && modelSetTipVersion?.DocumentVersions.Count > 1)
                {
                    var lineages = modelSetTipVersion.DocumentVersions
                                   .Where(v => v.DocumentStatus == ModelSetDocumentStatus.Succeeded)
                                   .Take(2)
                                   .Select(d => d.DocumentLineage).ToList();

                    var modelSetView = new NewModelSetView
                    {
                        Name        = SampleModelSetView.Name,
                        Description = SampleModelSetView.Description,
                        IsPrivate   = true,
                        Definition  = lineages
                                      .Select(
                            l => new LineageViewable
                        {
                            LineageUrn = l.LineageUrn
                        })
                                      .ToList()
                    };

                    // this extension method wraps the job polling
                    sampleView = await modelSetClient.CreateModelSetView(
                        modelSetCreateSampleState.ModelSet.ContainerId,
                        modelSetCreateSampleState.ModelSet.ModelSetId,
                        modelSetView);

                    ColourConsole.WriteSuccess($"Created sample view: {sampleView.Name}, is private: {sampleView.IsPrivate}");
                }
                else
                {
                    ColourConsole.WriteWarning($"No model set version with >= 2 lineages in {modelSetCreateSampleState.ModelSet.ModelSetId}, skip sample view create!");
                }
            }

            // if we have a sample view toggle its privacy flag. If the view was created above ^^
            // then this should make the sample view public, otherwise it will become private
            if (sampleView != null)
            {
                // this extension method wraps the job polling
                sampleView = await modelSetClient.UpdateModelSetView(
                    modelSetCreateSampleState.ModelSet.ContainerId,
                    modelSetCreateSampleState.ModelSet.ModelSetId,
                    sampleView.ViewId,
                    new UpdateModelSetView
                {
                    OldIsPrivate = sampleView.IsPrivate,
                    NewIsPrivate = !sampleView.IsPrivate
                });

                ColourConsole.WriteSuccess($"Toggled sample view is private: {sampleView.IsPrivate}");
            }

            // finally if the sample view exists, instance it against the tip
            if (sampleView != null)
            {
                var viewVersion = await modelSetClient.GetModelSetViewVersionAsync(
                    modelSetCreateSampleState.ModelSet.ContainerId,
                    modelSetTipVersion.ModelSetId,
                    modelSetTipVersion.Version,
                    sampleView.ViewId);

                foreach (var doc in viewVersion.DocumentVersions)
                {
                    ColourConsole.WriteSuccess($"View member {doc.DisplayName} => {doc.VersionUrn}");
                }
            }
        }
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // cache some local state for later...
            var state = new GetClashResultsSampleState();

            // load the state from CreateModelSetSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var modelSetCreateSampleState = await fileManager.ReadJsonAsync <CreateModelSetSampleState>()
                                            ?? throw new InvalidOperationException("Could not load CreateModelSetSampleState, have you run CreateModelSetSample?");

            state.Container = modelSetCreateSampleState.ModelSet.ContainerId;

            // load the clash tests for the model set we created in  CreateModelSetSample
            // and try and find the latest clash test to the latest model set verison
            var clashClient = serviceProvider.GetRequiredService <IClashClient>();

            var moelSetClashTests = await clashClient.GetModelSetClashTestsAsync(modelSetCreateSampleState.ModelSet.ContainerId, modelSetCreateSampleState.ModelSet.ModelSetId, null, null);

            if (moelSetClashTests.Tests.Count > 0)
            {
                foreach (var test in moelSetClashTests.Tests.OrderBy(t => t.ModelSetVersion))
                {
                    ColourConsole.WriteSuccess($"Test {test.ModelSetId}:{test.ModelSetVersion:00}; status {test.Status}; completed {test.CompletedOn?.ToString("u")}");
                }

                state.Latest = moelSetClashTests.Tests.OrderBy(t => t.ModelSetVersion).Last();
            }
            else
            {
                ColourConsole.WriteInfo($"No clash test result found for {modelSetCreateSampleState.ModelSet.ModelSetId}, try again later.");
            }

            // make sure we can load this clash test
            if (state.HasLatest)
            {
                _ = await clashClient.GetClashTestAsync(modelSetCreateSampleState.ModelSet.ContainerId, state.Latest.Id)
                    ?? throw new InvalidOperationException($"Error getting model set clash test {state.Latest.Id}");
            }
            else
            {
                ColourConsole.WriteInfo("No latest clash test skipping get model set clash test");
            }

            // get the resources for the latest clash test
            if (state.HasLatest)
            {
                state.ResourceCollection = await clashClient.GetClashTestResourcesAsync(modelSetCreateSampleState.ModelSet.ContainerId, state.Latest.Id);

                if (state.ResourceCollection?.Resources.Count > 0)
                {
                    foreach (var res in state.ResourceCollection.Resources)
                    {
                        ColourConsole.WriteSuccess($"Found clash resource {res.Type}");
                    }
                }
                else
                {
                    ColourConsole.WriteInfo($"No resources found for latest clash test {state.Latest.Id}");
                }
            }
            else
            {
                ColourConsole.WriteInfo("No latest clash test skipping get model set clash test resources");
            }

            // download the reources for the latest clash test
            if (state.HasResources)
            {
                foreach (var resource in state.ResourceCollection.Resources)
                {
                    var name = new Uri(resource.Url).Segments.Last();

                    var fout = fileManager.NewPath(name);

                    ColourConsole.WriteInfo($"Download {fout.Name}");

                    await resource.DownloadClashTestResource(fout);

                    state.LocalResourcePaths[resource.Url] = fout;

                    ColourConsole.WriteSuccess($"Downloaded {fout.Name}");
                }
            }

            // Query the results of the clash test
            if (state.HasResources)
            {
                var documentIndexFile = state.LocalResourcePaths.Values.Single(f => f.Name.Equals("scope-version-document.2.0.0.json.gz", StringComparison.OrdinalIgnoreCase));

                var documentIndexReader = new ClashResultReader <ClashDocument>(documentIndexFile, true);

                var documentIndex = new Dictionary <int, string>();

                await documentIndexReader.Read(doc =>
                {
                    documentIndex[doc.Index] = doc.Urn;

                    return(Task.FromResult(true));
                });

                var clashResultFile = state.LocalResourcePaths.Values.Single(f => f.Name.Equals("scope-version-clash.2.0.0.json.gz", StringComparison.OrdinalIgnoreCase));

                var clashIndex = new Dictionary <int, Clash>();

                var clashReader = new ClashResultReader <Clash>(clashResultFile, true);

                await clashReader.Read(c =>
                {
                    clashIndex[c.Id] = c;

                    return(Task.FromResult(true));
                });

                // Show counts for the different clash statuses
                foreach (var group in clashIndex.Values.GroupBy(c => c.Status))
                {
                    ColourConsole.WriteSuccess($"Clash count for status {group.Key}: {group.Count()}");
                }

                // get the clsh instance details
                var clashInstanceFile = state.LocalResourcePaths.Values.Single(f => f.Name.Equals("scope-version-clash-instance.2.0.0.json.gz", StringComparison.OrdinalIgnoreCase));

                var clashInstanceReader = new ClashResultReader <ClashInstance>(clashInstanceFile, true);

                var clashInstanceIndex = new Dictionary <int, ClashInstance>();

                await clashInstanceReader.Read(ci =>
                {
                    clashInstanceIndex[ci.ClashId] = ci;

                    return(Task.FromResult(true));
                });

                // pick a random clash an view it
                var rnd = new Random(Guid.NewGuid().GetHashCode());

                var clash = clashIndex.Values.Skip(rnd.Next(0, clashIndex.Count - 1)).Take(1).Single();

                ColourConsole.WriteSuccess($"Clash : {clash.Id}");
                ColourConsole.WriteSuccess($"Left Document : {documentIndex[clashInstanceIndex[clash.Id].LeftDocumentIndex]}");
                ColourConsole.WriteSuccess($"Left Stable Object ID : {clashInstanceIndex[clash.Id].LeftStableObjectId}");
                ColourConsole.WriteSuccess($"Left LMV ID : {clashInstanceIndex[clash.Id].LeftLmvObjectId}");
                ColourConsole.WriteSuccess($"Right Document : {documentIndex[clashInstanceIndex[clash.Id].RightDocumentIndex]}");
                ColourConsole.WriteSuccess($"Right Stable Object ID : {clashInstanceIndex[clash.Id].RightStableObjectId}");
                ColourConsole.WriteSuccess($"Right LMV ID : {clashInstanceIndex[clash.Id].RightLmvObjectId}");
            }

            // save the state for use in subsequent samples
            var stateFile = await fileManager.WriteJsonAsync(state);

            ColourConsole.WriteSuccess($"Sample state written to {stateFile.FullName}");
        }
Exemple #8
0
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from GetClashResultsSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var clashResultSampleState = await fileManager.ReadJsonAsync <GetClashResultsSampleState>()
                                         ?? throw new InvalidOperationException("Could not load GetClashResultsSampleState.json, have you run GetClashResultsSample?");

            // get the model set versoin associated with the clash test
            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            var modelSetVersion = await modelSetClient.GetModelSetVersionAsync(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.ModelSetId,
                clashResultSampleState.Latest.ModelSetVersion);

            ColourConsole.WriteSuccess($"Loaded model set version {clashResultSampleState.Latest.ModelSetId}:{clashResultSampleState.Latest.ModelSetVersion}");

            // Get the first page of assigned clash groups AKA coordination issues.
            var clashClient = serviceProvider.GetRequiredService <IClashClient>();

            var assignedClashGroups = await clashClient.GetClashTestAssignedClashGroupIntersectionAsync(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.Id,
                null,
                null);

            ColourConsole.WriteSuccess($"Loaded page 1 of asigned clash issues with test context {clashResultSampleState.Latest.Id}");

            // Get the details of the clash groups returned
            var assignedClashGroupDetails = await clashClient.GetAssignedClashGroupBatchAsync(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.Id,
                false,
                assignedClashGroups.Groups.Select(cg => cg.Id));

            ColourConsole.WriteSuccess($"Loaded {assignedClashGroups.Groups.Count} clash groups");

            // get the index manifest and map the document URNs from the clash data to index seed file document keys
            var modelSetIndex = serviceProvider.GetRequiredService <IModelSetIndex>();

            var indexManifest = await modelSetIndex.GetManifest(
                clashResultSampleState.Container,
                clashResultSampleState.Latest.ModelSetId,
                clashResultSampleState.Latest.ModelSetVersion)
                                ?? throw new InvalidOperationException($"No indfex manifest found for {clashResultSampleState.Latest.ModelSetVersion}:{clashResultSampleState.Latest.ModelSetVersion}");

            ColourConsole.WriteSuccess($"Loaded index manifest {clashResultSampleState.Latest.ModelSetId}:{clashResultSampleState.Latest.ModelSetVersion}");

            // map of document version URN -> document index key
            var documentObjectIndexIdMap = modelSetVersion.DocumentVersions.ToDictionary(
                doc => doc.VersionUrn,
                doc => new IndexDocumentObjects
            {
                SeedFileVersionUrn = doc.OriginalSeedFileVersionUrn,
                DocumentVersionUrn = doc.VersionUrn,
                IndexFileKey       = indexManifest.SeedFiles.Single(sf => sf.Urn.Equals(doc.OriginalSeedFileVersionUrn, StringComparison.OrdinalIgnoreCase)).Id,
                IndexDocumentKey   = indexManifest.SeedFiles.SelectMany(sf => sf.Documents).Single(d => d.VersionUrn.Equals(doc.VersionUrn, StringComparison.OrdinalIgnoreCase)).Id
            },
                StringComparer.OrdinalIgnoreCase);

            foreach (var kvp in documentObjectIndexIdMap)
            {
                ColourConsole.WriteInfo($"File {kvp.Value.IndexFileKey} contains document {kvp.Value.IndexDocumentKey}");
            }

            // Grab all of the objects we are intrested in by getting the left and right LMV object IDs from the individual clashes.
            foreach (var clashGroupDetail in assignedClashGroupDetails)
            {
                var documentIdMap = clashGroupDetail.ClashData.Documents.ToDictionary(d => d.Id, d => d.Urn);

                foreach (var clashInstance in clashGroupDetail.ClashData.ClashInstances)
                {
                    documentObjectIndexIdMap[documentIdMap[clashInstance.Ldid]].AddObjectId(clashInstance.Lvid);
                    documentObjectIndexIdMap[documentIdMap[clashInstance.Rdid]].AddObjectId(clashInstance.Rvid);
                }
            }

            var rows = new List <IndexRow>();

            foreach (var kvp in documentObjectIndexIdMap)
            {
                if (kvp.Value.Objects.Count == 0)
                {
                    continue;
                }

                ColourConsole.WriteInfo($"Query file key {kvp.Value.IndexFileKey}, objects {string.Join(',', kvp.Value.Objects)}");

                // get the objects in this seed file using WHERE IN - watch out for ovweflow
                // this might need to be chunked into multiple queries if you have lots of objects...
                string query = $"select * from s3object s where s.file = '{kvp.Value.IndexFileKey}' and s.id in (" + string.Join(',', kvp.Value.Objects) + ")";

                ColourConsole.WriteInfo(query);

                var queryResults = await modelSetIndex.Query(
                    clashResultSampleState.Container,
                    clashResultSampleState.Latest.ModelSetId,
                    clashResultSampleState.Latest.ModelSetVersion, query);

                ColourConsole.WriteSuccess($"Query results downloaded to {queryResults.FullName}");

                // itterate over the results and pull out the rows
                var reader = new IndexResultReader(queryResults, null);

                var summary = await reader.ReadToEndAsync(obj =>
                {
                    rows.Add(obj);
                    return(Task.FromResult(true));
                }, false);
            }

            // get the issue container for this project
            var dataClient = serviceProvider.GetRequiredService <IForgeDataClient>();

            dynamic obj = await dataClient.GetProjectAsJObject()
                          ?? throw new InvalidOperationException($"Could not load prject {configuration.ProjectId}");

            Guid issueContainer = obj.data.relationships.issues.data.id;

            var issueClient = serviceProvider.GetRequiredService <IForgeIssueClient>();

            foreach (var clashGroupDetail in assignedClashGroupDetails)
            {
                dynamic issue = await issueClient.GetIssue(issueContainer, clashGroupDetail.IssueId);

                ColourConsole.WriteSuccess($"Issue : {issue.data.attributes.title}");

                foreach (var clashInstance in clashGroupDetail.ClashData.ClashInstances)
                {
                    var leftDocument = documentObjectIndexIdMap[clashGroupDetail.ClashData.Documents.Single(d => d.Id == clashInstance.Ldid).Urn];
                    var leftObject   = rows.SingleOrDefault(r => r.Id == clashInstance.Lvid && r.DocumentIds.Contains(leftDocument.IndexDocumentKey));

                    var rightDocument = documentObjectIndexIdMap[clashGroupDetail.ClashData.Documents.Single(d => d.Id == clashInstance.Rdid).Urn];
                    var rightObject   = rows.SingleOrDefault(r => r.Id == clashInstance.Rvid && r.DocumentIds.Contains(rightDocument.IndexDocumentKey));

                    ColourConsole.WriteInfo($"  {(string)leftObject.Data["p153cb174"]} clashes with {(string)rightObject.Data["p153cb174"]}");
                }
            }
        }
        public static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Configure(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            // load the state from GetClashResultsSample
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            var clashResultSampleState = await fileManager.ReadJsonAsync <GetClashResultsSampleState>()
                                         ?? throw new InvalidOperationException("Could not load GetClashResultsSampleState.json, have you run GetClashResultsSample?");

            // get the issue container for this project
            var dataClient = serviceProvider.GetRequiredService <IForgeDataClient>();

            dynamic obj = await dataClient.GetProjectAsJObject()
                          ?? throw new InvalidOperationException($"Could not load prject {configuration.ProjectId}");

            Guid issueContainer = obj.data.relationships.issues.data.id;

            ColourConsole.WriteSuccess($"Using issue container {issueContainer}");

            // get the first page of assigned clash groups AKA coordination issues
            // and display their details by calling the BIM 360 issues service
            var clashClient = serviceProvider.GetRequiredService <IClashClient>();

            var issueClient = serviceProvider.GetRequiredService <IForgeIssueClient>();

            var assignedClashGroups = await clashClient.GetClashTestAssignedClashGroupIntersectionAsync(clashResultSampleState.Container, clashResultSampleState.Latest.Id, null, null);

            if (assignedClashGroups.Groups.Count > 0)
            {
                var assignedClashGroupDetails = await clashClient.GetAssignedClashGroupBatchAsync(
                    clashResultSampleState.Container,
                    clashResultSampleState.Latest.Id,
                    false,
                    assignedClashGroups.Groups.Select(cg => cg.Id));

                foreach (var cg in assignedClashGroups.Groups)
                {
                    var detail = assignedClashGroupDetails.Single(i => i.Id == cg.Id);

                    // https://forge.autodesk.com/en/docs/bim360/v1/reference/http/field-issues-:id-GET/
                    dynamic issue = await issueClient.GetIssue(issueContainer, detail.IssueId);

                    Console.WriteLine();
                    ColourConsole.WriteSuccess($"Issue : {detail.IssueId}");
                    ColourConsole.WriteSuccess($"Title : {issue.data.attributes.title}");
                    ColourConsole.WriteSuccess($"Status : {issue.data.attributes.status}");
                    ColourConsole.WriteSuccess($"Created on : {issue.data.attributes.created_at}");
                    ColourConsole.WriteSuccess($"Clash count : {detail.ClashData.Clashes.Count}");
                    ColourConsole.WriteSuccess($"Clash document count : {detail.ClashData.Documents.Count}");
                    ColourConsole.WriteSuccess($"Clash instance count : {detail.ClashData.ClashInstances.Count}");
                    ColourConsole.WriteSuccess($"Existing clash count : {cg?.Existing.Count}");
                    ColourConsole.WriteSuccess($"Resolved clash count : {cg?.Resolved.Count}");
                }
            }

            // do the samme for the closed (ignored) clash groups - instead of querying the issue
            // (one does not exist) download the screenshot associated with the closed clash group
            var closedClashGroups = await clashClient.GetClashTestClosedClashGroupIntersectionAsync(clashResultSampleState.Container, clashResultSampleState.Latest.Id, null, null);

            if (closedClashGroups.Groups.Count > 0)
            {
                var closedClashGroupDetails = await clashClient.GetClosedClashGroupDataBatchAsync(
                    clashResultSampleState.Container,
                    clashResultSampleState.Latest.Id,
                    closedClashGroups.Groups.Select(cg => cg.Id));

                foreach (var cg in closedClashGroups.Groups)
                {
                    var detail = closedClashGroupDetails.Single(i => i.Id == cg.Id);

                    Console.WriteLine();
                    ColourConsole.WriteSuccess($"Closed : {detail.Title}");
                    ColourConsole.WriteSuccess($"Reason : {detail.Reason}");
                    ColourConsole.WriteSuccess($"Created on : {detail.CreatedOn}");
                    ColourConsole.WriteSuccess($"Clash count : {detail.ClashData.Clashes.Count}");
                    ColourConsole.WriteSuccess($"Clash document count : {detail.ClashData.Documents.Count}");
                    ColourConsole.WriteSuccess($"Clash instance count : {detail.ClashData.ClashInstances.Count}");
                    ColourConsole.WriteSuccess($"Existing clash count : {cg?.Existing.Count}");
                    ColourConsole.WriteSuccess($"Resolved clash count : {cg?.Resolved.Count}");

                    if (detail?.ScreenShots.Count > 0)
                    {
                        var screenShotId = detail.ScreenShots.First();

                        var screenShot = fileManager.NewPath($"{screenShotId}.png");

                        using (var ss = await clashClient.GetScreenShotAsync(clashResultSampleState.Container, clashResultSampleState.Latest.ModelSetId, screenShotId))
                            using (var fout = screenShot.Open(FileMode.Create))
                            {
                                await ss.Stream.CopyToAsync(fout);

                                ColourConsole.WriteSuccess($"First screenshot : {screenShot.FullName}");
                            }
                    }
                }
            }
        }
        private static async Task RunAsync()
        {
            var configuration = new SampleConfiguration();

            //-----------------------------------------------------------------------------------------------------
            // Sample Configuration
            //-----------------------------------------------------------------------------------------------------
            // Either add a .adsk-forge/SampleConfiguration.json file to the Environment.SpecialFolder.UserProfile
            // folder (this will be different on windows vs mac/linux) or pass the optional configuration
            // Dictionary<string, string> to set AuthToken, Account and Project values on SampleConfiguration
            //
            // configuration.Load(new Dictionary<string, string>
            // {
            //     { "AuthToken", "Your Forge App OAuth token" },
            //     { "AccountId", "Your BIM 360 account GUID (no b. prefix)" },
            //     { "ProjectId", "Your BIM 360 project GUID (no b. prefix)"}
            // });
            //
            // See: SampleConfigurationExtensions.cs for more information.
            //-----------------------------------------------------------------------------------------------------

            configuration.Load();

            // create a service provider for IoC composition
            var serviceProvider = new ServiceCollection()
                                  .AddSampleForgeServices(configuration)
                                  .BuildServiceProvider();

            var state = new CreateModelSetSampleState();

            // find the project files folder
            var forgeDataClient = serviceProvider.GetRequiredService <IForgeDataClient>();

            state.PlansFolder = await forgeDataClient.FindTopFolderByName("Plans") ?? throw new InvalidOperationException("Could not find Plans folder!");

            ColourConsole.WriteSuccess($"Found project plans folder {state.PlansFolder.Id}");

            // try and find the sample root folder MC_SAMPLE in the root of Plans, if not found create it.
            state.TestFolderRoot = await forgeDataClient.FindFolderByName(state.PlansFolder.Id, CreateModelSetSampleState.MC_SAMPLE_FOLDER_NAME);

            if (state.TestFolderRoot == null)
            {
                state.TestFolderRoot = await forgeDataClient.CreateFolder(state.PlansFolder.Id, CreateModelSetSampleState.MC_SAMPLE_FOLDER_NAME)
                                       ?? throw new InvalidOperationException($"Create {CreateModelSetSampleState.MC_SAMPLE_FOLDER_NAME} failed!");
            }

            // Now that the MC sample root folder has been created make a new folder beneath this folder for this run
            // of this console application. This is just a MC_{UTD-DATA-NOW} folder. This is not a convention for
            // Model Coordination in general, it is just how this sample code is laid out.
            var testFolderName = $"MC_{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}";

            state.TestFolder = await forgeDataClient.CreateFolder(state.TestFolderRoot.Id, testFolderName)
                               ?? throw new InvalidOperationException($"Create {testFolderName} failed!");

            ColourConsole.WriteSuccess($"Created test folder {testFolderName}");

            // Find the models that this sample code uploads into the test folder created above. This will trigger the
            // BIM 360 Docs extraction workflow for these files. The upload process has two phases, (1) sending the bytes
            // to OSS (Autodesk cloud bucket storage) and (2) registerig these OSS resources with Forge-DM to make them
            // accessable via BIM 360 Docs.
            var fileManager = serviceProvider.GetRequiredService <ILocalFileManager>();

            state.Uploads.Add(new ForgeUpload
            {
                File = fileManager.GetPathToSampleModelFile(SampleModelSet.Audubon.V1, "Audubon_Architecture.rvt")
            });

            state.Uploads.Add(new ForgeUpload
            {
                File = fileManager.GetPathToSampleModelFile(SampleModelSet.Audubon.V1, "Audubon_Structure.rvt")
            });

            state.Uploads.Add(new ForgeUpload
            {
                File = fileManager.GetPathToSampleModelFile(SampleModelSet.Audubon.V1, "Audubon_Mechanical.rvt")
            });

            // For each file :-
            // - Create an OSS storage location
            // - Uplaod the files to OSS in chunks
            // - Create the Item(and Version) representing the bytes via Forge - DM
            foreach (var upload in state.Uploads)
            {
                ColourConsole.WriteInfo($"Upload {upload.File.FullName}");

                upload.Storage = await forgeDataClient.CreateOssStorage(state.TestFolder.Id, upload.File.Name)
                                 ?? throw new InvalidOperationException($"Crete OSS storage object for {upload.File.Name} failed!");

                upload.Result = await forgeDataClient.Upload(upload.File, upload.Storage);

                await forgeDataClient.CreateItem(state.TestFolder.Id, upload.Storage.Id, upload.File.Name);

                ColourConsole.WriteSuccess($"Uploaded {upload.File.Name}");
            }

            // create a model set for the new folder
            var modelSetClient = serviceProvider.GetRequiredService <IModelSetClient>();

            state.ModelSet = await modelSetClient.CreateModelSet(
                configuration.ProjectId,
                state.TestFolder.Name,
                new ModelSetFolder[]
            {
                new ModelSetFolder
                {
                    FolderUrn = state.TestFolder.Id
                }
            });

            ColourConsole.WriteSuccess($"Created model set {state.ModelSet.ModelSetId}");

            // save the state for use in subsequent samples
            var stateFile = await fileManager.WriteJsonAsync(state);

            ColourConsole.WriteSuccess($"Sample state written to {stateFile.FullName}");
        }