How to create Room name Text inside the room element? - autodesk-forge
Is there any way to create room name text inside the room element in forge viewer?
I have room elements in the forge viewer as per the below image.
So, I can read a room name from element properties. Then, I want to create room name text in forge viewer. May I have the solution?
Thanks In advance,
Update 2021-06-29
Added some conditions to avoid invalid data input.
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
//ref: https://stackoverflow.com/a/61262544
class TextMeasurer {
constructor() {
const SVG_NS = 'http://www.w3.org/2000/svg';
this.svg = document.createElementNS(SVG_NS, 'svg');
this.svg.style.visibility = 'hidden';
this.svg.setAttribute('xmlns', SVG_NS)
this.svg.setAttribute('width', 0);
this.svg.setAttribute('height', 0);
this.svgtext = document.createElementNS(SVG_NS, 'text');
this.svg.appendChild(this.svgtext);
this.svgtext.setAttribute('x', 0);
this.svgtext.setAttribute('y', 0);
document.querySelector('body').appendChild(this.svg);
}
/**
* Measure a single line of text, including the bounding box, inner size and lead and trail X
* #param {string} text Single line of text
* #param {string} fontFamily Name of font family
* #param {string} fontSize Font size including units
*/
measureText(text, fontFamily, fontSize) {
this.svgtext.setAttribute('font-family', fontFamily);
this.svgtext.setAttribute('font-size', fontSize);
this.svgtext.textContent = text;
let bbox = this.svgtext.getBBox();
let textLength = this.svgtext.getComputedTextLength();
// measure the overflow before and after the line caused by font side bearing
// Rendering should start at X + leadX to have the edge of the text appear at X
// when rendering left-aligned left-to-right
let baseX = parseInt(this.svgtext.getAttribute('x'));
let overflow = bbox.width - textLength;
let leadX = Math.abs(baseX - bbox.x);
let trailX = overflow - leadX;
document.querySelector('body').removeChild(this.svg);
return {
bbWidth: bbox.width,
textLength: textLength,
leadX: leadX,
trailX: trailX,
bbHeight: bbox.height
};
}
}
class AecRoomTagsExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.modelBuilder = null;
this.idPrefix = 100;
}
async load() {
const modelBuilderExt = await this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder');
const modelBuilder = await modelBuilderExt.addNewModel({
conserveMemory: false,
modelNameOverride: 'Room Tags'
});
this.modelBuilder = modelBuilder;
if (!this.viewer.isLoadDone()) {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.createRoomTags(),
{ once: true }
);
} else {
this.createRoomTags();
}
return true;
}
unload() {
this.viewer.impl.unloadModel(this.modelBuilder.model);
return true;
}
pxToMm(val) {
return val / 3.7795275591;
}
mmToFt(val) {
return val / 304.8;
}
createLabel(params) {
const text = params.text;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const fontSize = params.fontSize || 512;
const fontName = 'serif';
let offset = 2;
//Usage:
let m = new TextMeasurer();
let textDimensions = m.measureText(text, fontName, `${fontSize}px`);
canvas.height = textDimensions.bbHeight - (fontSize / 32 + 2) * offset;
canvas.width = textDimensions.bbWidth + offset + 3 * offset;
ctx.textBaseline = 'top';
ctx.fillStyle = '#000';
ctx.textAlign = 'left';
ctx.font = `${fontSize}px ${fontName}`;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
ctx.fillStyle = '#000';
ctx.fillText(text, offset, offset + (fontSize / 32 + 3) * offset);
ctx.strokeRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
const labelBlobUrl = canvas.toDataURL();
//console.log(labelBlobUrl);
const image = new Image();
const texture = new THREE.Texture();
texture.image = image;
image.src = labelBlobUrl;
image.onload = function () {
texture.needsUpdate = true;
};
const labelDbId = this.idPrefix++;
const matName = `label-mat-${labelDbId}`;
const material = new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide, opacity: 0.8, transparent: true });
material.map.minFilter = THREE.LinearFilter;
this.modelBuilder.addMaterial(matName, material);
const labelMat = this.modelBuilder.findMaterial(matName);
const planeWidth = this.mmToFt(this.pxToMm(canvas.width));
const planeHeight = this.mmToFt(this.pxToMm(canvas.height));
let planeGeo = new THREE.PlaneBufferGeometry(planeWidth, planeHeight);
let plane = new THREE.Mesh(planeGeo, labelMat);
plane.matrix = new THREE.Matrix4().compose(
params.position,
new THREE.Quaternion(0, 0, 0, 1),
new THREE.Vector3(1, 1, 1)
);
plane.dbId = labelDbId;
this.modelBuilder.addMesh(plane);
}
async createRoomTags() {
const getRoomDbIdsAsync = () => {
return new Promise((resolve, reject) => {
this.viewer.search(
'Revit Rooms',
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
};
const getPropertiesAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
};
const getBoxAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
let bounds = new THREE.Box3();
tree.enumNodeFragments(dbId, function (fragId) {
let box = new THREE.Box3();
frags.getWorldBounds(fragId, box);
bounds.union(box);
}, true);
return resolve(bounds);
});
};
const getRoomNameAsync = async (dbId, model) => {
const tree = model.getInstanceTree();
let name = tree.getNodeName(dbId);
if (!name) {
const props = await getPropertiesAsync(dbId, model);
name = props?.name;
}
return name;
};
try {
let roomDbIds = await getRoomDbIdsAsync();
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
const model = this.viewer.model;
const currentViewableId = this.viewer.model?.getDocumentNode().data.viewableID;
const masterViews = this.viewer.model?.getDocumentNode().getMasterViews();
const masterViewIds = masterViews?.map(v => v.data.viewableID);
if (!masterViewIds.includes(currentViewableId)) {
throw new Error('Current view does not contain any Rooms');
}
for (let i = 0; i < roomDbIds.length; i++) {
const dbId = roomDbIds[i];
const name = await getRoomNameAsync(dbId, model);
if (!name) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` doesn't have valid name`);
continue;
}
const roomProps = await getPropertiesAsync(dbId, model);
const possibleViewableIds = roomProps.properties.filter(prop => prop.attributeName === 'viewable_in').map(prop => prop.displayValue);
if (!possibleViewableIds.includes(currentViewableId)) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` is not visible in current view \`${currentViewableId}\``);
continue;
}
const box = await getBoxAsync(dbId, model);
if (!box) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` has an invalid bounding box`);
continue;
}
const center = box.center();
if (isNaN(center.x) || isNaN(center.y) || isNaN(center.z)) {
console.warn(`[AecRoomTagsExtension]: ${dbId} Room \`${name}\` has an invalid bounding box`);
continue;
}
//console.log(i, dbId, name, box, center);
const pos = new THREE.Vector3(
center.x,
center.y,
box.min.z + this.mmToFt(10)
);
this.createLabel({
text: name.replace(/ *\[[^)]*\] */g, ""),
position: pos,
fontSize: 512 // in pixel
});
}
// uncomment to prevent selection on tags
// const dbIds = this.modelBuilder.model.getFragmentList().fragments.fragId2dbId;
// const model = this.modelBuilder.model;
// this.viewer.lockSelection(dbIds, true, model);
} catch (ex) {
console.warn(`[AecRoomTagsExtension]: ${ex}`);
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.AecRoomTagsExtension', AecRoomTagsExtension);
=============================
It's similar to the Gird solution: https://stackoverflow.com/a/68129012/7745569
Not perfect, but it works. You may need to adjust the tag placement point (position) based on your model. Currently, tags are placed on the center of the bottom face of the Room bounding box.
/////////////////////////////////////////////////////////////////////
// Copyright (c) Autodesk, Inc. All rights reserved
// Written by Forge Partner Development
//
// Permission to use, copy, modify, and distribute this software in
// object code form for any purpose and without fee is hereby granted,
// provided that the above copyright notice appears in all copies and
// that both that copyright notice and the limited warranty and
// restricted rights notice below appear in all supporting
// documentation.
//
// AUTODESK PROVIDES THIS PROGRAM 'AS IS' AND WITH ALL FAULTS.
// AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF
// MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC.
// DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE
// UNINTERRUPTED OR ERROR FREE.
/////////////////////////////////////////////////////////////////////
//ref: https://stackoverflow.com/a/61262544
class TextMeasurer {
constructor() {
const SVG_NS = 'http://www.w3.org/2000/svg';
this.svg = document.createElementNS(SVG_NS, 'svg');
this.svg.style.visibility = 'hidden';
this.svg.setAttribute('xmlns', SVG_NS)
this.svg.setAttribute('width', 0);
this.svg.setAttribute('height', 0);
this.svgtext = document.createElementNS(SVG_NS, 'text');
this.svg.appendChild(this.svgtext);
this.svgtext.setAttribute('x', 0);
this.svgtext.setAttribute('y', 0);
document.querySelector('body').appendChild(this.svg);
}
/**
* Measure a single line of text, including the bounding box, inner size and lead and trail X
* #param {string} text Single line of text
* #param {string} fontFamily Name of font family
* #param {string} fontSize Font size including units
*/
measureText(text, fontFamily, fontSize) {
this.svgtext.setAttribute('font-family', fontFamily);
this.svgtext.setAttribute('font-size', fontSize);
this.svgtext.textContent = text;
let bbox = this.svgtext.getBBox();
let textLength = this.svgtext.getComputedTextLength();
// measure the overflow before and after the line caused by font side bearing
// Rendering should start at X + leadX to have the edge of the text appear at X
// when rendering left-aligned left-to-right
let baseX = parseInt(this.svgtext.getAttribute('x'));
let overflow = bbox.width - textLength;
let leadX = Math.abs(baseX - bbox.x);
let trailX = overflow - leadX;
document.querySelector('body').removeChild(this.svg);
return {
bbWidth: bbox.width,
textLength: textLength,
leadX: leadX,
trailX: trailX,
bbHeight: bbox.height
};
}
}
class AecRoomTagsExtension extends Autodesk.Viewing.Extension {
constructor(viewer, options) {
super(viewer, options);
this.modelBuilder = null;
this.idPrefix = 100;
}
async load() {
const modelBuilderExt = await this.viewer.loadExtension('Autodesk.Viewing.SceneBuilder');
const modelBuilder = await modelBuilderExt.addNewModel({
conserveMemory: false,
modelNameOverride: 'Room Tags'
});
this.modelBuilder = modelBuilder;
if (!this.viewer.isLoadDone()) {
this.viewer.addEventListener(
Autodesk.Viewing.GEOMETRY_LOADED_EVENT,
() => this.createRoomTags(),
{ once: true }
);
} else {
this.createRoomTags();
}
return true;
}
unload() {
this.viewer.impl.unloadModel(this.modelBuilder.model);
return true;
}
pxToMm(val) {
return val / 3.7795275591;
}
mmToFt(val) {
return val / 304.8;
}
createLabel(params) {
const text = params.text;
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.fillStyle = 'yellow';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const fontSize = params.fontSize || 512;
const fontName = 'serif';
let offset = 2;
//Usage:
let m = new TextMeasurer();
let textDimensions = m.measureText(text, fontName, `${fontSize}px`);
canvas.height = textDimensions.bbHeight - (fontSize / 32 + 2) * offset;
canvas.width = textDimensions.bbWidth + offset + 3 * offset;
ctx.textBaseline = 'top';
ctx.fillStyle = '#000';
ctx.textAlign = 'left';
ctx.font = `${fontSize}px ${fontName}`;
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
ctx.fillStyle = '#000';
ctx.fillText(text, offset, offset + (fontSize / 32 + 3) * offset);
ctx.strokeRect(0, 0, textDimensions.bbWidth + offset * 2, canvas.height);
const labelBlobUrl = canvas.toDataURL();
//console.log(labelBlobUrl);
const image = new Image();
const texture = new THREE.Texture();
texture.image = image;
image.src = labelBlobUrl;
image.onload = function () {
texture.needsUpdate = true;
};
const planeWidth = this.mmToFt(this.pxToMm(canvas.width));
const planeHeight = this.mmToFt(this.pxToMm(canvas.height));
let planeGeo = new THREE.PlaneBufferGeometry(planeWidth, planeHeight);
let plane = new THREE.Mesh(planeGeo, new THREE.MeshPhongMaterial({ map: texture, side: THREE.DoubleSide, opacity: 0.8, transparent: true }));
plane.matrix = new THREE.Matrix4().compose(
params.position,
new THREE.Quaternion(0, 0, 0, 1),
new THREE.Vector3(1, 1, 1)
);
plane.dbId = this.idPrefix++;
this.modelBuilder.addMesh(plane);
}
async createRoomTags() {
const getRoomDbIdsAsync = () => {
return new Promise((resolve, reject) => {
this.viewer.search(
'Revit Rooms',
(dbIds) => resolve(dbIds),
(error) => reject(error),
['Category'],
{ searchHidden: true }
);
});
};
const getPropertiesAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
model.getProperties2(
dbId,
(result) => resolve(result),
(error) => reject(error)
);
});
};
const getBoxAsync = (dbId, model) => {
return new Promise((resolve, reject) => {
const tree = model.getInstanceTree();
const frags = model.getFragmentList();
tree.enumNodeFragments(dbId, function (fragId) {
let bounds = new THREE.Box3();
frags.getWorldBounds(fragId, bounds);
return resolve(bounds);
}, true);
});
};
const getRoomName = (dbId, model) => {
const tree = model.getInstanceTree();
return tree.getNodeName(dbId);
};
try {
const roomDbIds = await getRoomDbIdsAsync();
if (!roomDbIds || roomDbIds.length <= 0) {
throw new Error('No Rooms found in current model');
}
const model = this.viewer.model;
const currentViewableId = this.viewer.model?.getDocumentNode().data.viewableID;
const firstRoomProps = await getPropertiesAsync(roomDbIds[0], this.viewer.model);
const possibleViewableIds = firstRoomProps.properties.filter(prop => prop.attributeName === 'viewable_in').map(prop => prop.displayValue);
const masterViews = this.viewer.model?.getDocumentNode().getMasterViews();
const masterViewIds = masterViews?.map(v => v.data.viewableID);
if (!masterViewIds.includes(currentViewableId) || !possibleViewableIds.includes(currentViewableId)) {
throw new Error('Current view does not contain any Rooms');
}
for (let i = roomDbIds.length - 1; i >= 0; i--) {
const dbId = roomDbIds[i];
const box = await getBoxAsync(dbId, model);
const name = getRoomName(dbId, model);
const center = box.center();
const pos = new THREE.Vector3(
center.x,
center.y,
box.min.z + this.mmToFt(10)
);
this.createLabel({
text: name.replace(/ *\[[^)]*\] */g, ""),
position: pos,
fontSize: 512 // in pixel
});
}
// uncomment to prevent selection on tags
// const dbIds = this.modelBuilder.model.getFragmentList().fragments.fragId2dbId;
// const model = this.modelBuilder.model;
// this.viewer.lockSelection(dbIds, true, model);
} catch (ex) {
console.warn(`[AecRoomTagsExtension]: ${ex}`);
}
}
}
Autodesk.Viewing.theExtensionManager.registerExtension('Autodesk.ADN.AecRoomTagsExtension', AecRoomTagsExtension);
Here are the dmeo snapshots:
Related
I am drawing a real-time chart using React html canvas. The chart flashes when data is updated. is there any solution
If you run the code below, the chart graph will be updated while flashing. Is there any way to get rid of the glitter? When the data changes, the value of the child changes, so it seems to be my rendering problem, but I don't know what format to change. Any help please import CanvasLine from '#components/CanvasLine'; import { memo, useContext, useEffect, useState } from 'react'; const CHART_DUMMY_DATA2 = (): Array<number> => { const date = new Date(); date.setSeconds(date.getSeconds() - 249); return [...new Array(249)].map((_, _idx) => { return 0; }); }; const CanvasLineChartDemo = () => { const [chartHRArray, setChartHRArray] = useState(CHART_DUMMY_DATA2); const [chartSP02Array, setChartSP02Array] = useState(CHART_DUMMY_DATA2); const [chartRESPArray, setChartRESPArray] = useState(CHART_DUMMY_DATA2); const returnRandomFloat = (min: number, max: number) => { return (Math.random() * (max - min) + min).toFixed(2); }; const validate = () => { setChartHRArray((prevState: any) => [...prevState, returnRandomFloat(-100, 100)].slice(1), ); setChartSP02Array((prevState: any) => [...prevState, returnRandomFloat(-100, 100)].slice(1), ); setChartRESPArray((prevState: any) => [...prevState, returnRandomFloat(-100, 100)].slice(1), ); }; useEffect(() => { const interval = setInterval(() => { validate(); }, 100); return () => { clearInterval(interval); }; }, [connectStatus]); return ( <> <div style={{ width: '700px', background: '#000', marginRight: 10 }} > vdrDevice {vdrDevice} <CanvasLine height={50} canvasData={chartHRArray} /> <CanvasLine height={50} canvasData={chartSP02Array} /> <CanvasLine height={50} canvasData={chartRESPArray} /> </div> </> ); }; export default memo(CanvasLineChartDemo); import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react'; type CanvasType = { yMin?: number; yMax?: number; height: number; lineColor: string; canvasData: number[]; }; const CanvasLine = ({ yMax, yMin, height, lineColor, canvasData, }: CanvasType) => { const canvasRef = useRef<HTMLCanvasElement>(null); let CanvasContext: CanvasRenderingContext2D | null = null; // Array Max number const getYMax = (data: number[]) => { return Math.max.apply(null, data); }; // Array Min number const getYMin = (data: number[]) => { return Math.min.apply(null, data); }; const calculateY = ( canvasData: number[], index: number, height: number, yMaxNum: number, yMinNum: number, ) => { const xAxis = (yMaxNum / (yMaxNum - yMinNum)) * height; const yScale = -height / (yMaxNum - yMinNum); const valueRatio = canvasData[index] * yScale + xAxis; // 마이너스영역 및 플러스 영역 return valueRatio; }; const createCanvas = () => { const canvas = canvasRef.current; if (canvas) { canvas.width = canvas.offsetWidth; canvas.height = canvas.offsetHeight; canvas.style.width = '100%'; canvas.style.height = `${height}px`; } }; useEffect(() => { createCanvas(); }, []); useEffect(() => { const canvas = canvasRef.current; const yMaxNum = yMax ?? getYMax(canvasData); const yMinNum = yMin ?? getYMin(canvasData); if (canvas) { let xStep = canvas.width / canvasData.length, xAxis = xStep, xStart = 0, yAxis = calculateY( canvasData, 0, canvas.height, yMinNum, yMaxNum, ); CanvasContext = canvas.getContext('2d'); if (!canvas.hasChildNodes()) { CanvasContext?.clearRect(0, 0, canvas.width, canvas.height); } else { CanvasContext?.clearRect(0, 0, canvas.width, canvas.height); } const canvasDraw = (canvas: HTMLCanvasElement) => { if (CanvasContext) { CanvasContext?.clearRect(0, 0, canvas.width, canvas.height); CanvasContext.lineWidth = 1.5; CanvasContext.lineJoin = 'miter'; CanvasContext.beginPath(); CanvasContext.moveTo(xStart, yAxis); CanvasContext.lineCap = 'round'; CanvasContext.strokeStyle = lineColor; CanvasContext.fillStyle = lineColor; for (let i = 1; i < canvasData.length; i++) { yAxis = calculateY( canvasData, i, canvas.height, yMinNum, yMaxNum, ); CanvasContext.lineTo(xAxis, yAxis); CanvasContext.moveTo(xAxis, yAxis); xAxis = xAxis + xStep; } CanvasContext.stroke(); CanvasContext.fill(); } }; canvasDraw(canvas); } }, [canvasData]); return ( <> <canvas id="canvas" style={{ width: '100%' }} ref={canvasRef} /> </> ); }; export default React.memo(CanvasLine); CanvasLine.defaultProps = { height: 50, lineColor: '#2C82C9', canvasData: [], }; You can see that the chart line flickers finely. [1]: https://i.stack.imgur.com/XvtTd.gif If you look at the gif, you can find the sparkling phenomenon. Please suggest a way to solve the problem. Thanks in advance. `
Why when hovering sprite on DataVisualization the Card Position not correctly in center?
I try to perform a Sprite using DataViz Extension. However the position of the Info Card is not correct when I zoom it. Here is my Code onSpriteHovering(event) { const hovered = event.hovering; if (hovered) { const firstDbId = event.dbId; const model = event.target.model; const instanceTree = model.getData().instanceTree; const fragList = model.getFragmentList(); let bounds = new THREE.Box3(); instanceTree.enumNodeFragments( firstDbId, (fragId) => { let box = new THREE.Box3(); fragList.getWorldBounds(fragId, box); bounds.union(box); }, true ); this.posModel = bounds.getCenter(); const dataSprite = { id: firstDbId, }; this.infoChart.showIcon(firstDbId, dataSprite, this.posModel); // this.infoChart.makeChart(); } else { this.infoChart.clearInfoChart(); } } Updating Position : updateIcons() { if (this.infoChart && this.posModel) { const pos = this.viewer.worldToClient(this.posModel); this.infoChart.style.left = `${Math.floor( 100 + pos.x - this.infoChart.offsetWidth / 2 )}px`; this.infoChart.style.top = `${Math.floor( 100 + pos.y - this.infoChart.offsetWidth / 2 )}px`; } }
Rooms with Forge
My goal is to see the Revit rooms in the Forge viewer. The application is in .NET Core. I have tried implementing GenerateMasterViews. The code I am using to achieve this is: [Route("api/forge/modelderivative/jobs")] public async Task<dynamic> TranslateObject([FromBody]TranslateObjectModel objModel) { dynamic oauth = await OAuthController.GetInternalAsync(); // prepare the payload var advOutputPayload = new JobSvf2OutputPayloadAdvanced(); advOutputPayload.GenerateMasterViews = true; List<JobPayloadItem> outputs = new List<JobPayloadItem>() { new JobPayloadItem( JobPayloadItem.TypeEnum.Svf2, new List<JobPayloadItem.ViewsEnum>() { JobPayloadItem.ViewsEnum._2d, JobPayloadItem.ViewsEnum._3d }, advOutputPayload ) }; JobPayload job; job = new JobPayload(new JobPayloadInput(objModel.objectName), new JobPayloadOutput(outputs)); // start the translation DerivativesApi derivative = new DerivativesApi(); derivative.Configuration.AccessToken = oauth.access_token; dynamic jobPosted = await derivative.TranslateAsync(job); return jobPosted; } Autodesk.Viewing.Initializer(options, () => { viewer = new Autodesk.Viewing.GuiViewer3D(document.getElementById('forgeViewer')); viewer.start(); var documentId = 'urn:' + urn; Autodesk.Viewing.Document.load(documentId, onDocumentLoadSuccess, onDocumentLoadFailure); }); } function onDocumentLoadSuccess(doc) { var viewables = doc.getRoot().getDefaultGeometry(); viewer.loadDocumentNode(doc, viewables).then(i => { // documented loaded, any action? }); } But I can't get it to work. I have looked for information, but this url: https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option2/ and this url: https://forge.autodesk.com/en/docs/model-derivative/v2/tutorials/prep-roominfo4viewer/option1/ they don't work and I couldn't see how to do it.
To check if the object is in the room, we can do the following: Get bounds for each room and object getBoundingBox(dbId, model) { const it = model.getInstanceTree(); const fragList = model.getFragmentList(); let bounds = new THREE.Box3(); it.enumNodeFragments(dbId, (fragId) => { let box = new THREE.Box3(); fragList.getWorldBounds(fragId, box); bounds.union(box); }, true); return bounds; } Iterate rooms and objects and use containsBox or containsPoint to check if their bounding box has intersection. If you want to do an acute collision check, you can take advantage of the ThreeCSG.js to do geometry intersection. Here is a blog post demonstrating how to integrate ThreeCSG.js with Forge Viewer. https://forge.autodesk.com/blog/boolean-operations-forge-viewer Note. This process would reduce the viewer performance since JavaScript is running on a single thread on the Web Browser, so you may use some technologies like the web worker to do the complex calculations on a separate thread. Update: Here is a working sample extension demonstrating the above idea: ///////////////////////////////////////////////////////////////////// // Copyright (c) Autodesk, Inc. All rights reserved // Written by Forge Partner Development // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. ///////////////////////////////////////////////////////////////////// (function () { const Utility = { /** * Rest an object * #param {Object} obj An object to be reset. * ref: https://stackoverflow.com/a/24090180 */ resetObject: function (obj) { for (let key in Object.getOwnPropertyNames(obj)) { if (!obj.hasOwnProperty(key)) continue; let val = obj[key]; switch (typeof val) { case 'string': obj[key] = ''; break; case 'number': obj[key] = 0; break; case 'boolean': obj[key] = false; break; case 'object': if (val === null) break; if (val instanceof Array) { while (obj[key].length > 0) { obj[key].pop(); } break; } val = {}; //Or recursively clear the sub-object //resetObject(val); break; } } } }; /** * A Forge Viewer extension for loading and rendering Revit Grids by AEC Model Data * #class */ class RoomLocatorExtension extends Autodesk.Viewing.Extension { constructor(viewer, options) { super(viewer, options); this.roomCategoryName = options.roomCategoryName || 'Revit Rooms';//'Revit Habitaciones' this.onContextMenu = this.onContextMenu.bind(this); } onContextMenu(menu, status) { if (status.hasSelected) { menu.push({ title: 'Find room', target: async () => { let selSet = this.viewer.getSelection(); this.viewer.clearSelection(); const roomDbIds = await this.locateElementByRoom(selSet[0]); if (!roomDbIds || roomDbIds.length <= 0) return; this.viewer.select(roomDbIds); } }); } } async getPropertiesAsync(dbId, model) { return new Promise((resolve, reject) => { model.getProperties2( dbId, (result) => resolve(result), (error) => reject(error) ); }); } async getElementsByCategoryAsync(category) { return new Promise((resolve, reject) => { this.viewer.search( category, (dbIds) => resolve(dbIds), (error) => reject(error), ['Category'], { searchHidden: true } ); }); } async getRoomDbIds() { try { const roomDbIds = await this.getElementsByCategoryAsync(this.roomCategoryName); if (!roomDbIds || roomDbIds.length <= 0) { throw new Error('No Rooms found in current model'); } return roomDbIds; } catch (ex) { console.warn(`[RoomLocatorExtension]: ${ex}`); throw new Error('No room found'); } } getBoundingBox(dbId, model) { const it = model.getInstanceTree(); const fragList = model.getFragmentList(); let bounds = new THREE.Box3(); it.enumNodeFragments(dbId, (fragId) => { let box = new THREE.Box3(); fragList.getWorldBounds(fragId, box); bounds.union(box); }, true); return bounds; } getLeafFragIds(model, leafId) { const instanceTree = model.getData().instanceTree; const fragIds = []; instanceTree.enumNodeFragments(leafId, function (fragId) { fragIds.push(fragId); }); return fragIds; } getComponentGeometryInfo(dbId, model) { const viewer = this.viewer; const viewerImpl = viewer.impl; const fragIds = this.getLeafFragIds(model, dbId); let matrixWorld = null; const meshes = fragIds.map((fragId) => { const renderProxy = viewerImpl.getRenderProxy(model, fragId); const geometry = renderProxy.geometry; const attributes = geometry.attributes; const positions = geometry.vb ? geometry.vb : attributes.position.array; const indices = attributes.index.array || geometry.ib; const stride = geometry.vb ? geometry.vbstride : 3; const offsets = geometry.offsets; matrixWorld = matrixWorld || renderProxy.matrixWorld.elements; return { positions, indices, offsets, stride }; }); return { matrixWorld, meshes }; } getComponentGeometry(data, vertexArray) { const offsets = [ { count: data.indices.length, index: 0, start: 0 } ]; for (let oi = 0, ol = offsets.length; oi < ol; ++oi) { let start = offsets[oi].start; let count = offsets[oi].count; let index = offsets[oi].index; for (let i = start, il = start + count; i < il; i += 3) { const a = index + data.indices[i]; const b = index + data.indices[i + 1]; const c = index + data.indices[i + 2]; const vA = new THREE.Vector3(); const vB = new THREE.Vector3(); const vC = new THREE.Vector3(); vA.fromArray(data.positions, a * data.stride); vB.fromArray(data.positions, b * data.stride); vC.fromArray(data.positions, c * data.stride); vertexArray.push(vA); vertexArray.push(vB); vertexArray.push(vC); } } } buildComponentMesh(data) { const vertexArray = []; for (let idx = 0; idx < data.nbMeshes; ++idx) { const meshData = { positions: data['positions' + idx], indices: data['indices' + idx], stride: data['stride' + idx] } this.getComponentGeometry(meshData, vertexArray); } const geometry = new THREE.Geometry(); for (let i = 0; i < vertexArray.length; i += 3) { geometry.vertices.push(vertexArray[i]); geometry.vertices.push(vertexArray[i + 1]); geometry.vertices.push(vertexArray[i + 2]); const face = new THREE.Face3(i, i + 1, i + 2); geometry.faces.push(face); } const matrixWorld = new THREE.Matrix4(); matrixWorld.fromArray(data.matrixWorld); const mesh = new THREE.Mesh(geometry); mesh.applyMatrix(matrixWorld); mesh.boundingBox = data.boundingBox; mesh.bsp = new ThreeBSP(mesh) mesh.dbId = data.dbId; return mesh; } buildCsgMesh(dbId, model) { const geometry = this.getComponentGeometryInfo(dbId, model); const data = { boundingBox: this.getBoundingBox(dbId, model), matrixWorld: geometry.matrixWorld, nbMeshes: geometry.meshes.length, dbId }; geometry.meshes.forEach((mesh, idx) => { data['positions' + idx] = mesh.positions; data['indices' + idx] = mesh.indices; data['stride' + idx] = mesh.stride; }); return this.buildComponentMesh(data); } async buildBBoxes() { try { const model = this.viewer.model; const roomBBoxes = {}; const roomDbIds = await this.getRoomDbIds(); for (let i = 0; i < roomDbIds.length; i++) { let dbId = roomDbIds[i]; let bbox = await this.getBoundingBox(dbId, model); roomBBoxes[dbId] = bbox; } this.cachedBBoxes['rooms'] = roomBBoxes; } catch (ex) { console.warn(`[RoomLocatorExtension]: ${ex}`); throw new Error('Cannot build bounding boxes from rooms'); } } async locateElementByRoom(dbId) { let bbox = await this.getBoundingBox(dbId, this.viewer.model); const roomDbIds = Object.keys(this.cachedBBoxes['rooms']); const roomBoxes = Object.values(this.cachedBBoxes['rooms']); // Coarse Phase Collision const coarseResult = []; for (let i = 0; i < roomDbIds.length; i++) { let roomDbId = roomDbIds[i]; let roomBox = roomBoxes[i]; if (roomBox.containsBox(bbox)) { coarseResult.push(parseInt(roomDbId)); } else { if (roomBox.containsPoint(bbox.min) || roomBox.containsPoint(bbox.max) || roomBox.containsPoint(bbox.center())) { coarseResult.push(parseInt(roomDbId)); } } } // Fine Phase Collision const fineResult = []; let elementCsgMesh = this.buildCsgMesh(dbId, this.viewer.model); for (let i = 0; i < coarseResult.length; i++) { let roomDbId = coarseResult[i]; let roomCsgMesh = this.buildCsgMesh(roomDbId, this.viewer.model); let result = elementCsgMesh.bsp.intersect(roomCsgMesh.bsp); if (result.tree.polygons.length <= 0) { result = roomCsgMesh.bsp.intersect(elementCsgMesh.bsp); // if (!this.viewer.overlays.hasScene('csg')) // this.viewer.overlays.addScene('csg'); // else // this.viewer.overlays.clearScene('csg'); // let mat = new THREE.MeshBasicMaterial({ color: 'red' }) // let mesh = result.toMesh(mat); // this.viewer.overlays.addMesh(mesh, 'csg') if (result.tree.polygons.length <= 0) continue; } fineResult.push(roomDbId); } return fineResult; } async load() { await Autodesk.Viewing.Private.theResourceLoader.loadScript( 'https://cdn.jsdelivr.net/gh/Wilt/ThreeCSG#develop/ThreeCSG.js', 'ThreeBSP' ); if (!window.ThreeBSP) throw new Error('Cannot load ThreeCSG.js, please download a copy from https://github.com/Wilt/ThreeCSG/blob/develop/ThreeCSG.js') await this.viewer.waitForLoadDone(); this.cachedBBoxes = {}; await this.buildBBoxes(); this.viewer.registerContextMenuCallback( 'RoomLocatorExtension', this.onContextMenu ); return true; } unload() { Utility.resetObject(this.cachedBBoxes); this.viewer.unregisterContextMenuCallback( 'RoomLocatorExtension', this.onContextMenu ); return true; } } Autodesk.Viewing.theExtensionManager.registerExtension('RoomLocatorExtension', RoomLocatorExtension); })(); Snapshots:
How can I assign dynamic data from service via subscribe to doughnut chart in angular?
how can I dynamically assign data to doughnutchartdata. how can I link data coming from service file via subscribe method on ngOnIt to doughnutchartdata. so that I can link multisetdata to one coming dynamically without hardcoding it Can I also show that data in center dynamically by linking to ctx.fillText my .ts file public doughnutChartLabels: Label[] = ['Download Sales', 'In-Store Sales', 'Mail-Order Sales']; public doughnutChartData: MultiDataSet = [ ]; radius = length * Math.SQRT2 / 2; colors= [ { backgroundColor: [ '#E6B01C', '#1454A3', '#22C3BD', 'yellow' ] } ]; public doughnutChartType: ChartType = 'doughnut'; public doughnutChartPlugins: PluginServiceGlobalRegistrationAndOptions[] = [{ beforeDraw(chart) { const ctx = chart.ctx; const txt = '26'; //Get options from the center object in options const sidePadding = 60; const sidePaddingCalculated = (sidePadding / 100) * (this.radius * 2) ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; const centerX = ((chart.chartArea.left + chart.chartArea.right) / 2); const centerY = ((chart.chartArea.top + chart.chartArea.bottom) / 2); //Get the width of the string and also the width of the element minus 10 to give it 5px side padding const stringWidth = ctx.measureText(txt).width; const elementWidth = (this.radius * 2) - sidePaddingCalculated; // Find out how much the font can grow in width. const widthRatio = elementWidth / stringWidth; const newFontSize = Math.floor(30 * widthRatio); const elementHeight = (this.radius * 2); // Pick a new font size so it will not be larger than the height of label. // const fontSizeToUse = Math.min(newFontSize, elementHeight); // ctx.font = fontSizeToUse + 'px Arial'; ctx.fillStyle = 'blue'; // Draw text in center ctx.fillText('26', centerX, centerY); } }]; constructor(private roleService:RoleDashboardService) { } ngOnInit() { this.roleService.getWidget).subscribe((data)=>{ console.log(data) this.doughnutChartData console.log(this.doughnutChartData) }) } // events public chartClicked({ event, active }: { event: MouseEvent, active: {}[] }): void { console.log(event, active); } public chartHovered({ event, active }: { event: MouseEvent, active: {}[] }): void { console.log(event, active); }
Depends on what backend you use. For example, if u obtain data through php from mysql you would use the below: array1=[]; array2=[]; //api call on backend this.http.get('http://localhost/angular9/chartData.php').subscribe(data => { //create array and push the data this.array1.push(data); //assign this array to another arraya and map it this.array2 = this.array1[0].map(function(item, keys) { var mixarrayy = Object.keys(item).concat(Object.values(item)); return mixarrayy; });
why i can't get full image size from convertToDataURLviaCanvas method in ionic 3?
I want original image size in offline mode also as shown in online mode in image 2. My problem is that when i gone in app without internet connection, i loss full quality and size of images, because of i am using convertToDataURLviaCanvas(). so, please give here solution as soon as possible. My code is: I am using this function to convert all images: convertToDataURLviaCanvas(url, outputFormat) { return new Promise((resolve, reject) => { let img = new Image(); img.crossOrigin = 'Anonymous'; img.onload = function () { let canvas = <HTMLCanvasElement>document.createElement('CANVAS'), ctx = canvas.getContext('2d'), dataURL; canvas.height = 1000; canvas.width = 1000; ctx.drawImage(img, 0, 0); dataURL = canvas.toDataURL(); //callback(dataURL); canvas = null; resolve(dataURL); }; img.src = url; }); } getTravelAdviceData() { if(this.restProvider.getNetworkType() == 'none') { // this.onlineGrid = false; // this.offlineGrid = true; this.storage.get('companyLogoOffline').then((data) => { // Do something with latitude value // console.log("offline data", data); this.getcompanyLogo = data; }); this.storage.get('travelTips64Image').then((data) => { // Do something with latitude value // console.log("offline data", data); this.adviceArray = data; // console.log("offline this.adviceArray", this.adviceArray); }); } else { // this.offlineGrid = false; // this.onlineGrid = true; this.restProvider.getTravelAdvice() .then(data => { let serviceData : any = data['consejosviaje']; // this.adviceArray = serviceData; let base64Image; for (let i in serviceData) { this.imagen = serviceData[i].imagen; this.convertToDataURLviaCanvas(this.imagen, "image/jpeg").then(base64 => { base64Image = base64; this.texto = serviceData[i].texto; this.adviceArrays64.push({'texto': this.texto, 'imagen': base64Image}); this.storage.set("travelTips64Image", this.adviceArrays64); this.adviceArray = this.adviceArrays64; }); } }); } }