bim-inspector-pro / script.js
Petsoundspeter's picture
I need more specific data. Construction timber should be divided in type ex. c24 45x195. the data presented in excel should be length in total, total amount. do this for each uniqe component
7b3f95f verified
Raw History Blame Contribute Delete
26 kB
document.addEventListener('DOMContentLoaded', function() {
const fileInput = document.getElementById('file-upload');
const fileInfo = document.getElementById('file-info');
const fileName = document.getElementById('file-name');
const fileSize = document.getElementById('file-size');
const removeFileBtn = document.getElementById('remove-file');
const analysisSection = document.getElementById('analysis-section');
const resultsSection = document.getElementById('results-section');
const analyzeBtn = document.getElementById('analyze-btn');
const exportExcelBtn = document.getElementById('export-excel');
const resultsTable = document.getElementById('results-table');
// Drag and drop functionality
const dropZone = document.querySelector('.border-dashed');
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, preventDefaults, false);
});
// Helper function to format timber dimensions
function formatTimberDimensions(width, height) {
return `${(width*1000).toFixed(0)}x${(height*1000).toFixed(0)}`;
}
function preventDefaults(e) {
e.preventDefault();
e.stopPropagation();
}
['dragenter', 'dragover'].forEach(eventName => {
dropZone.addEventListener(eventName, highlight, false);
});
['dragleave', 'drop'].forEach(eventName => {
dropZone.addEventListener(eventName, unhighlight, false);
});
function highlight() {
dropZone.classList.add('border-blue-500');
dropZone.classList.add('bg-blue-50');
}
function unhighlight() {
dropZone.classList.remove('border-blue-500');
dropZone.classList.remove('bg-blue-50');
}
dropZone.addEventListener('drop', handleDrop, false);
function handleDrop(e) {
const dt = e.dataTransfer;
const files = dt.files;
handleFiles(files);
}
fileInput.addEventListener('change', function() {
handleFiles(this.files);
});
function handleFiles(files) {
if (files.length > 0) {
const file = files[0];
if (file.name.endsWith('.ifc')) {
fileName.textContent = file.name;
fileSize.textContent = formatFileSize(file.size);
fileInfo.classList.remove('hidden');
analysisSection.classList.remove('hidden');
resultsSection.classList.add('hidden');
} else {
alert('Please upload a valid .ifc file');
}
}
}
removeFileBtn.addEventListener('click', function() {
fileInput.value = '';
fileInfo.classList.add('hidden');
analysisSection.classList.add('hidden');
resultsSection.classList.add('hidden');
});
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
analyzeBtn.addEventListener('click', function() {
// Simulate analysis (in a real app, this would use an IFC parser library)
analyzeBtn.innerHTML = '<span class="loading-spinner mr-2">🌀</span> Analyzing...';
analyzeBtn.disabled = true;
setTimeout(() => {
// Enhanced mock data with more detailed analysis
const mockData = {
elements: 156,
properties: 412,
materials: 24,
quantities: 67,
totalArea: 0,
totalVolume: 0,
groups: {
'Walls': [],
'Doors': [],
'Windows': [],
'Structural': [],
'Furniture': [],
'Plumbing': [],
'Electrical': [],
'HVAC': [],
'Other': [],
'Timber': []
},
timberTypes: {
'C24 45x195': [],
'C24 70x195': [],
'C24 95x195': [],
'GL24h 120x120': [],
'GL24h 160x160': [],
'GL24h 200x200': []
},
items: [
{
id: '12345',
type: 'Wall',
name: 'Interior Wall 1',
properties: 15,
width: 0.2,
height: 2.7,
depth: 5.8,
area: 15.66,
volume: 3.132,
material: 'Gypsum Board',
group: 'Walls',
location: 'Level 1',
classification: 'Interior Partition',
propertiesList: [
{name: 'Fire Rating', value: '60 min'},
{name: 'STC Rating', value: '45'},
{name: 'U-Value', value: '0.35 W/m²K'}
]
},
{
id: '12346',
type: 'Door',
name: 'Main Entrance',
properties: 12,
width: 1.0,
height: 2.1,
depth: 0.1,
area: 2.1,
volume: 0.21,
material: 'Wood',
group: 'Doors',
location: 'Level 1',
classification: 'Fire Exit',
propertiesList: [
{name: 'Fire Rating', value: '90 min'},
{name: 'Material', value: 'Solid Core'},
{name: 'Swing', value: 'Double'},
{name: 'Finish', value: 'Stained Oak'}
]
},
{
id: '12347',
type: 'Window',
name: 'Living Room Window',
properties: 14,
width: 1.5,
height: 1.2,
depth: 0.2,
area: 1.8,
volume: 0.36,
material: 'Aluminum/Glass',
group: 'Windows',
location: 'Level 1',
classification: 'Fixed Glazing',
propertiesList: [
{name: 'U-Value', value: '1.4 W/m²K'},
{name: 'SHGC', value: '0.35'},
{name: 'Glazing', value: 'Double Low-E'},
{name: 'Frame', value: 'Thermal Break'}
]
},
{
id: '12348',
type: 'Slab',
name: 'First Floor',
properties: 18,
width: 12.0,
height: 0.3,
depth: 8.0,
area: 96.0,
volume: 28.8,
material: 'Concrete',
group: 'Structural',
location: 'Level 1',
classification: 'Composite Deck',
propertiesList: [
{name: 'Concrete Strength', value: '4000 psi'},
{name: 'Reinforcement', value: '#4 @ 12" OC'},
{name: 'Fire Rating', value: '2 hr'},
{name: 'Topping', value: '2" Lightweight'}
]
},
{
id: '12349',
type: 'Column',
name: 'Structural Column A',
properties: 16,
width: 0.4,
height: 3.0,
depth: 0.4,
area: 4.8,
volume: 0.48,
material: 'Steel',
group: 'Structural',
location: 'Grid A-1',
classification: 'HSS Column',
propertiesList: [
{name: 'Shape', value: 'HSS6x6x1/4'},
{name: 'Material Grade', value: 'A500 Gr.B'},
{name: 'Fireproofing', value: 'Spray-applied'},
{name: 'Connection Type', value: 'Moment'}
]
},
{
id: '12350',
type: 'Timber',
name: 'Roof Beam 1',
properties: 14,
width: 0.045,
height: 0.195,
length: 4.8,
area: 0.936,
volume: 0.042,
material: 'C24 45x195',
group: 'Timber',
location: 'Roof Level',
classification: 'Structural Timber',
propertiesList: [
{name: 'Grade', value: 'C24'},
{name: 'Section', value: '45x195 mm'},
{name: 'Moisture Content', value: '18%'},
{name: 'Treatment', value: 'Tanalith E'}
]
},
{
id: '12351',
type: 'Timber',
name: 'Floor Joist 1',
properties: 14,
width: 0.07,
height: 0.195,
length: 3.6,
area: 0.702,
volume: 0.049,
material: 'C24 70x195',
group: 'Timber',
location: 'First Floor',
classification: 'Structural Timber',
propertiesList: [
{name: 'Grade', value: 'C24'},
{name: 'Section', value: '70x195 mm'},
{name: 'Moisture Content', value: '18%'},
{name: 'Treatment', value: 'Tanalith E'}
]
},
{
id: '12352',
type: 'Timber',
name: 'Wall Stud 1',
properties: 14,
width: 0.045,
height: 0.195,
length: 2.7,
area: 0.526,
volume: 0.024,
material: 'C24 45x195',
group: 'Timber',
location: 'Wall A',
classification: 'Structural Timber',
propertiesList: [
{name: 'Grade', value: 'C24'},
{name: 'Section', value: '45x195 mm'},
{name: 'Moisture Content', value: '18%'},
{name: 'Treatment', value: 'Tanalith E'}
]
},
{
id: '12353',
type: 'Timber',
name: 'Glulam Beam',
properties: 14,
width: 0.16,
height: 0.16,
length: 5.4,
area: 0.864,
volume: 0.138,
material: 'GL24h 160x160',
group: 'Timber',
location: 'Main Hall',
classification: 'Structural Timber',
propertiesList: [
{name: 'Grade', value: 'GL24h'},
{name: 'Section', value: '160x160 mm'},
{name: 'Moisture Content', value: '12%'},
{name: 'Fire Rating', value: 'R30'}
]
},
]
};
// Calculate totals and group items
mockData.items.forEach(item => {
mockData.totalArea += item.area;
mockData.totalVolume += item.volume;
mockData.groups[item.group].push(item);
// Group timber by type
if (item.group === 'Timber' && mockData.timberTypes[item.material]) {
mockData.timberTypes[item.material].push(item);
}
});
// Calculate timber summaries
const timberSummaries = {};
for (const [type, items] of Object.entries(mockData.timberTypes)) {
if (items.length > 0) {
const totalLength = items.reduce((sum, item) => sum + item.length, 0);
timberSummaries[type] = {
count: items.length,
totalLength: totalLength.toFixed(2),
totalVolume: items.reduce((sum, item) => sum + item.volume, 0).toFixed(3),
section: items[0].width*1000 + 'x' + items[0].height*1000 + ' mm'
};
}
}
// Update counts and totals
document.getElementById('elements-count').textContent = mockData.elements;
document.getElementById('properties-count').textContent = mockData.properties;
document.getElementById('materials-count').textContent = mockData.materials;
document.getElementById('quantities-count').textContent = mockData.quantities;
document.getElementById('total-volume').textContent = mockData.totalVolume.toFixed(3) + ' m³';
// Add timber summary section
if (Object.keys(timberSummaries).length > 0) {
const resultsContainer = document.querySelector('#results-section .max-w-4xl');
let timberHTML = `
<div class="bg-white rounded-lg p-4 shadow-sm mb-4">
<h4 class="text-lg font-semibold mb-3">Timber Summary</h4>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-100">
<tr>
<th class="py-2 px-3 text-left">Type</th>
<th class="py-2 px-3 text-left">Section</th>
<th class="py-2 px-3 text-left">Count</th>
<th class="py-2 px-3 text-left">Total Length</th>
<th class="py-2 px-3 text-left">Total Volume</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200">
`;
for (const [type, summary] of Object.entries(timberSummaries)) {
timberHTML += `
<tr>
<td class="py-2 px-3">${type}</td>
<td class="py-2 px-3">${summary.section}</td>
<td class="py-2 px-3">${summary.count}</td>
<td class="py-2 px-3">${summary.totalLength} m</td>
<td class="py-2 px-3">${summary.totalVolume} m³</td>
</tr>
`;
}
timberHTML += `
</tbody>
</table>
</div>
</div>
`;
if (resultsContainer.querySelector('#timber-summary')) {
resultsContainer.querySelector('#timber-summary').innerHTML = timberHTML;
} else {
const timberDiv = document.createElement('div');
timberDiv.id = 'timber-summary';
timberDiv.innerHTML = timberHTML;
resultsContainer.insertBefore(timberDiv, resultsContainer.querySelector('.overflow-x-auto'));
}
}
// Populate detailed table
resultsTable.innerHTML = '';
mockData.items.forEach(item => {
const row = document.createElement('tr');
row.className = 'hover:bg-gray-50 cursor-pointer';
row.innerHTML = `
<td class="py-3 px-4">${item.id}</td>
<td class="py-3 px-4">${item.type}</td>
<td class="py-3 px-4">${item.name}</td>
<td class="py-3 px-4">
<div class="flex flex-col">
<span>${item.material}</span>
<span class="text-xs text-gray-500">${item.location}</span>
</div>
</td>
<td class="py-3 px-4">${item.properties}</td>
<td class="py-3 px-4">
<span class="inline-block px-2 py-1 rounded-full text-xs ${item.group === 'Walls' ? 'bg-blue-100 text-blue-800' :
item.group === 'Structural' ? 'bg-gray-100 text-gray-800' :
item.group === 'Doors' ? 'bg-green-100 text-green-800' :
item.group === 'Windows' ? 'bg-purple-100 text-purple-800' : 'bg-yellow-100 text-yellow-800'}">
${item.group}
</span>
</td>
<td class="py-3 px-4">
<button class="details-btn text-blue-500 hover:text-blue-700" data-id="${item.id}">
<i data-feather="chevron-down" class="w-4 h-4"></i>
</button>
</td>
`;
resultsTable.appendChild(row);
// Add details row
const detailsRow = document.createElement('tr');
detailsRow.id = `details-${item.id}`;
detailsRow.className = 'hidden bg-gray-50';
const propertiesHTML = item.propertiesList.map(prop =>
`<div class="grid grid-cols-2 gap-2 py-1">
<span class="font-medium text-gray-700">${prop.name}:</span>
<span>${prop.value}</span>
</div>`
).join('');
detailsRow.innerHTML = `
<td colspan="7" class="p-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div>
<h6 class="font-medium text-gray-700 mb-2">Dimensions</h6>
<div class="space-y-1">
<div class="grid grid-cols-2 gap-2">
<span>Width:</span>
<span>${item.width.toFixed(2)} m</span>
</div>
<div class="grid grid-cols-2 gap-2">
<span>Height:</span>
<span>${item.height.toFixed(2)} m</span>
</div>
<div class="grid grid-cols-2 gap-2">
<span>Depth:</span>
<span>${item.depth.toFixed(2)} m</span>
</div>
<div class="grid grid-cols-2 gap-2">
<span>Area:</span>
<span>${item.area.toFixed(2)} m²</span>
</div>
<div class="grid grid-cols-2 gap-2">
<span>Volume:</span>
<span>${item.volume.toFixed(2)} m³</span>
</div>
</div>
</div>
<div>
<h6 class="font-medium text-gray-700 mb-2">Classification</h6>
<div class="space-y-1">
<div class="grid grid-cols-2 gap-2">
<span>Location:</span>
<span>${item.location}</span>
</div>
<div class="grid grid-cols-2 gap-2">
<span>Type:</span>
<span>${item.classification}</span>
</div>
</div>
</div>
<div>
<h6 class="font-medium text-gray-700 mb-2">Properties</h6>
${propertiesHTML}
</div>
</div>
</td>
`;
resultsTable.appendChild(detailsRow);
});
// Add event listeners for details toggles
document.querySelectorAll('.details-btn').forEach(btn => {
btn.addEventListener('click', function() {
const id = this.getAttribute('data-id');
const detailsRow = document.getElementById(`details-${id}`);
const icon = this.querySelector('i');
if (detailsRow.classList.contains('hidden')) {
detailsRow.classList.remove('hidden');
icon.setAttribute('data-feather', 'chevron-up');
} else {
detailsRow.classList.add('hidden');
icon.setAttribute('data-feather', 'chevron-down');
}
feather.replace();
});
});
analyzeBtn.innerHTML = '<i data-feather="activity" class="w-5 h-5 mr-2"></i><span>Analyze IFC File</span>';
analyzeBtn.disabled = false;
resultsSection.classList.remove('hidden');
feather.replace();
}, 2000);
});
exportExcelBtn.addEventListener('click', function() {
// In a real app, this would generate an Excel file from the IFC data
const btn = this;
btn.innerHTML = '<span class="loading-spinner mr-2">🌀</span> Exporting...';
btn.disabled = true;
setTimeout(() => {
// Mock Excel data structure with timber summary
const excelData = {
summary: [
['IFC Analysis Report', '', '', '', ''],
['Generated on', new Date().toLocaleString(), '', '', ''],
['', '', '', '', ''],
['Summary Metrics', '', '', '', ''],
['Total Elements', mockData.elements, '', '', ''],
['Total Properties', mockData.properties, '', '', ''],
['Total Materials', mockData.materials, '', '', ''],
['Total Volume', mockData.totalVolume.toFixed(3) + ' m³', '', '', '']
],
timberSummary: [
['Timber Summary', '', '', '', ''],
['Type', 'Section', 'Count', 'Total Length (m)', 'Total Volume (m³)'],
...Object.entries(timberSummaries).map(([type, summary]) => [
type,
summary.section,
summary.count,
summary.totalLength,
summary.totalVolume
])
],
elements: [
['Element Details', '', '', '', '', '', '', ''],
['ID', 'Type', 'Name', 'Material', 'Location', 'Length (m)', 'Volume (m³)', 'Properties'],
...mockData.items.map(item => [
item.id,
item.type,
item.name,
item.material,
item.location,
item.length ? item.length.toFixed(2) : '',
item.volume ? item.volume.toFixed(3) : '',
item.properties
])
]
};
console.log('Excel data prepared:', excelData); // For demo purposes
// In a real implementation, we would use SheetJS to generate the Excel file
alert('Excel export would generate:\n- Summary sheet\n- Timber summary by type\n- Detailed elements list');
btn.innerHTML = '<i data-feather="download" class="w-4 h-4 mr-2"></i><span>Export to Excel</span>';
btn.disabled = false;
feather.replace();
}, 1500);
});
});