2[WorkbenchToolAttribute(
3 name:
"Parallel Shape Tool",
4 description:
"Create a selected polyline's/spline's parallel shape",
5 awesomeFontCode: 0xF7A5)]
6class SCR_ParallelShapeTool : WorldEditorTool
13 protected float m_fOffsetSafetyMargin;
15 [
Attribute(defvalue:
"1",
desc:
"Minimum distance between points, as to not saturate inner curves (Spline only)",
params:
"0 20 0.1",
precision: 1, uiwidget: UIWidgets.Slider,
category:
"Margins")]
16 protected float m_fMinDistanceBetweenPoints;
23 protected bool m_bShowDebugLog;
29 [
Attribute(defvalue:
"1",
desc:
"Snap the newly created shapes' points to the ground",
category:
"Shape Creation")]
30 protected bool m_bSnapToGround;
32 [
Attribute(defvalue:
"0",
desc:
"Use spline if shape is a spline, may help in sharp turns; generally not needed, polyline should be enough",
category:
"Shape Creation")]
33 protected bool m_bUseSplineIfPossible;
35 [
Attribute(defvalue:
"1",
desc:
"Create shapes as the selected shape(s)' children; otherwise, create them on the same level",
category:
"Shape Creation")]
36 protected bool m_bCreateShapeAsChild;
38 [
Attribute(defvalue:
"1",
desc:
"Create shapes on the provided offsets and their negative, bordering the selected shape(s)",
category:
"Shape Creation")]
39 protected bool m_bSymmetricalShapes;
41 [
Attribute(defvalue:
"1",
desc:
"Cleans up the duplicated offsets to only keep unique ones - also applies to Symmetrical Shapes above",
category:
"Shape Creation")]
42 protected bool m_bRemoveDuplicates;
44 [
Attribute(defvalue:
"0",
desc:
"New shapes' offset(s) - negative means \"to the left\" of the shape, positive means \"to the right\" (from its first to its last point)",
params:
"-50 50 0.1",
precision: 1, uiwidget: UIWidgets.Slider,
category:
"Shape Creation")]
45 protected ref array<float> m_aOffsets;
47 protected static const ref array<float> DEFAULT_OFFSETS = { -10.0, 10.0 };
49 protected static const typename POLYLINE_TYPENAME = PolylineShapeEntity;
50 protected static const typename SPLINE_TYPENAME = SplineShapeEntity;
51 protected static const string POLYLINE_CLASSNAME = POLYLINE_TYPENAME.ToString();
52 protected static const string SPLINE_CLASSNAME = SPLINE_TYPENAME.ToString();
58 WorldEditorAPI worldEditorAPI = SCR_WorldEditorToolHelper.GetWorldEditorAPI();
65 BaseWorld world = worldEditorAPI.GetWorld();
72 int selectedEntities = worldEditorAPI.GetSelectedEntitiesCount();
73 if (selectedEntities < 1)
79 bool updatePropertyPanel;
80 if (m_aOffsets.IsEmpty())
82 m_aOffsets.InsertAll(DEFAULT_OFFSETS);
83 updatePropertyPanel =
true;
86 if (m_bRemoveDuplicates)
88 int offsetCounts = m_aOffsets.Count();
89 SCR_ArrayHelperT<float>.RemoveDuplicates(m_aOffsets);
90 if (offsetCounts != m_aOffsets.Count())
91 updatePropertyPanel =
true;
94 if (updatePropertyPanel)
95 UpdatePropertyPanel();
97 array<float> offsets = {};
98 offsets.Copy(m_aOffsets);
100 if (m_bSymmetricalShapes)
102 offsets.Reserve(offsets.Count() * 2);
103 foreach (
float offset : m_aOffsets)
106 offsets.Insert(-offset);
109 if (m_bRemoveDuplicates)
110 SCR_ArrayHelperT<float>.RemoveDuplicates(offsets);
114 IEntitySource source;
115 ShapeEntity shapeEntity;
117 IEntitySource parentSource;
119 array<vector> newPoints;
120 ShapeEntity createdShape;
121 IEntitySource createdShapeSource;
122 int currentLayerId = worldEditorAPI.GetCurrentEntityLayerId();
123 map<IEntitySource, ref array<vector>> shapeSourcePoints =
new map<IEntitySource, ref array<vector>>();
124 for (
int selectedEntityIndex; selectedEntityIndex < selectedEntities; ++selectedEntityIndex)
126 source = worldEditorAPI.GetSelectedEntity(selectedEntityIndex);
129 Print(
"No entity selected, skipping index " + selectedEntityIndex,
LogLevel.WARNING);
133 entity = worldEditorAPI.SourceToEntity(source);
136 Print(
"Null entity; skipping index " + selectedEntityIndex,
LogLevel.WARNING);
140 shapeEntity = ShapeEntity.Cast(entity);
144 Print(
"The selected entity is not a shape (polyline/spline) at index " + selectedEntityIndex,
LogLevel.NORMAL);
149 if (shapeEntity.GetPointCount() < 2)
151 Print(
"The selected shape #" + selectedEntityIndex +
" requires at least two points - " + shapeEntity.GetOrigin(),
LogLevel.NORMAL);
155 CreateParallelOffsetShapes(shapeEntity, offsets);
162 static IEntitySource CreateParallelOffsetShape(notnull ShapeEntity shapeEntity,
float offset, SCR_ParallelShapeToolSettings settings = null)
164 array<IEntitySource> shapes = CreateParallelOffsetShapes(shapeEntity, { offset });
165 if (!shapes || shapes.IsEmpty())
178 static array<IEntitySource> CreateParallelOffsetShapes(notnull ShapeEntity shapeEntity, notnull array<float> offsets, SCR_ParallelShapeToolSettings settings = null)
180 WorldEditorAPI worldEditorAPI = ((WorldEditor)Workbench.GetModule(WorldEditor)).GetApi();
183 Print(
"[SCR_ParallelShapeTool.CreateParallelOffsetShapes] WorldEditorAPI is not available (" + __FILE__ +
" L" + __LINE__ +
")",
LogLevel.WARNING);
187 if (shapeEntity.GetPointCount() < 2)
189 Print(
"[SCR_ParallelShapeTool.CreateParallelOffsetShapes] The provided shape requires at least two points (" + __FILE__ +
" L" + __LINE__ +
")",
LogLevel.WARNING);
193 array<IEntitySource> result = {};
194 int layerId = worldEditorAPI.GetCurrentEntityLayerId();
196 array<vector> newPoints;
197 IEntitySource createdShapeSource;
200 IEntitySource parentSource;
204 if (settings && settings.m_bCreateAsChildren)
206 parentSource = worldEditorAPI.EntityToSource(shapeEntity);
210 origin = shapeEntity.GetOrigin();
211 angles = shapeEntity.GetAngles();
214 bool manageEntityAction = worldEditorAPI.BeginEntityAction();
216 foreach (
int offsetIndex,
float offset : offsets)
218 float absOffset = offset;
220 absOffset = -absOffset;
222 newPoints = SCR_ParallelShapeHelper.GetRelativeOffsetPointsFromShape(shapeEntity, offset, shapeEntity.IsClosed());
226 if (settings.m_fOffsetSafetyMargin > 0)
227 SCR_ParallelShapeHelper.RemovePointsCloseToShape(shapeEntity, absOffset - settings.m_fOffsetSafetyMargin, newPoints);
229 if (settings.m_bSnapToGround)
230 SCR_ParallelShapeHelper.SnapRelativePointsToGround(shapeEntity, newPoints);
233 if (settings.m_fMinDistanceBetweenPoints > 0 && SplineShapeEntity.Cast(createdShape) != null)
234 SCR_ParallelShapeHelper.ReducePointsDensity(settings.m_fMinDistanceBetweenPoints, newPoints);
237 int newPointsCount = newPoints.Count();
238 if (newPointsCount < 2)
240 float minDistanceBetweenPoints;
242 minDistanceBetweenPoints = settings.m_fMinDistanceBetweenPoints;
245 "The number of created points (" + newPointsCount +
") is not enough for a proper shape - "
246 +
"try changing the offset safety margin (current: " + offset.ToString(-1, 2) +
"m) "
247 +
"or the min distance between points (current: " + minDistanceBetweenPoints.ToString(-1, 2) +
"m)",
LogLevel.WARNING);
252 && settings.m_bUseSplineForSpline
253 && shapeEntity.ClassName().ToType()
254 && shapeEntity.ClassName().ToType().IsInherited(SPLINE_TYPENAME))
255 createdShapeSource = worldEditorAPI.CreateEntity(SPLINE_CLASSNAME,
string.Empty, layerId, parentSource, origin,
angles);
257 createdShapeSource = worldEditorAPI.CreateEntity(POLYLINE_CLASSNAME,
string.Empty, layerId, parentSource, origin,
angles);
259 if (!createdShapeSource)
261 Print(
"Cannot create shape of offset #" + offsetIndex +
"'s shape",
LogLevel.ERROR);
265 createdShape = ShapeEntity.Cast(worldEditorAPI.SourceToEntity(createdShapeSource));
269 worldEditorAPI.DeleteEntity(createdShapeSource);
273 foreach (
int i, vector newPoint : newPoints)
275 if (!worldEditorAPI.CreateObjectArrayVariableMember(createdShapeSource, null,
"Points",
"ShapePoint", i))
277 Print(
"[SCR_ParallelShapeHelper.CreateParallelOffsetShapes] Cannot create point " + i +
" (" + __FILE__ +
" L" + __LINE__ +
")",
LogLevel.ERROR);
281 newPoints[i] = shapeEntity.CoordToLocal(newPoint);
282 worldEditorAPI.SetVariableValue(createdShapeSource, {
new ContainerIdPathEntry(
"Points", i) },
"Position",
string.Format(
"%1 %2 %3", newPoint[0], newPoint[1], newPoint[2]));
285 result.Insert(createdShapeSource);
288 if (manageEntityAction)
289 worldEditorAPI.EndEntityAction();
295class SCR_ParallelShapeToolSettings
297 bool m_bCreateAsChildren;
298 bool m_bSnapToGround;
299 bool m_bUseSplineForSpline;
301 float m_fMinDistanceBetweenPoints;
302 float m_fOffsetSafetyMargin;
void ContainerIdPathEntry(string propertyName, int index=-1)
ref array< string > angles
UI Textures DeployMenu Briefing conflict_HintBanner_1_UI desc
enum EVehicleType IEntity
class WorkbenchDialog_AbortRetryIgnore ButtonAttribute("OK", true)
proto void Print(void var, LogLevel level=LogLevel.NORMAL)
Prints content of variable to console/log.
LogLevel
Enum with severity of the logging message.
SCR_FieldOfViewSettings Attribute