/// <summary> /// Wrap input geometry into module cages. /// </summary> /// <param name="DA">The DA object can be used to retrieve data from /// input parameters and to store data in output parameters.</param> protected override void SolveInstance(IGH_DataAccess DA) { var geometryRaw = new List <IGH_GeometricGoo>(); var module = new Module(); var basePlane = new Plane(); if (!DA.GetDataList(0, geometryRaw)) { return; } if (!DA.GetData(1, ref module)) { return; } if (!DA.GetData(2, ref basePlane)) { return; } if (module == null || !module.IsValid) { AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Module is null or invalid."); return; } // Transform the geometry to be oriented to world XYZ fore easier scanning var geometryTransform = Transform.PlaneToPlane(basePlane, Plane.WorldXY); var geometryClean = geometryRaw .Select(goo => GH_Convert.ToGeometryBase(goo)) .Where(geo => geo != null) .Select(geo => { var transformedGeometry = geo.Duplicate(); transformedGeometry.Transform(geometryTransform); return(transformedGeometry); }).ToList(); if (!geometryClean.Any()) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Failed to collect any valid geometry to scan."); return; } var moduleGeometry = module.Geometry .Concat(module.ReferencedGeometry); if (!moduleGeometry.Any()) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Module \"" + module.Name + "\" contains " + "no geometry and therefore will be skipped."); return; } var moduleGeometryBBoxes = moduleGeometry.Select(geo => geo.GetBoundingBox(true)); var bBoxUnionModule = BoundingBox.Empty; bBoxUnionModule.Union(module.Pivot.Origin); foreach (var moduleBBox in moduleGeometryBBoxes) { bBoxUnionModule.Union(moduleBBox); } var moduleDimensionSafetyBuffer = new Point3i( (int)Math.Ceiling(bBoxUnionModule.Diagonal.X / module.PartDiagonal.X) + 1, (int)Math.Ceiling(bBoxUnionModule.Diagonal.Y / module.PartDiagonal.Y) + 1, (int)Math.Ceiling(bBoxUnionModule.Diagonal.Z / module.PartDiagonal.Z) + 1); var geometryBBoxes = geometryClean.Select(geo => geo.GetBoundingBox(true)).ToList(); var bBoxUnionGeometry = BoundingBox.Empty; foreach (var bBox in geometryBBoxes) { bBoxUnionGeometry.Union(bBox); } var slots = new List <Slot>(); for (int z = (int)Math.Floor(bBoxUnionGeometry.Min.Z / module.PartDiagonal.Z) - moduleDimensionSafetyBuffer.Z; z < Math.Ceiling(bBoxUnionGeometry.Max.Z / module.PartDiagonal.Z) + moduleDimensionSafetyBuffer.Z; z++) { for (int y = (int)Math.Floor(bBoxUnionGeometry.Min.Y / module.PartDiagonal.Y) - moduleDimensionSafetyBuffer.Y; y < Math.Ceiling(bBoxUnionGeometry.Max.Y / module.PartDiagonal.Y) + moduleDimensionSafetyBuffer.Y; y++) { for (int x = (int)Math.Floor(bBoxUnionGeometry.Min.X / module.PartDiagonal.X) - moduleDimensionSafetyBuffer.X; x < Math.Ceiling(bBoxUnionGeometry.Max.X / module.PartDiagonal.X) + moduleDimensionSafetyBuffer.X; x++) { var currentRelativePosition = new Point3i(x, y, z); var currentPivot = Plane.WorldXY; currentPivot.Origin = currentRelativePosition.ToCartesian(Plane.WorldXY, module.PartDiagonal); var transformModuleToCurrentPivot = Transform.PlaneToPlane(module.Pivot, currentPivot); var moduleGeometryBBoxesAtCurrentPivot = moduleGeometryBBoxes.Select(bBox => { var transformedBBox = bBox; transformedBBox.Transform(transformModuleToCurrentPivot); return(transformedBBox); }); var indicesOfSimilarBBoxes = moduleGeometryBBoxesAtCurrentPivot.Select(moduleGeometryBBox => geometryBBoxes.Select((geometryBBox, index) => { var moduleCorners = moduleGeometryBBox.GetCorners().ToList(); var geometryCorners = geometryBBox.GetCorners().ToList(); moduleCorners.Sort(); geometryCorners.Sort(); if (Enumerable.SequenceEqual(moduleCorners, geometryCorners)) { return(index); } else { return(-1); } }).Where(index => index != -1) ); // If any module geometry doesn't have a bbox similar to any geometry, then early continue if (!indicesOfSimilarBBoxes.All(similarBBoxes => similarBBoxes.Any())) { continue; } // Heavy calculations var transformedModuleGeometry = moduleGeometry.Select(geo => { var transformedGeometry = geo.Duplicate(); transformedGeometry.Transform(transformModuleToCurrentPivot); return(transformedGeometry); }); var geometriesToCheck = indicesOfSimilarBBoxes.Select(indices => indices.Select(index => geometryClean[index])); var geometryEqualityPattern = transformedModuleGeometry .Zip(geometriesToCheck, (current, others) => others.Any(other => // TODO: when the original geometry is moved, the meshes become unequal // TODO: replace with visual similarity check (pull random points to geometry) // TODO: check if the two are of the same type first GeometryBase.GeometryEquals(current, other) )); if (geometryEqualityPattern.All(equal => equal)) { var firstModulePartRelativeCenter = module.PartCenters[0]; var modulePartsRelativeCentersRelativeToModulePivot = module.PartCenters.Select(center => center - firstModulePartRelativeCenter); var currentModuleSlots = modulePartsRelativeCentersRelativeToModulePivot .Zip( module.PartNames, (partCenter, partName) => new Slot(basePlane, currentRelativePosition + partCenter, module.PartDiagonal, false, new List <string>() { module.Name }, new List <string>() { partName }, 0)); slots.AddRange(currentModuleSlots); } } } } if (!Slot.AreSlotLocationsUnique(slots)) { AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Slot centers are not unique."); } DA.SetDataList(0, slots); }