VillaTerras VillaMineral
VillaTerras VillaMineral Engineering Input
Engineering Output
Waiting for input...
Engineering Warnings
No warnings yet.
function exportCSV() {
const vm = structuredClone(VillaMineral);
const form = document.forms[‘vmForm’];
Array.from(form.elements).forEach(el => {
if (el.name && el.value !== “”) {
const path = el.name.split(‘.’);
let ref = vm.inputs;
while (path.length > 1) ref = ref[path.shift()];
const finalKey = path[0];
ref[finalKey] = isNaN(el.value) || el.type === ‘select-one’
? (el.value === ‘true’ ? true : (el.value === ‘false’ ? false : el.value))
: parseFloat(el.value);
}
});
const output = vm.calc.resultSummary.call(vm);
let csv = “Metric,Value\n”;
for (let key in output) {
csv += `”${key}”,”${output[key]}”\n`;
}
const blob = new Blob([csv], { type: ‘text/csv’ });
const link = document.createElement(“a”);
link.href = URL.createObjectURL(blob);
link.download = “VillaMineral_Results.csv”;
link.click();
}
function runWarnings() {
const vm = structuredClone(VillaMineral);
const form = document.forms[‘vmForm’];
Array.from(form.elements).forEach(el => {
if (el.name && el.value !== “”) {
const path = el.name.split(‘.’);
let ref = vm.inputs;
while (path.length > 1) ref = ref[path.shift()];
const finalKey = path[0];
ref[finalKey] = isNaN(el.value) || el.type === ‘select-one’
? (el.value === ‘true’ ? true : (el.value === ‘false’ ? false : el.value))
: parseFloat(el.value);
}
});
const w = [];
const idlerSpacing = vm.inputs.idler.spacing_ft;
const materialDensity = vm.inputs.material.density_lbft3;
const incline = vm.inputs.loading.fallAngle_deg;
const plys = vm.inputs.belt.plys;
const power = vm.calc.horsepowerRequired.call(vm);
if (materialDensity > 100 && idlerSpacing > 4) {
w.push(“Warning: Idler spacing is too far for dense materials. Consider reducing to 3 ft.”);
}
if (incline > 18) {
w.push(“Warning: Incline exceeds recommended max for smooth belts. Consider cleated belts or reducing angle.”);
}
if (plys <= 3 && power > 75) {
w.push(“Warning: Power demand may exceed safe tension limits for 3-ply belts. Consider 4-ply or steel cord.”);
}
if (w.length === 0) {
w.push(“All values within standard engineering parameters.”);
}
document.getElementById(“warningBox”).textContent = w.join(“\n”);
}
Power vs. Throughput Chart
function drawPowerTPHChart() {
const vm = structuredClone(VillaMineral);
const form = document.forms[‘vmForm’];
Array.from(form.elements).forEach(el => {
if (el.name && el.value !== “”) {
const path = el.name.split(‘.’);
let ref = vm.inputs;
while (path.length > 1) ref = ref[path.shift()];
const finalKey = path[0];
ref[finalKey] = isNaN(el.value) || el.type === ‘select-one’
? (el.value === ‘true’ ? true : (el.value === ‘false’ ? false : el.value))
: parseFloat(el.value);
}
});
const ctx = document.getElementById(“powerChart”).getContext(“2d”);
ctx.clearRect(0, 0, 1000, 300);
// Axes
ctx.strokeStyle = “#000”;
ctx.beginPath();
ctx.moveTo(60, 10); ctx.lineTo(60, 270); ctx.lineTo(980, 270); ctx.stroke();
ctx.fillStyle = “#000”;
ctx.font = “14px Arial”;
ctx.fillText(“Power (HP)”, 5, 20);
ctx.fillText(“TPH →”, 900, 290);
// Simulate points: vary cross section from 0.5 to 4.5 ft²
ctx.strokeStyle = “#0077cc”;
ctx.beginPath();
for (let i = 0.5; i <= 4.5; i += 0.1) {
vm.inputs.loading.crossSection_ft2 = i;
const tph = vm.calc.tph.call(vm);
const hp = vm.calc.horsepowerRequired.call(vm);
const x = 60 + (tph / 2); // scale factor
const y = 270 - (hp * 2); // scale factor
if (i === 0.5) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.stroke();
// Draw points
for (let i = 0.5; i <= 4.5; i += 0.5) {
vm.inputs.loading.crossSection_ft2 = i;
const tph = vm.calc.tph.call(vm);
const hp = vm.calc.horsepowerRequired.call(vm);
const x = 60 + (tph / 2);
const y = 270 - (hp * 2);
ctx.beginPath();
ctx.arc(x, y, 3, 0, 2 * Math.PI);
ctx.fillStyle = "#00aa00";
ctx.fill();
ctx.fillStyle = "#000";
ctx.fillText(`${tph.toFixed(0)} TPH`, x - 15, y - 10);
}
}
drawPowerTPHChart();
function saveTemplate() {
const name = document.getElementById(“templateName”).value.trim();
if (!name) return alert(“Enter a name.”);
const form = document.forms[‘vmForm’];
const vm = structuredClone(VillaMineral);
Array.from(form.elements).forEach(el => {
if (el.name && el.value !== “”) {
const path = el.name.split(‘.’);
let ref = vm.inputs;
while (path.length > 1) ref = ref[path.shift()];
const finalKey = path[0];
ref[finalKey] = isNaN(el.value) || el.type === ‘select-one’
? (el.value === ‘true’ ? true : (el.value === ‘false’ ? false : el.value))
: parseFloat(el.value);
}
});
const templates = JSON.parse(localStorage.getItem(“villaTemplates”) || “{}”);
templates[name] = vm.inputs;
localStorage.setItem(“villaTemplates”, JSON.stringify(templates));
refreshTemplateList();
}
function loadTemplate() {
const selected = document.getElementById(“templateSelector”).value;
if (!selected) return alert(“Choose a template.”);
const templates = JSON.parse(localStorage.getItem(“villaTemplates”) || “{}”);
const data = templates[selected];
if (!data) return;
for (const key in data) {
for (const prop in data[key]) {
const inputName = `${key}.${prop}`;
const el = document.querySelector(`[name=”${inputName}”]`);
if (el) {
el.value = data[key][prop];
}
}
}
calculateVM();
}
function deleteTemplate() {
const selected = document.getElementById(“templateSelector”).value;
if (!selected) return alert(“Choose a template to delete.”);
const templates = JSON.parse(localStorage.getItem(“villaTemplates”) || “{}”);
delete templates[selected];
localStorage.setItem(“villaTemplates”, JSON.stringify(templates));
refreshTemplateList();
}
function refreshTemplateList() {
const templates = JSON.parse(localStorage.getItem(“villaTemplates”) || “{}”);
const select = document.getElementById(“templateSelector”);
select.innerHTML = ‘
‘;
Object.keys(templates).forEach(name => {
const opt = document.createElement(“option”);
opt.value = name;
opt.textContent = name;
select.appendChild(opt);
});
}
window.onload = refreshTemplateList;
Motor Sizing Assistant
Press "Run VillaMineral Engine" to get motor sizing suggestions...
function runMotorAssistant(vm) {
const power = vm.calc.horsepowerRequired.call(vm);
const torque = vm.calc.motorTorqueOutput.call(vm);
const rpm = vm.inputs.motor.rpm / vm.inputs.motor.gearboxRatio;
const plys = vm.inputs.belt.plys;
let sizeClass = “Standard”;
if (power > 50 && power <= 100) sizeClass = "Heavy-Duty";
if (power > 100) sizeClass = “Oversized”;
let torqueComment = “OK”;
if (torque > 2000 && plys <= 3) {
torqueComment = "Exceeds safe belt ply limit";
}
const recommendations = [
`Required Horsepower: ${power.toFixed(2)} HP`,
`Motor RPM after gearbox: ${rpm.toFixed(0)} RPM`,
`Torque Output: ${torque.toFixed(0)} ft-lb`,
`Recommended Motor Class: ${sizeClass}`,
`Torque Compatibility: ${torqueComment}`,
(power > vm.inputs.motor.hp)
? “Warning: Motor undersized. Upgrade required.”
: “Motor size is sufficient.”
];
document.getElementById(“motorAssistBox”).textContent = recommendations.join(‘\n’);
}
runMotorAssistant(vm);
Wrap Angle & Take-Up Tension
Calculated wrap angle and belt tensions will appear here...
function runWrapAndTension(vm) {
const Te = vm.calc.effectiveTension.call(vm); // Effective tension
const μ = 0.35; // Friction coefficient pulley-to-belt (adjustable later)
const wrapAngleDeg = 210; // Typical head pulley wrap (adjustable later)
const θ_rad = wrapAngleDeg * Math.PI / 180;
// Use Euler’s equation: T1 = Te * e^(μθ) / (e^(μθ) – 1)
const expFactor = Math.exp(μ * θ_rad);
const T2 = Te / (expFactor – 1);
const T1 = T2 * expFactor;
const takeUpTension = T1 – T2;
const out = [
`Wrap Angle: ${wrapAngleDeg}° (${θ_rad.toFixed(2)} rad)`,
`Tight Side Tension (T1): ${T1.toFixed(2)} lb`,
`Slack Side Tension (T2): ${T2.toFixed(2)} lb`,
`Effective Tension (Te = T1 – T2): ${Te.toFixed(2)} lb`,
`Required Take-Up Tension: ${takeUpTension.toFixed(2)} lb`,
(takeUpTension > vm.inputs.pulleys.counterweight_lb)
? “Warning: Counterweight is too small!”
: “Counterweight is sufficient.”
];
document.getElementById(“tensionBox”).textContent = out.join(“\n”);
}
runWrapAndTension(vm);
function runSurgeSimulator() {
const surgePct = parseFloat(document.getElementById(“surgePct”).value) / 100;
const surgeSeconds = parseFloat(document.getElementById(“surgeDuration”).value);
const vm = structuredClone(VillaMineral);
// Clone and increase cross-section temporarily
const baseCrossSection = vm.inputs.loading.crossSection_ft2;
vm.inputs.loading.crossSection_ft2 *= surgePct;
const tphSurge = vm.calc.tph.call(vm);
const hpSurge = vm.calc.horsepowerRequired.call(vm);
const tensionSurge = vm.calc.effectiveTension.call(vm);
const torqueSurge = vm.calc.motorTorqueOutput.call(vm);
const warnings = [];
if (hpSurge > vm.inputs.motor.hp * 1.1) warnings.push(“Motor may stall or trip under surge load.”);
if (torqueSurge > 3000) warnings.push(“Torque exceeds typical 4-ply/5-ply belt threshold.”);
if (tensionSurge > 2 * vm.inputs.pulleys.counterweight_lb) warnings.push(“Counterweight insufficient for surge tension.”);
const result = [
`Surge TPH: ${tphSurge.toFixed(2)} TPH`,
`Surge Power Required: ${hpSurge.toFixed(2)} HP`,
`Surge Effective Tension: ${tensionSurge.toFixed(2)} lb`,
`Surge Torque: ${torqueSurge.toFixed(2)} ft-lb`,
`Surge Duration: ${surgeSeconds} seconds`,
warnings.length ? “Warnings:\n- ” + warnings.join(“\n- “) : “All within safe surge range.”
];
document.getElementById(“surgeBox”).textContent = result.join(“\n”);
// Reset model back
vm.inputs.loading.crossSection_ft2 = baseCrossSection;
}
Pulley Sizing Assistant
Analysis of drive and tail pulley sizing will appear here...
function runPulleySizingAssistant(vm) {
const bw = vm.inputs.belt.width_in;
const plys = vm.inputs.belt.plys;
const speed = vm.inputs.belt.speed_fpm;
const beltType = vm.inputs.belt.construction;
const torque = vm.calc.motorTorqueOutput.call(vm);
const faceWidth = vm.inputs.pulleys.faceWidth_in;
const driveDia = vm.inputs.pulleys.driveDiameter_in;
const wrapAngle = 210; // assumed default, or replace later
let minDriveDia = 0;
if (beltType === “Steel Cord”) minDriveDia = 24;
else if (plys <= 3) minDriveDia = 16;
else if (plys === 4) minDriveDia = 18;
else if (plys >= 5) minDriveDia = 20;
const laggingRecommended = torque > 2000 ? “Ceramic” : torque > 1200 ? “Rubber” : “Plain”;
const minFaceWidth = bw + 2; // +2 inch side margin
const warnings = [];
if (driveDia < minDriveDia) warnings.push(`Drive pulley diameter (${driveDia}") is too small. Min recommended: ${minDriveDia}".`);
if (faceWidth < minFaceWidth) warnings.push(`Face width (${faceWidth}") is too narrow. Recommended: ${minFaceWidth}".`);
if (wrapAngle < 180) warnings.push(`Wrap angle too low. May reduce traction under load.`);
const out = [
`Belt Width: ${bw}" | Ply Count: ${plys}`,
`Recommended Min Drive Diameter: ${minDriveDia}"`,
`Recommended Face Width: ${minFaceWidth}"`,
`Motor Torque: ${torque.toFixed(2)} ft-lb`,
`Suggested Lagging: ${laggingRecommended}`,
warnings.length ? "Warnings:\n- " + warnings.join("\n- ") : "Pulley setup looks safe and balanced."
];
document.getElementById("pulleyAssistBox").textContent = out.join("\n");
}
runPulleySizingAssistant(vm);
Belt Cleaner Selector
Cleaner recommendation and risk analysis will appear here...
function runCleanerSelector(vm) {
const material = vm.inputs.material.type.toLowerCase();
const speed = vm.inputs.belt.speed_fpm;
const width = vm.inputs.belt.width_in;
const moisture = vm.inputs.material.moisture_pct;
const existingCleaner = vm.inputs.environment.cleaner;
const stickyMaterials = [“clay”, “soil”, “wet sand”, “fertilizer”, “ore slurry”];
const isSticky = stickyMaterials.some(m => material.includes(m)) || moisture > 8;
let recommended = “None”;
if (isSticky && speed > 300 && width >= 24) {
recommended = “Primary + Secondary + V-Plow”;
} else if (isSticky && speed > 250) {
recommended = “Primary + V-Plow”;
} else if (!isSticky && speed > 300) {
recommended = “Primary Only”;
}
const warnings = [];
if (recommended !== “None” && existingCleaner === “None”) {
warnings.push(“Carryback risk detected. No cleaner installed.”);
}
if (recommended !== “None” && existingCleaner !== “None” && !recommended.includes(existingCleaner)) {
warnings.push(`Installed cleaner “${existingCleaner}” may be insufficient. Consider upgrading to: ${recommended}`);
}
const out = [
`Material: ${material}`,
`Belt Speed: ${speed} ft/min`,
`Moisture: ${moisture}%`,
`Recommended Cleaner Setup: ${recommended}`,
`Installed Cleaner: ${existingCleaner}`,
warnings.length ? “Warnings:\n- ” + warnings.join(“\n- “) : “Cleaner setup is valid and safe.”
];
document.getElementById(“cleanerBox”).textContent = out.join(“\n”);
}
runCleanerSelector(vm);
Transition Zone & Edge Stress
Transition analysis and belt edge stress will appear here...
function runTransitionZoneAnalysis(vm) {
const bw = vm.inputs.belt.width_in;
const plys = vm.inputs.belt.plys;
const troughAngle = vm.inputs.idler.angle_deg;
const transitionLen = vm.inputs.idler.transitionLength_ft;
const tension = vm.calc.effectiveTension.call(vm);
// Empirical minimum transition length formula
const minTransitionLen = (bw / 12) * (troughAngle / 20) * (plys / 2.5);
// Edge stress estimation: edge carries more load if transition too short
const edgeTensionFactor = minTransitionLen / Math.max(transitionLen, 0.1);
const edgeStress = tension * edgeTensionFactor * 0.3;
const warnings = [];
if (transitionLen < minTransitionLen) {
warnings.push(`Transition length too short. Min recommended: ${minTransitionLen.toFixed(2)} ft.`);
warnings.push(`Edge tension is excessive: ${edgeStress.toFixed(2)} lb`);
}
const output = [
`Belt Width: ${bw}"`,
`Ply Count: ${plys}`,
`Troughing Angle: ${troughAngle}°`,
`Input Transition Length: ${transitionLen.toFixed(2)} ft`,
`Recommended Min Transition Length: ${minTransitionLen.toFixed(2)} ft`,
`Estimated Edge Stress: ${edgeStress.toFixed(2)} lb`,
warnings.length ? "Warnings:\n- " + warnings.join("\n- ") : "Transition length is sufficient for safe operation."
];
document.getElementById("transitionBox").textContent = output.join("\n");
}
runTransitionZoneAnalysis(vm);