Levelling & Sag

Quick Start

When you paint or coat a surface you can often obtain lines, which need to level out, and, on steep surfaces, you get sagging.

Naive attempts to relate the phenomena to rheology have consistently failed - but by focussing on shear stresses it's possible to better understand them.

Credits

This app uses the Anti-Sag Index calculation from Chang-Sheng Wang et al1 at Polytechnique Montréal.

Levelling & Sag

τ0 Pa
K (Pa.sn)
n
ρ kg/m³
γ̇ max /s
Log Plots
Stress Plots
ASI
Values @ ASI
//One universal basic required here to get things going once loaded
window.onload = function () {
    //restoreDefaultValues(); //Un-comment this if you want to start with defaults
    Main();
};

//Main() is hard wired as THE place to start calculating when inputs change
//It does no calculations itself, it merely sets them up, sends off variables, gets results and, if necessary, plots them.
function Main() {
    //Save settings every time you calculate, so they're always ready on a reload
    saveSettings();

    //Send all the inputs as a structured object
    //If you need to convert to, say, SI units, do it here!
    const inputs = {
        t0: sliders.Slidet0.value,
        K: sliders.SlideK.value,
        HBn: sliders.SlideHBn.value,
        rho: sliders.Sliderho.value,
        SRmax: sliders.SlideSRmax.value,
        Log: document.getElementById('Log').checked,
        Stress: document.getElementById('Stress').checked,

   };

    //Send inputs off to CalcIt where the names are instantly available
    //Get all the resonses as an object, result


    const result = CalcIt(inputs);

    //Set all the text box outputs
    document.getElementById('ASI').value = result.ASI; 
    //Do all relevant plots by calling plotIt - if there's no plot, nothing happens
    //plotIt is part of the app infrastructure in app.new.js
    if (result.plots) {
        for (let i = 0; i < result.plots.length; i++) {
            plotIt(result.plots[i], result.canvas[i]);
        }
    }

    //You might have some other stuff to do here, but for most apps that's it for Main!
}

//Here's the app calculation
function CalcIt({ t0,K,HBn, rho,SRmax, Log, Stress }) {
    let VPts=[], SPts=[],i=0.1 //0.318
    while (i <= 100) {
        sr = i * i
        if (sr>SRmax) break
            sstress=t0+K*Math.pow(sr,HBn)
            v=sstress/sr
        if (!Stress){
            VPts.push({x:sr,y:v})
            SPts.push({x:sstress,y:v})
        } else {
            VPts.push({x:sr,y:sstress})
            SPts.push({x:sstress,y:sstress})

        }
        if (i < 10) {
            i += 0.1
        } else {
            i += 1
        }
    }
    const n=HBn, rhog=rho*9.81, vc=600/1e6
    const ASI = 1e6/25.4*(Math.pow(vc*(n+1)/n*Math.pow(rhog/K,-1/n),n/(n+1))+t0/rhog)
    const plotData = [VPts]
    const plotData1 = [SPts]
    const yLabel=! Stress?'Viscosity&Pa.s':"Shear Stress&Pa"
    //Now set up all the graphing data detail by detail.
    const prmap = {
        plotData: plotData, //An array of 1 or more datasets
        lineLabels: "Viscosity", //An array of labels for each dataset
        colors: ["blue"], //An array of colors for each dataset
        hideLegend: true,
        borderWidth: 2,
        xLabel: 'Shear Rate γ̇ &/s', //Label for the x axis, with an & to separate the units
        yLabel: yLabel, //Label for the y axis, with an & to separate the units
        y2Label: null, //Label for the y2 axis, null if not needed
        yAxisL1R2: [], //Array to say which axis each dataset goes on. Blank=Left=1
        logX: Log, //Is the x-axis in log form?
        xTicks: function(value, index, ticks) {if (!Log) {return value} else {if (value.toPrecision(2).includes("1")  || value.toPrecision(2).includes("4")) {if (value>=1) {return value.toFixed(0)} else {return value.toPrecision(1)} } else {return ""}}; }, //We can define a tick function if we're being fancy
        logY: Log, //Is the y-axis in log form?
        yTicks: function(value, index, ticks) {if (!Log) {return value} else {if (value.toPrecision(2).includes("1") || value.toPrecision(2).includes("4")) {if (value>=1) {return value.toFixed(0)} else {return value.toPrecision(1)} } else {return ""}}; }, //We can define a tick function if we're being fancy
        legendPosition: 'top', //Where we want the legend - top, bottom, left, right
        xMinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        yMinMax: [0,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        y2MinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        xSigFigs: 'P3', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'P3', //F for Fixed, P for Precision, E for exponential
    };
    const prmap1 = {
        plotData: plotData1, //An array of 1 or more datasets
        lineLabels: "Viscosity", //An array of labels for each dataset
        colors: ["orange"], //An array of colors for each dataset
        hideLegend: true,
        borderWidth: 2,
        xLabel: 'Shear Stress&Pa', //Label for the x axis, with an & to separate the units
        yLabel: yLabel, //Label for the y axis, with an & to separate the units
        y2Label: null, //Label for the y2 axis, null if not needed
        yAxisL1R2: [], //Array to say which axis each dataset goes on. Blank=Left=1
        logX: Log, //Is the x-axis in log form?
        xTicks: function(value, index, ticks) {if (!Log) {return value} else {if (value.toPrecision(2).includes("1") || value.toPrecision(2).includes("4")) {if (value>=1) {return value.toFixed(0)} else {return value.toPrecision(1)} } else {return ""}}; },
        logY: Log, //Is the y-axis in log form?
        yTicks: function(value, index, ticks) {if (!Log) {return value} else {if (value.toPrecision(2).includes("1") || value.toPrecision(2).includes("4")) {if (value>=1) {return value.toFixed(0)} else {return value.toPrecision(1)} } else {return ""}}; },
        legendPosition: 'top', //Where we want the legend - top, bottom, left, right
        xMinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        yMinMax: [0,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        y2MinMax: [,], //Set min and max, e.g. [-10,100], leave one or both blank for auto
        xSigFigs: 'P3', //These are the sig figs for the Tooltip readout. A wide choice!
        ySigFigs: 'P3', //F for Fixed, P for Precision, E for exponential
    };

    //Now we return everything - text boxes, plot and the name of the canvas, which is 'canvas' for a single plot
    return {
        plots: [prmap, prmap1],
        canvas: ['canvas', 'canvas1'],
        ASI: ASI.toFixed(1)
     };
}

            

It turns out that a simple fit to a Power Law curve over a small range of small shear rates or shear stresses is enough to provide good predictions of the likelihood of levelling (good) or sag (bad).

For example, a thick layer of paint might have a shear stress of 1 to 10 Pa. If that stress is beyond the viscosity of the paint, the paint will level or sag (as measured by the Anti-Sag Index). The shear rate of levelling or sagging is an effect, while the shear stress is a cause.

Attempts to correlate these effects with viscosities at controlled shear rates are usually a failure. Correlations with viscosities at controlled shear stresses are generally good. It's not a coincidence (as shown in the paper) that there is also a good correlation with G' (or tanδ values, G''/G') and these effects - as long as the oscillatory measure is made at low strain amplitudes (e.g. 0.001), and shortly after a strong pre-shear to mimic the application process.

To use the app for the Anti-Sag index you (visually) fit your data to (preferably) a power law, then those fitted values are used by the app.

Herschel-Bulkley & Power Law

Here we calculate the shear stress τ which might have a yield stress τ0 ...

`τ = τ_0+Kγ̇^n`

... and then calculate viscosity as stress/shear rate.

`η = (τ_0+Kγ̇^n)/"γ̇"`

If we want a pure Power Law fluid then τ0 is set to 0 so that `η=Kγ̇^(n-1)`. The n value is the power and K is the "consistency factor" with the strange units of (Pa.sn). For paint sag, a correlation with a simple power law turns out to be better than with fits involving the hard-to-pin down yield stress.

Sag correlation

A classic test for sag is the ASI, Anti-Sag Index. A comb with gaps of different height is drawn over a thick paint layer so that stripes of different heights (alas, specified in mils, so you need to multiply by 25 to get μm) are created. When the panel is made vertical, thin stipes won't sag as the shear stress is low, while thick stripes have a higher stress so sag starts. The ASI is the thickest stripe which doesn't sag.

The paper by Chang-Sheng Wang et al1 derives a useful relationship between ASI and the Herschel-Bulkley model or, even better, just the Power Law fit (fitted with τ0 set to 0). The first constant, 39370 is the conversion of metres to mils, the second, 0.0005, is the m/s value of the "critical velocity" of 500μm/s quoted in the paper as being a suitable value.

Herschel-Bulkley fit

`ASI = 39370{[0.0005(n+1)/n("ρg"/K)^(-1/n)]^(n/(n+1)) + τ_0/(ρg)}`

Power Law fit

`ASI = 39370[0.0005(n+1)/n("ρg"/K)^(-1/n)]^(n/(n+1))`

1Chang-Sheng Wang, Gérard Chapelle, Pierre Carreau, Marie-Claude Heuzey, Prediction of sag resistance in paints using rheological measurements, Progress in Organic Coatings, 153, (2021), 106139

In this early version of the app there is not yet a correlation with the levelling score of 1 to 10.