@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 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
- Material (color) is added to X3D shapes when found on the geometry.
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",
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.11.0",
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": "5899622c5ffc640001da7261d7c06a1223064ccc"
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, toArray } = require('@jscad/array-utils')
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: '4.0',
89
+ version: '3.3',
84
90
  'xmlns:xsd': 'http://www.w3.org/2001/XMLSchema-instance',
85
- 'xsd:noNamespaceSchemaLocation': 'http://www.web3d.org/specifications/x3d-4.0.xsd'
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
- scene = scene.concat(shapes)
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', {}, convertPolyline2D(poly2.create(points), options)]
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', {}, convertPolyline2D(poly2.create(outline), options)]
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
- const convertAppearance = (object, options) => {
174
- const diffuseColor = object.color.join(' ')
175
- const emissiveColor = object.color.join(' ')
176
- return ['Appearance', ['Material', {diffuseColor, emissiveColor}]]
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', {}, convertMesh(object, options)]
217
+ const shape = ['Shape', shapeAttributes(object), convertMesh(object, options)]
218
+ let appearance = ['Appearance', {}, ['Material']]
184
219
  if (object.color) {
185
- shape.push(convertAppearance(object, options))
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 (! object.color) {
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 = Math.round(vertex[0] * options.decimals) / options.decimals
261
- const y = Math.round(vertex[1] * options.decimals) / options.decimals
262
- const z = Math.round(vertex[2] * options.decimals) / options.decimals
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
  }
@@ -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), 1)
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), 1)
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
-
@@ -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), 1)
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), 3)
61
- t.is(countOf('Appearance', obs), 2)
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
-
@@ -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), 1)
51
+ t.is(countOf('diffuseColor', obs), 0)
52
52
  t.is(countOf('emissiveColor', obs), 1)
53
-
54
53
  })
55
-