public List <PolyLine2d> GetPolylinesForLayer(int layer)
        {
            ToolpathSet pathSetIn = Paths;

            SKColor    extrudeColor = SkiaUtil.Color(0, 0, 0, 255);
            Interval1d layer_zrange = Layers.GetLayerZInterval(layer);

            List <PolyLine2d> polylines = new List <PolyLine2d>();

            Action <LinearToolpath3 <PrintVertex> > drawPath3F = (polyPath) => {
                Vector3d v0 = polyPath.Start.Position;
                if (layer_zrange.Contains(v0.z) == false)
                {
                    return;
                }
                if (polyPath.Type != ToolpathTypes.Deposition)
                {
                    return;
                }

                PolyLine2d pline = new PolyLine2d();
                for (int i = 0; i < polyPath.VertexCount; ++i)
                {
                    pline.AppendVertex(polyPath[i].Position.xy);
                }

                polylines.Add(pline);
            };

            ProcessLinearPaths(pathSetIn, drawPath3F);

            return(polylines);
        }
Example #2
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="interval"></param>
        /// <returns></returns>
        public static BoundingBox ToBoundingBox(this Interval2d interval)
        {
            Interval1d x = interval.X;
            Interval1d y = interval.Y;

            return(new BoundingBox(x.A, y.A, 0.0, x.B, y.B, 0.0));
        }
            public void Build(ToolpathSet toolpaths, Interval1d zrange, double cellSize = 2.0)
            {
                segments = new DVector <Segment2d>();
                grid     = new SegmentHashGrid2d <int>(cellSize, -1);

                Action <LinearToolpath3 <PrintVertex> > processPathF = (polyPath) => {
                    if (polyPath.Type != ToolpathTypes.Deposition || polyPath.IsPlanar == false)
                    {
                        return;
                    }
                    if ((polyPath.TypeModifiers & FillTypeFlags.OutermostShell) == 0)
                    {
                        return;
                    }
                    Vector3d v0 = polyPath.Start.Position;
                    Vector3d v1 = polyPath.End.Position;
                    if (zrange.Contains(v0.z) == false || zrange.Contains(v1.z) == false)
                    {
                        return;
                    }
                    append_path(polyPath, cellSize);
                };

                process_linear_paths(toolpaths, processPathF);
            }
Example #4
0
 public static PlanarSlice FactoryF(Interval1d ZSpan, double Zheight, int idx)
 {
     return(new PlanarSlicePro()
     {
         LayerZSpan = ZSpan, Z = Zheight, LayerIndex = idx
     });
 }
Example #5
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="interval"></param>
        /// <returns></returns>
        public static BoundingBox ToBoundingBox(this Interval3d interval)
        {
            Interval1d x = interval.X;
            Interval1d y = interval.Y;
            Interval1d z = interval.Z;

            return(new BoundingBox(x.A, y.A, z.A, x.B, y.B, z.B));
        }
Example #6
0
 public static double[] RandomScalars(int Count, Random r, Interval1d range)
 {
     double[] v = new double[Count];
     for (int k = 0; k < Count; ++k)
     {
         v[k] = range.a + r.NextDouble() * range.Length;
     }
     return(v);
 }
 /// <summary>
 ///
 /// </summary>
 public static void Evaluate(this IDiscreteField <double> field, Interval1d interval, IDiscreteField <double> result, bool parallel = false)
 {
     if (parallel)
     {
         ArrayMath.EvaluateParallel(field.Values, interval, result.Values);
     }
     else
     {
         ArrayMath.Evaluate(field.Values, interval, result.Values);
     }
 }
 /// <summary>
 ///
 /// </summary>
 public static void Remap(this IDiscreteField <double> field, Interval1d from, Interval1d to, IDiscreteField <double> result, bool parallel = false)
 {
     if (parallel)
     {
         ArrayMath.RemapParallel(field.Values, from, to, result.Values);
     }
     else
     {
         ArrayMath.Remap(field.Values, from, to, result.Values);
     }
 }
Example #9
0
 public double SquaredDist(Interval1d o)
 {
     if (b < o.a)
     {
         return((o.a - b) * (o.a - b));
     }
     else if (a > o.b)
     {
         return((a - o.b) * (a - o.b));
     }
     else
     {
         return(0);
     }
 }
Example #10
0
 public double Dist(Interval1d o)
 {
     if (b < o.a)
     {
         return(o.a - b);
     }
     else if (a > o.b)
     {
         return(a - o.b);
     }
     else
     {
         return(0);
     }
 }
Example #11
0
        // internal api

        public InputField RegisterFloatInput(string inputName, string toolParamName, Interval1d validRange, string formatString = null)
        {
            InputField input = UnityUIUtil.FindInputAndAddFloatHandlers(this.gameObject, inputName,
                                                                        () => { return((float)ActiveParameterSet.GetValueDouble(toolParamName)); },
                                                                        (floatValue) => { ActiveParameterSet.SetValue <double>(toolParamName, floatValue); update_values_from_tool(); },
                                                                        (float)validRange.a, (float)validRange.b);

            TabOrder.Add(input);

            float_params.Add(new FloatInputParam()
            {
                widget = input, paramName = toolParamName, formatString = formatString
            });

            return(input);
        }
Example #12
0
        protected List <List <Segment2d> > ComputeSegments(GeneralPolygon2d poly, SegmentSet2d polyCache)
        {
            List <List <Segment2d> > PerRaySpans = new List <List <Segment2d> >();

            double   angleRad = AngleDeg * MathUtil.Deg2Rad;
            Vector2d dir      = new Vector2d(Math.Cos(angleRad), Math.Sin(angleRad));

            // compute projection span along axis
            Vector2d   axis         = dir.Perp;
            Interval1d axisInterval = Interval1d.Empty;
            Interval1d dirInterval  = Interval1d.Empty;

            foreach (Vector2d v in poly.Outer.Vertices)
            {
                dirInterval.Contain(v.Dot(dir));
                axisInterval.Contain(v.Dot(axis));
            }
            // [TODO] also check holes? or assume they are contained?

            dirInterval.a -= 10 * ToolWidth;
            dirInterval.b += 10 * ToolWidth;
            double extent = dirInterval.Length;

            axisInterval.a += ToolWidth * 0.1 + PathShift;
            axisInterval.b -= ToolWidth * 0.1;
            if (axisInterval.b < axisInterval.a)
            {
                return(PerRaySpans);                            // [RMS] is this right? I guess so. interval is too small to fill?
            }
            Vector2d startCorner = axisInterval.a * axis + dirInterval.a * dir;
            double   range       = axisInterval.Length;
            int      N           = (int)(range / PathSpacing);

            for (int ti = 0; ti <= N; ++ti)
            {
                double    t   = (double)ti / (double)N;
                Vector2d  o   = startCorner + (t * range) * axis;
                Segment2d ray = new Segment2d(o, o + extent * dir);

                List <Segment2d> spans = compute_polygon_ray_spans(poly, ray, startCorner, axis, t, polyCache);
                PerRaySpans.Add(spans);
            }

            return(PerRaySpans);
        }
Example #13
0
 public void FindTrianglesInScalarInterval(Interval1d interval, List <int> triangles)
 {
     if (tri_ordering == null)
     {
         throw new InvalidOperationException("MeshFaceSelectionCace.FindTrianglesInScalarInterval: ordering is not computed");
     }
     for (int k = 0; k < tri_ordering.Length; ++k)
     {
         double s = tri_ordering[k].scalar;
         if (s > interval.b)
         {
             break;
         }
         if (s > interval.a)
         {
             triangles.Add(tri_ordering[k].tid);
         }
     }
 }
        /// <summary>
        ///
        /// </summary>
        /// <returns></returns>
        Mesh SolveInstanceImpl(HeMesh3d mesh, IReadOnlyList <Color> colors, Interval1d interval, out Interval1d range)
        {
            var verts = mesh.Vertices;
            var faces = mesh.Faces;

            var planarDev = new double[faces.Count];

            mesh.GetFacePlanarity(v => v.Position, (f, t) => planarDev[f] = t);

            // get planarity range
            range = new Interval1d(planarDev);
            if (!interval.IsValid)
            {
                interval = range;
            }

            // create new mesh
            return(mesh.ToPolySoup(f => colors.Lerp(interval.Normalize(planarDev[f]))));
        }
Example #15
0
        public override DeformInfo Apply(Frame3f vNextPos)
        {
            Interval1d edgeRangeSqr = Interval1d.Empty;

            int N = Curve.VertexCount;

            if (N > NewV.Size)
            {
                NewV.resize(N);
            }
            if (N > ModifiedV.Length)
            {
                ModifiedV = new BitArray(2 * N);
            }

            // clear modified
            ModifiedV.SetAll(false);

            bool   bSmooth = (SmoothAlpha > 0 && SmoothIterations > 0);
            double r2      = Radius * Radius;

            // deform pass
            if (DeformF != null)
            {
                for (int i = 0; i < N; ++i)
                {
                    Vector3D v  = Curve[i];
                    double   d2 = (v - vPreviousPos.Origin).LengthSquared;
                    if (d2 < r2)
                    {
                        double t = WeightFunc(Math.Sqrt(d2), Radius);

                        Vector3D vNew = DeformF(i, t);

                        if (bSmooth == false)
                        {
                            if (i > 0)
                            {
                                edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i - 1]));
                            }
                            if (i < N - 1)
                            {
                                edgeRangeSqr.Contain(vNew.DistanceSquared(Curve[i + 1]));
                            }
                        }

                        NewV[i]      = vNew;
                        ModifiedV[i] = true;
                    }
                }
            }
            else
            {
                // anything?
            }

            // smooth pass
            if (bSmooth)
            {
                for (int j = 0; j < SmoothIterations; ++j)
                {
                    int iStart = (Curve.Closed) ? 0 : 1;
                    int iEnd   = (Curve.Closed) ? N : N - 1;
                    for (int i = iStart; i < iEnd; ++i)
                    {
                        Vector3D v  = (ModifiedV[i]) ? NewV[i] : Curve[i];
                        double   d2 = (v - vPreviousPos.Origin).LengthSquared;
                        if (ModifiedV[i] || d2 < r2)            // always smooth any modified verts
                        {
                            double a = SmoothAlpha * WeightFunc(Math.Sqrt(d2), Radius);

                            int      iPrev = (i == 0) ? N - 1 : i - 1;
                            int      iNext = (i + 1) % N;
                            Vector3D vPrev = (ModifiedV[iPrev]) ? NewV[iPrev] : Curve[iPrev];
                            Vector3D vNext = (ModifiedV[iNext]) ? NewV[iNext] : Curve[iNext];
                            Vector3D c     = (vPrev + vNext) * 0.5f;
                            NewV[i]      = (1 - a) * v + (a) * c;
                            ModifiedV[i] = true;

                            if (i > 0)
                            {
                                edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i - 1]));
                            }
                            if (i < N - 1)
                            {
                                edgeRangeSqr.Contain(NewV[i].DistanceSquared(Curve[i + 1]));
                            }
                        }
                    }
                }
            }

            // bake
            for (int i = 0; i < N; ++i)
            {
                if (ModifiedV[i])
                {
                    Curve[i] = NewV[i];
                }
            }

            return(new DeformInfo()
            {
                minEdgeLenSqr = edgeRangeSqr.a, maxEdgeLenSqr = edgeRangeSqr.b
            });
        }
Example #16
0
 public void SetValidRange(double min, double max)
 {
     ValidRange = new Interval1d(min, max);
 }
Example #17
0
 public DoubleParameter()
 {
     name         = "double_parameter";
     defaultValue = 0.0;
     ValidRange   = new Interval1d(double.MinValue, double.MaxValue);
 }
Example #18
0
 public void SetValidRange(float min, float max)
 {
     ValidRange = new Interval1d(min, max);
 }
        public void ComputeOnBackgroundThread()
        {
            Deviation = null;

            DebugUtil.Log(SO.GetToolpathStats());

            ToolpathSet      paths  = SO.GetToolpaths();
            PlanarSliceStack slices = SO.GetSlices();
            var settings            = SO.GetSettings();

            // AAHHH
            double   bed_width  = settings.Machine.BedSizeXMM;
            double   bed_height = settings.Machine.BedSizeYMM;
            Vector3d origin     = new Vector3d(-bed_width / 2, -bed_height / 2, 0);

            if (settings is gs.info.MakerbotSettings)
            {
                origin = Vector3d.Zero;
            }

            List <DeviationPt>   points       = new List <DeviationPt>();
            SpinLock             pointsLock   = new SpinLock();
            Action <DeviationPt> appendPointF = (pt) => {
                bool entered = false;
                pointsLock.Enter(ref entered);
                points.Add(pt);
                pointsLock.Exit();
            };

            double tolerance = settings.Machine.NozzleDiamMM * 0.5 + DeviationToleranceMM;

            gParallel.ForEach(Interval1i.Range(slices.Count), (slicei) => {
                PlanarSlice slice = slices[slicei];

                //Interval1d zrange = (slicei < slices.Count - 1) ?
                //    new Interval1d(slice.Z, slices[slicei + 1].Z - 0.5*settings.LayerHeightMM) :
                //    new Interval1d(slice.Z, slice.Z + 0.5*settings.LayerHeightMM);
                double dz         = 0.5 * settings.LayerHeightMM;
                Interval1d zrange = new Interval1d(slice.Z - dz, slice.Z + dz);

                double cellSize = 2.0f;

                ToolpathsLayerGrid grid = new ToolpathsLayerGrid();
                grid.Build(paths, zrange, cellSize);

                foreach (GeneralPolygon2d poly in slice.Solids)
                {
                    measure_poly(poly.Outer, slice.Z, grid, tolerance, appendPointF);
                    foreach (var hole in poly.Holes)
                    {
                        measure_poly(poly.Outer, slice.Z, grid, tolerance, appendPointF);
                    }
                }
            });

            int N = points.Count;

            for (int k = 0; k < N; ++k)
            {
                DeviationPt pt = points[k];
                Vector3d    v  = origin + pt.pos;
                v         = MeshTransforms.ConvertZUpToYUp(v);
                pt.pos    = MeshTransforms.FlipLeftRightCoordSystems(v);
                points[k] = pt;
            }

            Deviation = new DeviationData();
            Deviation.DeviationPoints = points;
            OnGeometryUpdateRequired?.Invoke(this);
        }
        public virtual void Update()
        {
            if (MeshSource == null)
            {
                throw new Exception("LegSqueezeOp: must set valid MeshSource to compute!");
            }

            IMesh mesh = MeshSource.GetIMesh();

            if (mesh.HasVertexNormals == false)
            {
                throw new Exception("LegSqueezeOp: input mesh does not have surface normals...");
            }

            Displacement.Resize(mesh.MaxVertexID);

            // compute extents along axis
            double     upper_t      = UpperPoint.Dot(Axis);
            Interval1d axis_extents = MeshMeasurements.ExtentsOnAxis(mesh, axis);
            double     lower_t      = axis_extents.a;


            // compute approximate skeleton
            int nVertices = midPoints.Count + 2;

            Vector3d[] centers = new Vector3d[nVertices];
            int[]      counts  = new int[nVertices];
            foreach (int vid in mesh.VertexIndices())
            {
                Vector3d v    = mesh.GetVertex(vid);
                double   t    = v.Dot(ref axis);
                int      iBin = 0;
                if (t > upper_t)
                {
                    iBin = nVertices - 1;
                }
                else if (t > lower_t)
                {
                    double unit_t = (t - lower_t) / (upper_t - lower_t);
                    iBin = 1;
                    for (int k = 0; k < midPoints.Count; ++k)
                    {
                        if (unit_t > midPoints[k].x)
                        {
                            iBin++;
                        }
                    }
                }   // else iBin = 0, as initialized
                centers[iBin] += v;
                counts[iBin]++;
            }
            for (int k = 0; k < centers.Length; ++k)
            {
                centers[k] /= counts[k];
            }


            // todo: can do this in parallel
            foreach (int vid in mesh.VertexIndices())
            {
                Vector3d v = mesh.GetVertex(vid);
                double   t = v.Dot(axis);
                if (t > upper_t)
                {
                    continue;
                }

                Vector3d center  = centers[0];
                double   percent = 0;

                if (t >= upper_t)
                {
                    percent = reduce_percent_top;
                    center  = centers[nVertices - 1];
                }
                else if (t <= lower_t)
                {
                    percent = reduce_percent_bottom;
                    center  = centers[0];
                }
                else if (midPoints.Count == 0)
                {
                    double unit_t = (t - lower_t) / (upper_t - lower_t);
                    unit_t  = MathUtil.WyvillRise01(unit_t);
                    percent = MathUtil.Lerp(reduce_percent_bottom, reduce_percent_top, unit_t);
                }
                else
                {
                    double   unit_t = (t - lower_t) / (upper_t - lower_t);
                    double   low_percent = reduce_percent_bottom;
                    double   high_percent = reduce_percent_top;
                    Vector3d low_center = centers[0];
                    Vector3d high_center = centers[0];
                    double   low_t = 0.0, high_t = 0;
                    for (int i = 0; i < midPoints.Count; ++i)
                    {
                        if (unit_t < midPoints[i].x)
                        {
                            high_t       = midPoints[i].x;
                            high_percent = midPoints[i].y;
                            high_center  = centers[i + 1];
                            break;
                        }
                        low_t       = midPoints[i].x;
                        low_percent = midPoints[i].y;
                        low_center  = centers[i + 1];
                    }
                    if (high_t == 0)
                    {
                        high_t       = 1.0;
                        high_percent = reduce_percent_top;
                        high_center  = centers[nVertices - 1];
                    }
                    double a = (unit_t - low_t) / (high_t - low_t);
                    a       = MathUtil.WyvillRise01(a);
                    percent = MathUtil.Lerp(low_percent, high_percent, a);
                    center  = Vector3d.Lerp(low_center, high_center, a);
                }

                percent = percent / 100;

                double   scale    = 1.0 - percent;
                Vector3d v_scaled = (v - center) * new Vector3d(scale, 1, scale) + center;
                Displacement[vid] = v_scaled - v;
            }

            result_valid = true;
        }
Example #21
0
 public bool Overlaps(Interval1d o)
 {
     return(!(o.a > b || o.b < a));
 }
Example #22
0
        /// <summary>
        /// Slice the meshes and return the slice stack.
        /// </summary>
        public PlanarSliceStack Compute()
        {
            if (Meshes.Count == 0)
            {
                return(new PlanarSliceStack());
            }

            Interval1d zrange = Interval1d.Empty;

            foreach (var meshinfo in Meshes)
            {
                zrange.Contain(meshinfo.bounds.Min.z);
                zrange.Contain(meshinfo.bounds.Max.z);
            }
            if (SetMinZValue != double.MinValue)
            {
                zrange.a = SetMinZValue;
            }

            int nLayers = (int)(zrange.Length / LayerHeightMM);

            if (nLayers > MaxLayerCount)
            {
                throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount.");
            }

            // make list of slice heights (could be irregular)
            List <double> heights = new List <double>();

            for (int i = 0; i < nLayers + 1; ++i)
            {
                double t = zrange.a + (double)i * LayerHeightMM;
                if (SliceLocation == SliceLocations.EpsilonBase)
                {
                    t += 0.01 * LayerHeightMM;
                }
                else if (SliceLocation == SliceLocations.MidLine)
                {
                    t += 0.5 * LayerHeightMM;
                }
                heights.Add(t);
            }
            int NH = heights.Count;

            // process each *slice* in parallel
            PlanarSlice[] slices = new PlanarSlice[NH];
            for (int i = 0; i < NH; ++i)
            {
                slices[i] = SliceFactoryF(heights[i], i);
                slices[i].EmbeddedPathWidth = OpenPathDefaultWidthMM;
            }

            // assume Resolve() takes 2x as long as meshes...
            TotalCompute = (Meshes.Count * NH) + (2 * NH);
            Progress     = 0;


            // compute slices separately for each mesh
            for (int mi = 0; mi < Meshes.Count; ++mi)
            {
                if (Cancelled())
                {
                    break;
                }

                DMesh3           mesh         = Meshes[mi].mesh;
                PrintMeshOptions mesh_options = Meshes[mi].options;

                // [TODO] should we hang on to this spatial? or should it be part of assembly?
                DMeshAABBTree3   spatial = new DMeshAABBTree3(mesh, true);
                AxisAlignedBox3d bounds  = Meshes[mi].bounds;

                bool is_cavity   = mesh_options.IsCavity;
                bool is_crop     = mesh_options.IsCropRegion;
                bool is_support  = mesh_options.IsSupport;
                bool is_closed   = (mesh_options.IsOpen) ? false : mesh.IsClosed();
                var  useOpenMode = (mesh_options.OpenPathMode == PrintMeshOptions.OpenPathsModes.Default) ?
                                   DefaultOpenPathMode : mesh_options.OpenPathMode;

                // each layer is independent so we can do in parallel
                gParallel.ForEach(Interval1i.Range(NH), (i) => {
                    if (Cancelled())
                    {
                        return;
                    }

                    double z = heights[i];
                    if (z < bounds.Min.z || z > bounds.Max.z)
                    {
                        return;
                    }

                    // compute cut
                    Polygon2d[] polys; PolyLine2d[] paths;
                    compute_plane_curves(mesh, spatial, z, is_closed, out polys, out paths);

                    // if we didn't hit anything, try again with jittered plane
                    // [TODO] this could be better...
                    if ((is_closed && polys.Length == 0) || (is_closed == false && polys.Length == 0 && paths.Length == 0))
                    {
                        compute_plane_curves(mesh, spatial, z + LayerHeightMM * 0.25, is_closed, out polys, out paths);
                    }

                    if (is_closed)
                    {
                        // construct planar complex and "solids"
                        // (ie outer polys and nested holes)
                        PlanarComplex complex = new PlanarComplex();
                        foreach (Polygon2d poly in polys)
                        {
                            complex.Add(poly);
                        }

                        PlanarComplex.FindSolidsOptions options
                            = PlanarComplex.FindSolidsOptions.Default;
                        options.WantCurveSolids            = false;
                        options.SimplifyDeviationTolerance = 0.001;
                        options.TrustOrientations          = true;
                        options.AllowOverlappingHoles      = true;

                        PlanarComplex.SolidRegionInfo solids =
                            complex.FindSolidRegions(options);

                        if (is_support)
                        {
                            add_support_polygons(slices[i], solids.Polygons, mesh_options);
                        }
                        else if (is_cavity)
                        {
                            add_cavity_polygons(slices[i], solids.Polygons, mesh_options);
                        }
                        else if (is_crop)
                        {
                            add_crop_region_polygons(slices[i], solids.Polygons, mesh_options);
                        }
                        else
                        {
                            add_solid_polygons(slices[i], solids.Polygons, mesh_options);
                        }
                    }
                    else if (useOpenMode != PrintMeshOptions.OpenPathsModes.Ignored)
                    {
                        foreach (PolyLine2d pline in paths)
                        {
                            if (useOpenMode == PrintMeshOptions.OpenPathsModes.Embedded)
                            {
                                slices[i].AddEmbeddedPath(pline);
                            }
                            else
                            {
                                slices[i].AddClippedPath(pline);
                            }
                        }

                        // [TODO]
                        //   - does not really handle clipped polygons properly, there will be an extra break somewhere...
                        foreach (Polygon2d poly in polys)
                        {
                            PolyLine2d pline = new PolyLine2d(poly, true);
                            if (useOpenMode == PrintMeshOptions.OpenPathsModes.Embedded)
                            {
                                slices[i].AddEmbeddedPath(pline);
                            }
                            else
                            {
                                slices[i].AddClippedPath(pline);
                            }
                        }
                    }

                    Interlocked.Increment(ref Progress);
                });       // end of parallel.foreach
            }             // end mesh iter

            // resolve planar intersections, etc
            gParallel.ForEach(Interval1i.Range(NH), (i) => {
                if (Cancelled())
                {
                    return;
                }
                slices[i].Resolve();
                Interlocked.Add(ref Progress, 2);
            });

            // discard spurious empty slices
            int last = slices.Length - 1;

            while (slices[last].IsEmpty && last > 0)
            {
                last--;
            }
            int first = 0;

            if (DiscardEmptyBaseSlices)
            {
                while (slices[first].IsEmpty && first < slices.Length)
                {
                    first++;
                }
            }

            PlanarSliceStack stack = SliceStackFactoryF();

            for (int k = first; k <= last; ++k)
            {
                stack.Add(slices[k]);
            }

            if (SupportMinZTips)
            {
                stack.AddMinZTipSupportPoints(MinZTipMaxDiam, MinZTipExtraLayers);
            }

            return(stack);
        }
Example #23
0
        /// <summary>
        /// shoot parallel set of 2D rays at input polygon, and find portions
        /// of rays that are inside the polygon (we call these "spans"). These
        /// are inserted into the polygon, resulting in a non-manifold 2D graph.
        /// </summary>
        protected DGraph2 ComputeSpanGraph(GeneralPolygon2d poly)
        {
            double   angleRad = AngleDeg * MathUtil.Deg2Rad;
            Vector2d dir      = new Vector2d(Math.Cos(angleRad), Math.Sin(angleRad));

            // compute projection span along axis
            Vector2d   axis         = dir.Perp;
            Interval1d axisInterval = Interval1d.Empty;
            Interval1d dirInterval  = Interval1d.Empty;

            foreach (Vector2d v in poly.Outer.Vertices)
            {
                dirInterval.Contain(v.Dot(dir));
                axisInterval.Contain(v.Dot(axis));
            }
            // [TODO] also check holes? or assume they are contained? should be
            //  classified as outside by winding check anyway...

            // construct interval we will step along to shoot parallel rays
            dirInterval.a -= 10 * ToolWidth;
            dirInterval.b += 10 * ToolWidth;
            double extent = dirInterval.Length;

            // nudge in a very tiny amount so that if poly is a rectangle, first
            // line is not directly on boundary
            axisInterval.a += ToolWidth * 0.01;
            axisInterval.b -= ToolWidth * 0.01;
            axisInterval.a -= PathShift;
            if (axisInterval.b < axisInterval.a)
            {
                return(null);     // [RMS] is this right? I guess so. interval is too small to fill?
            }
            // If we are doing a dense fill, we want to pack as tightly as possible.
            // But if we are doing a sparse fill, then we want layers to stack.
            // So in that case, snap the interval to increments of the spacing
            //  (does this work?)
            bool bIsSparse = (PathSpacing > ToolWidth * 2);

            if (bIsSparse)
            {
                // snap axisInterval.a to grid so that layers are aligned
                double snapped_a = Snapping.SnapToIncrement(axisInterval.a, PathSpacing);
                if (snapped_a > axisInterval.a)
                {
                    snapped_a -= PathSpacing;
                }
                axisInterval.a = snapped_a;
            }

            Vector2d startCorner = axisInterval.a * axis + dirInterval.a * dir;
            double   range       = axisInterval.Length;
            int      N           = (int)(range / PathSpacing) + 1;

            // nudge spacing so that we exactly fill the available space
            double use_spacing = PathSpacing;

            if (bIsSparse == false && AdjustSpacingToMaximizeFill)
            {
                int nn = (int)(range / use_spacing);
                use_spacing = range / (double)nn;
                N           = (int)(range / use_spacing) + 1;
            }

            DGraph2 graph = new DGraph2();

            graph.AppendPolygon(poly);
            GraphSplitter2d splitter = new GraphSplitter2d(graph);

            splitter.InsideTestF = poly.Contains;

            // insert sequential rays
            for (int ti = 0; ti <= N; ++ti)
            {
                Vector2d o   = startCorner + (double)ti * use_spacing * axis;
                Line2d   ray = new Line2d(o, dir);
                splitter.InsertLine(ray, ti);
            }

            return(graph);
        }
Example #24
0
 public void Set(Interval1d o)
 {
     a = o.a; b = o.b;
 }
Example #25
0
        /// <summary>
        /// This is the action we give to the trim-scan tool, to run on accept
        /// </summary>
        public static void CropScanFromSelection(DMeshSO so, MeshFaceSelection selection, object tool)
        {
            DMesh3 beforeMesh = new DMesh3(so.Mesh);
            DMesh3 mesh       = so.Mesh;

            // [RMS] if we are using the two-point tool, then we can use the user input points to
            // try to figure out an up axis, by assuming the first point is on the base of the scan. Steps are:
            //   1) guess a midpoint. Currently centroid of upper-half of geodesic selection.
            //   2) construct up axis as (midpoint-basepoint). this axis to Y-up.
            Vector3f upAxisS = Vector3f.AxisY;
            TwoPointFaceSelectionTool ptool = tool as TwoPointFaceSelectionTool;

            if (ptool != null)
            {
                var        cache     = ptool.SelectionCache;
                Interval1d range     = new Interval1d(cache.CurrentScalarThreshold / 2, cache.CurrentScalarThreshold);
                List <int> triangles = new List <int>(selection.Count);
                cache.FindTrianglesInScalarInterval(range, triangles);
                Vector3d c        = MeshMeasurements.Centroid(triangles, mesh.GetTriCentroid);
                Vector3d cS       = SceneTransforms.ObjectToSceneP(so, c);
                Vector3d basePosS = ptool.SourcePositionS.Origin;
                upAxisS = (Vector3f)(cS - basePosS).Normalized;
            }

            // crop scan and fill top hole
            List <int> borderTris = selection.FindBorderTris();
            MeshEditor editor     = new MeshEditor(mesh);

            editor.RemoveTriangles((tid) => { return(selection.IsSelected(tid) == false); }, true);
            if (OGActions.FillHoleInScan)
            {
                SmoothedHoleFill fill = new SmoothedHoleFill(mesh)
                {
                    TargetEdgeLength = 2.5f,
                    SmoothAlpha      = 0.5f,
                    BorderHintTris   = borderTris,
                    OffsetDirection  = SceneTransforms.SceneToObjectN(so, upAxisS),
                    OffsetDistance   = (ptool != null) ? 25.0 : 0.0
                };
                fill.Apply();
            }

            so.NotifyMeshEdited();
            DMesh3 afterMesh = new DMesh3(so.Mesh);

            so.GetScene().History.PushChange(new ReplaceEntireMeshChange(so, beforeMesh, afterMesh), true);
            mesh = so.Mesh;

            // Now we auto-align the scan so it points upwards, and then
            // recenter pivot and shift to above ground plane
            if (ptool != null)
            {
                Vector3d    basePosS = ptool.SourcePositionS.Origin;
                Quaternionf alignUp  = Quaternionf.FromTo(upAxisS, Vector3f.AxisY);

                // rotate part so that axis points up
                Frame3f           curF          = so.GetLocalFrame(CoordSpace.SceneCoords);
                Frame3f           newF          = curF.Rotated(alignUp);
                TransformSOChange alignUpChange = new TransformSOChange(so, curF, newF, CoordSpace.SceneCoords);
                basePosS = newF.FromFrameP(curF.ToFrameP(basePosS));   // map to new frame
                so.GetScene().History.PushChange(alignUpChange, false);

                // recenter pivot at bbox center
                // [RMS] previously was using vertex centroid, but this is then affected by mesh density
                //   (maybe tri centroid? but bbox makes more sense...and below we assume box center)
                Vector3d centerL   = mesh.CachedBounds.Center;
                Vector3d centerO   = newF.FromFrameP(centerL);
                Frame3f  newPivotO = new Frame3f(centerO);
                so.GetScene().History.PushChange(new RepositionPivotChangeOp(newPivotO, so), false);

                // position above ground plane
                AxisAlignedBox3d bounds     = so.Mesh.CachedBounds;
                float            h          = (float)bounds.Height;
                Vector3f         o          = newPivotO.Origin;
                Vector3f         translateO = new Vector3f(-o.x, h * 0.5f - o.y + BaseHeightAboveGroundPlaneMM, -o.z);
                //Vector3f translateO = new Vector3f(0, h * 0.5f - o.y + BaseHeightAboveGroundPlaneMM, 0);
                newPivotO.Translate(translateO);
                so.GetScene().History.PushChange(new TransformSOChange(so, newPivotO, CoordSpace.ObjectCoords), false);

                // save base point in frame of scan
                basePosS += translateO;
                Vector3d basePosL = SceneTransforms.SceneToObjectP(so, basePosS);
                OG.Scan.UserBasePoint = basePosL;
            }

            so.GetScene().History.PushInteractionCheckpoint();
        }
Example #26
0
 public Interval1d(Interval1d copy)
 {
     a = copy.a; b = copy.b;
 }
Example #27
0
        /// <summary>
        /// Slice the meshes and return the slice stack.
        /// </summary>
        public Result Compute()
        {
            Result result = new Result();

            if (Meshes.Count == 0)
            {
                return(result);
            }

            // find Z interval we want to slice in
            Interval1d zrange = Interval1d.Empty;

            foreach (var meshinfo in Meshes)
            {
                zrange.Contain(meshinfo.bounds.Min.z);
                zrange.Contain(meshinfo.bounds.Max.z);
            }
            if (SetMinZValue != double.MinValue)
            {
                zrange.a = SetMinZValue;
            }

            result.TopZ  = Math.Round(zrange.b, PrecisionDigits);
            result.BaseZ = Math.Round(zrange.a, PrecisionDigits);

            // [TODO] might be able to make better decisions if we took flat regions
            // into account when constructing initial Z-heights? if we have large flat
            // region just below Zstep, might make sense to do two smaller Z-steps so we
            // can exactly hit it??

            // construct list of clearing Z-heights
            List <double> clearingZLayers = new List <double>();
            double        cur_layer_z     = zrange.b;
            int           layer_i         = 0;

            while (cur_layer_z > zrange.a)
            {
                double layer_height = get_layer_height(layer_i);
                cur_layer_z -= layer_height;
                double z = Math.Round(cur_layer_z, PrecisionDigits);
                clearingZLayers.Add(z);
                layer_i++;
            }
            if (clearingZLayers.Last() < result.BaseZ)
            {
                clearingZLayers[clearingZLayers.Count - 1] = result.BaseZ;
            }
            if (clearingZLayers.Last() == clearingZLayers[clearingZLayers.Count - 2])
            {
                clearingZLayers.RemoveAt(clearingZLayers.Count - 1);
            }

            // construct layer slices from Z-heights
            List <PlanarSlice> clearing_slice_list = new List <PlanarSlice>();

            layer_i = 0;
            for (int i = 0; i < clearingZLayers.Count; ++i)
            {
                double layer_height = (i == clearingZLayers.Count - 1) ?
                                      (result.TopZ - clearingZLayers[i]) : (clearingZLayers[i + 1] - clearingZLayers[i]);
                double     z     = clearingZLayers[i];
                Interval1d zspan = new Interval1d(z, z + layer_height);

                if (SliceLocation == SliceLocations.EpsilonBase)
                {
                    z += 0.001;
                }

                PlanarSlice slice = SliceFactoryF(zspan, z, layer_i);
                clearing_slice_list.Add(slice);
                layer_i++;
            }


            int NH = clearing_slice_list.Count;

            if (NH > MaxLayerCount)
            {
                throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount.");
            }

            PlanarSlice[] clearing_slices = clearing_slice_list.ToArray();

            // assume Resolve() takes 2x as long as meshes...
            TotalCompute = (Meshes.Count * NH) + (2 * NH);
            Progress     = 0;

            // compute slices separately for each mesh
            for (int mi = 0; mi < Meshes.Count; ++mi)
            {
                if (Cancelled())
                {
                    break;
                }

                DMesh3           mesh         = Meshes[mi].mesh;
                PrintMeshOptions mesh_options = Meshes[mi].options;

                // [TODO] should we hang on to this spatial? or should it be part of assembly?
                DMeshAABBTree3   spatial = new DMeshAABBTree3(mesh, true);
                AxisAlignedBox3d bounds  = Meshes[mi].bounds;

                bool is_cavity   = mesh_options.IsCavity;
                bool is_crop     = mesh_options.IsCropRegion;
                bool is_support  = mesh_options.IsSupport;
                bool is_closed   = (mesh_options.IsOpen) ? false : mesh.IsClosed();
                var  useOpenMode = (mesh_options.OpenPathMode == PrintMeshOptions.OpenPathsModes.Default) ?
                                   DefaultOpenPathMode : mesh_options.OpenPathMode;

                if (is_crop || is_support)
                {
                    throw new Exception("Not supported!");
                }

                // each layer is independent so we can do in parallel
                gParallel.ForEach(Interval1i.Range(NH), (i) => {
                    if (Cancelled())
                    {
                        return;
                    }

                    double z = clearing_slices[i].Z;
                    if (z < bounds.Min.z || z > bounds.Max.z)
                    {
                        return;
                    }

                    // compute cut
                    Polygon2d[] polys; PolyLine2d[] paths;
                    ComputeSlicePlaneCurves(mesh, spatial, z, is_closed, out polys, out paths);

                    if (is_closed)
                    {
                        // construct planar complex and "solids"
                        // (ie outer polys and nested holes)
                        PlanarComplex complex = new PlanarComplex();
                        foreach (Polygon2d poly in polys)
                        {
                            complex.Add(poly);
                        }

                        PlanarComplex.FindSolidsOptions options
                            = PlanarComplex.FindSolidsOptions.Default;
                        options.WantCurveSolids            = false;
                        options.SimplifyDeviationTolerance = 0.001;
                        options.TrustOrientations          = true;
                        options.AllowOverlappingHoles      = true;

                        PlanarComplex.SolidRegionInfo solids   = complex.FindSolidRegions(options);
                        List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons);

                        if (is_cavity)
                        {
                            add_cavity_polygons(clearing_slices[i], solid_polygons, mesh_options);
                        }
                        else
                        {
                            if (ExpandStockAmount > 0)
                            {
                                solid_polygons = ClipperUtil.MiterOffset(solid_polygons, ExpandStockAmount);
                            }
                            add_solid_polygons(clearing_slices[i], solid_polygons, mesh_options);
                        }
                    }

                    Interlocked.Increment(ref Progress);
                });       // end of parallel.foreach
            }             // end mesh iter

            // resolve planar intersections, etc
            gParallel.ForEach(Interval1i.Range(NH), (i) => {
                if (Cancelled())
                {
                    return;
                }
                clearing_slices[i].Resolve();
                Interlocked.Add(ref Progress, 2);
            });
            // add to clearing stack
            result.Clearing = SliceStackFactoryF();
            for (int k = 0; k < clearing_slices.Length; ++k)
            {
                result.Clearing.Add(clearing_slices[k]);
            }



            /*
             * Horizontal planar regions finishing pass.
             * First we find all planar horizontal Z-regions big enough to mill.
             * Then we add slices at the Z's we haven't touched yet.
             *
             * Cannot just 'fill' planar regions because we will miss edges that might
             * be millable. So we grow region and then intersect with full-slice millable area.
             */

            // find set of horizontal flat regions
            Dictionary <double, List <PlanarRegion> > flat_regions = FindPlanarZRegions(ToolDiameter);

            if (flat_regions.Count == 0)
            {
                goto done_slicing;
            }

            // if we have already milled this exact Z-height in clearing pass, then we can skip it
            List <double> doneZ = new List <double>();

            foreach (double z in flat_regions.Keys)
            {
                if (clearingZLayers.Contains(z))
                {
                    doneZ.Add(z);
                }
            }
            foreach (var z in doneZ)
            {
                flat_regions.Remove(z);
            }

            // create slice for each layer
            PlanarSlice[] horz_slices = new PlanarSlice[flat_regions.Count];
            List <double> flatZ       = new List <double>(flat_regions.Keys);

            flatZ.Sort();
            for (int k = 0; k < horz_slices.Length; ++k)
            {
                double     z     = flatZ[k];
                Interval1d zspan = new Interval1d(z, z + LayerHeightMM);
                horz_slices[k] = SliceFactoryF(zspan, z, k);

                // compute full millable region slightly above this slice.
                PlanarSlice clip_slice = ComputeSolidSliceAtZ(z + 0.0001, false);
                clip_slice.Resolve();

                // extract planar polys
                List <Polygon2d> polys   = GetPlanarPolys(flat_regions[z]);
                PlanarComplex    complex = new PlanarComplex();
                foreach (Polygon2d poly in polys)
                {
                    complex.Add(poly);
                }

                // convert to planar solids
                PlanarComplex.FindSolidsOptions options
                    = PlanarComplex.FindSolidsOptions.SortPolygons;
                options.SimplifyDeviationTolerance = 0.001;
                options.TrustOrientations          = true;
                options.AllowOverlappingHoles      = true;
                PlanarComplex.SolidRegionInfo solids         = complex.FindSolidRegions(options);
                List <GeneralPolygon2d>       solid_polygons = ApplyValidRegions(solids.Polygons);

                // If planar solid has holes, then when we do inset later, we might lose
                // too-thin parts. Shrink the holes to avoid this case.
                //FilterHoles(solid_polygons, 0.55 * ToolDiameter);

                // ok now we need to expand region and intersect with full region.
                solid_polygons = ClipperUtil.MiterOffset(solid_polygons, ToolDiameter * 0.5, 0.0001);
                solid_polygons = ClipperUtil.Intersection(solid_polygons, clip_slice.Solids, 0.0001);

                // Same idea as above, but if we do after, we keep more of the hole and
                // hence do less extra clearing.
                // Also this could then be done at the slicer level instead of here...
                // (possibly this entire thing should be done at slicer level, except we need clip_slice!)
                FilterHoles(solid_polygons, 1.1 * ToolDiameter);

                add_solid_polygons(horz_slices[k], solid_polygons, PrintMeshOptions.Default());
            }

            // resolve planar intersections, etc
            int NF = horz_slices.Length;

            gParallel.ForEach(Interval1i.Range(NF), (i) => {
                if (Cancelled())
                {
                    return;
                }
                horz_slices[i].Resolve();
                Interlocked.Add(ref Progress, 2);
            });
            // add to clearing stack
            result.HorizontalFinish = SliceStackFactoryF();
            for (int k = 0; k < horz_slices.Length; ++k)
            {
                result.HorizontalFinish.Add(horz_slices[k]);
            }

done_slicing:
            return(result);
        }
Example #28
0
 public FloatParameter()
 {
     name         = "float_parameter";
     defaultValue = 0.0f;
     ValidRange   = new Interval1d(float.MinValue, float.MaxValue);
 }
Example #29
0
        /// <summary>
        /// Slice the meshes and return the slice stack.
        /// </summary>
        public PlanarSliceStack Compute()
        {
            if (Meshes.Count == 0)
            {
                return(new PlanarSliceStack());
            }

            Interval1d zrange = Interval1d.Empty;

            foreach (var meshinfo in Meshes)
            {
                zrange.Contain(meshinfo.bounds.Min.z);
                zrange.Contain(meshinfo.bounds.Max.z);
            }
            if (SetMinZValue != double.MinValue)
            {
                zrange.a = SetMinZValue;
            }

            // construct layers
            List <PlanarSlice> slice_list = new List <PlanarSlice>();

            double cur_layer_z = zrange.a;
            int    layer_i     = 0;

            while (cur_layer_z < zrange.b)
            {
                double     layer_height = get_layer_height(layer_i);
                double     z            = cur_layer_z;
                Interval1d zspan        = new Interval1d(z, z + layer_height);
                if (SliceLocation == SliceLocations.EpsilonBase)
                {
                    z += 0.01 * layer_height;
                }
                else if (SliceLocation == SliceLocations.MidLine)
                {
                    z += 0.5 * layer_height;
                }

                PlanarSlice slice = SliceFactoryF(zspan, z, layer_i);
                slice.EmbeddedPathWidth = OpenPathDefaultWidthMM;
                slice_list.Add(slice);

                layer_i++;
                cur_layer_z += layer_height;
            }
            int NH = slice_list.Count;

            if (NH > MaxLayerCount)
            {
                throw new Exception("MeshPlanarSlicer.Compute: exceeded layer limit. Increase .MaxLayerCount.");
            }

            PlanarSlice[] slices = slice_list.ToArray();

            // determine if we have crop objects
            bool have_crop_objects = false;

            foreach (var mesh in Meshes)
            {
                if (mesh.options.IsCropRegion)
                {
                    have_crop_objects = true;
                }
            }

            // assume Resolve() takes 2x as long as meshes...
            TotalCompute = (Meshes.Count * NH) + (2 * NH);
            Progress     = 0;

            // compute slices separately for each mesh
            for (int mi = 0; mi < Meshes.Count; ++mi)
            {
                if (Cancelled())
                {
                    break;
                }

                DMesh3           mesh         = Meshes[mi].mesh;
                PrintMeshOptions mesh_options = Meshes[mi].options;

                // [TODO] should we hang on to this spatial? or should it be part of assembly?
                DMeshAABBTree3   spatial = new DMeshAABBTree3(mesh, true);
                AxisAlignedBox3d bounds  = Meshes[mi].bounds;

                bool is_cavity   = mesh_options.IsCavity;
                bool is_crop     = mesh_options.IsCropRegion;
                bool is_support  = mesh_options.IsSupport;
                bool is_closed   = (mesh_options.IsOpen) ? false : mesh.IsClosed();
                var  useOpenMode = (mesh_options.OpenPathMode == OpenPathsModes.Default) ?
                                   DefaultOpenPathMode : mesh_options.OpenPathMode;

                // each layer is independent so we can do in parallel
                gParallel.ForEach(Interval1i.Range(NH), (i) =>
                {
                    if (Cancelled())
                    {
                        return;
                    }

                    double z = slices[i].Z;
                    if (z < bounds.Min.z || z > bounds.Max.z)
                    {
                        return;
                    }

                    // compute cut
                    Polygon2d[] polys; PolyLine2d[] paths;
                    compute_plane_curves(mesh, spatial, z, is_closed, out polys, out paths);

                    // if we didn't hit anything, try again with jittered plane
                    // [TODO] this could be better...
                    if ((is_closed && polys.Length == 0) || (is_closed == false && polys.Length == 0 && paths.Length == 0))
                    {
                        double jitterz = slices[i].LayerZSpan.Interpolate(0.75);
                        compute_plane_curves(mesh, spatial, jitterz, is_closed, out polys, out paths);
                    }

                    if (is_closed)
                    {
                        // construct planar complex and "solids"
                        // (ie outer polys and nested holes)
                        PlanarComplex complex = new PlanarComplex();
                        foreach (Polygon2d poly in polys)
                        {
                            complex.Add(poly);
                        }

                        PlanarComplex.FindSolidsOptions options
                            = PlanarComplex.FindSolidsOptions.Default;
                        options.WantCurveSolids            = false;
                        options.SimplifyDeviationTolerance = 0.001;
                        options.TrustOrientations          = true;
                        options.AllowOverlappingHoles      = true;

                        PlanarComplex.SolidRegionInfo solids   = complex.FindSolidRegions(options);
                        List <GeneralPolygon2d> solid_polygons = ApplyValidRegions(solids.Polygons);

                        if (is_support)
                        {
                            add_support_polygons(slices[i], solid_polygons, mesh_options);
                        }
                        else if (is_cavity)
                        {
                            add_cavity_polygons(slices[i], solid_polygons, mesh_options);
                        }
                        else if (is_crop)
                        {
                            add_crop_region_polygons(slices[i], solid_polygons, mesh_options);
                        }
                        else
                        {
                            add_solid_polygons(slices[i], solid_polygons, mesh_options);
                        }
                    }
                    else if (useOpenMode != OpenPathsModes.Ignored)
                    {
                        // [TODO]
                        //   - does not really handle clipped polygons properly, there will be an extra break somewhere...
                        List <PolyLine2d> all_paths = new List <PolyLine2d>(paths);
                        foreach (Polygon2d poly in polys)
                        {
                            all_paths.Add(new PolyLine2d(poly, true));
                        }

                        List <PolyLine2d> open_polylines = ApplyValidRegions(all_paths);
                        foreach (PolyLine2d pline in open_polylines)
                        {
                            if (useOpenMode == OpenPathsModes.Embedded)
                            {
                                slices[i].AddEmbeddedPath(pline);
                            }
                            else
                            {
                                slices[i].AddClippedPath(pline);
                            }
                        }
                    }

                    Interlocked.Increment(ref Progress);
                });  // end of parallel.foreach
            } // end mesh iter

            // resolve planar intersections, etc
            gParallel.ForEach(Interval1i.Range(NH), (i) =>
            {
                if (Cancelled())
                {
                    return;
                }

                if (have_crop_objects && slices[i].InputCropRegions.Count == 0)
                {
                    // don't resolve, we have fully cropped this layer
                }
                else
                {
                    slices[i].Resolve();
                }

                Interlocked.Add(ref Progress, 2);
            });

            // discard spurious empty slices
            int last = slices.Length - 1;

            while (slices[last].IsEmpty && last > 0)
            {
                last--;
            }
            int first = 0;

            if (DiscardEmptyBaseSlices || have_crop_objects)
            {
                while (slices[first].IsEmpty && first < slices.Length)
                {
                    first++;
                }
            }

            PlanarSliceStack stack = SliceStackFactoryF();

            for (int k = first; k <= last; ++k)
            {
                stack.Add(slices[k]);
            }

            if (SupportMinZTips)
            {
                stack.AddMinZTipSupportPoints(MinZTipMaxDiam, MinZTipExtraLayers);
            }

            return(stack);
        }
Example #30
0
        protected virtual void compute_shell_distancefield()
        {
            if (cached_is_closed == false)
            {
                compute_shell_distancefield_unsigned();
                return;
            }

            double     offset_distance = shell_thickness;
            Interval1d shell_range     = new Interval1d(0, offset_distance);

            if (shell_direction == ShellDirections.Symmetric)
            {
                shell_range = new Interval1d(-offset_distance / 2, offset_distance / 2);
            }
            else if (shell_direction == ShellDirections.Inner)
            {
                shell_range     = new Interval1d(-offset_distance, 0);
                offset_distance = -offset_distance;
            }


            if (cached_sdf == null ||
                shell_thickness > cached_sdf_max_offset ||
                grid_cell_size != cached_sdf.CellSize)
            {
                DMesh3 meshIn      = MeshSource.GetDMeshUnsafe();
                int    exact_cells = (int)((shell_thickness) / grid_cell_size) + 1;

                // only use spatial DS if we are computing enough cells
                DMeshAABBTree3         use_spatial = GenerateClosedMeshOp.MeshSDFShouldUseSpatial(input_spatial, exact_cells, grid_cell_size, input_mesh_edge_stats.z);
                MeshSignedDistanceGrid sdf         = new MeshSignedDistanceGrid(meshIn, grid_cell_size, use_spatial)
                {
                    ExactBandWidth = exact_cells
                };
                if (use_spatial != null)
                {
                    sdf.NarrowBandMaxDistance = shell_thickness + grid_cell_size;
                    sdf.ComputeMode           = MeshSignedDistanceGrid.ComputeModes.NarrowBand_SpatialFloodFill;
                }

                sdf.CancelF = is_invalidated;
                sdf.Compute();
                if (is_invalidated())
                {
                    return;
                }
                cached_sdf            = sdf;
                cached_sdf_max_offset = shell_thickness;
                cached_sdf_bounds     = meshIn.CachedBounds;
            }

            var iso = new DenseGridTrilinearImplicit(cached_sdf.Grid, cached_sdf.GridOrigin, cached_sdf.CellSize);
            BoundedImplicitFunction3d shell_field = (shell_direction == ShellDirections.Symmetric) ?
                                                    (BoundedImplicitFunction3d) new ImplicitShell3d()
            {
                A = iso, Inside = shell_range
            } :
            (BoundedImplicitFunction3d) new ImplicitOffset3d()
            {
                A = iso, Offset = offset_distance
            };
            //var shell_field = new ImplicitShell3d() { A = iso, Inside = shell_range };
            //BoundedImplicitFunction3d shell_field = (signed_field) ?
            //    (BoundedImplicitFunction3d)new ImplicitShell3d() { A = iso, Inside = shell_range } :
            //    (BoundedImplicitFunction3d)new ImplicitOffset3d() { A = iso, Offset = offset_distance };
            //ImplicitOffset3d offset = new ImplicitOffset3d() { A = iso, Offset = offset_distance };

            MarchingCubes c = new MarchingCubes();

            c.Implicit = shell_field;
            c.IsoValue = 0;
            c.Bounds   = cached_sdf_bounds;
            c.CubeSize = mesh_cell_size;
            c.Bounds.Expand(offset_distance + 3 * c.CubeSize);
            c.RootMode      = MarchingCubes.RootfindingModes.LerpSteps;
            c.RootModeSteps = 5;

            c.CancelF = is_invalidated;
            c.Generate();
            if (is_invalidated())
            {
                return;
            }

            Reducer r = new Reducer(c.Mesh);

            r.FastCollapsePass(c.CubeSize * 0.5, 3, true);
            if (is_invalidated())
            {
                return;
            }

            if (min_component_volume > 0)
            {
                MeshEditor.RemoveSmallComponents(c.Mesh, min_component_volume, min_component_volume);
            }
            if (is_invalidated())
            {
                return;
            }

            if (shell_surface_only)
            {
                if (shell_direction == ShellDirections.Inner || shell_direction == ShellDirections.Outer)
                {
                    c.Mesh.AttachMetadata("is_partial", new object());
                }
            }

            ResultMesh = c.Mesh;
        }