@jscad/x3d-serializer 2.4.3 → 2.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +23 -0
- package/README.md +4 -1
- package/package.json +3 -3
- package/src/index.js +62 -24
- package/tests/geom2ToX3D.test.js +6 -7
- package/tests/geom3ToX3D.test.js +14 -8
- package/tests/path2ToX3D.test.js +3 -5
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,29 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [2.4.5](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/x3d-serializer@2.4.4...@jscad/x3d-serializer@2.4.5) (2023-06-27)
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### Bug Fixes
|
|
10
|
+
|
|
11
|
+
* **x3d-serializer:** added specular highlights and conversion of shape names ([ba26567](https://github.com/jscad/OpenJSCAD.org/commit/ba26567f207a3e7529778ec89436956cf03592bf))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
## [2.4.4](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/x3d-serializer@2.4.3...@jscad/x3d-serializer@2.4.4) (2023-04-30)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
### Bug Fixes
|
|
21
|
+
|
|
22
|
+
* **x3d-serializer:** corrected color and transparency conversions ([cc99548](https://github.com/jscad/OpenJSCAD.org/commit/cc9954853c30c6a82fcd26cda686e7f51f9fa304))
|
|
23
|
+
* **x3d-serializer:** corrected colors, orientation of scene to Y up, and added new option for smoothing ([3444e2d](https://github.com/jscad/OpenJSCAD.org/commit/3444e2d462446694e62da71b130dfb4e56e92f82))
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
6
29
|
## [2.4.3](https://github.com/jscad/OpenJSCAD.org/compare/@jscad/x3d-serializer@2.4.2...@jscad/x3d-serializer@2.4.3) (2022-11-26)
|
|
7
30
|
|
|
8
31
|
**Note:** Version bump only for package @jscad/x3d-serializer
|
package/README.md
CHANGED
|
@@ -27,7 +27,10 @@ The serialization of the following geometries are possible.
|
|
|
27
27
|
- serialization of 2D geometries (geom2) to X3D Polyline2D
|
|
28
28
|
- serialization of 2D paths (path2) to X3D Polyline2D
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
The id attribute is used as the DEF name for the generated X3D shape when found on a geometry.
|
|
31
|
+
Material (color) is added to X3D shapes when found on a geometry.
|
|
32
|
+
|
|
33
|
+
All shapes are wrapped in a rotation transform to align the positive Z direction of JSCAD with the vertical up direction in X3D.
|
|
31
34
|
|
|
32
35
|
## Table of Contents
|
|
33
36
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jscad/x3d-serializer",
|
|
3
|
-
"version": "2.4.
|
|
3
|
+
"version": "2.4.5",
|
|
4
4
|
"description": "X3D Serializer for JSCAD",
|
|
5
5
|
"homepage": "https://openjscad.xyz/",
|
|
6
6
|
"repository": "https://github.com/jscad/OpenJSCAD.org",
|
|
@@ -33,12 +33,12 @@
|
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"@jscad/array-utils": "2.1.4",
|
|
36
|
-
"@jscad/modeling": "2.
|
|
36
|
+
"@jscad/modeling": "2.12.0",
|
|
37
37
|
"onml": "1.3.0"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"ava": "3.15.0",
|
|
41
41
|
"nyc": "15.1.0"
|
|
42
42
|
},
|
|
43
|
-
"gitHead": "
|
|
43
|
+
"gitHead": "e269f212db5a00cda740d2f7ad3e5206d1eb839f"
|
|
44
44
|
}
|
package/src/index.js
CHANGED
|
@@ -34,7 +34,7 @@ Notes:
|
|
|
34
34
|
const { geometries, modifiers } = require('@jscad/modeling')
|
|
35
35
|
const { geom2, geom3, path2, poly2, poly3 } = geometries
|
|
36
36
|
|
|
37
|
-
const { flatten
|
|
37
|
+
const { flatten } = require('@jscad/array-utils')
|
|
38
38
|
|
|
39
39
|
const stringify = require('onml/lib/stringify')
|
|
40
40
|
|
|
@@ -43,11 +43,15 @@ const stringify = require('onml/lib/stringify')
|
|
|
43
43
|
// https://x3dgraphics.com/examples/X3dForWebAuthors/Chapter13GeometryTrianglesQuadrilaterals/
|
|
44
44
|
|
|
45
45
|
const mimeType = 'model/x3d+xml'
|
|
46
|
+
const defNames = new Map()
|
|
46
47
|
|
|
47
48
|
/**
|
|
48
49
|
* Serialize the give objects to X3D elements (XML).
|
|
49
50
|
* @param {Object} options - options for serialization, REQUIRED
|
|
50
51
|
* @param {Array} [options.color=[0,0,1,1]] - default color for objects
|
|
52
|
+
* @param {Number} [options.shininess=8/256] - x3d shininess for specular highlights
|
|
53
|
+
* @param {Boolean} [options.smooth=false] - use averaged vertex normals
|
|
54
|
+
* @param {Number} [options.decimals=1000] - multiplier before rounding to limit precision
|
|
51
55
|
* @param {Boolean} [options.metadata=true] - add metadata to 3MF contents, such at CreationDate
|
|
52
56
|
* @param {String} [options.unit='millimeter'] - unit of design; millimeter, inch, feet, meter or micrometer
|
|
53
57
|
* @param {Function} [options.statusCallback] - call back function for progress ({ progress: 0-100 })
|
|
@@ -61,6 +65,8 @@ const mimeType = 'model/x3d+xml'
|
|
|
61
65
|
const serialize = (options, ...objects) => {
|
|
62
66
|
const defaults = {
|
|
63
67
|
color: [0, 0, 1, 1.0], // default colorRGBA specification
|
|
68
|
+
shininess: 8 / 256,
|
|
69
|
+
smooth: false,
|
|
64
70
|
decimals: 1000,
|
|
65
71
|
metadata: true,
|
|
66
72
|
unit: 'millimeter', // millimeter, inch, feet, meter or micrometer
|
|
@@ -80,20 +86,20 @@ const serialize = (options, ...objects) => {
|
|
|
80
86
|
let body = ['X3D',
|
|
81
87
|
{
|
|
82
88
|
profile: 'Interchange',
|
|
83
|
-
version: '
|
|
89
|
+
version: '3.3',
|
|
84
90
|
'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
85
|
-
'xsd:noNamespaceSchemaLocation': 'http://www.web3d.org/specifications/x3d-
|
|
91
|
+
'xsd:noNamespaceSchemaLocation': 'http://www.web3d.org/specifications/x3d-3.3.xsd'
|
|
86
92
|
}
|
|
87
93
|
]
|
|
88
94
|
if (options.metadata) {
|
|
89
95
|
body.push(['head', {},
|
|
90
96
|
['meta', { name: 'creator', content: 'Created by JSCAD' }],
|
|
91
97
|
['meta', { name: 'reference', content: 'https://www.openjscad.xyz' }],
|
|
92
|
-
['meta', { name: 'created', content: new Date().toISOString()}]
|
|
98
|
+
['meta', { name: 'created', content: new Date().toISOString() }]
|
|
93
99
|
])
|
|
94
100
|
} else {
|
|
95
101
|
body.push(['head', {},
|
|
96
|
-
['meta', { name: 'creator', content: 'Created by JSCAD' }]
|
|
102
|
+
['meta', { name: 'creator', content: 'Created by JSCAD' }]
|
|
97
103
|
])
|
|
98
104
|
}
|
|
99
105
|
body = body.concat(convertObjects(objects, options))
|
|
@@ -108,7 +114,6 @@ ${stringify(body, 2)}`
|
|
|
108
114
|
}
|
|
109
115
|
|
|
110
116
|
const convertObjects = (objects, options) => {
|
|
111
|
-
let scene = ['Scene', {}]
|
|
112
117
|
const shapes = []
|
|
113
118
|
objects.forEach((object, i) => {
|
|
114
119
|
options.statusCallback && options.statusCallback({ progress: 100 * i / objects.length })
|
|
@@ -128,7 +133,8 @@ const convertObjects = (objects, options) => {
|
|
|
128
133
|
shapes.push(convertPath2(object, options))
|
|
129
134
|
}
|
|
130
135
|
})
|
|
131
|
-
|
|
136
|
+
const transform = ['Transform', { rotation: '1 0 0 -1.5708' }, ...shapes]
|
|
137
|
+
const scene = ['Scene', {}, transform]
|
|
132
138
|
return [scene]
|
|
133
139
|
}
|
|
134
140
|
|
|
@@ -138,9 +144,9 @@ const convertObjects = (objects, options) => {
|
|
|
138
144
|
const convertPath2 = (object, options) => {
|
|
139
145
|
const points = path2.toPoints(object).slice()
|
|
140
146
|
if (points.length > 1 && object.isClosed) points.push(points[0])
|
|
141
|
-
shape = ['Shape',
|
|
147
|
+
const shape = ['Shape', shapeAttributes(object), convertPolyline2D(poly2.create(points), options)]
|
|
142
148
|
if (object.color) {
|
|
143
|
-
shape.push(convertAppearance(object, options))
|
|
149
|
+
shape.push(convertAppearance(object, 'emissiveColor', options))
|
|
144
150
|
}
|
|
145
151
|
return shape
|
|
146
152
|
}
|
|
@@ -153,37 +159,67 @@ const convertGeom2 = (object, options) => {
|
|
|
153
159
|
const group = ['Group', {}]
|
|
154
160
|
outlines.forEach((outline) => {
|
|
155
161
|
if (outline.length > 1) outline.push(outline[0]) // close the outline for conversion
|
|
156
|
-
const shape = ['Shape',
|
|
162
|
+
const shape = ['Shape', shapeAttributes(object), convertPolyline2D(poly2.create(outline), options)]
|
|
157
163
|
if (object.color) {
|
|
158
|
-
shape.push(convertAppearance(object, options))
|
|
164
|
+
shape.push(convertAppearance(object, 'emissiveColor', options))
|
|
159
165
|
}
|
|
160
166
|
group.push(shape)
|
|
161
167
|
})
|
|
162
168
|
return group
|
|
163
169
|
}
|
|
164
170
|
|
|
171
|
+
/*
|
|
172
|
+
* generate attributes for Shape node
|
|
173
|
+
*/
|
|
174
|
+
|
|
175
|
+
const shapeAttributes = (object, attributes = {}) => {
|
|
176
|
+
if (object.id) {
|
|
177
|
+
Object.assign(attributes, { DEF: checkDefName(object.id) })
|
|
178
|
+
}
|
|
179
|
+
return attributes
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const checkDefName = (defName) => {
|
|
183
|
+
const count = defNames.get(defName) || 0
|
|
184
|
+
defNames.set(defName, count + 1)
|
|
185
|
+
if (count > 0) console.warn(`Warning: object.id set as DEF but not unique. ${defName} set ${count + 1} times.`)
|
|
186
|
+
return defName
|
|
187
|
+
}
|
|
188
|
+
|
|
165
189
|
/*
|
|
166
190
|
* Convert the given object (poly2) to X3D source
|
|
167
191
|
*/
|
|
168
192
|
const convertPolyline2D = (object, options) => {
|
|
169
193
|
const lineSegments = object.vertices.map((p) => `${p[0]} ${p[1]}`).join(' ')
|
|
170
|
-
return ['Polyline2D', {lineSegments}]
|
|
194
|
+
return ['Polyline2D', { lineSegments }]
|
|
171
195
|
}
|
|
172
196
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
197
|
+
/*
|
|
198
|
+
* Convert color to Appearance
|
|
199
|
+
*/
|
|
200
|
+
const convertAppearance = (object, colorField, options) => {
|
|
201
|
+
const colorRGB = object.color.slice(0, 3)
|
|
202
|
+
const color = colorRGB.join(' ')
|
|
203
|
+
const transparency = roundToDecimals(1.0 - object.color[3], options)
|
|
204
|
+
const materialFields = { [colorField]: color, transparency }
|
|
205
|
+
if (colorField === 'diffuseColor') {
|
|
206
|
+
Object.assign(
|
|
207
|
+
materialFields,
|
|
208
|
+
{ specularColor: '0.2 0.2 0.2', shininess: options.shininess })
|
|
209
|
+
}
|
|
210
|
+
return ['Appearance', ['Material', materialFields]]
|
|
177
211
|
}
|
|
178
212
|
|
|
179
213
|
/*
|
|
180
214
|
* Convert the given object (geom3) to X3D source
|
|
181
215
|
*/
|
|
182
216
|
const convertGeom3 = (object, options) => {
|
|
183
|
-
const shape = ['Shape',
|
|
217
|
+
const shape = ['Shape', shapeAttributes(object), convertMesh(object, options)]
|
|
218
|
+
let appearance = ['Appearance', {}, ['Material']]
|
|
184
219
|
if (object.color) {
|
|
185
|
-
|
|
220
|
+
appearance = convertAppearance(object, 'diffuseColor', options)
|
|
186
221
|
}
|
|
222
|
+
shape.push(appearance)
|
|
187
223
|
return shape
|
|
188
224
|
}
|
|
189
225
|
|
|
@@ -197,10 +233,10 @@ const convertMesh = (object, options) => {
|
|
|
197
233
|
|
|
198
234
|
const faceset = [
|
|
199
235
|
'IndexedTriangleSet',
|
|
200
|
-
{ ccw: 'true', colorPerVertex: 'false', solid: 'false', index: indexList },
|
|
201
|
-
['Coordinate', { point: pointList }]
|
|
236
|
+
{ ccw: 'true', colorPerVertex: 'false', normalPerVertex: options.smooth, solid: 'false', index: indexList },
|
|
237
|
+
['Coordinate', { point: pointList }]
|
|
202
238
|
]
|
|
203
|
-
if (!
|
|
239
|
+
if (!object.color) {
|
|
204
240
|
faceset.push(['Color', { color: colorList }])
|
|
205
241
|
}
|
|
206
242
|
return faceset
|
|
@@ -236,6 +272,8 @@ const convertToColor = (polygon, options) => {
|
|
|
236
272
|
return `${color[0]} ${color[1]} ${color[2]}`
|
|
237
273
|
}
|
|
238
274
|
|
|
275
|
+
const roundToDecimals = (float, options) => Math.round(float * options.decimals) / options.decimals
|
|
276
|
+
|
|
239
277
|
/*
|
|
240
278
|
* This function converts the given polygons into three lists
|
|
241
279
|
* - indexList : index of each vertex in the triangle (tuples)
|
|
@@ -257,9 +295,9 @@ const polygons2coordinates = (polygons, options) => {
|
|
|
257
295
|
|
|
258
296
|
// add the vertex to the list of points (and index) if not found
|
|
259
297
|
if (!vertexTagToCoordIndexMap.has(id)) {
|
|
260
|
-
const x =
|
|
261
|
-
const y =
|
|
262
|
-
const z =
|
|
298
|
+
const x = roundToDecimals(vertex[0], options)
|
|
299
|
+
const y = roundToDecimals(vertex[1], options)
|
|
300
|
+
const z = roundToDecimals(vertex[2], options)
|
|
263
301
|
pointList.push(`${x} ${y} ${z}`)
|
|
264
302
|
vertexTagToCoordIndexMap.set(id, pointList.length - 1)
|
|
265
303
|
}
|
package/tests/geom2ToX3D.test.js
CHANGED
|
@@ -24,7 +24,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
|
|
|
24
24
|
|
|
25
25
|
const shape2 = primitives.rectangle()
|
|
26
26
|
|
|
27
|
-
results = serialize({metadata: false}, shape2)
|
|
27
|
+
results = serialize({ metadata: false }, shape2)
|
|
28
28
|
t.is(results.length, 1)
|
|
29
29
|
|
|
30
30
|
obs = results[0]
|
|
@@ -41,7 +41,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
|
|
|
41
41
|
|
|
42
42
|
const shape3 = colors.colorize([0, 0, 0], shape2)
|
|
43
43
|
|
|
44
|
-
results = serialize({metadata: false}, shape3)
|
|
44
|
+
results = serialize({ metadata: false }, shape3)
|
|
45
45
|
t.is(results.length, 1)
|
|
46
46
|
|
|
47
47
|
obs = results[0]
|
|
@@ -58,11 +58,10 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
|
|
|
58
58
|
// for color
|
|
59
59
|
t.is(countOf('Appearance', obs), 2)
|
|
60
60
|
t.is(countOf('Material', obs), 1)
|
|
61
|
-
t.is(countOf('diffuseColor', obs),
|
|
61
|
+
t.is(countOf('diffuseColor', obs), 0)
|
|
62
62
|
t.is(countOf('emissiveColor', obs), 1)
|
|
63
63
|
|
|
64
|
-
|
|
65
|
-
results = serialize({metadata: false}, shape2, shape3)
|
|
64
|
+
results = serialize({ metadata: false }, shape2, shape3)
|
|
66
65
|
t.is(results.length, 1)
|
|
67
66
|
|
|
68
67
|
obs = results[0]
|
|
@@ -79,7 +78,7 @@ test('serialize 2D geometry to X3D Polyline2D', (t) => {
|
|
|
79
78
|
// for color
|
|
80
79
|
t.is(countOf('Appearance', obs), 2)
|
|
81
80
|
t.is(countOf('Material', obs), 1)
|
|
82
|
-
t.is(countOf('diffuseColor', obs),
|
|
81
|
+
t.is(countOf('diffuseColor', obs), 0)
|
|
82
|
+
t.is(countOf('specularColor', obs), 0)
|
|
83
83
|
t.is(countOf('emissiveColor', obs), 1)
|
|
84
84
|
})
|
|
85
|
-
|
package/tests/geom3ToX3D.test.js
CHANGED
|
@@ -19,11 +19,11 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {
|
|
|
19
19
|
t.is(countOf('name', obs), 3)
|
|
20
20
|
t.is(countOf('content', obs), 3)
|
|
21
21
|
t.is(countOf('Created by JSCAD', obs), 1)
|
|
22
|
-
t.is(countOf('Scene', obs),
|
|
22
|
+
t.is(countOf('Scene', obs), 2)
|
|
23
23
|
|
|
24
24
|
const geom2 = primitives.cube()
|
|
25
25
|
|
|
26
|
-
results = serializer.serialize({metadata: false}, geom2)
|
|
26
|
+
results = serializer.serialize({ metadata: false }, geom2)
|
|
27
27
|
t.is(results.length, 1)
|
|
28
28
|
|
|
29
29
|
obs = results[0]
|
|
@@ -34,15 +34,17 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {
|
|
|
34
34
|
t.is(countOf('content', obs), 1)
|
|
35
35
|
t.is(countOf('Created by JSCAD', obs), 1)
|
|
36
36
|
t.is(countOf('Scene', obs), 2)
|
|
37
|
+
t.is(countOf('Transform', obs), 2)
|
|
37
38
|
t.is(countOf('Shape', obs), 2)
|
|
39
|
+
t.is(countOf('DEF', obs), 0)
|
|
38
40
|
t.is(countOf('IndexedTriangleSet', obs), 2)
|
|
39
41
|
t.is(countOf('Coordinate', obs), 1)
|
|
40
42
|
t.is(countOf('Color', obs), 1)
|
|
41
43
|
|
|
42
|
-
|
|
43
44
|
const geom3 = colors.colorize([0.5, 1, 0.5, 1.0], transforms.center({ relativeTo: [5, 5, 5] }, primitives.cube()))
|
|
45
|
+
geom2.id = geom3.id = 'g23'
|
|
44
46
|
|
|
45
|
-
results = serializer.serialize({metadata: false}, geom2, geom3)
|
|
47
|
+
results = serializer.serialize({ metadata: false }, geom2, geom3)
|
|
46
48
|
t.is(results.length, 1)
|
|
47
49
|
|
|
48
50
|
obs = results[0]
|
|
@@ -54,11 +56,15 @@ test('serialize 3D geometry to X3D IndexedTriangleSet', (t) => {
|
|
|
54
56
|
t.is(countOf('Created by JSCAD', obs), 1)
|
|
55
57
|
t.is(countOf('Scene', obs), 2)
|
|
56
58
|
t.is(countOf('Shape', obs), 4)
|
|
59
|
+
t.is(countOf('DEF', obs), 2)
|
|
57
60
|
t.is(countOf('IndexedTriangleSet', obs), 4)
|
|
58
61
|
t.is(countOf('Coordinate', obs), 2)
|
|
59
62
|
// for color
|
|
60
|
-
t.is(countOf('Color', obs),
|
|
61
|
-
t.is(countOf('Appearance', obs),
|
|
62
|
-
|
|
63
|
+
t.is(countOf('<Color', obs), 1)
|
|
64
|
+
t.is(countOf('Appearance', obs), 4)
|
|
65
|
+
// for RGB
|
|
66
|
+
t.is(countOf('diffuseColor="0.5 1 0.5"', obs), 1)
|
|
67
|
+
t.is(countOf('specularColor', obs), 1)
|
|
68
|
+
// for facets
|
|
69
|
+
t.is(countOf('normalPerVertex="false"', obs), 2)
|
|
63
70
|
})
|
|
64
|
-
|
package/tests/path2ToX3D.test.js
CHANGED
|
@@ -22,7 +22,7 @@ test('serialize 2D path to X3D Polyline2D', (t) => {
|
|
|
22
22
|
|
|
23
23
|
const path2 = primitives.arc({ center: [5, 5], endAngle: 45, segments: 16 })
|
|
24
24
|
|
|
25
|
-
results = serialize({metadata: false}, path2)
|
|
25
|
+
results = serialize({ metadata: false }, path2)
|
|
26
26
|
t.is(results.length, 1)
|
|
27
27
|
|
|
28
28
|
obs = results[0]
|
|
@@ -35,7 +35,7 @@ test('serialize 2D path to X3D Polyline2D', (t) => {
|
|
|
35
35
|
|
|
36
36
|
const path3 = colors.colorize([0, 0, 0], path2)
|
|
37
37
|
|
|
38
|
-
results = serialize({metadata: false}, path2, path3)
|
|
38
|
+
results = serialize({ metadata: false }, path2, path3)
|
|
39
39
|
t.is(results.length, 1)
|
|
40
40
|
|
|
41
41
|
obs = results[0]
|
|
@@ -48,8 +48,6 @@ test('serialize 2D path to X3D Polyline2D', (t) => {
|
|
|
48
48
|
// and color on path3
|
|
49
49
|
t.is(countOf('Appearance', obs), 2)
|
|
50
50
|
t.is(countOf('Material', obs), 1)
|
|
51
|
-
t.is(countOf('diffuseColor', obs),
|
|
51
|
+
t.is(countOf('diffuseColor', obs), 0)
|
|
52
52
|
t.is(countOf('emissiveColor', obs), 1)
|
|
53
|
-
|
|
54
53
|
})
|
|
55
|
-
|