Skip to content

Commit

Permalink
update: add generic message handling
Browse files Browse the repository at this point in the history
  • Loading branch information
VsevolodX committed Jan 16, 2025
1 parent ec06502 commit 57b870f
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 17 deletions.
47 changes: 47 additions & 0 deletions dist/components/ThreeDEditor.js
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,12 @@ class ThreeDEditor extends _react.default.Component {
onClick: () => this.handleDownloadClick("poscar")
}];
return [{
id: "StartGif",
title: "Auto Rotate GIF",
content: "Auto Rotate GIF",
leftIcon: /*#__PURE__*/_react.default.createElement(_PictureInPicture.default, null),
onClick: this.handleStartGifRecording
}, {
id: "Screenshot",
title: "Screenshot",
content: "Screenshot",
Expand All @@ -262,6 +268,39 @@ class ThreeDEditor extends _react.default.Component {
handleChemicalConnectivityFactorChange: this.handleChemicalConnectivityFactorChange
});
});
_defineProperty(this, "handleStartGifRecording", (downloadPath, rotationSpeed = 60, frameDuration = 0.05) => {
this.WaveComponent.wave.takeGifScreenshot({
rotationSpeed,
frameDuration,
downloadPath
}).then(result => {
console.log("Recorded gif", result);
});
});
_defineProperty(this, "handleMessage", event => {
if (event.data && event.data.material) {
try {
const newMaterial = new _made.Made.Material(event.data.material);
this.setState({
originalMaterial: newMaterial,
material: newMaterial.clone()
}, () => {
// Force Wave component to update after state change
if (this.WaveComponent) {
this.WaveComponent.wave.rebuildScene();
}
});
} catch (error) {
alert("Error creating material: " + error.message);
}
} else if (event.data && event.data.action && this[event.data.action]) {
const {
action,
parameters
} = event.data;
this[action](...parameters);
}
});
const {
boundaryConditions,
isConventionalCellShown: _isConventionalCellShown,
Expand Down Expand Up @@ -327,14 +366,19 @@ class ThreeDEditor extends _react.default.Component {
this.onMeasurementParam = this.onMeasurementParam.bind(this);
this.addHotKeyListener = this.addHotKeyListener.bind(this);
this.removeHotKeyListener = this.removeHotKeyListener.bind(this);
this.handleStartGifRecording = this.handleStartGifRecording.bind(this);
this.handleMessage = this.handleMessage.bind(this);
this.doWaveFunc = this.doWaveFunc.bind(this);
}
componentDidMount() {
this.addHotKeyListener();
window.addEventListener("message", this.handleMessage);
}
componentWillUnmount() {
this.handleResetMeasurements();
this.WaveComponent.wave.destroyListeners();
this.removeHotKeyListener();
window.removeEventListener("message", this.handleMessage);
}

// TODO: update component to fully controlled or fully uncontrolled with a key?
Expand Down Expand Up @@ -746,6 +790,9 @@ class ThreeDEditor extends _react.default.Component {
enableColorScheme: true
}, this.renderWaveOrThreejsEditorModal()));
}
doWaveFunc(func, ...args) {
this.WaveComponent.wave[func](...args);
}
}
exports.ThreeDEditor = ThreeDEditor;
ThreeDEditor.propTypes = {
Expand Down
1 change: 1 addition & 0 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ var _ThreeDEditor = require("./components/ThreeDEditor");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const domElement = document.getElementById("root");
const material = new _made.Made.Material(_made.Made.defaultMaterialConfig);

// eslint-disable-next-line react/no-render-return-value
window.threeDEditor = _reactDom.default.render(/*#__PURE__*/_react.default.createElement(_ThreeDEditor.ThreeDEditor, {
editable: true,
Expand Down
66 changes: 65 additions & 1 deletion dist/mixins/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ Object.defineProperty(exports, "__esModule", {
value: true
});
exports.UtilsMixin = exports.ApplyGlow = void 0;
exports.createRotatingGif = createRotatingGif;
var _gifshot = _interopRequireDefault(require("gifshot"));
var THREE = _interopRequireWildcard(require("three"));
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const UtilsMixin = superclass => class extends superclass {
// toggles a boolean variable and optionally sets all variables in the antagonists array to the opposite value
toggleBoolean(name, antagonistNames = []) {
Expand Down Expand Up @@ -68,4 +71,65 @@ const ApplyGlow = (meshObjet, baseColor, offset = 0) => {
meshObjet.material.emissive.setHSL(hue, saturation, atomHSL.l);
}
};
exports.ApplyGlow = ApplyGlow;
exports.ApplyGlow = ApplyGlow;
function createRotatingGif(wave, options = {}) {
const ROTATION_SPEED = options.rotationSpeed || 60; // RPM
const frameDuration = options.frameDuration || 0.05; // seconds
const sampleInterval = ROTATION_SPEED * frameDuration;
const canvas = wave.renderer.domElement;
const frames = [];
let frameCount = 0;
const totalFrames = ROTATION_SPEED;
const {
width
} = canvas;
const {
height
} = canvas;

// Store original auto-rotate settings
const wasAutoRotating = wave.orbitControls.autoRotate;
const originalSpeed = wave.orbitControls.autoRotateSpeed;

// Enable rotation
wave.orbitControls.autoRotate = true;
wave.orbitControls.autoRotateSpeed = ROTATION_SPEED;
return new Promise((resolve, reject) => {
const captureFrame = () => {
wave.render();
frames.push(canvas.toDataURL("image/png"));
};
const createGif = () => {
console.log("Creating GIF from frames...");
// Restore original rotation settings
wave.orbitControls.autoRotateSpeed = originalSpeed;
wave.orbitControls.autoRotate = wasAutoRotating;
_gifshot.default.createGIF({
images: frames,
gifWidth: width,
gifHeight: height,
numFrames: totalFrames,
frameDuration,
sampleInterval
}, result => {
frames.length = 0; // Clear frames array
if (!result.error) {
resolve(result.image);
} else {
reject(new Error(result.error));
}
});
};
const animate = () => {
if (frameCount < totalFrames) {
wave.orbitControls.update();
captureFrame();
frameCount += 1;
requestAnimationFrame(animate);
} else {
createGif();
}
};
animate();
});
}
27 changes: 25 additions & 2 deletions dist/wave.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ var _controls = require("./mixins/controls");
var _labels = require("./mixins/labels");
var _measurement = require("./mixins/measurement");
var _repetition = require("./mixins/repetition");
var _utils = require("./mixins/utils");
var _settings = _interopRequireDefault(require("./settings"));
var _utils = require("./utils");
var _utils2 = require("./utils");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
Expand Down Expand Up @@ -243,7 +244,7 @@ class Wave extends (0, _mixwith.mix)(WaveBase).with(_atoms.AtomsMixin, _bonds.Bo
this.doFunc = this.doFunc.bind(this);
}
takeScreenshot() {
(0, _utils.saveImageDataToFile)(this.renderer.domElement.toDataURL("image/png"));
(0, _utils2.saveImageDataToFile)(this.renderer.domElement.toDataURL("image/png"));
}
clearView() {
while (this.structureGroup.children.length) {
Expand Down Expand Up @@ -310,5 +311,27 @@ class Wave extends (0, _mixwith.mix)(WaveBase).with(_atoms.AtomsMixin, _bonds.Bo
doFunc(func) {
func(this);
} // for scripting

async takeGifScreenshot(options = {}) {
try {
const gifDataUrl = await (0, _utils.createRotatingGif)(this, options);

// Use custom filename from options or fall back to default
const fileName = options.downloadPath ? options.downloadPath.split("/").pop() // Extract filename from path
: (this._structure.name || this._structure.formula || "wave-visualization") + ".gif";

// Download the GIF
const a = document.createElement("a");
a.href = gifDataUrl;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
return gifDataUrl;
} catch (error) {
console.error("Error creating GIF:", error);
throw error;
}
}
}
exports.Wave = Wave;
35 changes: 24 additions & 11 deletions src/components/ThreeDEditor.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -111,18 +111,20 @@ export class ThreeDEditor extends React.Component {
this.addHotKeyListener = this.addHotKeyListener.bind(this);
this.removeHotKeyListener = this.removeHotKeyListener.bind(this);
this.handleStartGifRecording = this.handleStartGifRecording.bind(this);
this.handleMessage = this.handleMessage.bind(this);
this.doWaveFunc = this.doWaveFunc.bind(this);
}

componentDidMount() {
this.addHotKeyListener();
window.addEventListener("message", this.handleMaterialMessage);
window.addEventListener("message", this.handleMessage);
}

componentWillUnmount() {
this.handleResetMeasurements();
this.WaveComponent.wave.destroyListeners();
this.removeHotKeyListener();
window.removeEventListener("message", this.handleMaterialMessage);
window.removeEventListener("message", this.handleMessage);
}

// TODO: update component to fully controlled or fully uncontrolled with a key?
Expand Down Expand Up @@ -685,11 +687,16 @@ export class ThreeDEditor extends React.Component {
return toolbarConfig;
}

handleStartGifRecording = () => {
this.WaveComponent.wave.takeGifScreenshot({
rotationSpeed: 60,
frameDuration: 0.05,
});
handleStartGifRecording = (downloadPath, rotationSpeed = 60, frameDuration = 0.05) => {
this.WaveComponent.wave
.takeGifScreenshot({
rotationSpeed,
frameDuration,
downloadPath,
})
.then((result) => {
console.log("Recorded gif", result);
});
};

onThreejsEditorModalHide(material) {
Expand Down Expand Up @@ -752,7 +759,7 @@ export class ThreeDEditor extends React.Component {
);
}

handleMaterialMessage = (event) => {
handleMessage = (event) => {
if (event.data && event.data.material) {
try {
const newMaterial = new Made.Material(event.data.material);
Expand All @@ -764,22 +771,28 @@ export class ThreeDEditor extends React.Component {
() => {
// Force Wave component to update after state change
if (this.WaveComponent) {
this.WaveComponent.reloadViewer(true);
this.WaveComponent.wave.rebuildScene();
}
},
);
} catch (error) {
alert("Error creating material: " + error.message);
}
} else if (event.data && event.data.action && this[event.data.action]) {
const { action, parameters } = event.data;
this[action](...parameters);
}
};

doWaveFunc(func, ...args) {
this.WaveComponent.wave[func](...args);
}
}

ThreeDEditor.propTypes = {
material: PropTypes.instanceOf(Made.Material).isRequired,
editable: PropTypes.bool,
isConventionalCellShown: PropTypes.bool,
// eslint-disable-next-line react/forbid-prop-types
isConventionalCellShown: PropTypes.bool, // eslint-disable-next-line react/forbid-prop-types
boundaryConditions: PropTypes.object,
onUpdate: PropTypes.func,
};
Expand Down
9 changes: 6 additions & 3 deletions src/wave.js
Original file line number Diff line number Diff line change
Expand Up @@ -342,13 +342,16 @@ export class Wave extends mix(WaveBase).with(
try {
const gifDataUrl = await createRotatingGif(this, options);

const fileName =
this._structure.name || this._structure.formula || "wave-visualization";
// Use custom filename from options or fall back to default
const fileName = options.downloadPath
? options.downloadPath.split("/").pop() // Extract filename from path
: (this._structure.name || this._structure.formula || "wave-visualization") +
".gif";

// Download the GIF
const a = document.createElement("a");
a.href = gifDataUrl;
a.download = `${fileName}.gif`;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
Expand Down

0 comments on commit 57b870f

Please sign in to comment.