nobim-image-generator

SKILL.md

noBIM Image Generator

Business Case

Problem Statement

Creating visualizations from BIM models typically requires:

  • Expensive BIM software licenses
  • Manual screenshot capture
  • Time-consuming rendering
  • Impossible to batch process

Solution

noBIM tool extracts data and generates visualizations using Python libraries, processing hundreds of projects without BIM software.

Business Value

  • No license required - Pure Python solution
  • Batch processing - Generate images for 1000s of projects
  • Customizable - Create exactly the visualizations you need
  • Automatable - Integrate into data pipelines

Technical Implementation

Installation

pip install pandas matplotlib seaborn plotly ifcopenshell

Core Functionality

import pandas as pd
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from pathlib import Path
from typing import List, Optional, Tuple

class NoBIMVisualizer:
    def __init__(self):
        self.elements = None
        self.project_name = ""

    def load_from_excel(self, xlsx_path: str) -> int:
        """Load BIM data from converted Excel file."""
        self.elements = pd.read_excel(xlsx_path, sheet_name="Elements")
        self.project_name = Path(xlsx_path).stem
        return len(self.elements)

    def generate_3d_scatter(self, output_path: str,
                            color_by: str = "Category",
                            size: Tuple[int, int] = (12, 10)) -> str:
        """Generate 3D scatter plot of elements."""
        if not all(col in self.elements.columns
                   for col in ['BBox_CenterX', 'BBox_CenterY', 'BBox_CenterZ']):
            raise ValueError("Bounding box data required. Export with 'bbox' option.")

        fig = plt.figure(figsize=size)
        ax = fig.add_subplot(111, projection='3d')

        # Get unique categories for coloring
        categories = self.elements[color_by].unique()
        colors = plt.cm.tab20(np.linspace(0, 1, len(categories)))
        color_map = dict(zip(categories, colors))

        for cat in categories:
            subset = self.elements[self.elements[color_by] == cat]
            ax.scatter(
                subset['BBox_CenterX'],
                subset['BBox_CenterY'],
                subset['BBox_CenterZ'],
                c=[color_map[cat]],
                label=cat[:20],
                alpha=0.6,
                s=10
            )

        ax.set_xlabel('X')
        ax.set_ylabel('Y')
        ax.set_zlabel('Z')
        ax.set_title(f'{self.project_name} - 3D Element Distribution')
        ax.legend(loc='upper left', fontsize=8, ncol=2)

        plt.savefig(output_path, dpi=150, bbox_inches='tight')
        plt.close()
        return output_path

    def generate_floor_plan(self, output_path: str, level: str,
                            size: Tuple[int, int] = (14, 10)) -> str:
        """Generate floor plan visualization for specific level."""
        level_elements = self.elements[self.elements['Level'] == level]

        if level_elements.empty:
            raise ValueError(f"No elements found for level: {level}")

        fig, ax = plt.subplots(figsize=size)

        # Draw walls
        walls = level_elements[level_elements['Category'] == 'Walls']
        for _, wall in walls.iterrows():
            rect = plt.Rectangle(
                (wall['BBox_MinX'], wall['BBox_MinY']),
                wall['BBox_MaxX'] - wall['BBox_MinX'],
                wall['BBox_MaxY'] - wall['BBox_MinY'],
                fill=True, facecolor='gray', edgecolor='black', alpha=0.7
            )
            ax.add_patch(rect)

        # Draw rooms
        rooms = level_elements[level_elements['Category'] == 'Rooms']
        for _, room in rooms.iterrows():
            center_x = (room['BBox_MinX'] + room['BBox_MaxX']) / 2
            center_y = (room['BBox_MinY'] + room['BBox_MaxY']) / 2
            ax.annotate(room.get('RoomName', 'Room'),
                       (center_x, center_y), ha='center', fontsize=8)

        ax.set_aspect('equal')
        ax.set_title(f'{self.project_name} - {level}')
        ax.set_xlabel('X (m)')
        ax.set_ylabel('Y (m)')

        plt.savefig(output_path, dpi=150, bbox_inches='tight')
        plt.close()
        return output_path

    def generate_category_chart(self, output_path: str,
                                 size: Tuple[int, int] = (12, 8)) -> str:
        """Generate bar chart of element categories."""
        cat_counts = self.elements['Category'].value_counts().head(20)

        fig, ax = plt.subplots(figsize=size)
        bars = ax.barh(cat_counts.index, cat_counts.values,
                       color=plt.cm.viridis(np.linspace(0, 1, len(cat_counts))))

        ax.set_xlabel('Element Count')
        ax.set_title(f'{self.project_name} - Element Categories')

        # Add count labels
        for bar, count in zip(bars, cat_counts.values):
            ax.text(bar.get_width() + 1, bar.get_y() + bar.get_height()/2,
                   f'{count}', va='center', fontsize=9)

        plt.tight_layout()
        plt.savefig(output_path, dpi=150, bbox_inches='tight')
        plt.close()
        return output_path

    def generate_volume_treemap(self, output_path: str) -> str:
        """Generate treemap of volumes by category."""
        import plotly.express as px

        vol_by_cat = self.elements.groupby('Category')['Volume'].sum().reset_index()
        vol_by_cat = vol_by_cat[vol_by_cat['Volume'] > 0].sort_values('Volume', ascending=False)

        fig = px.treemap(
            vol_by_cat.head(30),
            path=['Category'],
            values='Volume',
            title=f'{self.project_name} - Volume Distribution'
        )

        fig.write_image(output_path)
        return output_path

    def batch_generate(self, xlsx_files: List[str], output_dir: str) -> List[str]:
        """Generate standard visualizations for multiple projects."""
        output_dir = Path(output_dir)
        output_dir.mkdir(parents=True, exist_ok=True)

        generated = []
        for xlsx in xlsx_files:
            try:
                self.load_from_excel(xlsx)
                base_name = Path(xlsx).stem

                # Generate all visualizations
                self.generate_3d_scatter(str(output_dir / f"{base_name}_3d.png"))
                self.generate_category_chart(str(output_dir / f"{base_name}_categories.png"))

                generated.append(base_name)
                print(f"Generated visualizations for: {base_name}")

            except Exception as e:
                print(f"Error processing {xlsx}: {e}")

        return generated

Usage Examples

Single Project

viz = NoBIMVisualizer()
viz.load_from_excel("C:/Projects/Office.xlsx")

# Generate 3D view
viz.generate_3d_scatter("office_3d.png", color_by="Category")

# Generate floor plan
viz.generate_floor_plan("office_level1.png", level="Level 1")

# Generate category breakdown
viz.generate_category_chart("office_categories.png")

Batch Processing

from pathlib import Path

viz = NoBIMVisualizer()

# Find all converted files
xlsx_files = list(Path("C:/ConvertedProjects").glob("*.xlsx"))

# Generate visualizations for all
generated = viz.batch_generate(
    [str(f) for f in xlsx_files],
    output_dir="C:/Visualizations"
)

print(f"Generated visualizations for {len(generated)} projects")

Output Examples

Visualization Use Case
3D Scatter Overall project structure
Floor Plan Level-by-level layout
Category Chart Element distribution
Volume Treemap Material quantities
Level Comparison Multi-floor analysis

Integration with Reporting

from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4

def create_project_report(xlsx_path: str, output_pdf: str):
    """Generate PDF report with all visualizations."""
    viz = NoBIMVisualizer()
    viz.load_from_excel(xlsx_path)

    # Generate images
    images = {
        '3D View': viz.generate_3d_scatter("temp_3d.png"),
        'Categories': viz.generate_category_chart("temp_cat.png"),
    }

    # Create PDF
    c = canvas.Canvas(output_pdf, pagesize=A4)
    c.drawString(100, 800, f"Project Report: {viz.project_name}")

    y_pos = 700
    for title, img_path in images.items():
        c.drawString(100, y_pos, title)
        c.drawImage(img_path, 100, y_pos - 300, width=400, height=280)
        y_pos -= 350

    c.save()
    return output_pdf

Resources

Weekly Installs
2
GitHub Stars
52
First Seen
11 days ago
Installed on
amp2
cline2
opencode2
cursor2
kimi-cli2
codex2