npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@thezelijah/majik-blender-edu-analyzer

v1.0.0

Published

Majik Blender Edu is a submission integrity and student activity tracking addon for Blender designed to help educators verify a student's work. It records Blender-specific actions such as editing meshes, adding modifiers, importing assets, and scene stati

Downloads

62

Readme

Majik Blender Edu Analyzer

Majik Blender Edu Analyzer is a TypeScript/JavaScript SDK that processes decrypted JSON logs exported from the Majik Blender Edu Blender addon and converts them into verifiable integrity checks, behavioral analytics, KPIs, charts, and explainable verdicts.

The Majik Blender Edu Analyzer is your student activity insight tool. It helps teachers assess submission authenticity, workflow efficiency, and originality by processing the encrypted logs generated by the Blender addon.

This package is designed for:

  • Educators & institutions
  • LMS / grading system integrations
  • Web dashboards
  • Research & academic integrity tooling

npm License


Live Demo

Majik Blender Edu Analyzer Thumbnail

Click the image to try Majik Blender Edu Analyzer live.



Overview

The Analyzer operates after Blender and after decryption.

Blender Addon (Student)
   ↓
Encrypted Logs in .blend
   ↓
Blender Addon (Teacher)
   ↓
Decrypted Action Log JSON
   ↓
Majik Blender Edu Analyzer (SDK)
   ↓
KPIs • Charts • Verdict • Export

It does not:

  • Touch Blender directly
  • Guess via ML
  • Hide logic behind black-box scoring

It does:

  • Verify cryptographic integrity
  • Reconstruct real working sessions
  • Analyze modeling behavior
  • Produce explainable authenticity scores

Key Features

  • Action Logging: Tracks all essential Blender operations including mesh edits, modifiers, imports, and scene statistics.
  • Blockchain-style Log Integrity: Each log entry contains a hash of the previous entry, forming a chain that guarantees log authenticity.
  • Secure Encryption: Uses AES Fernet to encrypt logs; decryption is only possible with the teacher’s key.
  • Teacher Verification: Teachers can decrypt, validate, and export logs to JSON for deeper analysis.
  • Student Timer & Visual Feedback: Tracks session times and overlays a red rectangle in the viewport to indicate an active logging session.
  • Cross-project Key Reuse: Teachers can reuse a key across multiple projects while keeping logs secure.
  • Reset Functionality: Teachers can reset a project, removing all prior logs and settings.

Technical Overview

  • Platform Tested: Blender 5 on Windows
  • Python Libraries:
    • cryptography (AES Fernet encryption)
    • Blender Python API (native)
  • Log Structure: Each log is chained via hash references, similar to blockchain, making tampering evident.
  • Performance: Optimized for projects up to ~60k vertices; higher complexity projects are untested.
  • Security: Logs cannot be decrypted or altered without the teacher’s key.

⚠️ Note: Ensure the cryptography library is installed; it’s a mandatory dependency. Other Blender versions and platforms are planned for future support.


Installation

npm install @thezelijah/majik-blender-edu-analyzer

Quick Start

import { MajikBlenderEdu } from "@thezelijah/majik-blender-edu-analyzer";

const edu = MajikBlenderEdu.initialize(
  decryptedActionLogJSON,
  teacherAESKey,
  studentId
);

const summary = edu.getSummary();
console.log(summary.score, summary.verdict);

Example Usage

"use client";

import React, { useMemo, useState } from "react";
import styled from "styled-components";
import {
  GearIcon,
  InfoIcon,
  LogIcon,
  ShieldCheckIcon,
  ClockIcon,
  RocketIcon,
  NotebookIcon,
  StarIcon,
  SquaresFourIcon,
  CubeIcon,
  LightningIcon,
  HourglassIcon,
  FunnelIcon,
} from "@phosphor-icons/react";
import DynamicPagedTab, {
  TabContent,
} from "@/components/functional/DynamicPagedTab";

import { formatPercentage, formatTime } from "@/utils/helper";

import { DynamicColoredValue } from "@/components/foundations/DynamicColoredValue";
import theme from "@/globals/theme";

import { MajikBlenderEdu } from "@/SDK/tools/education/majik-blender-edu/majik-blender-edu";
import MajikBlenderEduHealthIndicator from "./MajikBlenderEduHealthIndicator";
import SetupMajikBlenderEdu from "./SetupMajikBlenderEdu";
import ActionLogViewer from "./ActionLogViewer";
import PlotlyTrendChart from "@/components/plotly/PlotlyTrendChart";
import PlotlyPieChart from "@/components/plotly/PlotlyPieChart";

// ======== Styled Components ========
const RootContainer = styled.div`
//css
`;

const HeaderCards = styled.div`
//css
`;

const TopHeaderRow = styled.div`
//css
`;

const Card = styled.div`
//css
`;

const CardTitle = styled.div`
//css
`;

const CardValue = styled.div`
//css
`;

const CardSubtext = styled.div`
//css
`;

const MainGrid = styled.div`
//css
`;

const ChartPlaceholder = styled.div`
//css
`;

interface DashboardMajikBlenderEduProps {
  instance: MajikBlenderEdu;
  onUpdate?: (updatedInstance: MajikBlenderEdu) => void;
}

// ======== Main Component ========

const DashboardMajikBlenderEdu: React.FC<DashboardMajikBlenderEduProps> = ({
  instance,
}) => {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const [refreshKey, setRefreshKey] = useState<number>(0);
  const dashboardSnapshot = useMemo(
    () => instance.getSummary(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const health = useMemo(
    () => instance.getEduHealth(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const integrityStatus = useMemo(
    () => instance.validateLogChain(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartSceneGrowthOverTime = useMemo(
    () => instance.getPlotlySceneGrowthOverTime(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartActionDensity = useMemo(
    () => instance.getPlotlyActionDensity(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartActionTypePie = useMemo(
    () => instance.getPlotlyActionTypePie(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartAuthenticityScorePie = useMemo(
    () => instance.getPlotlyAuthenticityScorePie(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartEntropyTrend = useMemo(
    () => instance.getPlotlyEntropyTrend(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const chartIdleTimeTraces = useMemo(
    () => instance.getPlotlyIdleTimeTraces(),

    // eslint-disable-next-line react-hooks/exhaustive-deps
    [instance, refreshKey]
  );

  const InformationTabs: TabContent[] = [
    {
      id: "info-overview",
      name: "Overview",
      icon: InfoIcon,
      content: (
        <>
          <TopHeaderRow>
            <MajikBlenderEduHealthIndicator health={health} />
          </TopHeaderRow>

          {/* ===== Header / KPI Cards ===== */}
          <HeaderCards>
            <Card>
              <CardTitle>
                <ShieldCheckIcon size={20} /> Integrity Status
              </CardTitle>
              <CardValue>{integrityStatus ? "VALID" : "TAMPERED"}</CardValue>
              <CardSubtext>{!!dashboardSnapshot.flags.join(", ")}</CardSubtext>
            </Card>
            <Card>
              <CardTitle>
                <ClockIcon size={20} />
                Total Working Time
              </CardTitle>
              <CardValue>
                {formatTime(
                  dashboardSnapshot.totalWorkingTime,
                  "seconds",
                  undefined,
                  true,
                  "duration"
                )}
              </CardValue>
            </Card>

            <Card>
              <CardTitle>
                <RocketIcon size={20} />
                Effective Time
              </CardTitle>
              <CardValue>
                {formatTime(
                  dashboardSnapshot.effectiveTime,
                  "seconds",
                  undefined,
                  true,
                  "duration"
                )}
              </CardValue>
            </Card>

            <Card>
              <CardTitle>
                <NotebookIcon size={20} /> Total Logs
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.totalLogs.toLocaleString()}
              </CardValue>
            </Card>

            <Card>
              <CardTitle>
                <StarIcon size={20} /> Authenticity Score
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.score} // numeric months remaining
                  colorsMap={[
                    { color: theme.colors.error, max: 59.99 },
                    { color: theme.colors.brand.white, min: 60, max: 84.99 },
                    { color: theme.colors.brand.green, min: 85 },
                  ]}
                  size={28}
                  weight={700}
                >
                  {dashboardSnapshot.score}/100
                </DynamicColoredValue>
              </CardValue>
            </Card>
          </HeaderCards>

          {/* ===== SubHeader / Secondary KPI Cards ===== */}
          <HeaderCards>
            <Card>
              <CardTitle>
                <SquaresFourIcon size={20} /> Total Vertices
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.totalVertices.toLocaleString()}
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <CubeIcon size={20} /> Total Objects
              </CardTitle>
              <CardValue>
                {dashboardSnapshot.totalObjects.toLocaleString()}
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <LightningIcon size={20} /> Most Active Object
              </CardTitle>
              <CardValue>{dashboardSnapshot.mostActiveObject}</CardValue>
            </Card>

            <Card>
              <CardTitle>
                <HourglassIcon size={20} />
                Idle Ratio
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.idleRatio ?? 0}
                  colorsMap={[
                    { color: theme.colors.brand.green, max: 0.299 },
                    { color: theme.colors.brand.white, min: 0.3, max: 0.799 },
                    { color: theme.colors.error, min: 0.8 },
                  ]}
                  size={28}
                  weight={700}
                >
                  {formatPercentage(dashboardSnapshot.idleRatio ?? 0, true)}
                </DynamicColoredValue>
              </CardValue>
            </Card>
            <Card>
              <CardTitle>
                <FunnelIcon size={20} />
                Entropy Score
              </CardTitle>
              <CardValue>
                <DynamicColoredValue
                  value={dashboardSnapshot.entropyScore ?? 0}
                  colorsMap={[
                    { color: theme.colors.error, max: 1.299 },
                    { color: theme.colors.brand.white, min: 1.3, max: 1.499 },
                    { color: theme.colors.brand.green, min: 1.5 },
                  ]}
                  size={28}
                  weight={700}
                >
                  {formatPercentage(dashboardSnapshot.entropyScore ?? 0, true)}
                </DynamicColoredValue>
              </CardValue>
            </Card>
          </HeaderCards>

          {/* ===== Main Grid / Charts ===== */}
          <MainGrid>
            <ChartPlaceholder>
              <PlotlyTrendChart
                data={chartSceneGrowthOverTime}
                text={{
                  title: "Scene Growth Over Time",
                  x: {
                    text: "Time",
                    format: "%H:%M:%S", // or "%b %d %H:%M" if date-based
                  },
                  y: {
                    text: "Scene Complexity",
                    suffix: "", // no unit, just counts
                  },
                }}
                disableDragZoom={false}
              />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <PlotlyTrendChart
                data={chartActionDensity}
                text={{
                  title: "Action Density Over Time",
                  x: {
                    text: "Time (per minute)",
                    format: "%H:%M",
                  },
                  y: {
                    text: "Actions per Minute",
                  },
                }}
                disableDragZoom={false}
              />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <PlotlyPieChart
                data={chartActionTypePie}
                title="Distribution of Actions by Type"
              />
            </ChartPlaceholder>

            <ChartPlaceholder>
              <PlotlyPieChart
                data={chartAuthenticityScorePie}
                title="Authenticity Score vs Potential Issues"
              />
            </ChartPlaceholder>
            <ChartPlaceholder>
              <PlotlyTrendChart
                data={chartEntropyTrend}
                text={{
                  title: "Action Entropy Over Time",
                  x: {
                    text: "Time",
                    format: "%H:%M:%S", // or "%b %d %H:%M" for multi-day sessions
                  },
                  y: {
                    text: "Action Entropy",
                  },
                }}
                disableDragZoom={false}
                smooth={false}
              />
            </ChartPlaceholder>

            <ChartPlaceholder>
              <PlotlyTrendChart
                data={chartIdleTimeTraces}
                text={{
                  title: "Idle Periods Over Time",
                  x: {
                    text: "Time",
                    format: "%H:%M:%S", // or "%b %d %H:%M" for long sessions
                  },
                  y: {
                    text: "Idle Time",
                    suffix: " sec",
                  },
                }}
                disableDragZoom={false}
                smooth={false}
              />
            </ChartPlaceholder>
          </MainGrid>
        </>
      ),
    },

    {
      id: "info-logs",
      name: "Action Logs",
      icon: LogIcon,
      content: (
        <>
          <ActionLogViewer instance={instance} />
        </>
      ),
    },

    {
      id: "info-settings",
      name: "Settings",
      icon: GearIcon,
      content: (
        <>
          <SetupMajikBlenderEdu />
        </>
      ),
    },
  ];

  return (
    <RootContainer>
      {/* ===== Tabs ===== */}

      <DynamicPagedTab tabs={InformationTabs} position="left" />
    </RootContainer>
  );
};

export default DashboardMajikBlenderEdu;

API Reference


MajikBlenderEdu Class

1. Initialization & Parsing

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | -------------------------- | ---------------------------------------------------------------------------------------------------------------- | | initialize(json: object, teacherKey: string, studentId: string) | json – decrypted action log JSONteacherKey – AES key used for decryptionstudentId – student identifier | MajikBlenderEdu instance | Constructs and validates a new Analyzer instance from decrypted logs. Prepares KPIs, charts, and internal state. | | parseFromJSON(json: object) | json – previously serialized Analyzer state | MajikBlenderEdu instance | Reconstructs a full Analyzer instance from serialized state (for batch processing or replay). |


2. Integrity & Validation

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ----------------------------- | ------------------- | ----------- | -------------------------------------------------------------------------------------------------------------- | | validateGenesis() | None | boolean | Confirms correct teacher-student binding in the logs; returns true if genesis entry is valid. | | validateLogChain() | None | boolean | Verifies that the logs’ cryptographic hashes are intact and in order; returns true if no tampering detected. | | hasLogTamperingIndicators() | None | boolean | High-level flag indicating potential log tampering, missing entries, or suspicious sequences. |


3. Time & Session Metrics

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ------------------------------------ | --------------------------------------------------------- | --------------------------------------- | --------------------------------------------------------------- | | getTotalWorkingTime() | None | number (seconds) | Returns total session duration including idle periods. | | getEffectiveWorkingTime() | None | number (seconds) | Returns session duration excluding idle gaps. | | getIdleRatio() | None | number (0–1) | Ratio of idle time vs. total working time. | | getIdlePeriods(threshold?: number) | threshold – minimum idle duration in seconds (optional) | Array<{ start: number; end: number }> | Returns exact timestamps of idle periods exceeding threshold. |


4. Scene & Modeling Metrics

| Method | Inputs / Parameters | Return Type | Description / Purpose | | --------------------------------------------- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------------------------ | | getTotalVertices() | None | number | Final vertex count in the scene. | | getTotalObjects() | None | number | Final object count in the scene. | | getSceneGrowthOverTime() | None | Array<{ time: number; vertices: number; objects: number }> | Timeline of scene complexity (vertices/objects) over session time. | | hasMeaningfulProgress(minVertices?: number) | minVertices – optional threshold | boolean | Checks if student made minimum modeling progress. | | detectAllImportJumps() | None | Array<{ time: number; importedObject: string }> | Detects sudden additions of objects/assets that may indicate external copying. |


5. Behavioral Analysis

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ----------------------------------------------- | ---------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------- | | getActionEntropy() | None | number | Workflow randomness metric; higher values indicate unusual or erratic patterns. | | getRepetitiveActionBursts(threshold?: number) | threshold – actions/minute | Array<{ start: number; end: number; count: number }> | Detects repeated sequences of actions beyond threshold. | | getMostActiveObject() | None | string | Returns the object with the most actions applied during the session. |


6. Scoring & Verdicts

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ------------------------ | ------------------- | -------------------- | ------------------------------------------------------------------------------------------ | | getAuthenticityScore() | None | number (0–100) | Computes an explainable score for workflow authenticity. | | getFlags() | None | Array<string> | Returns a list of integrity warnings or behavioral flags. | | getEduHealth() | None | { status: "healthy" | "warning" | "critical"; reasons: string[] } | Returns overall session health assessment. | | getAssessmentVerdict() | None | string | Human-readable verdict summarizing session authenticity, progress, and integrity concerns. |


7. Charts & Visualization (Plotly Data)

| Method | Inputs / Parameters | Return Type | Description / Purpose | | --------------------------------- | ------------------- | --------------- | -------------------------------------------- | | getPlotlySceneGrowthOverTime() | None | Plotly.Data[] | Vertices / objects over time. | | getPlotlyActionDensity() | None | Plotly.Data[] | Actions-per-minute density chart. | | getPlotlyActionTypePie() | None | Plotly.Data[] | Action-type distribution pie chart. | | getPlotlyEntropyTrend() | None | Plotly.Data[] | Action entropy over time. | | getPlotlyIdleTimeTraces() | None | Plotly.Data[] | Idle periods timeline. | | getPlotlyAuthenticityScorePie() | None | Plotly.Data[] | Pie chart showing score breakdown vs issues. |


8. Export & Serialization

| Method | Inputs / Parameters | Return Type | Description / Purpose | | ------------------------------------------------- | -------------------- | ----------- | --------------------------------------------------------------------------------------------------- | | getSummary() | None | object | Aggregated KPIs, flags, metrics, and scores for dashboards or export. | | toJSON() | None | object | Serializable JSON representing current Analyzer state; can be reloaded later via parseFromJSON(). | | exportCSV(options?: { includeFlags?: boolean }) | Optional CSV options | string | Returns CSV string suitable for audits, research, or LMS integration. |


Types

| Name | Category | Description | Notes / Details | | ------------------------ | ------------ | ------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | ISODateString | Type | String representing an ISO 8601 date | string type alias | | MajikBlenderEduJSON | Interface | Full log dataset for a student | Contains id, data (RawActionLogEntry[]), total_working_time, period, timestamp, optional secret_key and student_id, plus stats | | RawActionLogJSON | Interface | Raw log data structure | Contains data, status, total_working_time, period, stats (v, f, o) | | LogPeriod | Interface | Start/end of a logging period | start and end as ISODateString | | RawActionLogEntry | Interface | Individual raw log entry | t (timestamp number), a (action), o (object), ot (object type), d (details), dt (duration), s (stats: v/f/o), ph (previous hash) | | ActionLogEntry | Interface | Processed log entry | timestamp, actionType, name, type, optional details, duration, sceneStats, hash | | SceneStats | Interface | Scene statistics | vertex, face, object counts | | RawSceneStats | Interface | Raw scene statistics | v, f, o counts | | HealthSeverity | Type | Severity of log health | "healthy" | "warning" | "critical" | | ActionLogHealth | Interface | Health status of a log | status (HealthSeverity) and array of reasons | | MajikBlenderEduSummary | Interface | Summary stats for a student/session | Includes totals (totalLogs, totalWorkingTime, effectiveTime), idle stats, total vertices/objects, mostActiveObject, actionCounts, score, flags, verdict, entropyScore | | DefaultColors | Interface | Predefined colors | green, red, blue, white |


Utils / Functions

| Name | Category | Description | Notes | | ------------------------------------------------------ | ------------ | ---------------------------------------------------- | --------------------------------------------------------------------- | | fernetKeyFromString(password, salt) | Crypto | Generates Fernet-compatible key from password + salt | Uses PBKDF2 + SHA256, returns base64url | | sha256Hex(data) | Crypto | SHA256 hash of string, hex-encoded | Used internally for integrity & key derivation | | aesEncrypt(metadata, key, salt) | Crypto | Encrypt JSON metadata using Fernet/AES | Low-level; returns encoded string | | aesDecrypt(encryptedMetadata, key, salt) | Crypto | Decrypt Fernet/AES metadata | Low-level; returns string | | encryptMetadata(metadata, key, salt) | Crypto | High-level encryption wrapper | Validates salt and hashes key before encryption | | decryptMetadata(encryptedMetadata, key, salt) | Crypto | High-level decryption wrapper | Returns parsed JSON object | | deepSortKeys(obj) | Helper | Recursively sorts object keys | Used for canonical JSON before hashing | | computeEntryHash(entry) | Hashing | Computes SHA256 hash for a RawActionLogEntry | Excludes ph and normalizes "dt":0 → 0.0 | | generateGenesisKey(teacherKey, studentId) | Crypto | Generates unique genesis key | Combines teacher key + studentId, SHA256 hash | | validateGenesisKey(current, teacherKey, studentId) | Crypto | Validates genesis key | Returns boolean | | validateLogIntegrity(rawLogs, teacherKey, studentId) | Crypto | Validates sequential log hashes | Checks genesis + chain of entry hashes | | calculateDuration(previousEntry?, currentEntry?) | Time | Computes elapsed seconds between logs | Safely returns 0 if invalid | | isSessionStartLog(entry) | Helper | Checks if entry is session start | actionType="Session Started", name="__SESSION__", type="SYSTEM" |


Integration Patterns

LMS Integration

Student JSON → Analyzer → Score → Grade

Research

Batch JSON → Analyzer → CSV → Statistical Analysis

Appeals / Audits

Stored JSON → Replay → Deterministic Verdict

Design Principles

  • Deterministic output
  • Explainable metrics
  • No ML dependency
  • Institution-tunable thresholds
  • Visualization-agnostic
  • Cryptographically grounded

How to Use the Analyzer

  1. Export Action Log JSON: From the Teacher version of the Blender addon, decrypt the project and export the action log JSON.
  2. Upload to Analyzer: Go to Majik Blender Edu Analyzer and upload the JSON file.
  3. Provide Teacher Key: Enter your AES key manually or import your key JSON to decrypt the logs.
  4. Enter Student ID: Use the identifier you assigned in Blender to match the logs.
  5. Analyze: The dashboard will generate KPIs, metrics, charts, and detailed logs for analysis.

Understanding the Dashboard

The dashboard is divided into several key areas to provide a comprehensive view of student behavior:

1. Overview KPIs

| KPI | Description | Example / Notes | | :--------------------- | :------------------------------------------------------------------- | :----------------------------- | | Integrity Status | Validates whether the action logs have been tampered with. | VALID / TAMPERED | | Total Working Time | Total duration of the student’s active Blender session. | 2h 15m | | Effective Time | Time spent actively performing actions (mesh edits, modifiers, etc.) | 1h 50m | | Total Logs | Number of individual actions logged during the session. | 245 logs | | Authenticity Score | Evaluates originality and potential issues in the workflow. | 92/100 | | Scene Complexity | Summary of total vertices and object count. | Vertices: 34,500 / Objects: 45 | | Most Active Object | The object where the student performed the most actions. | Cube.001 | | Idle Ratio | Percentage of time spent without performing any actions. | 15% | | Entropy Score | Measures workflow irregularity or randomness in actions. | 1.42 |

2. Visual Charts

| Chart | Description | Insights | | :-------------------- | :------------------------------------------------ | :---------------------------------------------- | | Scene Growth | Tracks vertices and objects over time. | Shows how quickly the scene was built. | | Action Density | Number of actions performed per minute. | Identifies bursts of activity vs. idle periods. | | Type Distribution | Pie chart of mesh edits, modifiers, imports, etc. | Shows which tasks dominated the session. | | Entropy Trend | Measures workflow irregularity over time. | Spikes may indicate unusual behavior or jumps. | | Idle Periods | Timeline of exact periods of inactivity. | Useful for identifying distractions or breaks. |

3. Action Logs

A chronological detailed view of every action recorded, including mesh edits, modifier applications, imports/exports, and scene statistics changes.

How the Numbers Work

The Analyzer aggregates data through several layers to ensure reliability:

  • Blockchain-style Verification: Hashing ensures logs haven't been edited post-export.
  • KPI Computation: Calculates "Effective Time" by filtering out periods of inactivity.
  • Trend Analysis: Uses Plotly to visualize temporal trends like scene growth and action entropy.
  • Reactive Updates: KPIs and charts refresh immediately upon importing a new JSON.

💡 Teacher Tip: Focus on the Authenticity Score and Entropy Trends to detect irregular workflows or potentially copied content.

Integration & SDK

Majik Blender Edu is designed to be flexible. You can integrate it into your own web projects or custom automated grading systems.

| Integration Type | Description | Link | | :---------------- | :------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------- | | Teacher Addon | Encrypt and validate student sessions. | Download | | Student Addon | Log actions for submission. | Download | | Analyzer SDK | TypeScript/NPM package to process logs programmatically. | @thezelijah/majik-blender-edu-analyzer | | Source Code | Full source, issues, and contribution guide. | GitHub Repo |


Contributing

If you want to contribute or help extend support to more Blender versions and platforms, reach out via email. All contributions are welcome!


Notes

  • Currently only tested on Blender 5 on Windows.
  • Works reliably with light to medium projects (~60k vertices).
  • No stress testing performed yet; report any issues in the comments.
  • Ensure secure handling of your encryption key; unauthorized access compromises log integrity.

License

The Analyzer web tool and the @thezelijah/majik-blender-edu-analyzer NPM package are licensed under the Apache License 2.0.

  • This is a permissive license that allows for broader integration into various software environments, including commercial and proprietary projects.
  • This ensures developers can build custom dashboards or automated grading systems using our logic without GPL restrictions.
  • See the LICENSE file for full details (or visit the Apache 2.0 Official Site).

Author

Made with 💙 by @thezelijah

About the Developer


Contact