@2112-lab/central-plant 0.1.49 → 0.1.51

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.
@@ -156,8 +156,30 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
156
156
  value: function createPipePaths(paths, crosscubeTextureSet) {
157
157
  var _this3 = this;
158
158
  var sceneViewer = this.sceneViewer;
159
- var globalSegmentIndex = 0; // Counter for globally unique segment indices
160
159
 
160
+ // Find the highest segment index currently in use (both computed SEGMENT-X and manual Segment-X)
161
+ var maxExistingIndex = -1;
162
+ sceneViewer.scene.traverse(function (obj) {
163
+ if (obj.uuid) {
164
+ // Check for computed segments: SEGMENT-123
165
+ var computedMatch = obj.uuid.match(/^SEGMENT-(\d+)$/);
166
+ if (computedMatch) {
167
+ var index = parseInt(computedMatch[1], 10);
168
+ maxExistingIndex = Math.max(maxExistingIndex, index);
169
+ }
170
+
171
+ // Check for manual segments: Segment-123
172
+ var manualMatch = obj.uuid.match(/^Segment-(\d+)$/);
173
+ if (manualMatch) {
174
+ var _index = parseInt(manualMatch[1], 10);
175
+ maxExistingIndex = Math.max(maxExistingIndex, _index);
176
+ }
177
+ }
178
+ });
179
+
180
+ // Start counter after the highest existing index to prevent UUID conflicts
181
+ var globalSegmentIndex = maxExistingIndex + 1;
182
+ console.log("\uD83D\uDD22 Starting segment index at ".concat(globalSegmentIndex, " (max existing: ").concat(maxExistingIndex, ")"));
161
183
  var pipeRadius = 0.1;
162
184
  var pipeMaterial = this.createPipeMaterial(crosscubeTextureSet);
163
185
  paths.forEach(function (pathData, index) {
@@ -176,6 +198,10 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
176
198
  }
177
199
  });
178
200
 
201
+ // Check if endpoints are component connectors (from pathfinder result)
202
+ var fromIsComponentConnector = pathData.fromObjectType === 'component-connector';
203
+ var toIsComponentConnector = pathData.toObjectType === 'component-connector';
204
+
179
205
  // Create one cylinder per segment (after colinear optimization, this should be minimal segments)
180
206
  for (var j = 0; j < pathPoints.length - 1; j++) {
181
207
  var start = pathPoints[j];
@@ -204,13 +230,19 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
204
230
  // Make pipe segments selectable and add identifying data
205
231
  var segmentId = cylinder.uuid;
206
232
 
233
+ // Determine if this segment should be immutable (first or last 0.5-unit segment connected to component)
234
+ var isFirstSegment = j === 0;
235
+ var isLastSegment = j === pathPoints.length - 2;
236
+ var isImmutable = fromIsComponentConnector && isFirstSegment || toIsComponentConnector && isLastSegment;
237
+
207
238
  // Add userData to make pipe segments selectable and for tooltip display
208
239
  cylinder.userData = {
209
240
  objectType: 'segment',
210
241
  segmentId: segmentId,
211
242
  segmentIndex: globalSegmentIndex,
212
243
  pathFrom: pathData.from,
213
- pathTo: pathData.to
244
+ pathTo: pathData.to,
245
+ immutable: isImmutable // Mark segments connected to component connectors as immutable
214
246
  };
215
247
 
216
248
  // Increment global segment counter
@@ -275,6 +307,8 @@ var PathRenderingManager = /*#__PURE__*/function (_BaseDisposable) {
275
307
  pathFrom: pathData.from,
276
308
  pathTo: pathData.to,
277
309
  angle: (angle * 180 / Math.PI).toFixed(1),
310
+ immutable: true,
311
+ // Mark computed elbows as immutable
278
312
  // Add component data for tooltips
279
313
  component: {
280
314
  type: 'PipeElbow',
@@ -1,4 +1,4 @@
1
- import { inherits as _inherits, createClass as _createClass, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator, objectSpread2 as _objectSpread2 } from '../../../_virtual/_rollupPluginBabelHelpers.js';
1
+ import { inherits as _inherits, createClass as _createClass, superPropGet as _superPropGet, classCallCheck as _classCallCheck, callSuper as _callSuper, asyncToGenerator as _asyncToGenerator, regenerator as _regenerator } from '../../../_virtual/_rollupPluginBabelHelpers.js';
2
2
  import 'three';
3
3
  import { Pathfinder } from '@2112-lab/pathfinder';
4
4
  import { BaseDisposable } from '../../core/baseDisposable.js';
@@ -123,13 +123,6 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
123
123
  connectionsCopy,
124
124
  simplifiedSceneData,
125
125
  pathfindingResult,
126
- intersectionCheck,
127
- _this$sceneViewer,
128
- operationHistoryManager,
129
- lastOp,
130
- undoSuccess,
131
- correctedResult,
132
- _this$sceneViewer2,
133
126
  _args = arguments;
134
127
  return _regenerator().w(function (_context) {
135
128
  while (1) switch (_context.n) {
@@ -164,93 +157,79 @@ var PathfindingManager = /*#__PURE__*/function (_BaseDisposable) {
164
157
  console.log('Rewired connections:', JSON.parse(JSON.stringify(pathfindingResult.rewiredConnections)));
165
158
  console.log("intersectionCheck input:", pathfindingResult.paths);
166
159
 
167
- // Check for path intersections (including manual segments)
168
- intersectionCheck = this.checkPathIntersections(pathfindingResult.paths, 0.2, true);
169
- pathfindingResult.intersections = intersectionCheck;
170
- console.log("intersectionCheck:", intersectionCheck);
171
- console.log("intersectionCheck.hasIntersections:", intersectionCheck.hasIntersections);
172
-
173
- // Handle intersection detection - undo last operation if intersections found
174
- // Skip this if we're already in the middle of an undo operation
175
- if (!(intersectionCheck.hasIntersections && !this.isUndoInProgress)) {
176
- _context.n = 8;
177
- break;
178
- }
179
- console.warn('⚠️ Path intersections detected! Attempting to undo last operation...');
180
-
181
- // Set flag to prevent recursive undo attempts
182
- this.isUndoInProgress = true;
183
-
184
- // Access operationHistoryManager through sceneViewer.managers.operationHistory
185
- operationHistoryManager = (_this$sceneViewer = this.sceneViewer) === null || _this$sceneViewer === void 0 || (_this$sceneViewer = _this$sceneViewer.managers) === null || _this$sceneViewer === void 0 ? void 0 : _this$sceneViewer.operationHistory;
186
- if (!operationHistoryManager) {
187
- _context.n = 6;
188
- break;
189
- }
190
- lastOp = operationHistoryManager.getLastOperation();
191
- if (!lastOp) {
192
- _context.n = 4;
193
- break;
194
- }
195
- console.warn("\u26A0\uFE0F Intersection caused by operation: ".concat(lastOp.operationName), lastOp.params);
196
-
197
- // Attempt to undo the operation FIRST (before removing computed objects)
198
- // This is important because undo needs to find the segment that was moved
199
- undoSuccess = operationHistoryManager.undoLastOperation();
200
- if (!undoSuccess) {
201
- _context.n = 2;
202
- break;
203
- }
204
- console.log('✅ Successfully undid operation that caused intersection');
160
+ // // Check for path intersections (including manual segments)
161
+ // const intersectionCheck = this.checkPathIntersections(pathfindingResult.paths, 0.2, true);
162
+ // pathfindingResult.intersections = intersectionCheck;
163
+
164
+ // console.log("intersectionCheck:", intersectionCheck);
165
+ // console.log("intersectionCheck.hasIntersections:", intersectionCheck.hasIntersections);
166
+
167
+ // // Handle intersection detection - undo last operation if intersections found
168
+ // // Skip this if we're already in the middle of an undo operation
169
+ // if (intersectionCheck.hasIntersections && !this.isUndoInProgress) {
170
+ // console.warn('⚠️ Path intersections detected! Attempting to undo last operation...');
171
+
172
+ // // Set flag to prevent recursive undo attempts
173
+ // this.isUndoInProgress = true;
174
+
175
+ // // Access operationHistoryManager through sceneViewer.managers.operationHistory
176
+ // const operationHistoryManager = this.sceneViewer?.managers?.operationHistory;
177
+
178
+ // if (operationHistoryManager) {
179
+ // const lastOp = operationHistoryManager.getLastOperation();
180
+
181
+ // if (lastOp) {
182
+ // console.warn(`⚠️ Intersection caused by operation: ${lastOp.operationName}`, lastOp.params);
183
+
184
+ // // Attempt to undo the operation FIRST (before removing computed objects)
185
+ // // This is important because undo needs to find the segment that was moved
186
+ // const undoSuccess = operationHistoryManager.undoLastOperation();
187
+
188
+ // if (undoSuccess) {
189
+ // console.log('✅ Successfully undid operation that caused intersection');
190
+
191
+ // // Now remove the buggy computed objects that were created with the invalid state
192
+ // console.log('🗑️ Removing buggy computed objects after undo...');
193
+ // this.removeComputedObjects();
194
+
195
+ // // Re-run pathfinding after undo to restore valid state
196
+ // console.log('🔄 Re-running pathfinding after undo to restore clean state...');
197
+
198
+ // // Recursively call _executePathfinding with the corrected scene state
199
+ // // This is safe because we've undone the operation, so we won't get into an infinite loop
200
+ // const correctedResult = await this._executePathfinding(
201
+ // sceneData,
202
+ // connections,
203
+ // { ...options, context: `${context} (After Undo)` }
204
+ // );
205
+
206
+ // // Mark that we performed an undo and return the corrected result
207
+ // correctedResult.undoPerformed = true;
208
+ // correctedResult.undoneOperation = lastOp;
209
+
210
+ // // Clear the undo flag before returning
211
+ // this.isUndoInProgress = false;
212
+
213
+ // return correctedResult;
214
+ // } else {
215
+ // console.error('❌ Failed to undo operation that caused intersection');
216
+ // pathfindingResult.undoFailed = true;
217
+ // // Clear the undo flag even on failure
218
+ // this.isUndoInProgress = false;
219
+ // }
220
+ // } else {
221
+ // console.warn('⚠️ No operation in history to undo');
222
+ // this.isUndoInProgress = false;
223
+ // }
224
+ // } else {
225
+ // console.error('❌ OperationHistoryManager not available for undo');
226
+ // console.error(' Available managers:', Object.keys(this.sceneViewer?.managers || {}));
227
+ // this.isUndoInProgress = false;
228
+ // }
229
+ // } else if (intersectionCheck.hasIntersections && this.isUndoInProgress) {
230
+ // console.log('⏭️ Skipping intersection handling - undo already in progress');
231
+ // }
205
232
 
206
- // Now remove the buggy computed objects that were created with the invalid state
207
- console.log('🗑️ Removing buggy computed objects after undo...');
208
- this.removeComputedObjects();
209
-
210
- // Re-run pathfinding after undo to restore valid state
211
- console.log('🔄 Re-running pathfinding after undo to restore clean state...');
212
-
213
- // Recursively call _executePathfinding with the corrected scene state
214
- // This is safe because we've undone the operation, so we won't get into an infinite loop
215
- _context.n = 1;
216
- return this._executePathfinding(sceneData, connections, _objectSpread2(_objectSpread2({}, options), {}, {
217
- context: "".concat(context, " (After Undo)")
218
- }));
219
- case 1:
220
- correctedResult = _context.v;
221
- // Mark that we performed an undo and return the corrected result
222
- correctedResult.undoPerformed = true;
223
- correctedResult.undoneOperation = lastOp;
224
-
225
- // Clear the undo flag before returning
226
- this.isUndoInProgress = false;
227
- return _context.a(2, correctedResult);
228
- case 2:
229
- console.error('❌ Failed to undo operation that caused intersection');
230
- pathfindingResult.undoFailed = true;
231
- // Clear the undo flag even on failure
232
- this.isUndoInProgress = false;
233
- case 3:
234
- _context.n = 5;
235
- break;
236
- case 4:
237
- console.warn('⚠️ No operation in history to undo');
238
- this.isUndoInProgress = false;
239
- case 5:
240
- _context.n = 7;
241
- break;
242
- case 6:
243
- console.error('❌ OperationHistoryManager not available for undo');
244
- console.error(' Available managers:', Object.keys(((_this$sceneViewer2 = this.sceneViewer) === null || _this$sceneViewer2 === void 0 ? void 0 : _this$sceneViewer2.managers) || {}));
245
- this.isUndoInProgress = false;
246
- case 7:
247
- _context.n = 9;
248
- break;
249
- case 8:
250
- if (intersectionCheck.hasIntersections && this.isUndoInProgress) {
251
- console.log('⏭️ Skipping intersection handling - undo already in progress');
252
- }
253
- case 9:
254
233
  // Create gateways in the scene if requested
255
234
  if (options.createGateways && pathfindingResult.gateways) {
256
235
  this.renderingManager.createGateways(pathfindingResult);
@@ -95,10 +95,23 @@ var SceneDataManager = /*#__PURE__*/function () {
95
95
  worldBoundingBox: worldBoundingBox
96
96
  })
97
97
  };
98
+
99
+ // For connectors and gateways, add world position
98
100
  if (obj.userData.objectType.includes('connector') || obj.userData.objectType === 'gateway') {
99
101
  var worldPosition = new THREE.Vector3();
100
102
  obj.getWorldPosition(worldPosition);
101
103
  results.userData.position = [worldPosition.x, worldPosition.y, worldPosition.z];
104
+
105
+ // Differentiate component connectors from segment connectors for pathfinder
106
+ // Component connectors are children of components
107
+ if (obj.userData.objectType === 'connector') {
108
+ var _parent$userData;
109
+ // Check if parent is a component (not a segment)
110
+ var parent = obj.parent;
111
+ if (parent && ((_parent$userData = parent.userData) === null || _parent$userData === void 0 ? void 0 : _parent$userData.objectType) === 'component') {
112
+ results.userData.objectType = 'component-connector';
113
+ }
114
+ }
102
115
  }
103
116
  return results;
104
117
  });
@@ -1328,7 +1328,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1328
1328
  }, {
1329
1329
  key: "manualizeSegment",
1330
1330
  value: function manualizeSegment(segment, currentSceneData) {
1331
- var _segment$userData, _this$sceneViewer$man;
1331
+ var _segment$userData, _segment$material$use, _this$sceneViewer$man;
1332
1332
  if (!segment || !((_segment$userData = segment.userData) !== null && _segment$userData !== void 0 && _segment$userData.objectType) === 'segment') {
1333
1333
  console.log('❌ Object is not a pipe segment:', {
1334
1334
  isObject: !!segment,
@@ -1345,6 +1345,16 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1345
1345
  return;
1346
1346
  }
1347
1347
 
1348
+ // CRITICAL: Clone the material BEFORE changing its color
1349
+ // This prevents the color change from affecting other segments that share the same material instance
1350
+ if (segment.material && !((_segment$material$use = segment.material.userData) !== null && _segment$material$use !== void 0 && _segment$material$use.isCloned)) {
1351
+ var originalMaterial = segment.material;
1352
+ segment.material = originalMaterial.clone();
1353
+ segment.material.userData = segment.material.userData || {};
1354
+ segment.material.userData.isCloned = true;
1355
+ console.log('📋 Cloned segment material to prevent shared material mutation');
1356
+ }
1357
+
1348
1358
  // Override material color to blue in dev mode
1349
1359
  var isDev = false;
1350
1360
  if (typeof window !== 'undefined') {
@@ -1369,7 +1379,7 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1369
1379
  // Change from "SEGMENT-X" to "Segment-X" (capital S to lowercase s)
1370
1380
  var segmentIndex = segment.userData.segmentIndex;
1371
1381
  var oldUuid = segment.uuid;
1372
- segment.uuid = "Segment-".concat(segmentIndex);
1382
+ segment.uuid = "SEGMENT-".concat(segmentIndex);
1373
1383
  console.log("\uD83D\uDD27 Renamed segment UUID from ".concat(oldUuid, " to: ").concat(segment.uuid));
1374
1384
 
1375
1385
  // Calculate segment endpoints in world coordinates
@@ -1626,147 +1636,6 @@ var SceneOperationsManager = /*#__PURE__*/function () {
1626
1636
  }
1627
1637
  console.log("\u2705 Gateway ".concat(gateway.uuid, " successfully converted to manual (declared)"));
1628
1638
  }
1629
-
1630
- /**
1631
- * Split a segment by manualizing it and shortening it by 0.5 units
1632
- * The segment gets converted to a declared (manual) segment with connectors,
1633
- * and is shortened from the end that has a component connector (or the end if no connectors)
1634
- * @param {string|THREE.Object3D} segmentIdOrObject - The UUID or Three.js object of the segment to split
1635
- * @param {Object} currentSceneData - Current scene data with connections
1636
- * @returns {THREE.Object3D|null} The shortened manual segment or null on failure
1637
- */
1638
- }, {
1639
- key: "splitSegment",
1640
- value: function splitSegment(segmentIdOrObject, currentSceneData) {
1641
- var _segment$userData3,
1642
- _this6 = this;
1643
- console.log('✂️ splitSegment started:', segmentIdOrObject);
1644
-
1645
- // Find the segment object
1646
- var segment = null;
1647
- if (typeof segmentIdOrObject === 'string') {
1648
- this.sceneViewer.scene.traverse(function (child) {
1649
- var _child$userData10;
1650
- if (child.uuid === segmentIdOrObject || ((_child$userData10 = child.userData) === null || _child$userData10 === void 0 ? void 0 : _child$userData10.originalUuid) === segmentIdOrObject) {
1651
- segment = child;
1652
- }
1653
- });
1654
- } else if (segmentIdOrObject && segmentIdOrObject.isObject3D) {
1655
- segment = segmentIdOrObject;
1656
- }
1657
-
1658
- // Validate segment
1659
- if (!segment || ((_segment$userData3 = segment.userData) === null || _segment$userData3 === void 0 ? void 0 : _segment$userData3.objectType) !== 'segment') {
1660
- console.error('❌ splitSegment(): Invalid segment or not found');
1661
- return null;
1662
- }
1663
- if (!currentSceneData) {
1664
- console.error('❌ splitSegment(): currentSceneData is required');
1665
- return null;
1666
- }
1667
- console.log('✂️ Splitting/shortening segment:', segment.uuid);
1668
-
1669
- // Get segment geometry to calculate endpoints
1670
- var geometry = segment.geometry;
1671
- if (!geometry || !geometry.parameters) {
1672
- console.error('❌ splitSegment(): Segment has invalid geometry');
1673
- return null;
1674
- }
1675
- var segmentLength = geometry.parameters.height || 1;
1676
- var segmentRadius = geometry.parameters.radiusTop || 0.1;
1677
- var shortenAmount = 0.5;
1678
- var newLength = segmentLength - shortenAmount;
1679
- if (newLength <= 0) {
1680
- console.error('❌ splitSegment(): Segment too short to shorten by 0.5');
1681
- return null;
1682
- }
1683
-
1684
- // Calculate segment direction vector
1685
- var direction = new THREE.Vector3(0, 1, 0);
1686
- direction.applyQuaternion(segment.quaternion);
1687
- direction.normalize();
1688
-
1689
- // Calculate original segment endpoints
1690
- var startPoint = new THREE.Vector3();
1691
- startPoint.copy(segment.position).sub(direction.clone().multiplyScalar(segmentLength / 2));
1692
- var endPoint = new THREE.Vector3();
1693
- endPoint.copy(segment.position).add(direction.clone().multiplyScalar(segmentLength / 2));
1694
-
1695
- // Check for component connectors at endpoints
1696
- var findConnectorsAtPosition = function findConnectorsAtPosition(position) {
1697
- var tolerance = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0.15;
1698
- var connectors = [];
1699
- _this6.sceneViewer.scene.traverse(function (child) {
1700
- var _child$userData11;
1701
- if (((_child$userData11 = child.userData) === null || _child$userData11 === void 0 ? void 0 : _child$userData11.objectType) === 'connector') {
1702
- var connectorWorldPos = new THREE.Vector3();
1703
- child.getWorldPosition(connectorWorldPos);
1704
- var distance = connectorWorldPos.distanceTo(position);
1705
- if (distance <= tolerance) {
1706
- connectors.push(child);
1707
- }
1708
- }
1709
- });
1710
- return connectors.filter(function (c) {
1711
- var _c$userData;
1712
- return ((_c$userData = c.userData) === null || _c$userData === void 0 ? void 0 : _c$userData.objectType) !== 'segment-connector';
1713
- });
1714
- };
1715
- var startConnectors = findConnectorsAtPosition(startPoint);
1716
- var endConnectors = findConnectorsAtPosition(endPoint);
1717
-
1718
- // Determine which end to shorten from
1719
- var newStartPoint, newEndPoint;
1720
- if (endConnectors.length > 0) {
1721
- // Component connector at end - shorten from end
1722
- newStartPoint = startPoint.clone();
1723
- newEndPoint = endPoint.clone().sub(direction.clone().multiplyScalar(shortenAmount));
1724
- console.log("\uD83D\uDCCD Component connector at END - shortening from end by ".concat(shortenAmount));
1725
- } else if (startConnectors.length > 0) {
1726
- // Component connector at start - shorten from start
1727
- newStartPoint = startPoint.clone().add(direction.clone().multiplyScalar(shortenAmount));
1728
- newEndPoint = endPoint.clone();
1729
- console.log("\uD83D\uDCCD Component connector at START - shortening from start by ".concat(shortenAmount));
1730
- } else {
1731
- // No component connectors - shorten from end (default)
1732
- newStartPoint = startPoint.clone();
1733
- newEndPoint = endPoint.clone().sub(direction.clone().multiplyScalar(shortenAmount));
1734
- console.log("\uD83D\uDCCD No component connectors - shortening from end by ".concat(shortenAmount));
1735
- }
1736
- console.log('📍 Segment shortening:', {
1737
- originalStart: startPoint.toArray(),
1738
- originalEnd: endPoint.toArray(),
1739
- newStart: newStartPoint.toArray(),
1740
- newEnd: newEndPoint.toArray(),
1741
- originalLength: segmentLength,
1742
- newLength: newLength,
1743
- shortenAmount: shortenAmount
1744
- });
1745
-
1746
- // Get material from existing segment
1747
- segment.material;
1748
-
1749
- // Dispose old geometry and create new shortened geometry
1750
- if (segment.geometry) {
1751
- segment.geometry.dispose();
1752
- }
1753
- var newGeometry = new THREE.CylinderGeometry(segmentRadius, segmentRadius, newLength, 16, 1, false);
1754
- segment.geometry = newGeometry;
1755
-
1756
- // Reposition the segment to the new center
1757
- var newPosition = new THREE.Vector3();
1758
- newPosition.lerpVectors(newStartPoint, newEndPoint, 0.5);
1759
- segment.position.copy(newPosition);
1760
- // Quaternion stays the same
1761
-
1762
- console.log('✅ Segment geometry updated and repositioned');
1763
-
1764
- // Now manualize the shortened segment
1765
- console.log('🔧 Manualizing the shortened segment...');
1766
- this.manualizeSegment(segment, currentSceneData);
1767
- console.log('✅ Segment split completed successfully - segment shortened and manualized');
1768
- return segment;
1769
- }
1770
1639
  }]);
1771
1640
  }();
1772
1641
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@2112-lab/central-plant",
3
- "version": "0.1.49",
3
+ "version": "0.1.51",
4
4
  "description": "Utility modules for the Central Plant Application",
5
5
  "main": "dist/bundle/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -30,7 +30,7 @@
30
30
  "author": "CentralPlant Team",
31
31
  "license": "MIT",
32
32
  "dependencies": {
33
- "@2112-lab/pathfinder": "1.0.31",
33
+ "@2112-lab/pathfinder": "1.0.32",
34
34
  "stats.js": "^0.17.0",
35
35
  "three": "^0.177.0"
36
36
  },