<commit_before>﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using g3;

namespace gs
{
    /// <summary>
    /// Fill the input GPolygon with N offset shells. Generates list of 2d paths (.Shells) and
    /// also returns border polygons of "inner" region inside shells (.InnerPolygons)
    /// 
    /// Parameters:
    ///     .Layers:     number of shells
    ///     .ToolWidth:  thickness of 'tool', this is the space between subsequent shells
    ///     
    /// Options:
    ///     .InsetFromInputPolygon    : if true, initial shell is inset half a ToolWidth from input Polygon
    ///                                 (ie outer edge of tool would be "on" Polygon)
    ///     .InsetInnerPolygons       : if true, inner polygons are inset a tool-width from innermost shell (ie are
    ///                                 path you would put next shell on). If false, inner polygons lie on innermost shell.
    ///     .FilterSelfOverlaps       : if true, we try to remove areas of path that would self-overlap.
    ///     .SelfOverlapTolerance     : distance that counts as self-overlap
    /// </summary>
	public class ShellsFillPolygon : IShellsFillPolygon
    {
		// polygon to fill
		public GeneralPolygon2d Polygon { get; set; }

        // parameters
        public int Layers = 2;
        public double ToolWidth = 0.4;
		public double PathSpacing = 0.4;    // [TODO] this parameter is currently ignored?

		public double DiscardTinyPerimterLengthMM = 1.0;
		public double DiscardTinyPolygonAreaMM2 = 1.0;

		// When offsets collide, we try to find polyline paths that will "fit"
		// This is multiplier on ToolWidth, we discard path segments within
		// ToolWidth*Multiplier distance from previous shell
		public double ToolWidthClipMultiplier = 0.8;


		// if true, we inset half of tool-width from Polygon,
		// otherwise first layer is polygon
		public bool InsetFromInputPolygon = true;

        // if true, inset InnerPolygons by a tool-width from last Shell,
        // otherwise InnerPolygons lies on that Shell
        public bool InsetInnerPolygons = true;

        // [RMS] hack that lets us know this is an 'internal' shell which may be processed differently
        public enum ShellTypes
        {
            ExternalPerimeters,
            InternalShell
        }
        public ShellTypes ShellType = ShellTypes.ExternalPerimeters;

        // if true, we try to filter out self-overlaps (is expensive)
        public bool FilterSelfOverlaps = false;
        public double SelfOverlapTolerance = 0.4 * 0.75;


        // Outputs

        // shell layers. Size of List == Layers
        public List<FillPaths2d> Shells { get; set; }
        public List<FillPaths2d> GetFillPaths() { return Shells; }

        // remaining interior polygons (to fill w/ other strategy, etc)
        public List<GeneralPolygon2d> InnerPolygons { get; set; }
        public List<GeneralPolygon2d> GetInnerPolygons() { return InnerPolygons; }


        public ShellsFillPolygon(GeneralPolygon2d poly)
		{
			Polygon = poly;
			Shells = new List<FillPaths2d>();
		}


		public bool Compute()
		{
			bool enable_thin_check = false;
			double thin_check_offset = ToolWidth * 0.45;
			double thin_check_thresh_sqr = ToolWidth * 0.3;
			thin_check_thresh_sqr *= thin_check_thresh_sqr;

			// first shell is either polygon, or inset from that polygon
			List<GeneralPolygon2d> current = (InsetFromInputPolygon) ?
				ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true) :
			   	new List<GeneralPolygon2d>() { Polygon };
            List<GeneralPolygon2d> current_prev = null;

			// convert previous layer to shell, and then compute next layer
			List<GeneralPolygon2d> failedShells = new List<GeneralPolygon2d>();
			List<GeneralPolygon2d> nextShellTooThin = new List<GeneralPolygon2d>();
			for (int i = 0; i < Layers; ++i ) {
                FillPaths2d paths = ShellPolysToPaths(current, i);
				Shells.Add(paths);

				List<GeneralPolygon2d> all_next = new List<GeneralPolygon2d>();
				foreach ( GeneralPolygon2d gpoly in current ) {
					List<GeneralPolygon2d> offsets =
						ClipperUtil.ComputeOffsetPolygon(gpoly, -ToolWidth, true);

					List<GeneralPolygon2d> filtered = new List<GeneralPolygon2d>();
					foreach (var v in offsets) {
						bool bTooSmall = (v.Perimeter < DiscardTinyPerimterLengthMM ||
										  v.Area < DiscardTinyPolygonAreaMM2);
						if (bTooSmall)
							continue;

						if ( enable_thin_check && is_too_thin(v, thin_check_offset, thin_check_thresh_sqr) )
							nextShellTooThin.Add(v);
						else
							filtered.Add(v);
					}

					if (filtered.Count == 0)
						failedShells.Add(gpoly);
					else
						all_next.AddRange(filtered);
				}

                current_prev = current;
                current = all_next;
			}


            // failedShells have no space for internal contours. But 
            // we might be able to fit a single line...
            //foreach (GeneralPolygon2d gpoly in failedShells) {
            //	if (gpoly.Perimeter < DiscardTinyPerimterLengthMM ||
            //		 gpoly.Area < DiscardTinyPolygonAreaMM2)
            //		continue;

            //	List<FillPolyline2d> thin_shells = thin_offset(gpoly);
            //	Shells[Shells.Count - 1].Append(thin_shells);
            //}


            // remaining inner polygons
            if (InsetInnerPolygons) {
                InnerPolygons = current;
                InnerPolygons.AddRange(nextShellTooThin);
            } else {
                InnerPolygons = current_prev;
                InnerPolygons.AddRange(nextShellTooThin);
            }
            return true;
		}



        public virtual FillPaths2d ShellPolysToPaths(List<GeneralPolygon2d> shell_polys, int nShell)
        {
            FillPaths2d paths = new FillPaths2d();

			PathTypeFlags flags = PathTypeFlags.PerimeterShell;
			if (nShell == 0 && ShellType == ShellTypes.ExternalPerimeters)
				flags = PathTypeFlags.OutermostShell;
			else if (ShellType == ShellTypes.InternalShell)
				flags = PathTypeFlags.InteriorShell;
				
            if ( FilterSelfOverlaps == false ) {
                foreach (GeneralPolygon2d shell in shell_polys)
                    paths.Append(shell, flags);
                return paths;
            }

            int outer_shell_edgegroup = 100;
            foreach (GeneralPolygon2d shell in shell_polys) {
                PathOverlapRepair repair = new PathOverlapRepair();
                repair.OverlapRadius = SelfOverlapTolerance;
                repair.Add(shell, outer_shell_edgegroup);

                // Ideally want to presreve outermost shell of external perimeters.
                // However in many cases internal holes are 'too close' to outer border.
                // So we will still apply to those, but use edge filter to preserve outermost loop.
                // [TODO] could we be smarter about this somehow?
                if (nShell == 0 && ShellType == ShellTypes.ExternalPerimeters)
                    repair.PreserveEdgeFilterF = (eid) => { return repair.Graph.GetEdgeGroup(eid) == outer_shell_edgegroup; };

                repair.Compute();

                DGraph2Util.Curves c = DGraph2Util.ExtractCurves(repair.GetResultGraph());

                foreach (var polygon in c.Loops) {
					paths.Append(polygon, flags);
                }<commit_msg>return outermost shell last in ShellsFillPolygon<commit_after>﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using g3;

namespace gs
{
    /// <summary>
    /// Fill the input GPolygon with N offset shells. Generates list of 2d paths (.Shells) and
    /// also returns border polygons of "inner" region inside shells (.InnerPolygons)
    /// 
    /// Parameters:
    ///     .Layers:     number of shells
    ///     .ToolWidth:  thickness of 'tool', this is the space between subsequent shells
    ///     
    /// Options:
    ///     .InsetFromInputPolygon    : if true, initial shell is inset half a ToolWidth from input Polygon
    ///                                 (ie outer edge of tool would be "on" Polygon)
    ///     .InsetInnerPolygons       : if true, inner polygons are inset a tool-width from innermost shell (ie are
    ///                                 path you would put next shell on). If false, inner polygons lie on innermost shell.
    ///     .FilterSelfOverlaps       : if true, we try to remove areas of path that would self-overlap (default false)
    ///     .PreserveOuterShells      : if true, we don't apply this removal to outermost shells (default true)
    ///     .SelfOverlapTolerance     : distance that counts as self-overlap (default 0.3)
    /// </summary>
	public class ShellsFillPolygon : IShellsFillPolygon
    {
		// polygon to fill
		public GeneralPolygon2d Polygon { get; set; }

        // parameters
        public int Layers = 2;
        public double ToolWidth = 0.4;
		public double PathSpacing = 0.4;    // [TODO] this parameter is currently ignored?

		public double DiscardTinyPerimterLengthMM = 1.0;
		public double DiscardTinyPolygonAreaMM2 = 1.0;

		// When offsets collide, we try to find polyline paths that will "fit"
		// This is multiplier on ToolWidth, we discard path segments within
		// ToolWidth*Multiplier distance from previous shell
		public double ToolWidthClipMultiplier = 0.8;


		// if true, we inset half of tool-width from Polygon,
		// otherwise first layer is polygon
		public bool InsetFromInputPolygon = true;

        // if true, inset InnerPolygons by a tool-width from last Shell,
        // otherwise InnerPolygons lies on that Shell
        public bool InsetInnerPolygons = true;

        // [RMS] hack that lets us know this is an 'internal' shell which may be processed differently
        public enum ShellTypes
        {
            ExternalPerimeters,
            InternalShell
        }
        public ShellTypes ShellType = ShellTypes.ExternalPerimeters;

        // if true, we try to filter out self-overlaps (is expensive)
        public bool FilterSelfOverlaps = false;
        public bool PreserveOuterShells = true;         // if true, we do not try to filter self-overlaps for shell 0
        public double SelfOverlapTolerance = 0.3;


        // Outputs

        /// <summary>
        /// Shell layers are sorted outer to inner. Size of list == layers
        /// </summary>
        public List<FillPaths2d> Shells { get; set; }

        /// <summary>
        /// Return shell paths groups. Same as .Shells property, but
        /// here returned nesting order is [1...N,0]
        /// </summary>
        public List<FillPaths2d> GetFillPaths() {
            List<FillPaths2d> result = new List<FillPaths2d>();
            for (int k = 1; k < Shells.Count; ++k)
                result.Add(Shells[k]);
            result.Add(Shells[0]);
            return result;
        }

        // remaining interior polygons (to fill w/ other strategy, etc)
        public List<GeneralPolygon2d> InnerPolygons { get; set; }
        public List<GeneralPolygon2d> GetInnerPolygons() { return InnerPolygons; }


        public ShellsFillPolygon(GeneralPolygon2d poly)
		{
			Polygon = poly;
			Shells = new List<FillPaths2d>();
		}


		public bool Compute()
		{
			bool enable_thin_check = false;
			double thin_check_offset = ToolWidth * 0.45;
			double thin_check_thresh_sqr = ToolWidth * 0.3;
			thin_check_thresh_sqr *= thin_check_thresh_sqr;

			// first shell is either polygon, or inset from that polygon
			List<GeneralPolygon2d> current = (InsetFromInputPolygon) ?
				ClipperUtil.ComputeOffsetPolygon(Polygon, -ToolWidth / 2, true) :
			   	new List<GeneralPolygon2d>() { Polygon };
            List<GeneralPolygon2d> current_prev = null;

			// convert previous layer to shell, and then compute next layer
			List<GeneralPolygon2d> failedShells = new List<GeneralPolygon2d>();
			List<GeneralPolygon2d> nextShellTooThin = new List<GeneralPolygon2d>();
			for (int i = 0; i < Layers; ++i ) {
                FillPaths2d paths = ShellPolysToPaths(current, i);
				Shells.Add(paths);

				List<GeneralPolygon2d> all_next = new List<GeneralPolygon2d>();
				foreach ( GeneralPolygon2d gpoly in current ) {
					List<GeneralPolygon2d> offsets =
						ClipperUtil.ComputeOffsetPolygon(gpoly, -ToolWidth, true);

					List<GeneralPolygon2d> filtered = new List<GeneralPolygon2d>();
					foreach (var v in offsets) {
						bool bTooSmall = (v.Perimeter < DiscardTinyPerimterLengthMM ||
										  v.Area < DiscardTinyPolygonAreaMM2);
						if (bTooSmall)
							continue;

						if ( enable_thin_check && is_too_thin(v, thin_check_offset, thin_check_thresh_sqr) )
							nextShellTooThin.Add(v);
						else
							filtered.Add(v);
					}

					if (filtered.Count == 0)
						failedShells.Add(gpoly);
					else
						all_next.AddRange(filtered);
				}

                current_prev = current;
                current = all_next;
			}


            // failedShells have no space for internal contours. But 
            // we might be able to fit a single line...
            //foreach (GeneralPolygon2d gpoly in failedShells) {
            //	if (gpoly.Perimeter < DiscardTinyPerimterLengthMM ||
            //		 gpoly.Area < DiscardTinyPolygonAreaMM2)
            //		continue;

            //	List<FillPolyline2d> thin_shells = thin_offset(gpoly);
            //	Shells[Shells.Count - 1].Append(thin_shells);
            //}


            // remaining inner polygons
            if (InsetInnerPolygons) {
                InnerPolygons = current;
                InnerPolygons.AddRange(nextShellTooThin);
            } else {
                InnerPolygons = current_prev;
                InnerPolygons.AddRange(nextShellTooThin);
            }
            return true;
		}



        public virtual FillPaths2d ShellPolysToPaths(List<GeneralPolygon2d> shell_polys, int nShell)
        {
            FillPaths2d paths = new FillPaths2d();

			PathTypeFlags flags = PathTypeFlags.PerimeterShell;
			if (nShell == 0 && ShellType == ShellTypes.ExternalPerimeters)
				flags = PathTypeFlags.OutermostShell;
			else if (ShellType == ShellTypes.InternalShell)
				flags = PathTypeFlags.InteriorShell;
				
            if ( FilterSelfOverlaps == false ) {
                foreach (GeneralPolygon2d shell in shell_polys)
                    paths.Append(shell, flags);
                return paths;
            }

            int outer_shell_edgegroup = 100;
            foreach (GeneralPolygon2d shell in shell_polys) {
                PathOverlapRepair repair = new PathOverlapRepair();
                repair.OverlapRadius = SelfOverlapTolerance;
                repair.Add(shell, outer_shell_edgegroup);

                // Ideally want to presreve outermost shell of external perimeters.
                // However in many cases internal holes are 'too close' to outer border.
                // So we will still apply to those, but use edge filter to preserve outermost loop.
                // [TODO] could we be smarter about this somehow?
                if (PreserveOuterShells && nShell == 0 && ShellType == ShellTypes.ExternalPerimeters)
                    repair.PreserveEdgeFilterF = (eid) => { return repair.Graph.GetEdgeGroup(eid) == outer_shell_edgegroup; };

                repair.Compute();

                DGraph2Util.Curves c = DGraph2Util.ExtractCurves(repair.GetResultGraph());

                foreach (var polygon in c.Loops) {
					paths.Append(polygon, flags);
                }
{'fraction_non_alphanumeric': 0.08384471287384536, 'fraction_numerical': 0.00891415283250436, 'mean_word_length': 2.2362040133779266, 'pattern_counts': {'":': 0, '<': 62, '<?xml version=': 0, '>': 53, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 6, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '797982', 'n_tokens_mistral': 4923, 'n_tokens_neox': 4321, 'n_words': 1287}