Skip to content
Snippets Groups Projects
Commit f04c3070 authored by Hernan Ponce de Leon's avatar Hernan Ponce de Leon
Browse files

Merge branch '3433-auto_layouter_label_size' into 'master'

3433 auto layouter label size

See merge request !6
parents e21b5791 f8318ee3
No related branches found
No related tags found
1 merge request!63433 auto layouter label size
AutoLayoutMenu.java ef3b897cc2fd99cf9ce201f03cffea036555e3ac GREEN
AutoLayoutMenu.java bca7986c209678ed937548a37db494430877d80e GREEN
DiagramTapeMeasure.java 72454e6fe5225dab11d3d691baad93aab7a171c0 GREEN
IAutoLayouter.java de1b11d9e202c7e23352ad85684dbf8a3fd17c7d GREEN
KielerAutoLayouter.java fae8692ca0131a6d14cff768aba446b7b35512d6 GREEN
IAutoLayouterTapeMeasure.java df186e0ba505e0ecda211b1df76cf12f3245b47e GREEN
KielerAutoLayouter.java a0f2d0cd94ffd043e6d4ddbe1589a1894412f81c GREEN
......@@ -21,7 +21,6 @@ import static org.fortiss.tooling.base.utils.AngleUtils.getAngle;
import static org.fortiss.tooling.kernel.utils.EcoreUtils.pickInstanceOf;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.emf.ecore.EObject;
......@@ -34,9 +33,10 @@ import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
import org.fortiss.tooling.base.ui.ToolingBaseUIActivator;
import org.fortiss.tooling.kernel.model.IProjectRootElement;
import org.fortiss.tooling.kernel.ui.extension.IContextMenuContributor;
import org.fortiss.tooling.kernel.ui.extension.IModelElementHandler;
import org.fortiss.tooling.kernel.ui.extension.data.ContextMenuContextProvider;
import org.fortiss.tooling.kernel.ui.service.IModelElementHandlerService;
/**
* Context menu entry to automatic layout {@link IHierarchicElement}-based models.
......@@ -96,16 +96,16 @@ public class AutoLayoutMenu implements IContextMenuContributor {
public List<IContributionItem> getContributedItems(EObject selection,
ContextMenuContextProvider contextProvider) {
if(!(selection instanceof IHierarchicElement)) {
return Collections.emptyList();
IModelElementHandler<EObject> handler =
IModelElementHandlerService.getInstance().getModelElementHandler(selection);
if(handler == null) {
return emptyList();
}
IHierarchicElement element = (IHierarchicElement)selection;
// Skip single top-level model element (e.g., AF3 component architecture)
if(element instanceof IProjectRootElement && element.getContainedElements().size() == 1) {
// There is exactly one child element
element = element.getContainedElements().get(0);
EObject editedObject = handler.handleOpenModelElementRequest(selection);
if(!(editedObject instanceof IHierarchicElement)) {
return emptyList();
}
IHierarchicElement element = (IHierarchicElement)editedObject;
// Check if model is empty
if(pickInstanceOf(ILayoutedModelElement.class, element.getContainedElements()).isEmpty()) {
......
/*-------------------------------------------------------------------------+
| Copyright 2018 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.base.ui.layout.auto;
import static java.lang.Math.max;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_CONNECTOR_SIZE;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_GRID_SIZE;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_SHAPE_MINIMUM_WIDTH;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodeSize;
import static org.fortiss.tooling.kernel.ui.util.KernelUIUtils.getName;
import static org.fortiss.tooling.kernel.utils.EcoreUtils.pickInstanceOf;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.Label;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Point;
import org.fortiss.tooling.base.model.base.EntryConnectorBase;
import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.base.model.element.IModelElement;
import org.fortiss.tooling.base.model.layout.Dimension;
import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
import org.fortiss.tooling.base.ui.editor.DiagramEditorBase;
import org.fortiss.tooling.base.ui.editpart.ConnectionEditPartBase;
import org.fortiss.tooling.base.ui.editpart.ElementEditPartBase;
/**
* {@link IAutoLayouterTapeMeasure} specialization to for automatic layout of models to be displayed
* in {@link DiagramEditorBase}-based editors.
*
* @author barner
*/
public class DiagramTapeMeasure implements IAutoLayouterTapeMeasure {
/** {@link EditPartViewer} used to display the underlying diagram. */
private EditPartViewer diagramEpv;
/** Underlying GC. */
private GC gc;
/** Constructor. */
public DiagramTapeMeasure(EditPartViewer epv, GC gc) {
this.diagramEpv = epv;
this.gc = gc;
}
/** Returns the {@code Font} used by the given {@code element} (may be {@code null}). */
private Font getFont(IModelElement element) {
if(diagramEpv == null) {
return null;
}
Object ep = diagramEpv.getEditPartRegistry().get(element);
if(ep instanceof ElementEditPartBase) {
Label label = ((ElementEditPartBase<?>)ep).createLabelFigure();
if(label != null) {
return label.getFont();
}
} else if(ep instanceof ConnectionEditPartBase) {
IFigure figure = ((ConnectionEditPartBase<?>)ep).getFigure();
if(figure != null) {
return figure.getFont();
}
}
return null;
}
/** Returns the text extend of the given {@code element}'s label (may be {@code null}). */
private Point getTextExtent(IModelElement element) {
if(gc == null) {
return null;
}
String name = getName(element);
if(name == null) {
return null;
}
gc.setFont(getFont(element));
Point rval = gc.textExtent(name);
return rval;
}
/** {@inheritDoc} */
@Override
public int getElementWidth(IHierarchicElement element) {
Dimension dimension = getNodeSize((ILayoutedModelElement)element);
int width = max(DEFAULT_SHAPE_MINIMUM_WIDTH, dimension.getWidth());
Point textExtend = getTextExtent(element);
if(textExtend != null) {
width = max(width, textExtend.x + 6 * DEFAULT_GRID_SIZE);
}
return width;
}
/** {@inheritDoc} */
@Override
public int getElementHeight(IHierarchicElement element) {
Dimension dimension = getNodeSize((ILayoutedModelElement)element);
int numElemConnectors = element.getConnectors().size();
int numElemInputConnectors =
pickInstanceOf(EntryConnectorBase.class, element.getConnectors()).size();
int numConnectors = max(numElemInputConnectors, numElemConnectors - numElemInputConnectors);
return max((1 + 2 * numConnectors) * DEFAULT_CONNECTOR_SIZE, dimension.getHeight());
}
/** {@inheritDoc} */
@Override
public int getConnectorWidth(IConnector connector) {
return 2 * DEFAULT_CONNECTOR_SIZE;
}
/** {@inheritDoc} */
@Override
public int getConnectorHeight(IConnector connector) {
return 2 * DEFAULT_CONNECTOR_SIZE;
}
/** {@inheritDoc} */
@Override
public int getConnectionWidth(IConnection connection) {
Point textExtend = getTextExtent(connection);
return textExtend != null ? textExtend.x : 0;
}
}
/*-------------------------------------------------------------------------+
| Copyright 2018 fortiss GmbH |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
+--------------------------------------------------------------------------*/
package org.fortiss.tooling.base.ui.layout.auto;
import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
/**
* Interface used by {@link IAutoLayouter} to estimate dimensions requirements of elements (for the
* respective editor for which the automatic layout is performed).
*
* @author barner
*/
public interface IAutoLayouterTapeMeasure {
/** Estimates the width requirements of the given element. */
public int getElementWidth(IHierarchicElement element);
/** Estimates the height requirements of the given element. */
public int getElementHeight(IHierarchicElement element);
/** Estimates the width requirements of the given connector. */
public int getConnectorWidth(IConnector connector);
/** Estimates the height requirements of the given connector. */
public int getConnectorHeight(IConnector connector);
/** Estimates the width requirements of the given connection. */
public int getConnectionWidth(IConnection connection);
}
......@@ -20,19 +20,17 @@ import static de.cau.cs.kieler.kiml.options.LayoutOptions.DIRECTION;
import static de.cau.cs.kieler.kiml.options.LayoutOptions.INTERACTIVE;
import static de.cau.cs.kieler.kiml.options.LayoutOptions.PORT_SIDE;
import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedEdge;
import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedLabel;
import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedNode;
import static de.cau.cs.kieler.kiml.util.KimlUtil.createInitializedPort;
import static de.cau.cs.kieler.klay.layered.properties.InteractiveReferencePoint.TOP_LEFT;
import static de.cau.cs.kieler.klay.layered.properties.Properties.FEEDBACK_EDGES;
import static de.cau.cs.kieler.klay.layered.properties.Properties.INTERACTIVE_REFERENCE_POINT;
import static java.lang.Math.max;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_CONNECTOR_SIZE;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_GRID_SIZE;
import static org.fortiss.tooling.base.layout.DefaultLayoutConstants.DEFAULT_SHAPE_MINIMUM_WIDTH;
import static org.fortiss.tooling.base.ui.annotation.view.AnnotationViewPartBase.isUpdateEnabled;
import static org.fortiss.tooling.base.ui.annotation.view.AnnotationViewPartBase.setUpdateEnabled;
import static org.fortiss.tooling.base.ui.utils.LayoutDataUIUtils.addBendPointToConnection;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.getNodeSize;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodeLayoutData;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.setNodePosition;
import static org.fortiss.tooling.base.utils.LayoutDataUtils.setStickyConnectorLayoutData;
......@@ -43,23 +41,29 @@ import org.eclipse.emf.common.util.BasicEList;
import org.eclipse.emf.common.util.BasicEMap;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.EMap;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.gef.EditPartViewer;
import org.eclipse.swt.graphics.GC;
import org.fortiss.tooling.base.model.base.EntryConnectorBase;
import org.fortiss.tooling.base.model.base.ExitConnectorBase;
import org.fortiss.tooling.base.model.element.IConnection;
import org.fortiss.tooling.base.model.element.IConnector;
import org.fortiss.tooling.base.model.element.IHierarchicElement;
import org.fortiss.tooling.base.model.layout.Dimension;
import org.fortiss.tooling.base.model.layout.EOrientation;
import org.fortiss.tooling.base.model.layout.ILayoutData;
import org.fortiss.tooling.base.model.layout.ILayoutedModelElement;
import org.fortiss.tooling.base.model.layout.Points;
import org.fortiss.tooling.base.model.layout.impl.PointsImpl;
import org.fortiss.tooling.base.ui.editor.DiagramEditorBase;
import org.fortiss.tooling.kernel.extension.data.ITopLevelElement;
import org.fortiss.tooling.kernel.service.IPersistencyService;
import org.fortiss.tooling.kernel.ui.extension.IModelEditor;
import org.fortiss.tooling.kernel.ui.service.IModelEditorBindingService;
import de.cau.cs.kieler.core.alg.BasicProgressMonitor;
import de.cau.cs.kieler.core.alg.IKielerProgressMonitor;
import de.cau.cs.kieler.core.kgraph.KEdge;
import de.cau.cs.kieler.core.kgraph.KLabel;
import de.cau.cs.kieler.core.kgraph.KNode;
import de.cau.cs.kieler.core.kgraph.KPort;
import de.cau.cs.kieler.kiml.AbstractLayoutProvider;
......@@ -109,11 +113,10 @@ public class KielerAutoLayouter implements IAutoLayouter {
/** {@inheritDoc} */
@Override
public void performAutoLayout(IHierarchicElement hierarchicElement) {
public void performAutoLayout(IHierarchicElement element) {
boolean avUpdateEnabled = isUpdateEnabled();
setUpdateEnabled(false);
KNode rootNode = createKIELERGraph(hierarchicElement);
KNode rootNode = createKIELERGraph(element);
KShapeLayout rootNodeLayout = rootNode.getData(KShapeLayout.class);
rootNodeLayout.setProperty(DIRECTION, RIGHT);
......@@ -125,7 +128,7 @@ public class KielerAutoLayouter implements IAutoLayouter {
IKielerProgressMonitor progressMonitor = new BasicProgressMonitor();
layoutProvider.doLayout(rootNode, progressMonitor);
applyLayout(rootNode, hierarchicElement);
applyLayout(rootNode, element);
setUpdateEnabled(avUpdateEnabled);
}
......@@ -173,19 +176,19 @@ public class KielerAutoLayouter implements IAutoLayouter {
*
* @param template
* The {@link KNode} that stores the layout.
* @param hierarchicElement
* @param element
* The {@link IHierarchicElement} to which the layout should be applied.
*/
private void applyLayout(KNode template, IHierarchicElement hierarchicElement) {
private void applyLayout(KNode template, IHierarchicElement element) {
ITopLevelElement topLevelElement =
IPersistencyService.getInstance().getTopLevelElementFor(hierarchicElement);
IPersistencyService.getInstance().getTopLevelElementFor(element);
topLevelElement.runAsCommand(() -> {
filterBendPoints(hierarchicElement);
filterBendPoints(element);
for(IHierarchicElement element : hierarchicElement.getContainedElements()) {
KNode kNode = modelElementsToKNodes.get(element);
for(IHierarchicElement child : element.getContainedElements()) {
KNode kNode = modelElementsToKNodes.get(child);
KShapeLayout kNodeLayout = kNode.getData(KShapeLayout.class);
setNodeLayoutData((ILayoutedModelElement)element,
setNodeLayoutData((ILayoutedModelElement)child,
truncateSnap2Grid(kNodeLayout.getXpos()),
truncateSnap2Grid(kNodeLayout.getYpos()),
truncateSnap2Grid(kNodeLayout.getWidth(), true),
......@@ -263,13 +266,9 @@ public class KielerAutoLayouter implements IAutoLayouter {
}
}
/**
* Removes all bend-points in connections contained in the given {@link IHierarchicElement}.
*
* @param hierarchicElement
*/
private void filterBendPoints(IHierarchicElement hierarchicElement) {
for(IConnection connection : hierarchicElement.getConnections()) {
/** Removes all bend-points in connections contained in the given {@link IHierarchicElement}. */
private void filterBendPoints(IHierarchicElement element) {
for(IConnection connection : element.getConnections()) {
for(ILayoutData layoutData : ((ILayoutedModelElement)connection).getLayoutData()) {
if(layoutData instanceof Points || layoutData instanceof PointsImpl) {
((Points)layoutData).getPoints().clear();
......@@ -278,17 +277,30 @@ public class KielerAutoLayouter implements IAutoLayouter {
}
}
/**
* Opens the given {@link IHierarchicElement} in an editor. Returns the editor in case it is a
* {@link DiagramEditorBase}-based, {@code null} otherwise.
*/
private EditPartViewer getDiagramEditPartViewer(IHierarchicElement element) {
IModelEditorBindingService es = IModelEditorBindingService.getInstance();
es.openInEditor(element);
IModelEditor<EObject> editor = es.getActiveEditor();
boolean isDiagramEditor = editor instanceof DiagramEditorBase;
return isDiagramEditor ? ((DiagramEditorBase<?>)editor).getViewer() : null;
}
/**
* Creates a KIELER {@link KNode} graph with the same structure as the given
* {@link IHierarchicElement}.
*
* @param hierarchicElement
* @param element
* The {@link IHierarchicElement} for which the {@link KNode} graph should be created
*
* @return A {@link KNode} graph with the same structure as the graph contained in the
* {@link IHierarchicElement}.
*/
private KNode createKIELERGraph(IHierarchicElement hierarchicElement) {
private KNode createKIELERGraph(IHierarchicElement element) {
KNode rootNode = createInitializedNode();
......@@ -298,22 +310,31 @@ public class KielerAutoLayouter implements IAutoLayouter {
connectionsToKEdges = new BasicEMap<IConnection, KEdge>();
undirectedConnectorsToKPorts = new BasicEMap<IConnector, KPort>();
// Create nodes
for(IHierarchicElement currentChild : hierarchicElement.getContainedElements()) {
EditPartViewer viewer = getDiagramEditPartViewer(element);
GC gc = viewer != null ? new GC(viewer.getControl()) : null;
IAutoLayouterTapeMeasure tapeMeasure = new DiagramTapeMeasure(viewer, gc);
if(!(currentChild instanceof ILayoutedModelElement)) {
// Create nodes
for(IHierarchicElement child : element.getContainedElements()) {
if(!(child instanceof ILayoutedModelElement)) {
continue;
}
// Adjust size of node to number of connectors and label size
KNode kNode = createInitializedNode();
KShapeLayout kNodeLayout = kNode.getData(KShapeLayout.class);
int width = tapeMeasure.getElementWidth(child);
kNodeLayout.setWidth(truncateSnap2Grid(width, true));
int height = tapeMeasure.getElementHeight(child);
kNodeLayout.setHeight(truncateSnap2Grid(height, true));
kNode.setParent(rootNode);
modelElementsToKNodes.put(child, kNode);
// Setup connectors
EList<IConnector> inputConnectors = new BasicEList<IConnector>();
EList<IConnector> outputConnectors = new BasicEList<IConnector>();
EList<IConnector> undirectedConnectors = new BasicEList<IConnector>();
for(IConnector connector : currentChild.getConnectors()) {
for(IConnector connector : child.getConnectors()) {
if(connector instanceof EntryConnectorBase) {
inputConnectors.add(connector);
} else if(connector instanceof ExitConnectorBase) {
......@@ -323,17 +344,6 @@ public class KielerAutoLayouter implements IAutoLayouter {
undirectedConnectors.add(connector);
}
}
// Adjust size of node to number of connectors
Dimension dimension = getNodeSize((ILayoutedModelElement)currentChild);
int numConnectors = max(inputConnectors.size(), outputConnectors.size());
kNodeLayout.setHeight(max((1 + 2 * numConnectors) * DEFAULT_CONNECTOR_SIZE,
truncateSnap2Grid(dimension.getHeight(), true)));
kNodeLayout.setWidth(max(DEFAULT_SHAPE_MINIMUM_WIDTH,
truncateSnap2Grid(dimension.getWidth(), true)));
kNode.setParent(rootNode);
modelElementsToKNodes.put(currentChild, kNode);
for(IConnector connector : outputConnectors) {
KPort k = createKPortFromIConnector(connector, kNode, outboundConnectorsToKPorts);
if(undirectedConnectors.contains(connector)) {
......@@ -349,11 +359,11 @@ public class KielerAutoLayouter implements IAutoLayouter {
globalInboundConnectorsToKPorts = new BasicEMap<IConnector, KPort>();
globalOutboundConnectorsToKPorts = new BasicEMap<IConnector, KPort>();
for(IConnector connector : hierarchicElement.getConnectors()) {
for(IConnector connector : element.getConnectors()) {
KNode kNodeVirtual = createInitializedNode();
KShapeLayout kNodeVirtualLayout = kNodeVirtual.getData(KShapeLayout.class);
kNodeVirtualLayout.setHeight(DEFAULT_CONNECTOR_SIZE);
kNodeVirtualLayout.setWidth(2 * DEFAULT_CONNECTOR_SIZE);
kNodeVirtualLayout.setWidth(tapeMeasure.getConnectorWidth(connector));
kNodeVirtualLayout.setHeight(tapeMeasure.getConnectorHeight(connector));
kNodeVirtual.setParent(rootNode);
KPort kPort = createInitializedPort();
......@@ -374,6 +384,12 @@ public class KielerAutoLayouter implements IAutoLayouter {
kEdge.setSource(kSourcePort.getNode());
kEdge.setSourcePort(kSourcePort);
KLabel kLabel = createInitializedLabel(kEdge);
KShapeLayout kLabelLayout = kLabel.getData(KShapeLayout.class);
int width = tapeMeasure.getConnectionWidth(connection);
kLabelLayout.setWidth(truncateSnap2Grid(width, true));
kSourcePort.getEdges().add(kEdge);
KPort kTargetPort = inboundConnectorsToKPorts.get(connection.getTarget());
if(kTargetPort == null) {
......@@ -388,11 +404,15 @@ public class KielerAutoLayouter implements IAutoLayouter {
connectionsToKEdges.put(connection, kEdge);
}
}
if(gc != null) {
gc.dispose();
}
return rootNode;
}
/**
* Converts a given {@link IConnection} into a {@link KPort}, and registers it with the given
* Converts a given {@link IConnector} into a {@link KPort}, and registers it with the given
* {@link KNode} (representing the {@link IHierarchicElement} containing the {@link IConnector})
* and a {@link IConnector}-to- {@link KPort} map.
*
......
ActionBarContributor.java 18d9db3744c5381cca8b6823b5f7bc18183a1cfa GREEN
ExtendableMultiPageEditor.java 968540e08143a77e8233bb8a7d8af6bd90b6b12e GREEN
ExtendableMultiPageEditor.java e2907990cfcc5ba4c2fc3d689ab3258e8014c79a GREEN
IActionContributingEditor.java 4aa7496d67822de919a8cf0af0ddaafc61bf2919 GREEN
ModelElementEditorInput.java 7edf3d4955c55ee595fdcd097ce9a7050ff1b383 GREEN
TutorialStepUIEditor.java 9eadc96c302b5131ff4cc3715777718fa06ec7e8 GREEN
......
......@@ -34,6 +34,7 @@ import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.ISaveablePart;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartConstants;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.MultiPageEditorPart;
......@@ -109,6 +110,14 @@ public class ExtendableMultiPageEditor extends MultiPageEditorPart implements
}
}
/** {@inheritDoc} */
@Override
public void setFocus() {
super.setFocus();
// Workaround to prevent tab icons from disappearing when tabs are reordered
firePropertyChange(IWorkbenchPart.PROP_TITLE);
}
/**
* This is called whenever something about the currently edited object
* changes. This is used to update the part name and to close the editor if
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment