Building a 3D Smart Photovoltaic Visualization System with WebGL (Three.js)

Overview

Energy is a critical foundation for civilization and development. From the Stone Age to modern civilization, our energy applications have advanced, but we also face the crisis and fear of energy depletion. Developing and utilizing renewible energy is the primary solution to this challenge. China has been following a practical and sustainable development path in this regard, with every energy enterprise pursuing the development and innovation of new energy sources.

Solar photovoltaic (PV) power generation emerges as a clean, effective, and sustainable high-quality energy source. It addresses issues like energy shortages and continuous supply while ensuring environmental hygiene. The Chinese government has introduced numerous policies and regulations to encourage and support the PV industry, including corporate subsidies and carbon neutrality goals. These have rapidly propelled the PV industry's growth in China, increasing its share in the energy mix and its role in human production and daily life. The prospects for the PV industry are vast, though challenges remain, such as energy density, conversion efficiency, land use, natural impact factors, system costs, and environmentally friendly material production, all of wich are bottlenecks yet to be overcome technologically.

Let's dive straight into the topic.

Introduction

A photovoltaic power generation system consists of solar panels, controllers, inverters, energy storage cabinets, grid connection cabinets, and other major equipment, along with their upper-layer application systems. The process involves design, construction, maintenance, and other important workflows. PV power generation uses the photovoltaic effect of solar cells to convert solar radiation into electricity. The process is simple and environmentally friendly. The main equipment includes:

  • Solar Panels: Common panels that generate electricity from sunlight.
  • Controller: A core component in off-grid systems that balances the system and protects batteries.
  • Inverter: Converts direct current (DC) into alternating current (AC).
  • Energy Storage Cabinet: Stores energy.
  • Grid Connection Cabinet: A distribution device that feeds generated power into the grid.
  • Application System: Uses an IoT+ model to monitor PV equipment status, capture generation data, and effectively dispatch grid power. It combines inverters with data sticks to integrate PV generation, IoT, and information technology, optimizing and innovating from construction to operation and maintenance to minimize manual intervention and achieve full automation.

This article focuses on a distributed PV model, similar to a microgrid, which offers flexibility, compatibility, value, and efficiency. It revolutionizes energy use for enterprises or homes. Installing PV equipment on corporate roofs not only reduces electricity costs but also provides carbon neutrality indicators and helps with roof insulation.

Now, we'll explain the visualization solution for smart PV systems from the application layer, analyzing the PV working principle, PV generation + IoT + visualization model, monitoring PV equipment status, and effectively dispatching microgrid capabilities.

1. Effect Demonstration

1.1 Building-Integrated Photovoltaics (BIPV)

Technological Blue Wireframe Effect

Wireframe BIPV

Technological Blue Solid Effect

Solid BIPV

Scene Switching Code

// Switch scenes
ModelBussiness.prototype.changeSceneModel = function (dataid, _obj) {
    const _this = this;
    if (_this.currentSceneState === dataid) {
        return;
    }
    modelBussiness.doAnimation = true;
    switch (_this.currentSceneState) {
        case "bwg":
            indexPage.hideCNGChart();
            modelBussiness.hideBWGModels(function () {
                doModel(dataid);
            });
            break;
        case "nbq":
            indexPage.hideGFChart();
            modelBussiness.hidewNBQModels(function () {
                doModel(dataid);
            });
            break;
        default:
            doModel(dataid);
            break;
    }
    _this.currentSceneState = dataid;

    function doModel(dataid) {
        $("#toolbarBoth").hide();
        switch (dataid) {
            case "bwg":
                indexPage.showCNGChart();
                modelBussiness.showBWGModels(function () {
                    modelBussiness.doAnimation = false;
                });
                break;
            case "nbq":
                indexPage.showGFChart();
                modelBussiness.showNBQModels(function () {
                    modelBussiness.doAnimation = false;
                });
                break;
            default:
                $("#toolbarBoth").show();
                modelBussiness.doAnimation = false;
                break;
        }
    }
}

1.2 PV Equipment Pipeline Topology

Understanding the connection logic through topology helps locate issues.

Topology

Chart Panel Creation Code

modelBussiness.chartNames.push(name);
const modelJson = {
    id: "",
    show: true,
    name: name,
    objType: "EchartPanel",
    position: { x: _obj.position.x, y: 10, z: _obj.position.z + 1 },
    showSortNub: 400,
    side: 2,
    size: { width: 200, height: 100, length: 0 },
    echartSize: { width: 1024, height: 512 },
    scale: { x: 1, y: 1, z: 1 },
    backgroundColor: 0xFFFFFF,
    rotation: { x: -0.5236, y: 0, z: 0 },
};
const zlnub = 1;
const option = {
    backgroundColor: "#2957A2",
    title: {
        text: zlnub + "# Grid Cabinet",
        textStyle: { color: "#fff" },
        left: '2%',
        top: '0%'
    },
    graphic: [
        {
            type: "text",
            left: "2%",
            top: "15%",
            style: {
                text: [
                    `Status: Charging`,
                    `SOC: 45%`,
                    `Current: 34A`,
                    `Voltage: 220V`,
                    `Active Power: 345KW`,
                    `Reactive Power: -0.3kVar`,
                    `Remaining Capacity: 345kWh`
                ].join("\n"),
                font: "700 14px",
                fontSize: 14,
                fill: "#fff",
                textLineHeight: 14,
            }
        }
    ],
    tooltip: { trigger: "axis" },
    calculable: true,
    series: [
        {
            name: "Max Voltage",
            type: "line",
            min: 10,
            max: 40,
            data: [...],
            lineStyle: {
                normal: {
                    width: 2,
                    color: { type: "linear", globalCoord: false },
                    shadowColor: "rgba(0,255,255, 1)",
                    shadowBlur: 15,
                    shadowOffsetY: 0,
                },
            },
            itemStyle: {
                normal: {
                    color: "#AAF487",
                    borderWidth: 2,
                    borderColor: "#AAF487",
                },
            },
            smooth: true,
            markLine: {
                data: [{ type: "average", name: "Average" }],
            },
        },
    ],
};
WT3DObj.addEchartPanel(WT3DObj, modelJson, option);

1.3 Grid Connection Cabinet Monitoring

View real-time data and historical curves of grid cabinets.

Grid Cabinet

Data Update Code

if (modelBussiness.chartNames.length >= 0) {
    const chartModels = WT3DObj.commonFunc.findObjectsByNames(modelBussiness.chartNames);
    $.each(chartModels, function (_index, _chartobj) {
        if (_chartobj.name.indexOf("dev_bygcb_") >= 0) {
            (function (_obj) {
                const dataparam = _obj.name.replace("dev_bygcb_", "").replace("EchartPanel", "");
                webapi.getLoadData(dataparam, function (result) {
                    const newxdata = [];
                    const newxValues = [];
                    const data = result.inDays;
                    $.each(data, function (_di, _do) {
                        newxdata.push(_do.name);
                        newxValues.push(_do.value);
                    });
                    _obj.myChartOption.xAxis[0].data = newxdata;
                    _obj.myChartOption.series[0].data = newxValues;
                    _obj.myChartOption.graphic[0].style.text = [
                        `Status: ${result.status}`,
                        `SOC: ${result.soc}`,
                        `Current: ${result.ia}`,
                        `Voltage: ${result.ua}`,
                        `Active Power: ${result.p}`,
                        `Reactive Power: ${result.q}`,
                        `Remaining Capacity: ${result.sp}`
                    ].join("\n");
                    _obj.myChart.setOption(_obj.myChartOption);
                    _obj.freshData();
                });
            })(_chartobj);
        }
    });
}

1.4 Inverter Monitoring

Monitor inverter information.

Inverter

Inverter Module Code

/*
 =============== Inverter Module ===================
*/
ModelBussiness.prototype.showNBQModels = function (callBack) {
    const _this = this;
    modelBussiness.hideVitureDevs(null, function () {
        modelBussiness.doFlashChart = true;
        if (callBack) {
            callBack();
        }
    });
    WT3DObj.commonFunc.changeCameraPosition(
        { x: -7847.362298856568, y: 2349.326497907068, z: 6092.394946263032 },
        { x: -3839.311641139668, y: 336.68538867317926, z: 896.2806691329519 },
        1000,
        function () {
            setTimeout(function () {
                WT3DObj.commonFunc.changeCameraPosition(
                    { x: -5823.791307544135, y: 546.2227755970467, z: 1683.797033787011 },
                    { x: -5258.110268870251, y: 166.46700423551266, z: 983.0003887760242 },
                    1000,
                    function () { }
                );
            }, 2000);
        }
    );
}

2. Implementation Logic

2.1 Model Creation

2.1.1 Environment Model

Environment

Model Code (Partial)

{
  "x": 1,
  "y": 0.1,
  "z": 1,
  "rotation": [
    { "direction": "x", "degree": 0 },
    { "direction": "y", "degree": 0 },
    { "direction": "z", "degree": 0 }
  ],
  "style": {
    "skinColor": 16772846,
    "imgurl": "../img/3dImg/traffic_01.png",
    "opacity": 1,
    "canvasSkin": {
      "cwidth": 512,
      "cheight": 64,
      "cwNub": 4,
      "chNub": 4,
      "cMarginW": 0.2,
      "cMarginH": 0.2,
      "speed": 8,
      "fps": 40,
      "direction": "w",
      "forward": "f",
      "side": 2,
      "run": true,
      "bgcolor": "rgba(255,102,0,0.07)"
    }
  },
  "segments": 9,
  "radialSegments": 4,
  "closed": false,
  "radius": 40,
  "showSortNub": 57
}

2.1.2 Building Model

Building

Building Model Code (Partial)

{
  "show": true,
  "uuid": "",
  "name": "dev_tyn_b1_1",
  "objType": "cube2",
  "length": 80,
  "width": 320,
  "height": 5,
  "x": 785.461,
  "y": 979.498,
  "z": 635.388,
  "style": {
    "skinColor": 16777215,
    "skin": {
      "skin_up": {
        "skinColor": 16777215,
        "materialType": "lambert",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/solarBattery4.png",
        "repeatx": true,
        "width": 2,
        "repeaty": true,
        "height": 14
      },
      "skin_down": { "skinColor": 8289918, "side": 1, "opacity": 1 },
      "skin_fore": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_behind": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_left": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_right": { "skinColor": 2634316, "side": 1, "opacity": 1 }
    }
  },
  "showSortNub": 68,
  "rotation": [
    { "direction": "x", "degree": 3.114138624455922 },
    { "direction": "y", "degree": -0.7853981633974483 },
    { "direction": "z", "degree": 2.7011064569714645 }
  ]
}

2.1.3 Inverter Model

Inverter Model

Inverter Model Code

{
  "show": true,
  "uuid": "",
  "name": "dev_tyn_b2_5",
  "objType": "cube2",
  "length": 120,
  "width": 220,
  "height": 5,
  "x": 6971.784,
  "y": 500,
  "z": 1350.98,
  "style": {
    "skinColor": 16777215,
    "skin": {
      "skin_up": {
        "skinColor": 16777215,
        "materialType": "lambert",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/solarBattery4.png",
        "repeatx": true,
        "width": 4,
        "repeaty": true,
        "height": 10
      },
      "skin_down": { "skinColor": 8289918, "side": 1, "opacity": 1 },
      "skin_fore": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_behind": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_left": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_right": { "skinColor": 2634316, "side": 1, "opacity": 1 }
    }
  },
  "showSortNub": 68,
  "rotation": [
    { "direction": "x", "degree": 0.14289010586077575 },
    { "direction": "y", "degree": 1.5707963267948966 },
    { "direction": "z", "degree": 0 }
  ]
}

2.1.4 Grid Connection Cabinet Model

Grid Cabinet Model

Cabinet Model Code

{
  "show": true,
  "uuid": "",
  "name": "dev_byg_2",
  "objType": "cube2",
  "length": 45,
  "width": 100,
  "height": 30,
  "x": 1751.012,
  "y": 50,
  "z": 639.269,
  "style": {
    "skinColor": 16777215,
    "skin": {
      "skin_up": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1door.jpg"
      },
      "skin_down": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1ce.jpg"
      },
      "skin_fore": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1ce.jpg"
      },
      "skin_behind": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1ce.jpg"
      },
      "skin_left": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1ding.jpg",
        "repeatx": true,
        "width": 0.5,
        "repeaty": true,
        "height": 1
      },
      "skin_right": {
        "skinColor": 16777215,
        "materialType": "Phong",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/ssn/dypdg1ding.jpg"
      }
    }
  },
  "showSortNub": 167,
  "rotation": [
    { "direction": "x", "degree": 1.5707963267948963 },
    { "direction": "y", "degree": 0 },
    { "direction": "z", "degree": 0 }
  ]
}

2.1.5 Solar Panel Model

Solar Panel

This model is simple, using a box with a solar panel texture.

{
  "show": true,
  "uuid": "",
  "name": "dev_tyn_b2_14",
  "objType": "cube2",
  "length": 120,
  "width": 220,
  "height": 5,
  "x": 7251.596,
  "y": 500,
  "z": 1068.337,
  "style": {
    "skinColor": 16777215,
    "skin": {
      "skin_up": {
        "skinColor": 16777215,
        "materialType": "lambert",
        "side": 1,
        "opacity": 1,
        "imgurl": "../img/3dImg/solarBattery4.png",
        "repeatx": true,
        "width": 4,
        "repeaty": true,
        "height": 10
      },
      "skin_down": { "skinColor": 8289918, "side": 1, "opacity": 1 },
      "skin_fore": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_behind": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_left": { "skinColor": 2634316, "side": 1, "opacity": 1 },
      "skin_right": { "skinColor": 2634316, "side": 1, "opacity": 1 }
    }
  },
  "showSortNub": 68,
  "rotation": [
    { "direction": "x", "degree": 0.14289010586077575 },
    { "direction": "y", "degree": 1.5707963267948966 },
    { "direction": "z", "degree": 0 }
  ]
}

2.1.6 Topology Connections

Topology Connections

The topology mainly defines pipeline routes. Here is one example:

// Create path
const path = [
    { "x": 0, "y": 0, "z": 0 },
    { "x": 0, "y": 0, "z": -150 },
    { "x": 0, "y": -1, "z": -150 },
    { "x": 0, "y": -250, "z": -150 },
    { "x": -1, "y": -250, "z": -150 },
    { "x": -350, "y": -250, "z": -150 },
    { "x": -350, "y": -250, "z": -149 },
    { "x": -350, "y": -250, "z": 30 },
    { "x": -350, "y": -251, "z": 30 },
    { "x": -350, "y": -680, "z": 30 }
];

const model = {
    "show": true,
    "uuid": "",
    "name": "dev_flowtube_2_3",
    "objType": "flowTube",
    "points": path,
    "position": { "x": 1560.492, "y": 976.703, "z": 605.466 },
    "scale": { "x": 1, "y": 1, "z": 1 },
    "rotation": [
        { "direction": "x", "degree": 0 },
        { "direction": "y", "degree": 0 },
        { "direction": "z", "degree": 0 }
    ],
    "style": {
        "skinColor": 16772846,
        "imgurl": "../../img/3dImg/lightL2.png",
        "opacity": 1,
        "canvasSkin": {
            "cwidth": 512,
            "cheight": 8,
            "cwNub": 16,
            "chNub": 2,
            "cMarginW": 0.5,
            "cMarginH": 0,
            "speed": 2,
            "fps": 40,
            "direction": "w",
            "forward": "f",
            "side": 1,
            "run": true,
            "bgcolor": "rgba(0,221,255,0.39)"
        }
    },
    "segments": 9,
    "radialSegments": 6,
    "closed": false,
    "radius": 2,
    "showSortNub": 100
};

// Create model
WT3Dobj.createLineByModelJSon(model);

2.2 Data Integration

2.2.1 Create Ajax Request Library

const httpInvoke = function (url, type, data, successCb, failedCb, userData, async) {
    return $.ajax({
        url: url,
        type: type,
        data: data,
        headers: {},
        async: async,
        times: 0,
        beforeSend: function (request) { },
        success: function (response, status, hreq) {
            if (response.code === 401) {
                // Handle unauthorized
                layer.msg('Login expired, please login again.');
                window.location.href = '/jigongly/login';
                return;
            }
            if (successCb != null) {
                successCb(response, status, userData);
            }
        },
        error: function (err) {
            console.log(err);
            if (failedCb != null) {
                failedCb(err.statusCode(), userData);
            }
        }
    });
}

2.2.2 Create API Repository

function WebAPI() {
    this.serverHead = "";
    this.serverHead2 = "";

    this.urls = {
        station: this.serverHead2 + "/station",
        airConditioner: this.serverHead2 + "/air",
        fireControl: this.serverHead2 + "/fire-ctrl",
        pcs: this.serverHead2 + "/pcs",
        batteryCabinetBasic: this.serverHead2 + "/battery-basic",
        batteryPower: this.serverHead2 + "/battery-power",
        powerMonth: this.serverHead2 + "/electric-month",
        chargeDischarge: this.serverHead2 + "/dis-charge",
        so: this.serverHead2 + "/soc",
        profitmonth: this.serverHead2 + "/profit-month",
        load: this.serverHead2 + "/load",
        surplus: this.serverHead2 + "/surplus-power/",
        hostPowerMonth: this.serverHead2 + "/ac/host-power-month",
        powerYear: this.serverHead2 + "/ac/power-year",
        abstract: this.serverHead2 + "/ac/abstract",
        consumptionDay: this.serverHead2 + "/consumption-day/",
        chargeDay: this.serverHead2 + "/charge-day/",
        temperatures: "../demoData/tempTureData.json",
    };
}

Example: Requesting Temperature Data

WebAPI.prototype.TempsCache = {};
WebAPI.prototype.getTemptureValue = function (sunFunc) {
    const _this = this;
    const url = this.serverHead + this.urls.temperatures + "?positionId=" + _this.roomid;
    httpGet(url, function (response) {
        if (response && response.data && sunFunc) {
            if (response.data.length > 0) {
                $.each(response.data, function (_index, _obj) {
                    _obj.id = _obj.name;
                    _obj.type = 0;
                    _obj.temptureValue = _obj.t1MValue;
                    _obj.temptureValueUp = _obj.t1UValue;
                    _obj.temptureValueDown = _obj.t1DValue;
                    _obj.lastUptime = new Date().getTime();
                    _obj.allBearing = 0;
                    _this.TempsCache["temp_" + _obj.name] = _obj;
                });
            }
            sunFunc(response.data);
        }
    }, function () {
        layer.msg("Failed to fetch data");
    });
}

2.3 Core Logic

2.3.1 Model Creation

As described in the previous sections.

2.3.2 Data Binding to Models

const data = [];
for (let i = 1; i <= 3; i++) {
    for (let j = 1; j <= 20; j++) {
        let modelname = "dev_cqpdx_" + i + "_" + j;
        if (i === 1) {
            modelname = "dev_cqpdx_" + j;
        }
        data.push({
            dataId: i + "_" + j,
            type: "pdg",
            name: modelname,
        });
    }
}
return data;

2.3.3 Business Logic Implementation

From loading models to instantiation and control:

const T3DModel = new T3D();
const initOption = {
    far: 100000000,
    antialias: true,
    loadSyn: false,
    showHelpGrid: false,
    clearCoolr: 0x4068b0,
    clearColorOp: 0,
};

const Aobjects = {
    objects: AllModelJsons,
    events: {
        dbclick: [
            {
                obj_name: "ALL",
                obj_event: function (_obj, face, objs) {
                    // Handle double click
                }
            },
        ],
        mouseDown: [
            {
                obj_name: "ALL",
                obj_event: function (_obj, face, objs) {
                    // Handle mouse down
                }
            },
        ],
        mouseMove: [
            {
                obj_name: "doorLeft",
                obj_event: function (_obj, face, objs) {
                    // Handle mouse move
                }
            },
        ]
    }
};

WT3DModel.initWT3D('canvas-frame', initOption, Aobjects);
WT3DModel.start();

Due to space constraints, we conclude this lesson here.

The next lesson will focus on implementing 3D energy storage stations and cabinets.

Tags: Three.js WebGL Smart Photovoltaic 3D Visualization Digital Twin

Posted on Tue, 16 Jun 2026 17:33:54 +0000 by Havery Jay