CreativeInquiry / PEmbroider

Embroidery Library for Processing
Other
442 stars 28 forks source link

Experiments with Hatch and stitch Lengths & suggestions for better fills #10

Closed tatyanade closed 4 years ago

tatyanade commented 4 years ago

Experiments with altering stitch length! and hatch

2020-06-08_16h51_07 2020-06-08_17h21_21

For fills I think it would be beneficial to make the thread stops on alternating layers as far from each other as possible (Like these yellow dots I've drawn in) or at least not very close to the thread stops on neighbor lines - the magenta dots in here are where the stitches start and stop and the yellow is a suggestion for how to make the fill a little better 2020-06-08_17h21_51

so the threads look more like this - this seems to be how other machine embroideries get their fills effectively.

2020-06-08_17h22_22 2020-06-08_17h22_30

other wise it creates valleys in the fill (highlighted in red below) that alter the shape - i think this skewed the circle since it causes the fabric to stretch and is harder on the fabric and more likely to deform or break it since its poking a lot of holes right next to each other instead of spacing them out more 2020-06-08_17h13_22

Also did some tests with outlines, the one on the left has a lower hatch value and the stitch length on both is the same and is the same as the width of the border, decreasing hatch and increasing stitch_length definitely help things look better

2020-06-08_17h20_24

golanlevin commented 4 years ago

Thanks @tatyanade, the staggered stitch endpoints is a very helpful suggestion. (@LingDong- , please see above). Also, Tatyana, please be aware of the setStitch() function,

E.setStitch( float min_stitch_length, float stitch_length, float resample_noise);
With larger resample noise, there's more variation among stitches, and therefore less of the problematic stitch alignment patterns. The default noise value is = 0.5.

LingDong- commented 4 years ago

0ca31e8c67d1051d9a891f5ed52f2bc34979fdd2 added some improvement (?) to resample_noise with a weighted random, which will favour extreme values over mediocre values, as opposed to the uniform random that was used. Seems to help a bit.

maybe we need to rethink the resample algorithm

golanlevin commented 4 years ago

@LingDong- , what if alternate (every other) hatch lines simply began with a half-length stitch? Then we wouldn't have to depend on noise, and could implement Tatyana's suggestion precisely:

IMG_8680

LingDong- commented 4 years ago

I tried that already.

However it doesn't seem to help much because the circles curvature is quite large compared to stitch length, so these offsets get cancelled out very soon.

It is easy to come up with a solution for one situation, but harder to have a resample algorithm that works for all situations, (different hatch modes, outlines, etc.) One way is to make an algorithm for each shape and hatch mode.

golanlevin commented 4 years ago

There's an interesting but ....kindof terrible... idea, which is to have the stitch points operate like a particle system, and mutually repel each other, but constrained to move only along their hatch lines, until an equilibrium is reached....

golanlevin commented 4 years ago

Hey @LingDong- , I tried this out with a particle repulsion system. It actually works OK, check it out in this animated GIF:

ezgif-4-71ab9ac3ea9f

Here's my quick filthy code for testing the idea:

int nHatches = 34; 
HatchLine hatches[];
float SL = 25; 
float friction = 0.975;

void setup() {
  size(400, 400); 

  hatches = new HatchLine[nHatches];
  for (int i=0; i<nHatches; i++) {
    float ay = map(i, 0, nHatches-1, height*0.25, height*0.75);
    float by = ay; 

    float dr = 100; 
    float dy = height/2 - ay; 
    float dx = sqrt(dr*dr - dy*dy); 
    float ax = width/2 - dx;
    float bx = width/2 + dx;
    PVector A = new PVector(ax, ay);  
    PVector B = new PVector(bx, by); 
    hatches[i] = new HatchLine(A, B);
  }
}

void draw() {
  background(255); 
  pushMatrix();
  translate( width/2, height/2); 
  rotate(radians(-30)); 
  translate(-width/2, -height/2);
  for (int i=0; i<nHatches; i++) {
    hatches[i].draw();
  }

  popMatrix(); 
  simulate();
}

void simulate() {
  float R = 3.3 * SL; 
  float STRENGTH = 0.001; 

  noFill(); 
  for (int i=0; i<nHatches; i++) {
    HatchLine H = hatches[i];
    for (int ip=0; ip<H.N; ip++) {
      float px = H.pts[ip].x;
      float py = H.pts[ip].y;

      for (int j=0; j<nHatches; j++) {
        HatchLine K = hatches[j];
        for (int jq=0; jq<K.N; jq++) {

          if (!((i == j) && (ip == jq))) {
            float qx = K.pts[jq].x;
            float qy = K.pts[jq].y;

            float dx = qx - px; 
            float dy = qy - py; 
            float dh = sqrt(dx*dx + dy*dy); 
            if ((dh < R) && (dh > 0)) {

              float F = STRENGTH/(dh*dh);
              float fx = dx/dh * F;

              if ((ip != 0) && (ip < (H.N-1))) {
                H.varray[ip] -= fx; // add acc
                H.varray[ip] *= friction; // add fric
                H.parray[ip] += H.varray[ip]; // add acc
                H.parray[ip] = constrain(H.parray[ip], 0,1); 
              }

              if ((jq != 0) && (jq < (K.N-1))) {
                K.varray[jq] += fx;
                K.varray[jq] *= friction; // add fric
                K.parray[jq] += K.varray[jq];
                K.parray[jq] = constrain(K.parray[jq], 0,1); 
              }
            }
          }
        }
      }
    }
  }
}

class HatchLine {
  PVector A; 
  PVector B; 
  int N; 
  float parray[];
  float varray[];
  PVector pts[];

  HatchLine (PVector a, PVector b) {
    A = a;
    B = b;
    N = 2 + (int)(dist(A.x, A.y, B.x, B.y)/SL); 
    parray = new float[N]; 
    varray = new float[N]; 
    pts = new PVector[N];

    for (int i=0; i<N; i++) {
      float t = map(i, 0, N-1, 0, 1); 
      float nt = 0; 
      if ((i > 0) && (i < N-1)) {
        nt = 0.75 * (noise(i/10.0 + A.x, A.y) - 0.5);
      }
      float tnt =  constrain(t + nt, 0, 1); 
      parray[i] = tnt;
      varray[i] = 0;
      pts[i] = new PVector(0, 0);
    }
  }

  void draw() {

    stroke(0); 
    line(A.x, A.y, B.x, B.y);
    fill(0); 
    noStroke(); 

    for (int i=0; i<N; i++) {
      float t = parray[i];
      float tx = lerp(A.x, B.x, t); 
      float ty = lerp(A.y, B.y, t); 
      pts[i].set(tx, ty);
    }
    for (int i=0; i<N; i++) {
      float px = pts[i].x;
      float py = pts[i].y;
      ellipse(px, py, 5, 4);
    }
  }
}
LingDong- commented 4 years ago

@golanlevin cool!

Since we're now specifically fixing parallel hatching, I just had another idea while watching your simulation:

The stitches for parallel hatching can actually be the intersection with cross hatching

IMG_0844 copy

I can add some code for a special case when doing parallel hatching. Currently the resampler is ignorant of the nature of the thing it is asked to resample (it just tries its best looking at 1 polyline it is given), but seems like sooner or later we need branching

golanlevin commented 4 years ago

I see what you're saying about the cross-hatch, but the relative angles or cross-spacing might need to be tinkered with to achieve the desired stitch length.

I improved my particle algorithm a little by having each particle only look at the two adjacent hatch lines (as opposed to all of them). Ultimately I don't think this simulation algorithm is practical, but it is interesting.

ezgif-4-258a3df60a98

I was inspired by the idea of Poisson-disc distribution sampling and was curious if it could be extended to a poly-1D world.

download

LingDong- commented 4 years ago

Hi @golanlevin

I think I was able to solve the problem (quite perfectly :) with the cross hatch method I mentioned earlier. 6d8c7929168e87008a7e158b3e4e43812b4d4e3e

Screen Shot 2020-06-09 at 10 58 35 PM

Utilizing some Ancient Greek technology I was able to figure out the correct relative angles and cross-spacing:

IMG_0845

golanlevin commented 4 years ago

Ha! So much better than my ridiculous particle system. @tatyanade, could you please test out Lingdong's new hatching (for PARALLEL mode)?

golanlevin commented 4 years ago

Hi @LingDong-, Next challenge: can you use the Ancient Greek Technology to plan stitch points for the cross-hatching method (with the goals of spacing the points as far apart as possible, and minimizing the occurrence of points that are too close together)? And how will this interact with the user being able to set variable stitch lengths?

IMG_8683

tatyanade commented 4 years ago

2020-06-12_16h43_34 tests - this is really effective for hatch spacing of 2; with a spacing of 1 it still warps - I think it would be good to have an option for how offset each layer is - right now this offset each alternate by half the stitch length but for denser fills i think it would be appropriate to offset each line by thirds or even fourths.

golanlevin commented 4 years ago

Hi @tatyanade , to be clear, do you mean something like the following?

On the left is a sparse hatch, and the threads are offset by 0%-50%-0%. On the right is a dense hatch, and the threads are offset by 0%-33%-66%-0%

Screen Shot 2020-06-12 at 5 43 38 PM

@LingDong- , is this something you could try?

Also, @tatyanade , have you tried hatch spacing of 1.5? (Does that work?)

LingDong- commented 4 years ago

@golanlevin Sure! this is so easy to try, I just need to change one number from 0.5 to 1/3 or 1/4 etc.

I'll accept the challenge of making it work for cross hatching ;) But out of curiosity, would simply applying the algorithm for parallel hatching twice for each direction work? and maybe afterwards we make some micro adjustments to have the points spread out. Or maybe would the stretching in two directions cancel out to become no stretching?

LingDong- commented 4 years ago
Screen Shot 2020-06-12 at 9 24 25 PM

Added experimental option E.PARALLEL_RESAMPLING_FACTOR to control the offset: eb069c2addf2519211043be64ecce09a2338c036

@golanlevin btw, just finished documenting PEmbroiderGraphics with javadoc style comments, all 147 functions. Never realized I wrote so much code until I need to document it ;)

LingDong- commented 4 years ago
Screen Shot 2020-06-12 at 11 00 22 PM

@golanlevin I implemented the resampling algorithm for cross hatching in your illustration (again with Greek tech ;) c7c86312742b657b266b85ea4f4630fc21827f97

However I don't know what to do about variable stitch length, so the solution I came up with is to round the stitch length to the nearest multiple.

So for user stitch lengths that are higher than the "perfect" spacing shown in your illustration, it will do every two steps, every three steps, every four steps, etc. (See the rightmost center circle in the screenshot) I think that's probably an acceptable compromise for the user?

golanlevin commented 4 years ago

@tatyanade , could you kindly test out the new hatching options. In particular, I made a new hatching demo (#3) that shows the effects of changing the offset percentage and the noise amount:

PEmbroider_shape_hatching_3
tatyanade commented 4 years ago

2020-06-13_17h45_23

Also ran the first row again with stitch length of 50, hatch spacing at 1.5, here is that: image 2020-06-13_17h45_26

golanlevin commented 4 years ago

This appears resolved :)