UNPKG

react-planner-electron

Version:

react-planner-electron is a React Component for plans design. Draw a 2D floorplan and navigate it in 3D mode.

390 lines (301 loc) 13.7 kB
import {Map, List, fromJS} from 'immutable'; import {Layer, Group} from './export'; import { IDBroker, NameGenerator } from '../utils/export'; import { nearestSnap, addLineSegmentSnap, } from '../utils/snap'; import { MODE_IDLE, MODE_DRAWING_HOLE, MODE_DRAGGING_HOLE, } from '../constants'; import { GeometryUtils } from '../utils/export'; class Hole { static create(state, layerID, type, lineID, offset, properties) { let holeID = IDBroker.acquireID(); let hole = state.catalog.factoryElement(type, { id: holeID, name: NameGenerator.generateName('holes', state.catalog.getIn(['elements', type, 'info', 'title'])), type, offset, line: lineID }, properties); state = state.setIn(['scene', 'layers', layerID, 'holes', holeID], hole); state = state.updateIn(['scene', 'layers', layerID, 'lines', lineID, 'holes'], holes => holes.push(holeID)); return {updatedState: state, hole}; } static select(state, layerID, holeID) { state = Layer.select(state, layerID).updatedState; state = Layer.selectElement(state, layerID, 'holes', holeID).updatedState; return {updatedState: state}; } static remove(state, layerID, holeID) { let hole = state.getIn(['scene', 'layers', layerID, 'holes', holeID]); state = this.unselect(state, layerID, holeID).updatedState; state = Layer.removeElement(state, layerID, 'holes', holeID).updatedState; state = state.updateIn(['scene', 'layers', layerID, 'lines', hole.line, 'holes'], holes => { let index = holes.findIndex(ID => holeID === ID); return index !== -1 ? holes.remove(index) : holes; }); state.getIn(['scene', 'groups']).forEach(group => state = Group.removeElement(state, group.id, layerID, 'holes', holeID).updatedState); return {updatedState: state}; } static unselect(state, layerID, holeID) { state = Layer.unselect(state, layerID, 'holes', holeID).updatedState; return {updatedState: state}; } static selectToolDrawingHole(state, sceneComponentType) { let snapElements = (new List()).withMutations(snapElements => { let {lines, vertices} = state.getIn(['scene', 'layers', state.scene.selectedLayer]); lines.forEach(line => { let {x: x1, y: y1} = vertices.get(line.vertices.get(0)); let {x: x2, y: y2} = vertices.get(line.vertices.get(1)); addLineSegmentSnap(snapElements, x1, y1, x2, y2, 20, 1, line.id); }); }); state = state.merge({ mode: MODE_DRAWING_HOLE, snapElements, drawingSupport: Map({ type: sceneComponentType }) }); return {updatedState: state}; } static updateDrawingHole(state, layerID, x, y) { let catalog = state.catalog; //calculate snap and overwrite coords if needed //force snap to segment let snap = nearestSnap(state.snapElements, x, y, state.snapMask.merge({SNAP_SEGMENT: true})); if (snap) ({x, y} = snap.point); let selectedHole = state.getIn(['scene', 'layers', layerID, 'selected', 'holes']).first(); if (snap) { let lineID = snap.snap.related.get(0); let vertices = state.getIn(['scene', 'layers', layerID, 'lines', lineID, 'vertices']); let {x: x1, y: y1} = state.getIn(['scene', 'layers', layerID, 'vertices', vertices.get(0)]); let {x: x2, y: y2} = state.getIn(['scene', 'layers', layerID, 'vertices', vertices.get(1)]); // I need min and max vertices on this line segment let minVertex = GeometryUtils.minVertex({x: x1, y: y1}, {x: x2, y: y2}); let maxVertex = GeometryUtils.maxVertex({x: x1, y: y1}, {x: x2, y: y2}); let width = catalog.factoryElement(state.drawingSupport.get('type')).properties.getIn(['width', 'length']); // Now I need min and max possible coordinates for the hole on the line. They depend on the width of the hole let lineLength = GeometryUtils.pointsDistance(x1, y1, x2, y2); let alpha = GeometryUtils.absAngleBetweenTwoPoints(x1, y1, x2, y2); let cosAlpha = GeometryUtils.cosWithThreshold(alpha, 0.0000001); let sinAlpha = GeometryUtils.sinWithThreshold(alpha, 0.0000001); let minLeftVertexHole = { x: minVertex.x + width / 2 * cosAlpha, y: minVertex.y + width / 2 * sinAlpha }; let maxRightVertexHole = { x: minVertex.x + lineLength * cosAlpha - width / 2 * cosAlpha, y: minVertex.y + lineLength * sinAlpha - width / 2 * sinAlpha }; let offset; if (x < minLeftVertexHole.x) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, minLeftVertexHole.x, minLeftVertexHole.y); } else if (x > maxRightVertexHole.x) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, maxRightVertexHole.x, maxRightVertexHole.y); } else { if (x === minLeftVertexHole.x && x === maxRightVertexHole.x) { if (y < minLeftVertexHole.y) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, minLeftVertexHole.x, minLeftVertexHole.y); offset = minVertex.x === x1 && minVertex.y === y1 ? offset : 1 - offset; } else if (y > maxRightVertexHole.y) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, maxRightVertexHole.x, maxRightVertexHole.y); offset = minVertex.x === x1 && minVertex.y === y1 ? offset : 1 - offset; } else { offset = GeometryUtils.pointPositionOnLineSegment(x1, y1, x2, y2, x, y); } } else { offset = GeometryUtils.pointPositionOnLineSegment(x1, y1, x2, y2, x, y); } } //if hole does exist, update if (selectedHole && snap) { state = state.mergeIn(['scene', 'layers', layerID, 'holes', selectedHole], {offset, line: lineID}); //remove from old line ( if present ) let index = state.getIn(['scene', 'layers', layerID, 'lines']).findEntry(line => { return line.id !== lineID && line.get('holes').contains(selectedHole); }); if (index) { let removed = index[1].get('holes').filter(hl => hl !== selectedHole); state = state.setIn(['scene', 'layers', layerID, 'lines', index[0], 'holes'], removed); } //add to line let line_holes = state.getIn(['scene', 'layers', layerID, 'lines', lineID, 'holes']); if (!line_holes.contains(selectedHole)) { state = state.setIn(['scene', 'layers', layerID, 'lines', lineID, 'holes'], line_holes.push(selectedHole)); } } else if (!selectedHole && snap) { //if hole does not exist, create let {updatedState: stateH, hole} = this.create(state, layerID, state.drawingSupport.get('type'), lineID, offset); state = Hole.select(stateH, layerID, hole.id).updatedState; } } //i've lost the snap while trying to drop the hole else if (false && selectedHole) //think if enable { state = Hole.remove(state, layerID, selectedHole).updatedState; } return {updatedState: state}; } static endDrawingHole(state, layerID, x, y) { state = this.updateDrawingHole(state, layerID, x, y).updatedState; state = Layer.unselectAll(state, layerID).updatedState; return {updatedState: state}; } static beginDraggingHole(state, layerID, holeID, x, y) { let layer = state.getIn(['scene', 'layers', layerID]); let hole = layer.getIn(['holes', holeID]); let line = layer.getIn(['lines', hole.line]); let v0 = layer.getIn(['vertices', line.vertices.get(0)]); let v1 = layer.getIn(['vertices', line.vertices.get(1)]); let snapElements = addLineSegmentSnap(List(), v0.x, v0.y, v1.x, v1.y, 9999999, 1, null); state = state.merge({ mode: MODE_DRAGGING_HOLE, snapElements, draggingSupport: Map({ layerID, holeID, startPointX: x, startPointY: y, }) }); return {updatedState: state}; } static updateDraggingHole(state, x, y) { //calculate snap and overwrite coords if needed //force snap to segment let snap = nearestSnap(state.snapElements, x, y, state.snapMask.merge({SNAP_SEGMENT: true})); if (!snap) return state; let {draggingSupport, scene} = state; let layerID = draggingSupport.get('layerID'); let holeID = draggingSupport.get('holeID'); let startPointX = draggingSupport.get('startPointX'); let startPointY = draggingSupport.get('startPointY'); let layer = state.getIn(['scene', 'layers', layerID]); let hole = layer.getIn(['holes', holeID]); let line = layer.getIn(['lines', hole.line]); let v0 = layer.getIn(['vertices', line.vertices.get(0)]); let v1 = layer.getIn(['vertices', line.vertices.get(1)]); ({x, y} = snap.point); // I need min and max vertices on this line segment let minVertex = GeometryUtils.minVertex(v0, v1); let maxVertex = GeometryUtils.maxVertex(v0, v1); // Now I need min and max possible coordinates for the hole on the line. They depend on the width of the hole let width = hole.properties.get('width').get('length'); let lineLength = GeometryUtils.pointsDistance(v0.x, v0.y, v1.x, v1.y); let alpha = Math.atan2(Math.abs(v1.y - v0.y), Math.abs(v1.x - v0.x)); let cosWithThreshold = (alpha) => { let cos = Math.cos(alpha); return cos < 0.0000001 ? 0 : cos; }; let sinWithThreshold = (alpha) => { let sin = Math.sin(alpha); return sin < 0.0000001 ? 0 : sin; }; let cosAlpha = cosWithThreshold(alpha); let sinAlpha = sinWithThreshold(alpha); let minLeftVertexHole = { x: minVertex.x + width / 2 * cosAlpha, y: minVertex.y + width / 2 * sinAlpha }; let maxRightVertexHole = { x: minVertex.x + lineLength * cosAlpha - width / 2 * cosAlpha, y: minVertex.y + lineLength * sinAlpha - width / 2 * sinAlpha }; // Now I need to verify if the snap vertex (with coordinates x and y) is on the line segment let offset; if (x < minLeftVertexHole.x) { // Snap point is previous the the line offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, minLeftVertexHole.x, minLeftVertexHole.y); } else { // Snap point is after the line or on the line if (x > maxRightVertexHole.x) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, maxRightVertexHole.x, maxRightVertexHole.y); } else if (x === minLeftVertexHole.x && x === maxRightVertexHole.x) { // I am on a vertical line, I need to check y coordinates if (y < minLeftVertexHole.y) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, minLeftVertexHole.x, minLeftVertexHole.y); offset = minVertex === v0 ? offset : 1 - offset; } else if (y > maxRightVertexHole.y) { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, maxRightVertexHole.x, maxRightVertexHole.y); offset = minVertex === v0 ? offset : 1 - offset; } else { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, x, y); offset = minVertex === v0 ? offset : 1 - offset; } } else { offset = GeometryUtils.pointPositionOnLineSegment(minVertex.x, minVertex.y, maxVertex.x, maxVertex.y, x, y); } } hole = hole.set('offset', offset); state = state.merge({ scene: scene.mergeIn(['layers', layerID, 'holes', holeID], hole) }); return {updatedState: state}; } static endDraggingHole(state, x, y) { state = this.updateDraggingHole(state, x, y).updatedState; state = state.merge({mode: MODE_IDLE}); return {updatedState: state}; } static setProperties(state, layerID, holeID, properties) { state = state.setIn(['scene', 'layers', layerID, 'holes', holeID, 'properties'], properties); return {updatedState: state}; } static setJsProperties(state, layerID, holeID, properties) { return this.setProperties(state, layerID, holeID, fromJS(properties)); } static updateProperties(state, layerID, holeID, properties) { properties.forEach((v, k) => { if (state.hasIn(['scene', 'layers', layerID, 'holes', holeID, 'properties', k])) state = state.mergeIn(['scene', 'layers', layerID, 'holes', holeID, 'properties', k], v); }); return {updatedState: state}; } static updateJsProperties(state, layerID, holeID, properties) { return this.updateProperties(state, layerID, holeID, fromJS(properties)); } static setAttributes(state, layerID, holeID, holesAttributes) { let hAttr = holesAttributes.toJS(); let {offsetA, offsetB, offset} = hAttr; delete hAttr['offsetA']; delete hAttr['offsetB']; delete hAttr['offset']; let misc = new Map({_unitA: offsetA._unit, _unitB: offsetB._unit}); state = state .mergeIn(['scene', 'layers', layerID, 'holes', holeID], fromJS(hAttr)) .mergeDeepIn(['scene', 'layers', layerID, 'holes', holeID], new Map({offset, misc})); return {updatedState: state}; } } export {Hole as default};