diff --git a/.changeset/swift-mirrors-scream.md b/.changeset/swift-mirrors-scream.md new file mode 100644 index 000000000..b3b3a9b7e --- /dev/null +++ b/.changeset/swift-mirrors-scream.md @@ -0,0 +1,5 @@ +--- +'@plait/draw': minor +--- + +add assembly requiredInterface and providedInterface shape for uml diff --git a/packages/draw/src/constants/geometry.ts b/packages/draw/src/constants/geometry.ts index 6f84c6bf0..40bbedc3b 100644 --- a/packages/draw/src/constants/geometry.ts +++ b/packages/draw/src/constants/geometry.ts @@ -161,6 +161,22 @@ export const DefaultPortProperty = { width: 20, height: 20 }; + +export const DefaultRequiredInterfaceProperty = { + width: 70, + height: 56 +}; + +export const DefaultAssemblyProperty = { + width: 120, + height: 56 +}; + +export const DefaultProvidedInterfaceProperty = { + width: 70, + height: 34 +}; + export const DefaultCombinedFragmentProperty = { width: 400, height: 280, @@ -260,7 +276,10 @@ export const DefaultUMLPropertyMap = { [UMLSymbols.template]: DefaultMultiDocumentProperty, [UMLSymbols.componentBox]: DefaultComponentBoxProperty, [UMLSymbols.port]: DefaultPortProperty, - [UMLSymbols.branchMerge]: DefaultDeletionProperty + [UMLSymbols.branchMerge]: DefaultDeletionProperty, + [UMLSymbols.assembly]: DefaultAssemblyProperty, + [UMLSymbols.providedInterface]: DefaultProvidedInterfaceProperty, + [UMLSymbols.requiredInterface]: DefaultRequiredInterfaceProperty }; export const MultipleTextGeometryTextKeys: { [key in GeometryShapes]?: string[] } = { @@ -280,7 +299,10 @@ export const GEOMETRY_WITHOUT_TEXT = [ UMLSymbols.activation, UMLSymbols.deletion, UMLSymbols.port, - UMLSymbols.branchMerge + UMLSymbols.branchMerge, + UMLSymbols.assembly, + UMLSymbols.providedInterface, + UMLSymbols.requiredInterface ] as GeometryShapes[]; export const GEOMETRY_WITH_MULTIPLE_TEXT = [UMLSymbols.package, UMLSymbols.combinedFragment]; diff --git a/packages/draw/src/engines/index.ts b/packages/draw/src/engines/index.ts index 85871da77..22eb7e2eb 100644 --- a/packages/draw/src/engines/index.ts +++ b/packages/draw/src/engines/index.ts @@ -58,6 +58,9 @@ import { CombinedFragmentEngine } from './uml/combined-fragment'; import { DeletionEngine } from './uml/deletion'; import { ActiveClassEngine } from './uml/activity-class'; import { NoteEngine } from './uml/note'; +import { AssemblyEngine } from './uml/assembly'; +import { RequiredInterfaceEngine } from './uml/required-interface'; +import { ProvidedInterfaceEngine } from './uml/provided-interface'; import { ComponentEngine } from './uml/component'; import { ComponentBoxEngine } from './uml/component-box'; import { TemplateEngine } from './uml/template'; @@ -128,7 +131,10 @@ const ShapeEngineMap: Record> = { [UMLSymbols.componentBox]: ComponentBoxEngine, [UMLSymbols.template]: TemplateEngine, [UMLSymbols.port]: RectangleEngine, - [UMLSymbols.branchMerge]: DiamondEngine + [UMLSymbols.branchMerge]: DiamondEngine, + [UMLSymbols.assembly]: AssemblyEngine, + [UMLSymbols.requiredInterface]: RequiredInterfaceEngine, + [UMLSymbols.providedInterface]: ProvidedInterfaceEngine }; export const getEngine = < diff --git a/packages/draw/src/engines/uml/assembly.ts b/packages/draw/src/engines/uml/assembly.ts new file mode 100644 index 000000000..60aceb083 --- /dev/null +++ b/packages/draw/src/engines/uml/assembly.ts @@ -0,0 +1,75 @@ +import { + PlaitBoard, + Point, + PointOfRectangle, + RectangleClient, + getNearestPointBetweenPointAndEllipse, + getNearestPointBetweenPointAndSegments, + setStrokeLinecap +} from '@plait/core'; +import { ShapeEngine } from '../../interfaces'; +import { Options } from 'roughjs/bin/core'; +import { RectangleEngine } from '../basic-shapes/rectangle'; +import { getUnitVectorByPointAndPoint, rotateVector } from '@plait/common'; + +export const AssemblyEngine: ShapeEngine = { + draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { + const rs = PlaitBoard.getRoughSVG(board); + const shape = rs.path( + ` + M${rectangle.x} ${rectangle.y + rectangle.height / 2} + H${rectangle.x + rectangle.width * 0.3} + A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x + + rectangle.width * 0.3 + + rectangle.width * 0.26} ${rectangle.y + rectangle.height / 2} + A${rectangle.width * 0.13} ${rectangle.height * 0.285}, 0, 1, 1 ${rectangle.x + rectangle.width * 0.3} ${rectangle.y + + rectangle.height / 2} + M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13} ${rectangle.y} + A${rectangle.width * 0.233} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x + + rectangle.width * 0.3 + + rectangle.width * 0.13} ${rectangle.y + rectangle.height} + M${rectangle.x + rectangle.width * 0.3 + rectangle.width * 0.13 + rectangle.width * 0.233} ${rectangle.y + + rectangle.height / 2} H${rectangle.x + rectangle.width} + `, + { + ...options, + fillStyle: 'solid' + } + ); + setStrokeLinecap(shape, 'round'); + + return shape; + }, + isInsidePoint(rectangle: RectangleClient, point: Point) { + const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); + return RectangleClient.isHit(rectangle, rangeRectangle); + }, + getCornerPoints(rectangle: RectangleClient) { + return RectangleClient.getCornerPoints(rectangle); + }, + getConnectorPoints(rectangle: RectangleClient) { + return RectangleClient.getEdgeCenterPoints(rectangle); + }, + getNearestPoint(rectangle: RectangleClient, point: Point) { + const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); + if (nearestPoint[0] === rectangle.x + rectangle.width / 2) { + return getNearestPointBetweenPointAndEllipse( + point, + [rectangle.x + rectangle.width * 0.43, rectangle.y + rectangle.height / 2], + rectangle.width * 0.223, + rectangle.height / 2 + ); + } + return nearestPoint; + }, + getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) { + const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); + if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] < rectangle.y + rectangle.height / 2) { + return rotateVector(getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]), -Math.PI); + } + if (connectionPoint[0] > rectangle.x + rectangle.width * 0.43 && connectionPoint[1] > rectangle.y + rectangle.height / 2) { + return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); + } + return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); + } +}; diff --git a/packages/draw/src/engines/uml/provided-interface.ts b/packages/draw/src/engines/uml/provided-interface.ts new file mode 100644 index 000000000..11d24e420 --- /dev/null +++ b/packages/draw/src/engines/uml/provided-interface.ts @@ -0,0 +1,62 @@ +import { + PlaitBoard, + Point, + PointOfRectangle, + RectangleClient, + getEllipseTangentSlope, + getNearestPointBetweenPointAndSegments, + getVectorFromPointAndSlope, + setStrokeLinecap +} from '@plait/core'; +import { ShapeEngine } from '../../interfaces'; +import { Options } from 'roughjs/bin/core'; +import { RectangleEngine } from '../basic-shapes/rectangle'; +import { getUnitVectorByPointAndPoint } from '@plait/common'; + +export const ProvidedInterfaceEngine: ShapeEngine = { + draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { + const rs = PlaitBoard.getRoughSVG(board); + const shape = rs.path( + ` M${rectangle.x} ${rectangle.y + rectangle.height / 2} + H${rectangle.x + rectangle.width * 0.54} + A${(rectangle.width * 0.46) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width} ${rectangle.y + + rectangle.height / 2} + A${(rectangle.width * 0.46) / 2} ${rectangle.height / 2}, 0, 1, 1 ${rectangle.x + rectangle.width * 0.54} ${rectangle.y + + rectangle.height / 2} + `, + { + ...options, + fillStyle: 'solid' + } + ); + setStrokeLinecap(shape, 'round'); + + return shape; + }, + isInsidePoint(rectangle: RectangleClient, point: Point) { + const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); + return RectangleClient.isHit(rectangle, rangeRectangle); + }, + getCornerPoints(rectangle: RectangleClient) { + return RectangleClient.getCornerPoints(rectangle); + }, + getConnectorPoints(rectangle: RectangleClient) { + return RectangleClient.getEdgeCenterPoints(rectangle); + }, + getNearestPoint(rectangle: RectangleClient, point: Point) { + const nearestPoint = getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); + return nearestPoint; + }, + getTangentVectorByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle) { + const connectionPoint = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); + const centerPoint: Point = [rectangle.x + (rectangle.width * 3) / 4, rectangle.y + rectangle.height / 2]; + if (connectionPoint[0] > rectangle.x + rectangle.width * 0.54) { + const point = [connectionPoint[0] - centerPoint[0], -(connectionPoint[1] - centerPoint[1])]; + const rx = (rectangle.width * 0.46) / 2; + const ry = rectangle.height / 2; + const slope = getEllipseTangentSlope(point[0], point[1], rx, ry) as any; + return getVectorFromPointAndSlope(point[0], point[1], slope); + } + return getUnitVectorByPointAndPoint(connectionPoint, [rectangle.x, rectangle.y + rectangle.height / 2]); + } +}; diff --git a/packages/draw/src/engines/uml/required-interface.ts b/packages/draw/src/engines/uml/required-interface.ts new file mode 100644 index 000000000..953e5fa98 --- /dev/null +++ b/packages/draw/src/engines/uml/required-interface.ts @@ -0,0 +1,49 @@ +import { + PlaitBoard, + Point, + PointOfRectangle, + RectangleClient, + getNearestPointBetweenPointAndSegments, + setStrokeLinecap +} from '@plait/core'; +import { ShapeEngine } from '../../interfaces'; +import { Options } from 'roughjs/bin/core'; +import { RectangleEngine } from '../basic-shapes/rectangle'; +import { getPolygonEdgeByConnectionPoint } from '../../utils/polygon'; + +export const RequiredInterfaceEngine: ShapeEngine = { + draw(board: PlaitBoard, rectangle: RectangleClient, options: Options) { + const rs = PlaitBoard.getRoughSVG(board); + const shape = rs.path( + `M${rectangle.x} ${rectangle.y} + A${rectangle.width * 0.39} ${rectangle.height / 2}, 0, 0, 1 ${rectangle.x} ${rectangle.y + rectangle.height} + M${rectangle.x + rectangle.width * 0.41} ${rectangle.y + rectangle.height / 2} H${rectangle.x + rectangle.width} + `, + { + ...options, + fillStyle: 'solid' + } + ); + setStrokeLinecap(shape, 'round'); + + return shape; + }, + isInsidePoint(rectangle: RectangleClient, point: Point) { + const rangeRectangle = RectangleClient.getRectangleByPoints([point, point]); + return RectangleClient.isHit(rectangle, rangeRectangle); + }, + getCornerPoints(rectangle: RectangleClient) { + return RectangleClient.getCornerPoints(rectangle); + }, + getNearestPoint(rectangle: RectangleClient, point: Point) { + return getNearestPointBetweenPointAndSegments(point, RectangleEngine.getCornerPoints(rectangle)); + }, + getEdgeByConnectionPoint(rectangle: RectangleClient, pointOfRectangle: PointOfRectangle): [Point, Point] | null { + const corners = RectangleEngine.getCornerPoints(rectangle); + const point = RectangleClient.getConnectionPoint(rectangle, pointOfRectangle); + return getPolygonEdgeByConnectionPoint(corners, point); + }, + getConnectorPoints(rectangle: RectangleClient) { + return RectangleClient.getEdgeCenterPoints(rectangle); + } +}; diff --git a/packages/draw/src/interfaces/geometry.ts b/packages/draw/src/interfaces/geometry.ts index eff40981d..3d53ab687 100644 --- a/packages/draw/src/interfaces/geometry.ts +++ b/packages/draw/src/interfaces/geometry.ts @@ -72,7 +72,10 @@ export enum UMLSymbols { componentBox = 'componentBox', template = 'template', activation = 'activation', - deletion = 'deletion' + deletion = 'deletion', + assembly = 'assembly', + providedInterface = 'providedInterface', + requiredInterface = 'requiredInterface' } export enum MultipleTextGeometryCommonTextKeys { diff --git a/src/app/components/main-toolbar/main-toolbar.component.html b/src/app/components/main-toolbar/main-toolbar.component.html index aa3537e53..eba03edf1 100644 --- a/src/app/components/main-toolbar/main-toolbar.component.html +++ b/src/app/components/main-toolbar/main-toolbar.component.html @@ -460,6 +460,26 @@ + + + + + + + + + + + + @@ -2024,3 +2044,62 @@ + + + + 1.Base基础/1.icon图标/11.editor/图形/UML/assembly + + + + + + + + 1.Base基础/1.icon图标/11.editor/图形/UML/required interface + + + + + + + + + 1.Base基础/1.icon图标/11.editor/图形/UML/provided interface + + + + +